mock_redis 0.19.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +161 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +41 -17
  5. data/lib/mock_redis/connection_method.rb +13 -0
  6. data/lib/mock_redis/database.rb +116 -41
  7. data/lib/mock_redis/expire_wrapper.rb +1 -1
  8. data/lib/mock_redis/future.rb +1 -1
  9. data/lib/mock_redis/geospatial_methods.rb +13 -21
  10. data/lib/mock_redis/hash_methods.rb +34 -15
  11. data/lib/mock_redis/indifferent_hash.rb +0 -8
  12. data/lib/mock_redis/info_method.rb +2 -2
  13. data/lib/mock_redis/list_methods.rb +39 -4
  14. data/lib/mock_redis/memory_method.rb +11 -0
  15. data/lib/mock_redis/multi_db_wrapper.rb +4 -4
  16. data/lib/mock_redis/pipelined_wrapper.rb +32 -15
  17. data/lib/mock_redis/set_methods.rb +34 -6
  18. data/lib/mock_redis/stream/id.rb +58 -0
  19. data/lib/mock_redis/stream.rb +88 -0
  20. data/lib/mock_redis/stream_methods.rb +102 -0
  21. data/lib/mock_redis/string_methods.rb +81 -30
  22. data/lib/mock_redis/transaction_wrapper.rb +32 -21
  23. data/lib/mock_redis/utility_methods.rb +9 -4
  24. data/lib/mock_redis/version.rb +1 -1
  25. data/lib/mock_redis/zset.rb +5 -8
  26. data/lib/mock_redis/zset_methods.rb +64 -12
  27. data/lib/mock_redis.rb +38 -10
  28. metadata +40 -336
  29. data/.gitignore +0 -5
  30. data/.mailmap +0 -2
  31. data/.overcommit.yml +0 -21
  32. data/.rspec +0 -1
  33. data/.rubocop.yml +0 -121
  34. data/.rubocop_todo.yml +0 -35
  35. data/.simplecov +0 -4
  36. data/.travis.yml +0 -33
  37. data/Gemfile +0 -12
  38. data/LICENSE +0 -19
  39. data/Rakefile +0 -2
  40. data/mock_redis.gemspec +0 -30
  41. data/spec/client_spec.rb +0 -17
  42. data/spec/cloning_spec.rb +0 -95
  43. data/spec/commands/append_spec.rb +0 -24
  44. data/spec/commands/auth_spec.rb +0 -7
  45. data/spec/commands/bgrewriteaof_spec.rb +0 -7
  46. data/spec/commands/bgsave_spec.rb +0 -7
  47. data/spec/commands/bitcount_spec.rb +0 -25
  48. data/spec/commands/bitfield_spec.rb +0 -169
  49. data/spec/commands/blpop_spec.rb +0 -59
  50. data/spec/commands/brpop_spec.rb +0 -58
  51. data/spec/commands/brpoplpush_spec.rb +0 -52
  52. data/spec/commands/connected_spec.rb +0 -7
  53. data/spec/commands/dbsize_spec.rb +0 -18
  54. data/spec/commands/decr_spec.rb +0 -34
  55. data/spec/commands/decrby_spec.rb +0 -34
  56. data/spec/commands/del_spec.rb +0 -35
  57. data/spec/commands/disconnect_spec.rb +0 -7
  58. data/spec/commands/echo_spec.rb +0 -11
  59. data/spec/commands/eval_spec.rb +0 -7
  60. data/spec/commands/evalsha_spec.rb +0 -10
  61. data/spec/commands/exists_spec.rb +0 -14
  62. data/spec/commands/expire_spec.rb +0 -111
  63. data/spec/commands/expireat_spec.rb +0 -47
  64. data/spec/commands/flushall_spec.rb +0 -38
  65. data/spec/commands/flushdb_spec.rb +0 -38
  66. data/spec/commands/future_spec.rb +0 -20
  67. data/spec/commands/geoadd_spec.rb +0 -58
  68. data/spec/commands/geodist_spec.rb +0 -114
  69. data/spec/commands/geohash_spec.rb +0 -52
  70. data/spec/commands/geopos_spec.rb +0 -55
  71. data/spec/commands/get_spec.rb +0 -30
  72. data/spec/commands/getbit_spec.rb +0 -34
  73. data/spec/commands/getrange_spec.rb +0 -22
  74. data/spec/commands/getset_spec.rb +0 -23
  75. data/spec/commands/hash_operator_spec.rb +0 -21
  76. data/spec/commands/hdel_spec.rb +0 -54
  77. data/spec/commands/hexists_spec.rb +0 -27
  78. data/spec/commands/hget_spec.rb +0 -28
  79. data/spec/commands/hgetall_spec.rb +0 -32
  80. data/spec/commands/hincrby_spec.rb +0 -58
  81. data/spec/commands/hincrbyfloat_spec.rb +0 -58
  82. data/spec/commands/hkeys_spec.rb +0 -19
  83. data/spec/commands/hlen_spec.rb +0 -19
  84. data/spec/commands/hmget_spec.rb +0 -40
  85. data/spec/commands/hmset_spec.rb +0 -43
  86. data/spec/commands/hscan_each_spec.rb +0 -48
  87. data/spec/commands/hscan_spec.rb +0 -27
  88. data/spec/commands/hset_spec.rb +0 -38
  89. data/spec/commands/hsetnx_spec.rb +0 -44
  90. data/spec/commands/hvals_spec.rb +0 -19
  91. data/spec/commands/incr_spec.rb +0 -34
  92. data/spec/commands/incrby_spec.rb +0 -44
  93. data/spec/commands/incrbyfloat_spec.rb +0 -44
  94. data/spec/commands/info_spec.rb +0 -62
  95. data/spec/commands/keys_spec.rb +0 -122
  96. data/spec/commands/lastsave_spec.rb +0 -8
  97. data/spec/commands/lindex_spec.rb +0 -49
  98. data/spec/commands/linsert_spec.rb +0 -68
  99. data/spec/commands/llen_spec.rb +0 -16
  100. data/spec/commands/lpop_spec.rb +0 -34
  101. data/spec/commands/lpush_spec.rb +0 -43
  102. data/spec/commands/lpushx_spec.rb +0 -46
  103. data/spec/commands/lrange_spec.rb +0 -51
  104. data/spec/commands/lrem_spec.rb +0 -80
  105. data/spec/commands/lset_spec.rb +0 -43
  106. data/spec/commands/ltrim_spec.rb +0 -45
  107. data/spec/commands/mapped_hmget_spec.rb +0 -29
  108. data/spec/commands/mapped_hmset_spec.rb +0 -47
  109. data/spec/commands/mapped_mget_spec.rb +0 -22
  110. data/spec/commands/mapped_mset_spec.rb +0 -19
  111. data/spec/commands/mapped_msetnx_spec.rb +0 -26
  112. data/spec/commands/mget_spec.rb +0 -34
  113. data/spec/commands/move_spec.rb +0 -147
  114. data/spec/commands/mset_spec.rb +0 -29
  115. data/spec/commands/msetnx_spec.rb +0 -40
  116. data/spec/commands/persist_spec.rb +0 -48
  117. data/spec/commands/pexpire_spec.rb +0 -86
  118. data/spec/commands/pexpireat_spec.rb +0 -48
  119. data/spec/commands/ping_spec.rb +0 -7
  120. data/spec/commands/pipelined_spec.rb +0 -42
  121. data/spec/commands/pttl_spec.rb +0 -41
  122. data/spec/commands/quit_spec.rb +0 -7
  123. data/spec/commands/randomkey_spec.rb +0 -20
  124. data/spec/commands/rename_spec.rb +0 -42
  125. data/spec/commands/renamenx_spec.rb +0 -41
  126. data/spec/commands/rpop_spec.rb +0 -34
  127. data/spec/commands/rpoplpush_spec.rb +0 -50
  128. data/spec/commands/rpush_spec.rb +0 -43
  129. data/spec/commands/rpushx_spec.rb +0 -46
  130. data/spec/commands/sadd_spec.rb +0 -45
  131. data/spec/commands/save_spec.rb +0 -7
  132. data/spec/commands/scan_each_spec.rb +0 -39
  133. data/spec/commands/scan_spec.rb +0 -55
  134. data/spec/commands/scard_spec.rb +0 -18
  135. data/spec/commands/script_spec.rb +0 -9
  136. data/spec/commands/sdiff_spec.rb +0 -47
  137. data/spec/commands/sdiffstore_spec.rb +0 -58
  138. data/spec/commands/select_spec.rb +0 -61
  139. data/spec/commands/set_spec.rb +0 -63
  140. data/spec/commands/setbit_spec.rb +0 -54
  141. data/spec/commands/setex_spec.rb +0 -22
  142. data/spec/commands/setnx_spec.rb +0 -25
  143. data/spec/commands/setrange_spec.rb +0 -30
  144. data/spec/commands/sinter_spec.rb +0 -39
  145. data/spec/commands/sinterstore_spec.rb +0 -53
  146. data/spec/commands/sismember_spec.rb +0 -29
  147. data/spec/commands/smembers_spec.rb +0 -28
  148. data/spec/commands/smove_spec.rb +0 -41
  149. data/spec/commands/sort_list_spec.rb +0 -21
  150. data/spec/commands/sort_set_spec.rb +0 -21
  151. data/spec/commands/sort_zset_spec.rb +0 -21
  152. data/spec/commands/spop_spec.rb +0 -25
  153. data/spec/commands/srandmember_spec.rb +0 -49
  154. data/spec/commands/srem_spec.rb +0 -40
  155. data/spec/commands/sscan_each_spec.rb +0 -48
  156. data/spec/commands/sscan_spec.rb +0 -39
  157. data/spec/commands/strlen_spec.rb +0 -18
  158. data/spec/commands/sunion_spec.rb +0 -42
  159. data/spec/commands/sunionstore_spec.rb +0 -59
  160. data/spec/commands/ttl_spec.rb +0 -40
  161. data/spec/commands/type_spec.rb +0 -36
  162. data/spec/commands/unwatch_spec.rb +0 -7
  163. data/spec/commands/watch_spec.rb +0 -16
  164. data/spec/commands/zadd_spec.rb +0 -123
  165. data/spec/commands/zcard_spec.rb +0 -19
  166. data/spec/commands/zcount_spec.rb +0 -39
  167. data/spec/commands/zincrby_spec.rb +0 -31
  168. data/spec/commands/zinterstore_spec.rb +0 -96
  169. data/spec/commands/zrange_spec.rb +0 -80
  170. data/spec/commands/zrangebyscore_spec.rb +0 -83
  171. data/spec/commands/zrank_spec.rb +0 -29
  172. data/spec/commands/zrem_spec.rb +0 -43
  173. data/spec/commands/zremrangebyrank_spec.rb +0 -27
  174. data/spec/commands/zremrangebyscore_spec.rb +0 -35
  175. data/spec/commands/zrevrange_spec.rb +0 -56
  176. data/spec/commands/zrevrangebyscore_spec.rb +0 -58
  177. data/spec/commands/zrevrank_spec.rb +0 -29
  178. data/spec/commands/zscan_each_spec.rb +0 -48
  179. data/spec/commands/zscan_spec.rb +0 -26
  180. data/spec/commands/zscore_spec.rb +0 -22
  181. data/spec/commands/zunionstore_spec.rb +0 -104
  182. data/spec/mock_redis_spec.rb +0 -86
  183. data/spec/spec_helper.rb +0 -63
  184. data/spec/support/redis_multiplexer.rb +0 -106
  185. data/spec/support/shared_examples/only_operates_on_hashes.rb +0 -13
  186. data/spec/support/shared_examples/only_operates_on_lists.rb +0 -13
  187. data/spec/support/shared_examples/only_operates_on_sets.rb +0 -13
  188. data/spec/support/shared_examples/only_operates_on_strings.rb +0 -13
  189. data/spec/support/shared_examples/only_operates_on_zsets.rb +0 -57
  190. data/spec/support/shared_examples/sorts_enumerables.rb +0 -56
  191. data/spec/transactions_spec.rb +0 -159
@@ -0,0 +1,102 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+ require 'mock_redis/stream'
4
+
5
+ # TODO: Implement the following commands
6
+ #
7
+ # * xgroup
8
+ # * xreadgroup
9
+ # * xack
10
+ # * xpending
11
+ # * xclaim
12
+ # * xinfo
13
+ # * xtrim
14
+ # * xdel
15
+ #
16
+ # TODO: Complete support for
17
+ #
18
+ # * xtrim
19
+ # - `approximate: true` argument is currently ignored
20
+ # * xadd
21
+ # - `approximate: true` argument (for capped streams) is currently ignored
22
+ #
23
+ # For details of these commands see
24
+ # * https://redis.io/topics/streams-intro
25
+ # * https://redis.io/commands#stream
26
+
27
+ class MockRedis
28
+ module StreamMethods
29
+ include Assertions
30
+ include UtilityMethods
31
+
32
+ def xadd(key, entry, opts = {})
33
+ id = opts[:id] || '*'
34
+ with_stream_at(key) do |stream|
35
+ stream.add id, entry
36
+ stream.trim opts[:maxlen] if opts[:maxlen]
37
+ return stream.last_id
38
+ end
39
+ end
40
+
41
+ def xtrim(key, count)
42
+ with_stream_at(key) do |stream|
43
+ stream.trim count
44
+ end
45
+ end
46
+
47
+ def xlen(key)
48
+ with_stream_at(key) do |stream|
49
+ return stream.count
50
+ end
51
+ end
52
+
53
+ def xrange(key, first = '-', last = '+', count: nil)
54
+ args = [first, last, false]
55
+ args += ['COUNT', count] if count
56
+ with_stream_at(key) do |stream|
57
+ return stream.range(*args)
58
+ end
59
+ end
60
+
61
+ def xrevrange(key, last = '+', first = '-', count: nil)
62
+ args = [first, last, true]
63
+ args += ['COUNT', count] if count
64
+ with_stream_at(key) do |stream|
65
+ return stream.range(*args)
66
+ end
67
+ end
68
+
69
+ def xread(keys, ids, count: nil, block: nil)
70
+ args = []
71
+ args += ['COUNT', count] if count
72
+ args += ['BLOCK', block.to_i] if block
73
+ result = {}
74
+ keys = keys.is_a?(Array) ? keys : [keys]
75
+ ids = ids.is_a?(Array) ? ids : [ids]
76
+ keys.each_with_index do |key, index|
77
+ with_stream_at(key) do |stream|
78
+ data = stream.read(ids[index], *args)
79
+ result[key] = data unless data.empty?
80
+ end
81
+ end
82
+ result
83
+ end
84
+
85
+ private
86
+
87
+ def with_stream_at(key, &blk)
88
+ with_thing_at(key, :assert_streamy, proc { Stream.new }, &blk)
89
+ end
90
+
91
+ def streamy?(key)
92
+ data[key].nil? || data[key].is_a?(Stream)
93
+ end
94
+
95
+ def assert_streamy(key)
96
+ unless streamy?(key)
97
+ raise Redis::CommandError,
98
+ 'WRONGTYPE Operation against a key holding the wrong kind of value'
99
+ end
100
+ end
101
+ end
102
+ end
@@ -38,7 +38,7 @@ class MockRedis
38
38
  type, offset = args.shift(2)
39
39
 
40
40
  is_signed = type.slice(0) == 'i'
41
- type_size = type[1..-1].to_i
41
+ type_size = type[1..].to_i
42
42
 
43
43
  if (type_size > 64 && is_signed) || (type_size >= 64 && !is_signed)
44
44
  raise Redis::CommandError,
@@ -47,7 +47,7 @@ class MockRedis
47
47
  end
48
48
 
49
49
  if offset.to_s[0] == '#'
50
- offset = offset[1..-1].to_i * type_size
50
+ offset = offset[1..].to_i * type_size
51
51
  end
52
52
 
53
53
  bits = []
@@ -85,17 +85,15 @@ class MockRedis
85
85
  end
86
86
 
87
87
  def get(key)
88
+ key = key.to_s
88
89
  assert_stringy(key)
89
90
  data[key]
90
91
  end
91
92
 
92
- def [](key)
93
- get(key)
94
- end
95
-
96
93
  def getbit(key, offset)
97
94
  assert_stringy(key)
98
95
 
96
+ offset = offset.to_i
99
97
  offset_of_byte = offset / 8
100
98
  offset_within_byte = offset % 8
101
99
 
@@ -109,6 +107,12 @@ class MockRedis
109
107
  end
110
108
  end
111
109
 
110
+ def getdel(key)
111
+ value = get(key)
112
+ del(key)
113
+ value
114
+ end
115
+
112
116
  def getrange(key, start, stop)
113
117
  assert_stringy(key)
114
118
  (data[key] || '')[start..stop]
@@ -156,12 +160,16 @@ class MockRedis
156
160
  new_value
157
161
  end
158
162
 
159
- def mget(*keys)
163
+ def mget(*keys, &blk)
164
+ keys.flatten!
165
+
160
166
  assert_has_args(keys, 'mget')
161
167
 
162
- keys.map do |key|
168
+ data = keys.map do |key|
163
169
  get(key) if stringy?(key)
164
170
  end
171
+
172
+ blk ? blk.call(data) : data
165
173
  end
166
174
 
167
175
  def mapped_mget(*keys)
@@ -170,6 +178,8 @@ class MockRedis
170
178
 
171
179
  def mset(*kvpairs)
172
180
  assert_has_args(kvpairs, 'mset')
181
+ kvpairs = kvpairs.first if kvpairs.size == 1 && kvpairs.first.is_a?(Enumerable)
182
+
173
183
  if kvpairs.length.odd?
174
184
  raise Redis::CommandError, 'ERR wrong number of arguments for MSET'
175
185
  end
@@ -188,7 +198,7 @@ class MockRedis
188
198
  def msetnx(*kvpairs)
189
199
  assert_has_args(kvpairs, 'msetnx')
190
200
 
191
- if kvpairs.each_slice(2).any? { |(k, _)| exists(k) }
201
+ if kvpairs.each_slice(2).any? { |(k, _)| exists?(k) }
192
202
  false
193
203
  else
194
204
  mset(*kvpairs)
@@ -200,18 +210,23 @@ class MockRedis
200
210
  msetnx(*hash.to_a.flatten)
201
211
  end
202
212
 
203
- def set(key, value, options = {})
213
+ # Parameter list required to ensure the ArgumentError is returned correctly
214
+ # rubocop:disable Metrics/ParameterLists
215
+ def set(key, value, _hash = nil, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil,
216
+ keepttl: nil, get: nil)
217
+ key = key.to_s
218
+ retval = self.get(key) if get
219
+
204
220
  return_true = false
205
- options = options.dup
206
- if options.delete(:nx)
207
- if exists(key)
221
+ if nx
222
+ if exists?(key)
208
223
  return false
209
224
  else
210
225
  return_true = true
211
226
  end
212
227
  end
213
- if options.delete(:xx)
214
- if exists(key)
228
+ if xx
229
+ if exists?(key)
215
230
  return_true = true
216
231
  else
217
232
  return false
@@ -219,21 +234,42 @@ class MockRedis
219
234
  end
220
235
  data[key] = value.to_s
221
236
 
222
- # take latter
223
- expire_option = options.to_a.last
224
- if expire_option
225
- type, duration = expire_option
226
- if duration == 0
237
+ remove_expiration(key) unless keepttl
238
+ if ex
239
+ if ex == 0
227
240
  raise Redis::CommandError, 'ERR invalid expire time in set'
228
241
  end
229
- expire(key, type.to_sym == :ex ? duration : duration / 1000.0)
242
+ expire(key, ex)
243
+ end
244
+
245
+ if px
246
+ if px == 0
247
+ raise Redis::CommandError, 'ERR invalid expire time in set'
248
+ end
249
+ pexpire(key, px)
250
+ end
251
+
252
+ if exat
253
+ if exat == 0
254
+ raise Redis::CommandError, 'ERR invalid expire time in set'
255
+ end
256
+ expireat(key, exat)
230
257
  end
231
- return_true ? true : 'OK'
232
- end
233
258
 
234
- def []=(key, value, options = {})
235
- set(key, value, options)
259
+ if pxat
260
+ if pxat == 0
261
+ raise Redis::CommandError, 'ERR invalid expire time in set'
262
+ end
263
+ pexpireat(key, pxat)
264
+ end
265
+
266
+ if get
267
+ retval
268
+ else
269
+ return_true ? true : 'OK'
270
+ end
236
271
  end
272
+ # rubocop:enable Metrics/ParameterLists
237
273
 
238
274
  def setbit(key, offset, value)
239
275
  assert_stringy(key, 'ERR bit is not an integer or out of range')
@@ -241,6 +277,7 @@ class MockRedis
241
277
 
242
278
  str = data[key] || ''
243
279
 
280
+ offset = offset.to_i
244
281
  offset_of_byte = offset / 8
245
282
  offset_within_byte = offset % 8
246
283
 
@@ -309,13 +346,27 @@ class MockRedis
309
346
  end
310
347
 
311
348
  def setex(key, seconds, value)
312
- set(key, value)
313
- expire(key, seconds)
314
- 'OK'
349
+ if seconds <= 0
350
+ raise Redis::CommandError, 'ERR invalid expire time in setex'
351
+ else
352
+ set(key, value)
353
+ expire(key, seconds)
354
+ 'OK'
355
+ end
356
+ end
357
+
358
+ def psetex(key, milliseconds, value)
359
+ if milliseconds <= 0
360
+ raise Redis::CommandError, 'ERR invalid expire time in psetex'
361
+ else
362
+ set(key, value)
363
+ pexpire(key, milliseconds)
364
+ 'OK'
365
+ end
315
366
  end
316
367
 
317
368
  def setnx(key, value)
318
- if exists(key)
369
+ if exists?(key)
319
370
  false
320
371
  else
321
372
  set(key, value)
@@ -329,7 +380,7 @@ class MockRedis
329
380
  old_value = (data[key] || '')
330
381
 
331
382
  prefix = zero_pad(old_value[0...offset], offset)
332
- data[key] = prefix + value + (old_value[(offset + value.length)..-1] || '')
383
+ data[key] = prefix + value + (old_value[(offset + value.length)..] || '')
333
384
  data[key].length
334
385
  end
335
386
 
@@ -11,13 +11,13 @@ class MockRedis
11
11
  def initialize(db)
12
12
  @db = db
13
13
  @transaction_futures = []
14
- @in_multi = false
14
+ @multi_stack = []
15
15
  @multi_block_given = false
16
16
  end
17
17
 
18
- def method_missing(method, *args, &block)
19
- if @in_multi
20
- future = MockRedis::Future.new([method, *args])
18
+ ruby2_keywords def method_missing(method, *args, &block)
19
+ if in_multi?
20
+ future = MockRedis::Future.new([method, *args], block)
21
21
  @transaction_futures << future
22
22
 
23
23
  if @multi_block_given
@@ -35,45 +35,54 @@ class MockRedis
35
35
  super
36
36
  @db = @db.clone
37
37
  @transaction_futures = @transaction_futures.clone
38
+ @multi_stack = @multi_stack.clone
38
39
  end
39
40
 
40
41
  def discard
41
- unless @in_multi
42
+ unless in_multi?
42
43
  raise Redis::CommandError, 'ERR DISCARD without MULTI'
43
44
  end
44
- @in_multi = false
45
- @multi_block_given = false
45
+ pop_multi
46
+
46
47
  @transaction_futures = []
47
48
  'OK'
48
49
  end
49
50
 
50
51
  def exec
51
- unless @in_multi
52
+ unless in_multi?
52
53
  raise Redis::CommandError, 'ERR EXEC without MULTI'
53
54
  end
54
- @in_multi = false
55
+ pop_multi
56
+ return if in_multi?
55
57
  @multi_block_given = false
56
58
 
57
59
  responses = @transaction_futures.map do |future|
58
- begin
59
- result = send(*future.command)
60
- future.store_result(result)
61
- result
62
- rescue StandardError => e
63
- e
64
- end
60
+ result = send(*future.command)
61
+ future.store_result(result)
62
+ future.value
63
+ rescue StandardError => e
64
+ e
65
65
  end
66
66
 
67
67
  @transaction_futures = []
68
68
  responses
69
69
  end
70
70
 
71
+ def in_multi?
72
+ @multi_stack.any?
73
+ end
74
+
75
+ def push_multi
76
+ @multi_stack.push(@multi_stack.size + 1)
77
+ end
78
+
79
+ def pop_multi
80
+ @multi_stack.pop
81
+ end
82
+
71
83
  def multi
72
- if @in_multi
73
- raise Redis::CommandError, 'ERR MULTI calls can not be nested'
74
- end
75
- @in_multi = true
76
84
  if block_given?
85
+ push_multi
77
86
  @multi_block_given = true
78
87
  begin
79
88
  yield(self)
@@ -83,6 +92,8 @@ class MockRedis
83
92
  raise e
84
93
  end
85
94
  else
95
+ raise Redis::CommandError, 'ERR MULTI calls can not be nested' if in_multi?
96
+ push_multi
86
97
  'OK'
87
98
  end
88
99
  end
@@ -95,7 +106,7 @@ class MockRedis
95
106
  'OK'
96
107
  end
97
108
 
98
- def watch(_)
109
+ def watch(*_)
99
110
  if block_given?
100
111
  yield self
101
112
  else
@@ -18,7 +18,7 @@ class MockRedis
18
18
  end
19
19
 
20
20
  def clean_up_empties_at(key)
21
- if data[key] && data[key].empty?
21
+ if data[key]&.empty? && data[key] != '' && !data[key].is_a?(Stream)
22
22
  del(key)
23
23
  end
24
24
  end
@@ -28,12 +28,17 @@ class MockRedis
28
28
  cursor = cursor.to_i
29
29
  match = opts[:match] || '*'
30
30
  key = opts[:key] || lambda { |x| x }
31
+ type_opt = opts[:type]
32
+ filtered_values = []
31
33
 
32
34
  limit = cursor + count
33
35
  next_cursor = limit >= values.length ? '0' : limit.to_s
34
36
 
35
- filtered_values = values[cursor...limit].select do |val|
36
- redis_pattern_to_ruby_regex(match).match(key.call(val))
37
+ unless values[cursor...limit].nil?
38
+ filtered_values = values[cursor...limit].select do |val|
39
+ redis_pattern_to_ruby_regex(match).match(key.call(val)) &&
40
+ (type_opt.nil? || type(val) == type_opt)
41
+ end
37
42
  end
38
43
 
39
44
  [next_cursor, filtered_values]
@@ -65,7 +70,7 @@ class MockRedis
65
70
  end
66
71
 
67
72
  def left_pad(str, size)
68
- str = '0' + str while str.length < size
73
+ str = "0#{str}" while str.length < size
69
74
 
70
75
  str
71
76
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  class MockRedis
5
- VERSION = '0.19.0'.freeze
5
+ VERSION = '0.44.0'
6
6
  end
@@ -10,9 +10,11 @@ class MockRedis
10
10
 
11
11
  def_delegators :members, :empty?, :include?, :size
12
12
 
13
- def initialize
13
+ def initialize(enum = nil)
14
14
  @members = Set.new
15
- @scores = {}
15
+ @members.merge(enum) if enum
16
+
17
+ @scores = {}
16
18
  end
17
19
 
18
20
  def initialize_copy(source)
@@ -23,12 +25,7 @@ class MockRedis
23
25
 
24
26
  def add(score, member)
25
27
  members.add(member)
26
- scores[member] =
27
- if score.to_f.to_i == score.to_f
28
- score.to_f.to_i
29
- else
30
- score.to_f
31
- end
28
+ scores[member] = score.to_f
32
29
  self
33
30
  end
34
31
 
@@ -11,7 +11,7 @@ class MockRedis
11
11
  zadd_options = {}
12
12
  zadd_options = args.pop if args.last.is_a?(Hash)
13
13
 
14
- if zadd_options && zadd_options.include?(:nx) && zadd_options.include?(:xx)
14
+ if zadd_options&.include?(:nx) && zadd_options&.include?(:xx)
15
15
  raise Redis::CommandError, 'ERR XX and NX options at the same time are not compatible'
16
16
  end
17
17
 
@@ -26,7 +26,7 @@ class MockRedis
26
26
  end
27
27
 
28
28
  def zadd_one_member(key, score, member, zadd_options = {})
29
- assert_scorey(score) unless score =~ /(\+|\-)inf/
29
+ assert_scorey(score) unless score.to_s =~ /(\+|-)inf/
30
30
 
31
31
  with_zset_at(key) do |zset|
32
32
  if zadd_options[:incr]
@@ -147,7 +147,7 @@ class MockRedis
147
147
  else
148
148
  retval = args.map { |member| !!zscore(key, member.to_s) }.count(true)
149
149
  with_zset_at(key) do |z|
150
- args.each { |member| z.delete?(member) }
150
+ args.each { |member| z.delete?(member.to_s) }
151
151
  end
152
152
  end
153
153
  end
@@ -155,6 +155,24 @@ class MockRedis
155
155
  retval
156
156
  end
157
157
 
158
+ def zpopmin(key, count = 1)
159
+ with_zset_at(key) do |z|
160
+ pairs = z.sorted.first(count)
161
+ pairs.each { |pair| z.delete?(pair.last) }
162
+ retval = to_response(pairs, with_scores: true)
163
+ count == 1 ? retval.first : retval
164
+ end
165
+ end
166
+
167
+ def zpopmax(key, count = 1)
168
+ with_zset_at(key) do |z|
169
+ pairs = z.sorted.reverse.first(count)
170
+ pairs.each { |pair| z.delete?(pair.last) }
171
+ retval = to_response(pairs, with_scores: true)
172
+ count == 1 ? retval.first : retval
173
+ end
174
+ end
175
+
158
176
  def zrevrange(key, start, stop, options = {})
159
177
  with_zset_at(key) do |z|
160
178
  to_response(z.sorted.reverse[start..stop] || [], options)
@@ -211,7 +229,16 @@ class MockRedis
211
229
  def zscore(key, member)
212
230
  with_zset_at(key) do |z|
213
231
  score = z.score(member.to_s)
214
- score.to_f if score
232
+ score&.to_f
233
+ end
234
+ end
235
+
236
+ def zmscore(key, *members)
237
+ with_zset_at(key) do |z|
238
+ members.map do |member|
239
+ score = z.score(member.to_s)
240
+ score&.to_f
241
+ end
215
242
  end
216
243
  end
217
244
 
@@ -264,7 +291,7 @@ class MockRedis
264
291
  raise Redis::CommandError, 'ERR syntax error'
265
292
  end
266
293
 
267
- with_zsets_at(*keys) do |*zsets|
294
+ with_zsets_at(*keys, coercible: true) do |*zsets|
268
295
  zsets.zip(weights).map do |(zset, weight)|
269
296
  zset.reduce(Zset.new) do |acc, (score, member)|
270
297
  acc.add(score * weight, member)
@@ -275,16 +302,30 @@ class MockRedis
275
302
  end
276
303
  end
277
304
 
278
- def with_zset_at(key, &blk)
279
- with_thing_at(key, :assert_zsety, proc { Zset.new }, &blk)
305
+ def coerce_to_zset(set)
306
+ zset = Zset.new
307
+ set.each do |member|
308
+ zset.add(1.0, member)
309
+ end
310
+ zset
280
311
  end
281
312
 
282
- def with_zsets_at(*keys, &blk)
313
+ def with_zset_at(key, coercible: false, &blk)
314
+ if coercible
315
+ with_thing_at(key, :assert_coercible_zsety, proc { Zset.new }) do |value|
316
+ blk.call value.is_a?(Set) ? coerce_to_zset(value) : value
317
+ end
318
+ else
319
+ with_thing_at(key, :assert_zsety, proc { Zset.new }, &blk)
320
+ end
321
+ end
322
+
323
+ def with_zsets_at(*keys, coercible: false, &blk)
283
324
  if keys.length == 1
284
- with_zset_at(keys.first, &blk)
325
+ with_zset_at(keys.first, coercible: coercible, &blk)
285
326
  else
286
- with_zset_at(keys.first) do |set|
287
- with_zsets_at(*(keys[1..-1])) do |*sets|
327
+ with_zset_at(keys.first, coercible: coercible) do |set|
328
+ with_zsets_at(*(keys[1..]), coercible: coercible) do |*sets|
288
329
  yield(*([set] + sets))
289
330
  end
290
331
  end
@@ -295,6 +336,10 @@ class MockRedis
295
336
  data[key].nil? || data[key].is_a?(Zset)
296
337
  end
297
338
 
339
+ def coercible_zsety?(key)
340
+ zsety?(key) || data[key].is_a?(Set)
341
+ end
342
+
298
343
  def assert_zsety(key)
299
344
  unless zsety?(key)
300
345
  raise Redis::CommandError,
@@ -302,13 +347,20 @@ class MockRedis
302
347
  end
303
348
  end
304
349
 
350
+ def assert_coercible_zsety(key)
351
+ unless coercible_zsety?(key)
352
+ raise Redis::CommandError,
353
+ 'WRONGTYPE Operation against a key holding the wrong kind of value'
354
+ end
355
+ end
356
+
305
357
  def looks_like_float?(x)
306
358
  # ugh, exceptions for flow control.
307
359
  !!Float(x) rescue false
308
360
  end
309
361
 
310
362
  def assert_scorey(value, message = 'ERR value is not a valid float')
311
- return if value =~ /\(?(\-|\+)inf/
363
+ return if value.to_s =~ /\(?(-|\+)inf/
312
364
 
313
365
  value = $1 if value.to_s =~ /\((.*)/
314
366
  unless looks_like_float?(value)