• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sds / mock_redis / 7533551965

15 Jan 2024 08:06PM UTC coverage: 98.033%. Remained the same
7533551965

Pull #297

github

web-flow
Merge 5fd7f6e83 into 43d978010
Pull Request #297: Cut version 0.42.0

1794 of 1830 relevant lines covered (98.03%)

1615.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.44
/lib/mock_redis/string_methods.rb
1
require 'mock_redis/assertions'
8✔
2

3
class MockRedis
8✔
4
  module StringMethods
8✔
5
    include Assertions
8✔
6
    include UtilityMethods
8✔
7

8
    def append(key, value)
8✔
9
      assert_stringy(key)
16✔
10
      data[key] ||= ''
12✔
11
      data[key] << value
12✔
12
      data[key].length
12✔
13
    end
14

15
    def bitfield(*args)
8✔
16
      if args.length < 4
516✔
17
        raise Redis::CommandError, 'ERR wrong number of arguments for BITFIELD'
×
18
      end
19

20
      key = args.shift
516✔
21
      output = []
516✔
22
      overflow_method = 'wrap'
516✔
23

24
      until args.empty?
516✔
25
        command = args.shift.to_s
636✔
26

27
        if command == 'overflow'
636✔
28
          new_overflow_method = args.shift.to_s.downcase
52✔
29

30
          unless %w[wrap sat fail].include? new_overflow_method
52✔
31
            raise Redis::CommandError, 'ERR Invalid OVERFLOW type specified'
4✔
32
          end
33

34
          overflow_method = new_overflow_method
48✔
35
          next
48✔
36
        end
37

38
        type, offset = args.shift(2)
584✔
39

40
        is_signed = type.slice(0) == 'i'
584✔
41
        type_size = type[1..].to_i
584✔
42

43
        if (type_size > 64 && is_signed) || (type_size >= 64 && !is_signed)
584✔
44
          raise Redis::CommandError,
8✔
45
            'ERR Invalid bitfield type. Use something like i16 u8. ' \
46
            'Note that u64 is not supported but i64 is.'
47
        end
48

49
        if offset.to_s[0] == '#'
576✔
50
          offset = offset[1..].to_i * type_size
12✔
51
        end
52

53
        bits = []
576✔
54

55
        type_size.times do |i|
576✔
56
          bits.push(getbit(key, offset + i))
4,832✔
57
        end
58

59
        val = is_signed ? twos_complement_decode(bits) : bits.join('').to_i(2)
576✔
60

61
        case command
576✔
62
        when 'get'
63
          output.push(val)
88✔
64
        when 'set'
65
          output.push(val)
412✔
66

67
          set_bitfield(key, args.shift.to_i, is_signed, type_size, offset)
412✔
68
        when 'incrby'
69
          new_val = incr_bitfield(val, args.shift.to_i, is_signed, type_size, overflow_method)
76✔
70

71
          set_bitfield(key, new_val, is_signed, type_size, offset) if new_val
76✔
72
          output.push(new_val)
76✔
73
        end
74
      end
75

76
      output
504✔
77
    end
78

79
    def decr(key)
8✔
80
      decrby(key, 1)
32✔
81
    end
82

83
    def decrby(key, n)
8✔
84
      incrby(key, -n)
64✔
85
    end
86

87
    def get(key)
8✔
88
      key = key.to_s
932✔
89
      assert_stringy(key)
932✔
90
      data[key]
924✔
91
    end
92

93
    def getbit(key, offset)
8✔
94
      assert_stringy(key)
8,672✔
95

96
      offset = offset.to_i
8,668✔
97
      offset_of_byte = offset / 8
8,668✔
98
      offset_within_byte = offset % 8
8,668✔
99

100
      # String#getbyte would be lovely, but it's not in 1.8.7.
101
      byte = (data[key] || '').each_byte.drop(offset_of_byte).first
8,668✔
102

103
      if byte
8,668✔
104
        (byte & (2**7 >> offset_within_byte)) > 0 ? 1 : 0
5,124✔
105
      else
106
        0
3,544✔
107
      end
108
    end
109

110
    def getdel(key)
8✔
111
      value = get(key)
×
112
      del(key)
×
113
      value
×
114
    end
115

116
    def getrange(key, start, stop)
8✔
117
      assert_stringy(key)
20✔
118
      (data[key] || '')[start..stop]
12✔
119
    end
120

121
    def getset(key, value)
8✔
122
      retval = get(key)
12✔
123
      set(key, value)
12✔
124
      retval
12✔
125
    end
126

127
    def incr(key)
8✔
128
      incrby(key, 1)
52✔
129
    end
130

131
    def incrby(key, n)
8✔
132
      assert_stringy(key)
184✔
133
      unless can_incr?(data[key])
168✔
134
        raise Redis::CommandError, 'ERR value is not an integer or out of range'
16✔
135
      end
136

137
      unless looks_like_integer?(n.to_s)
152✔
138
        raise Redis::CommandError, 'ERR value is not an integer or out of range'
4✔
139
      end
140

141
      new_value = data[key].to_i + n.to_i
148✔
142
      data[key] = new_value.to_s
148✔
143
      # for some reason, redis-rb doesn't return this as a string.
144
      new_value
148✔
145
    end
146

147
    def incrbyfloat(key, n)
8✔
148
      assert_stringy(key)
40✔
149
      unless can_incr_float?(data[key])
36✔
150
        raise Redis::CommandError, 'ERR value is not a valid float'
4✔
151
      end
152

153
      unless looks_like_float?(n.to_s)
32✔
154
        raise Redis::CommandError, 'ERR value is not a valid float'
4✔
155
      end
156

157
      new_value = data[key].to_f + n.to_f
28✔
158
      data[key] = new_value.to_s
28✔
159
      # for some reason, redis-rb doesn't return this as a string.
160
      new_value
28✔
161
    end
162

163
    def mget(*keys, &blk)
8✔
164
      keys.flatten!
60✔
165

166
      assert_has_args(keys, 'mget')
60✔
167

168
      data = keys.map do |key|
52✔
169
        get(key) if stringy?(key)
116✔
170
      end
171

172
      blk ? blk.call(data) : data
52✔
173
    end
174

175
    def mapped_mget(*keys)
8✔
176
      Hash[keys.zip(mget(*keys))]
8✔
177
    end
178

179
    def mset(*kvpairs)
8✔
180
      assert_has_args(kvpairs, 'mset')
48✔
181
      kvpairs = kvpairs.first if kvpairs.size == 1 && kvpairs.first.is_a?(Enumerable)
44✔
182

183
      if kvpairs.length.odd?
44✔
184
        raise Redis::CommandError, 'ERR wrong number of arguments for MSET'
12✔
185
      end
186

187
      kvpairs.each_slice(2) do |(k, v)|
32✔
188
        set(k, v)
48✔
189
      end
190

191
      'OK'
32✔
192
    end
193

194
    def mapped_mset(hash)
8✔
195
      mset(*hash.to_a.flatten)
4✔
196
    end
197

198
    def msetnx(*kvpairs)
8✔
199
      assert_has_args(kvpairs, 'msetnx')
32✔
200

201
      if kvpairs.each_slice(2).any? { |(k, _)| exists?(k) }
64✔
202
        false
12✔
203
      else
204
        mset(*kvpairs)
16✔
205
        true
12✔
206
      end
207
    end
208

209
    def mapped_msetnx(hash)
8✔
210
      msetnx(*hash.to_a.flatten)
8✔
211
    end
212

213
    # Parameter list required to ensure the ArgumentError is returned correctly
214
    # rubocop:disable Metrics/ParameterLists
215
    def set(key, value, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil, keepttl: nil,
8✔
216
      get: nil)
217
      key = key.to_s
3,272✔
218
      retval = self.get(key) if get
3,272✔
219

220
      return_true = false
3,268✔
221
      if nx
3,268✔
222
        if exists?(key)
8✔
223
          return false
4✔
224
        else
225
          return_true = true
4✔
226
        end
227
      end
228
      if xx
3,264✔
229
        if exists?(key)
8✔
230
          return_true = true
4✔
231
        else
232
          return false
4✔
233
        end
234
      end
235
      data[key] = value.to_s
3,260✔
236

237
      remove_expiration(key) unless keepttl
3,260✔
238
      if ex
3,260✔
239
        if ex == 0
24✔
240
          raise Redis::CommandError, 'ERR invalid expire time in set'
4✔
241
        end
242
        expire(key, ex)
20✔
243
      end
244

245
      if px
3,256✔
246
        if px == 0
8✔
247
          raise Redis::CommandError, 'ERR invalid expire time in set'
4✔
248
        end
249
        pexpire(key, px)
4✔
250
      end
251

252
      if exat
3,252✔
253
        if exat == 0
16✔
254
          raise Redis::CommandError, 'ERR invalid expire time in set'
4✔
255
        end
256
        expireat(key, exat)
12✔
257
      end
258

259
      if pxat
3,248✔
260
        if pxat == 0
8✔
261
          raise Redis::CommandError, 'ERR invalid expire time in set'
4✔
262
        end
263
        pexpireat(key, pxat)
4✔
264
      end
265

266
      if get
3,244✔
267
        retval
8✔
268
      else
269
        return_true ? true : 'OK'
3,236✔
270
      end
271
    end
272
    # rubocop:enable Metrics/ParameterLists
273

274
    def setbit(key, offset, value)
8✔
275
      assert_stringy(key, 'ERR bit is not an integer or out of range')
3,788✔
276
      retval = getbit(key, offset)
3,784✔
277

278
      str = data[key] || ''
3,784✔
279

280
      offset = offset.to_i
3,784✔
281
      offset_of_byte = offset / 8
3,784✔
282
      offset_within_byte = offset % 8
3,784✔
283

284
      if offset_of_byte >= str.bytesize
3,784✔
285
        str = zero_pad(str, offset_of_byte + 1)
396✔
286
      end
287

288
      char_index = byte_index = offset_within_char = 0
3,784✔
289
      str.each_char do |c|
3,784✔
290
        if byte_index < offset_of_byte
10,744✔
291
          char_index += 1
6,960✔
292
          byte_index += c.bytesize
6,960✔
293
        else
294
          offset_within_char = byte_index - offset_of_byte
3,784✔
295
          break
3,784✔
296
        end
297
      end
298

299
      char = str[char_index]
3,784✔
300
      char = char.chr if char.respond_to?(:chr) # ruby 1.8 vs 1.9
3,784✔
301
      char_as_number = char.each_byte.reduce(0) do |a, byte|
3,784✔
302
        (a << 8) + byte
3,784✔
303
      end
304

305
      bitmask_length = (char.bytesize * 8 - offset_within_char * 8 - offset_within_byte - 1)
3,784✔
306
      bitmask = 1 << bitmask_length
3,784✔
307

308
      if value.zero?
3,784✔
309
        bitmask ^= 2**(char.bytesize * 8) - 1
1,932✔
310
        char_as_number &= bitmask
1,932✔
311
      else
312
        char_as_number |= bitmask
1,852✔
313
      end
314

315
      str[char_index] = char_as_number.chr
3,784✔
316

317
      data[key] = str
3,784✔
318
      retval
3,784✔
319
    end
320

321
    def bitcount(key, start = 0, stop = -1)
8✔
322
      assert_stringy(key)
28✔
323

324
      str   = data[key] || ''
24✔
325
      count = 0
24✔
326
      m1    = 0x5555555555555555
24✔
327
      m2    = 0x3333333333333333
24✔
328
      m4    = 0x0f0f0f0f0f0f0f0f
24✔
329
      m8    = 0x00ff00ff00ff00ff
24✔
330
      m16   = 0x0000ffff0000ffff
24✔
331
      m32   = 0x00000000ffffffff
24✔
332

333
      str.bytes.to_a[start..stop].each do |byte|
24✔
334
        # Naive Hamming weight
335
        c = byte
72✔
336
        c = (c & m1) + ((c >> 1) & m1)
72✔
337
        c = (c & m2) + ((c >> 2) & m2)
72✔
338
        c = (c & m4) + ((c >> 4) & m4)
72✔
339
        c = (c & m8) + ((c >> 8) & m8)
72✔
340
        c = (c & m16) + ((c >> 16) & m16)
72✔
341
        c = (c & m32) + ((c >> 32) & m32)
72✔
342
        count += c
72✔
343
      end
344

345
      count
24✔
346
    end
347

348
    def setex(key, seconds, value)
8✔
349
      if seconds <= 0
20✔
350
        raise Redis::CommandError, 'ERR invalid expire time in setex'
8✔
351
      else
352
        set(key, value)
12✔
353
        expire(key, seconds)
12✔
354
        'OK'
12✔
355
      end
356
    end
357

358
    def psetex(key, milliseconds, value)
8✔
359
      if milliseconds <= 0
24✔
360
        raise Redis::CommandError, 'ERR invalid expire time in psetex'
8✔
361
      else
362
        set(key, value)
16✔
363
        pexpire(key, milliseconds)
16✔
364
        'OK'
16✔
365
      end
366
    end
367

368
    def setnx(key, value)
8✔
369
      if exists?(key)
16✔
370
        false
8✔
371
      else
372
        set(key, value)
8✔
373
        true
8✔
374
      end
375
    end
376

377
    def setrange(key, offset, value)
8✔
378
      assert_stringy(key)
20✔
379
      value = value.to_s
16✔
380
      old_value = (data[key] || '')
16✔
381

382
      prefix = zero_pad(old_value[0...offset], offset)
16✔
383
      data[key] = prefix + value + (old_value[(offset + value.length)..] || '')
16✔
384
      data[key].length
16✔
385
    end
386

387
    def strlen(key)
8✔
388
      assert_stringy(key)
12✔
389
      (data[key] || '').bytesize
8✔
390
    end
391

392
    private
8✔
393

394
    def stringy?(key)
8✔
395
      data[key].nil? || data[key].is_a?(String)
13,912✔
396
    end
397

398
    def assert_stringy(key,
8✔
399
        message = 'WRONGTYPE Operation against a key holding the wrong kind of value')
400
      unless stringy?(key)
13,712✔
401
        raise Redis::CommandError, message
60✔
402
      end
403
    end
404

405
    def set_bitfield(key, value, is_signed, type_size, offset)
8✔
406
      if is_signed
468✔
407
        val_array = twos_complement_encode(value, type_size)
308✔
408
      else
409
        str = left_pad(value.to_i.abs.to_s(2), type_size)
160✔
410
        val_array = str.split('').map(&:to_i)
160✔
411
      end
412

413
      val_array.each_with_index do |bit, i|
468✔
414
        setbit(key, offset + i, bit)
3,744✔
415
      end
416
    end
417

418
    def incr_bitfield(val, incrby, is_signed, type_size, overflow_method)
8✔
419
      new_val = val + incrby
76✔
420

421
      max = is_signed ? (2**(type_size - 1)) - 1 : (2**type_size) - 1
76✔
422
      min = is_signed ? (-2**(type_size - 1)) : 0
76✔
423
      size = 2**type_size
76✔
424

425
      return new_val if (min..max).cover?(new_val)
76✔
426

427
      case overflow_method
60✔
428
      when 'fail'
429
        new_val = nil
20✔
430
      when 'sat'
431
        new_val = new_val > max ? max : min
16✔
432
      when 'wrap'
433
        if is_signed
24✔
434
          if new_val > max
16✔
435
            remainder = new_val - (max + 1)
8✔
436
            new_val = min + remainder.abs
8✔
437
          else
438
            remainder = new_val - (min - 1)
8✔
439
            new_val = max - remainder.abs
8✔
440
          end
441
        else
442
          new_val = new_val > max ? new_val % size : size - new_val.abs
8✔
443
        end
444
      end
445

446
      new_val
60✔
447
    end
448
  end
449
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc