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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +161 -0
- data/LICENSE.md +21 -0
- data/README.md +41 -17
- data/lib/mock_redis/connection_method.rb +13 -0
- data/lib/mock_redis/database.rb +116 -41
- data/lib/mock_redis/expire_wrapper.rb +1 -1
- data/lib/mock_redis/future.rb +1 -1
- data/lib/mock_redis/geospatial_methods.rb +13 -21
- data/lib/mock_redis/hash_methods.rb +34 -15
- data/lib/mock_redis/indifferent_hash.rb +0 -8
- data/lib/mock_redis/info_method.rb +2 -2
- data/lib/mock_redis/list_methods.rb +39 -4
- data/lib/mock_redis/memory_method.rb +11 -0
- data/lib/mock_redis/multi_db_wrapper.rb +4 -4
- data/lib/mock_redis/pipelined_wrapper.rb +32 -15
- data/lib/mock_redis/set_methods.rb +34 -6
- data/lib/mock_redis/stream/id.rb +58 -0
- data/lib/mock_redis/stream.rb +88 -0
- data/lib/mock_redis/stream_methods.rb +102 -0
- data/lib/mock_redis/string_methods.rb +81 -30
- data/lib/mock_redis/transaction_wrapper.rb +32 -21
- data/lib/mock_redis/utility_methods.rb +9 -4
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset.rb +5 -8
- data/lib/mock_redis/zset_methods.rb +64 -12
- data/lib/mock_redis.rb +38 -10
- metadata +40 -336
- data/.gitignore +0 -5
- data/.mailmap +0 -2
- data/.overcommit.yml +0 -21
- data/.rspec +0 -1
- data/.rubocop.yml +0 -121
- data/.rubocop_todo.yml +0 -35
- data/.simplecov +0 -4
- data/.travis.yml +0 -33
- data/Gemfile +0 -12
- data/LICENSE +0 -19
- data/Rakefile +0 -2
- data/mock_redis.gemspec +0 -30
- data/spec/client_spec.rb +0 -17
- data/spec/cloning_spec.rb +0 -95
- data/spec/commands/append_spec.rb +0 -24
- data/spec/commands/auth_spec.rb +0 -7
- data/spec/commands/bgrewriteaof_spec.rb +0 -7
- data/spec/commands/bgsave_spec.rb +0 -7
- data/spec/commands/bitcount_spec.rb +0 -25
- data/spec/commands/bitfield_spec.rb +0 -169
- data/spec/commands/blpop_spec.rb +0 -59
- data/spec/commands/brpop_spec.rb +0 -58
- data/spec/commands/brpoplpush_spec.rb +0 -52
- data/spec/commands/connected_spec.rb +0 -7
- data/spec/commands/dbsize_spec.rb +0 -18
- data/spec/commands/decr_spec.rb +0 -34
- data/spec/commands/decrby_spec.rb +0 -34
- data/spec/commands/del_spec.rb +0 -35
- data/spec/commands/disconnect_spec.rb +0 -7
- data/spec/commands/echo_spec.rb +0 -11
- data/spec/commands/eval_spec.rb +0 -7
- data/spec/commands/evalsha_spec.rb +0 -10
- data/spec/commands/exists_spec.rb +0 -14
- data/spec/commands/expire_spec.rb +0 -111
- data/spec/commands/expireat_spec.rb +0 -47
- data/spec/commands/flushall_spec.rb +0 -38
- data/spec/commands/flushdb_spec.rb +0 -38
- data/spec/commands/future_spec.rb +0 -20
- data/spec/commands/geoadd_spec.rb +0 -58
- data/spec/commands/geodist_spec.rb +0 -114
- data/spec/commands/geohash_spec.rb +0 -52
- data/spec/commands/geopos_spec.rb +0 -55
- data/spec/commands/get_spec.rb +0 -30
- data/spec/commands/getbit_spec.rb +0 -34
- data/spec/commands/getrange_spec.rb +0 -22
- data/spec/commands/getset_spec.rb +0 -23
- data/spec/commands/hash_operator_spec.rb +0 -21
- data/spec/commands/hdel_spec.rb +0 -54
- data/spec/commands/hexists_spec.rb +0 -27
- data/spec/commands/hget_spec.rb +0 -28
- data/spec/commands/hgetall_spec.rb +0 -32
- data/spec/commands/hincrby_spec.rb +0 -58
- data/spec/commands/hincrbyfloat_spec.rb +0 -58
- data/spec/commands/hkeys_spec.rb +0 -19
- data/spec/commands/hlen_spec.rb +0 -19
- data/spec/commands/hmget_spec.rb +0 -40
- data/spec/commands/hmset_spec.rb +0 -43
- data/spec/commands/hscan_each_spec.rb +0 -48
- data/spec/commands/hscan_spec.rb +0 -27
- data/spec/commands/hset_spec.rb +0 -38
- data/spec/commands/hsetnx_spec.rb +0 -44
- data/spec/commands/hvals_spec.rb +0 -19
- data/spec/commands/incr_spec.rb +0 -34
- data/spec/commands/incrby_spec.rb +0 -44
- data/spec/commands/incrbyfloat_spec.rb +0 -44
- data/spec/commands/info_spec.rb +0 -62
- data/spec/commands/keys_spec.rb +0 -122
- data/spec/commands/lastsave_spec.rb +0 -8
- data/spec/commands/lindex_spec.rb +0 -49
- data/spec/commands/linsert_spec.rb +0 -68
- data/spec/commands/llen_spec.rb +0 -16
- data/spec/commands/lpop_spec.rb +0 -34
- data/spec/commands/lpush_spec.rb +0 -43
- data/spec/commands/lpushx_spec.rb +0 -46
- data/spec/commands/lrange_spec.rb +0 -51
- data/spec/commands/lrem_spec.rb +0 -80
- data/spec/commands/lset_spec.rb +0 -43
- data/spec/commands/ltrim_spec.rb +0 -45
- data/spec/commands/mapped_hmget_spec.rb +0 -29
- data/spec/commands/mapped_hmset_spec.rb +0 -47
- data/spec/commands/mapped_mget_spec.rb +0 -22
- data/spec/commands/mapped_mset_spec.rb +0 -19
- data/spec/commands/mapped_msetnx_spec.rb +0 -26
- data/spec/commands/mget_spec.rb +0 -34
- data/spec/commands/move_spec.rb +0 -147
- data/spec/commands/mset_spec.rb +0 -29
- data/spec/commands/msetnx_spec.rb +0 -40
- data/spec/commands/persist_spec.rb +0 -48
- data/spec/commands/pexpire_spec.rb +0 -86
- data/spec/commands/pexpireat_spec.rb +0 -48
- data/spec/commands/ping_spec.rb +0 -7
- data/spec/commands/pipelined_spec.rb +0 -42
- data/spec/commands/pttl_spec.rb +0 -41
- data/spec/commands/quit_spec.rb +0 -7
- data/spec/commands/randomkey_spec.rb +0 -20
- data/spec/commands/rename_spec.rb +0 -42
- data/spec/commands/renamenx_spec.rb +0 -41
- data/spec/commands/rpop_spec.rb +0 -34
- data/spec/commands/rpoplpush_spec.rb +0 -50
- data/spec/commands/rpush_spec.rb +0 -43
- data/spec/commands/rpushx_spec.rb +0 -46
- data/spec/commands/sadd_spec.rb +0 -45
- data/spec/commands/save_spec.rb +0 -7
- data/spec/commands/scan_each_spec.rb +0 -39
- data/spec/commands/scan_spec.rb +0 -55
- data/spec/commands/scard_spec.rb +0 -18
- data/spec/commands/script_spec.rb +0 -9
- data/spec/commands/sdiff_spec.rb +0 -47
- data/spec/commands/sdiffstore_spec.rb +0 -58
- data/spec/commands/select_spec.rb +0 -61
- data/spec/commands/set_spec.rb +0 -63
- data/spec/commands/setbit_spec.rb +0 -54
- data/spec/commands/setex_spec.rb +0 -22
- data/spec/commands/setnx_spec.rb +0 -25
- data/spec/commands/setrange_spec.rb +0 -30
- data/spec/commands/sinter_spec.rb +0 -39
- data/spec/commands/sinterstore_spec.rb +0 -53
- data/spec/commands/sismember_spec.rb +0 -29
- data/spec/commands/smembers_spec.rb +0 -28
- data/spec/commands/smove_spec.rb +0 -41
- data/spec/commands/sort_list_spec.rb +0 -21
- data/spec/commands/sort_set_spec.rb +0 -21
- data/spec/commands/sort_zset_spec.rb +0 -21
- data/spec/commands/spop_spec.rb +0 -25
- data/spec/commands/srandmember_spec.rb +0 -49
- data/spec/commands/srem_spec.rb +0 -40
- data/spec/commands/sscan_each_spec.rb +0 -48
- data/spec/commands/sscan_spec.rb +0 -39
- data/spec/commands/strlen_spec.rb +0 -18
- data/spec/commands/sunion_spec.rb +0 -42
- data/spec/commands/sunionstore_spec.rb +0 -59
- data/spec/commands/ttl_spec.rb +0 -40
- data/spec/commands/type_spec.rb +0 -36
- data/spec/commands/unwatch_spec.rb +0 -7
- data/spec/commands/watch_spec.rb +0 -16
- data/spec/commands/zadd_spec.rb +0 -123
- data/spec/commands/zcard_spec.rb +0 -19
- data/spec/commands/zcount_spec.rb +0 -39
- data/spec/commands/zincrby_spec.rb +0 -31
- data/spec/commands/zinterstore_spec.rb +0 -96
- data/spec/commands/zrange_spec.rb +0 -80
- data/spec/commands/zrangebyscore_spec.rb +0 -83
- data/spec/commands/zrank_spec.rb +0 -29
- data/spec/commands/zrem_spec.rb +0 -43
- data/spec/commands/zremrangebyrank_spec.rb +0 -27
- data/spec/commands/zremrangebyscore_spec.rb +0 -35
- data/spec/commands/zrevrange_spec.rb +0 -56
- data/spec/commands/zrevrangebyscore_spec.rb +0 -58
- data/spec/commands/zrevrank_spec.rb +0 -29
- data/spec/commands/zscan_each_spec.rb +0 -48
- data/spec/commands/zscan_spec.rb +0 -26
- data/spec/commands/zscore_spec.rb +0 -22
- data/spec/commands/zunionstore_spec.rb +0 -104
- data/spec/mock_redis_spec.rb +0 -86
- data/spec/spec_helper.rb +0 -63
- data/spec/support/redis_multiplexer.rb +0 -106
- data/spec/support/shared_examples/only_operates_on_hashes.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_lists.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_sets.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_strings.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_zsets.rb +0 -57
- data/spec/support/shared_examples/sorts_enumerables.rb +0 -56
- 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
|
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
|
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
|
-
|
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
|
-
|
206
|
-
|
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
|
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
|
-
|
223
|
-
|
224
|
-
|
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,
|
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
|
-
|
235
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
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)
|
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
|
-
@
|
14
|
+
@multi_stack = []
|
15
15
|
@multi_block_given = false
|
16
16
|
end
|
17
17
|
|
18
|
-
def method_missing(method, *args, &block)
|
19
|
-
if
|
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
|
42
|
+
unless in_multi?
|
42
43
|
raise Redis::CommandError, 'ERR DISCARD without MULTI'
|
43
44
|
end
|
44
|
-
|
45
|
-
|
45
|
+
pop_multi
|
46
|
+
|
46
47
|
@transaction_futures = []
|
47
48
|
'OK'
|
48
49
|
end
|
49
50
|
|
50
51
|
def exec
|
51
|
-
unless
|
52
|
+
unless in_multi?
|
52
53
|
raise Redis::CommandError, 'ERR EXEC without MULTI'
|
53
54
|
end
|
54
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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].
|
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
|
-
|
36
|
-
|
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 =
|
73
|
+
str = "0#{str}" while str.length < size
|
69
74
|
|
70
75
|
str
|
71
76
|
end
|
data/lib/mock_redis/version.rb
CHANGED
data/lib/mock_redis/zset.rb
CHANGED
@@ -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
|
-
@
|
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
|
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 =~ /(
|
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
|
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
|
279
|
-
|
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
|
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
|
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 =~ /\(?(
|
363
|
+
return if value.to_s =~ /\(?(-|\+)inf/
|
312
364
|
|
313
365
|
value = $1 if value.to_s =~ /\((.*)/
|
314
366
|
unless looks_like_float?(value)
|