mock_redis 0.22.0 → 0.27.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/.rubocop.yml +26 -5
- data/.rubocop_todo.yml +1 -1
- data/.travis.yml +1 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +4 -2
- data/lib/mock_redis.rb +1 -8
- data/lib/mock_redis/connection_method.rb +13 -0
- data/lib/mock_redis/database.rb +44 -14
- data/lib/mock_redis/expire_wrapper.rb +1 -1
- data/lib/mock_redis/future.rb +1 -1
- data/lib/mock_redis/geospatial_methods.rb +5 -5
- data/lib/mock_redis/hash_methods.rb +9 -4
- data/lib/mock_redis/info_method.rb +2 -2
- data/lib/mock_redis/multi_db_wrapper.rb +3 -3
- data/lib/mock_redis/pipelined_wrapper.rb +1 -1
- data/lib/mock_redis/stream.rb +22 -2
- data/lib/mock_redis/stream/id.rb +1 -1
- data/lib/mock_redis/stream_methods.rb +16 -1
- data/lib/mock_redis/string_methods.rb +27 -20
- data/lib/mock_redis/transaction_wrapper.rb +3 -3
- data/lib/mock_redis/utility_methods.rb +1 -1
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset_methods.rb +34 -9
- data/mock_redis.gemspec +1 -1
- data/spec/commands/blpop_spec.rb +0 -6
- data/spec/commands/brpop_spec.rb +6 -5
- data/spec/commands/connection_spec.rb +15 -0
- data/spec/commands/del_spec.rb +17 -0
- data/spec/commands/dump_spec.rb +19 -0
- data/spec/commands/exists_spec.rb +34 -5
- data/spec/commands/future_spec.rb +11 -1
- data/spec/commands/geoadd_spec.rb +1 -1
- data/spec/commands/hset_spec.rb +6 -6
- data/spec/commands/keys_spec.rb +17 -0
- data/spec/commands/mget_spec.rb +6 -0
- data/spec/commands/move_spec.rb +5 -5
- data/spec/commands/pipelined_spec.rb +20 -0
- data/spec/commands/restore_spec.rb +47 -0
- data/spec/commands/set_spec.rb +59 -9
- data/spec/commands/setbit_spec.rb +1 -0
- data/spec/commands/setex_spec.rb +16 -0
- data/spec/commands/srandmember_spec.rb +1 -1
- data/spec/commands/xadd_spec.rb +23 -3
- data/spec/commands/xlen_spec.rb +3 -1
- data/spec/commands/xrange_spec.rb +13 -0
- data/spec/commands/xread_spec.rb +66 -0
- data/spec/commands/xtrim_spec.rb +6 -0
- data/spec/commands/zinterstore_spec.rb +34 -0
- data/spec/commands/zrange_spec.rb +1 -1
- data/spec/commands/zrangebyscore_spec.rb +1 -1
- data/spec/commands/zrevrange_spec.rb +1 -1
- data/spec/commands/zrevrangebyscore_spec.rb +1 -1
- data/spec/commands/zunionstore_spec.rb +33 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/redis_multiplexer.rb +2 -1
- data/spec/transactions_spec.rb +16 -0
- metadata +14 -5
@@ -18,7 +18,7 @@ class MockRedis
|
|
18
18
|
@pipelined_futures = @pipelined_futures.clone
|
19
19
|
end
|
20
20
|
|
21
|
-
def method_missing(method, *args, &block)
|
21
|
+
ruby2_keywords def method_missing(method, *args, &block)
|
22
22
|
if in_pipeline?
|
23
23
|
future = MockRedis::Future.new([method, *args], block)
|
24
24
|
@pipelined_futures << future
|
data/lib/mock_redis/stream.rb
CHANGED
@@ -23,14 +23,26 @@ class MockRedis
|
|
23
23
|
|
24
24
|
def add(id, values)
|
25
25
|
@last_id = MockRedis::Stream::Id.new(id, min: @last_id)
|
26
|
+
if @last_id.to_s == '0-0'
|
27
|
+
raise Redis::CommandError,
|
28
|
+
'ERR The ID specified in XADD must be greater than 0-0'
|
29
|
+
end
|
26
30
|
members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]]
|
27
31
|
@last_id.to_s
|
28
32
|
end
|
29
33
|
|
30
34
|
def trim(count)
|
31
35
|
deleted = @members.size - count
|
32
|
-
|
33
|
-
|
36
|
+
if deleted > 0
|
37
|
+
@members = if count == 0
|
38
|
+
Set.new
|
39
|
+
else
|
40
|
+
@members.to_a[-count..-1].to_set
|
41
|
+
end
|
42
|
+
deleted
|
43
|
+
else
|
44
|
+
0
|
45
|
+
end
|
34
46
|
end
|
35
47
|
|
36
48
|
def range(start, finish, reversed, *opts_in)
|
@@ -45,6 +57,14 @@ class MockRedis
|
|
45
57
|
items
|
46
58
|
end
|
47
59
|
|
60
|
+
def read(id, *opts_in)
|
61
|
+
opts = options opts_in, %w[count block]
|
62
|
+
stream_id = MockRedis::Stream::Id.new(id)
|
63
|
+
items = members.select { |m| (stream_id < m[0]) }.map { |m| [m[0].to_s, m[1]] }
|
64
|
+
return items.first(opts['count'].to_i) if opts.key?('count')
|
65
|
+
items
|
66
|
+
end
|
67
|
+
|
48
68
|
def each
|
49
69
|
members.each { |m| yield m }
|
50
70
|
end
|
data/lib/mock_redis/stream/id.rb
CHANGED
@@ -31,7 +31,7 @@ class MockRedis
|
|
31
31
|
@timestamp = id
|
32
32
|
end
|
33
33
|
@sequence = @sequence.nil? ? sequence : @sequence.to_i
|
34
|
-
if
|
34
|
+
if self <= min
|
35
35
|
raise Redis::CommandError,
|
36
36
|
'ERR The ID specified in XADD is equal or smaller than ' \
|
37
37
|
'the target stream top item'
|
@@ -4,7 +4,6 @@ require 'mock_redis/stream'
|
|
4
4
|
|
5
5
|
# TODO: Implement the following commands
|
6
6
|
#
|
7
|
-
# * xread
|
8
7
|
# * xgroup
|
9
8
|
# * xreadgroup
|
10
9
|
# * xack
|
@@ -67,6 +66,22 @@ class MockRedis
|
|
67
66
|
end
|
68
67
|
end
|
69
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
|
+
|
70
85
|
private
|
71
86
|
|
72
87
|
def with_stream_at(key, &blk)
|
@@ -154,9 +154,10 @@ class MockRedis
|
|
154
154
|
end
|
155
155
|
|
156
156
|
def mget(*keys)
|
157
|
+
keys.flatten!
|
158
|
+
|
157
159
|
assert_has_args(keys, 'mget')
|
158
160
|
|
159
|
-
keys.flatten!
|
160
161
|
keys.map do |key|
|
161
162
|
get(key) if stringy?(key)
|
162
163
|
end
|
@@ -188,7 +189,7 @@ class MockRedis
|
|
188
189
|
def msetnx(*kvpairs)
|
189
190
|
assert_has_args(kvpairs, 'msetnx')
|
190
191
|
|
191
|
-
if kvpairs.each_slice(2).any? { |(k, _)| exists(k) }
|
192
|
+
if kvpairs.each_slice(2).any? { |(k, _)| exists?(k) }
|
192
193
|
false
|
193
194
|
else
|
194
195
|
mset(*kvpairs)
|
@@ -200,19 +201,20 @@ class MockRedis
|
|
200
201
|
msetnx(*hash.to_a.flatten)
|
201
202
|
end
|
202
203
|
|
203
|
-
|
204
|
+
# Parameer list required to ensure the ArgumentError is returned correctly
|
205
|
+
# rubocop:disable Metrics/ParameterLists
|
206
|
+
def set(key, value, ex: nil, px: nil, nx: nil, xx: nil, keepttl: nil)
|
204
207
|
key = key.to_s
|
205
208
|
return_true = false
|
206
|
-
|
207
|
-
|
208
|
-
if exists(key)
|
209
|
+
if nx
|
210
|
+
if exists?(key)
|
209
211
|
return false
|
210
212
|
else
|
211
213
|
return_true = true
|
212
214
|
end
|
213
215
|
end
|
214
|
-
if
|
215
|
-
if exists(key)
|
216
|
+
if xx
|
217
|
+
if exists?(key)
|
216
218
|
return_true = true
|
217
219
|
else
|
218
220
|
return false
|
@@ -220,24 +222,24 @@ class MockRedis
|
|
220
222
|
end
|
221
223
|
data[key] = value.to_s
|
222
224
|
|
223
|
-
|
224
|
-
if
|
225
|
-
if
|
225
|
+
remove_expiration(key) unless keepttl
|
226
|
+
if ex
|
227
|
+
if ex == 0
|
226
228
|
raise Redis::CommandError, 'ERR invalid expire time in set'
|
227
229
|
end
|
228
|
-
expire(key,
|
230
|
+
expire(key, ex)
|
229
231
|
end
|
230
232
|
|
231
|
-
|
232
|
-
|
233
|
-
if duration == 0
|
233
|
+
if px
|
234
|
+
if px == 0
|
234
235
|
raise Redis::CommandError, 'ERR invalid expire time in set'
|
235
236
|
end
|
236
|
-
|
237
|
+
pexpire(key, px)
|
237
238
|
end
|
238
239
|
|
239
240
|
return_true ? true : 'OK'
|
240
241
|
end
|
242
|
+
# rubocop:enable Metrics/ParameterLists
|
241
243
|
|
242
244
|
def setbit(key, offset, value)
|
243
245
|
assert_stringy(key, 'ERR bit is not an integer or out of range')
|
@@ -245,6 +247,7 @@ class MockRedis
|
|
245
247
|
|
246
248
|
str = data[key] || ''
|
247
249
|
|
250
|
+
offset = offset.to_i
|
248
251
|
offset_of_byte = offset / 8
|
249
252
|
offset_within_byte = offset % 8
|
250
253
|
|
@@ -313,13 +316,17 @@ class MockRedis
|
|
313
316
|
end
|
314
317
|
|
315
318
|
def setex(key, seconds, value)
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
+
if seconds <= 0
|
320
|
+
raise Redis::CommandError, 'ERR invalid expire time in setex'
|
321
|
+
else
|
322
|
+
set(key, value)
|
323
|
+
expire(key, seconds)
|
324
|
+
'OK'
|
325
|
+
end
|
319
326
|
end
|
320
327
|
|
321
328
|
def setnx(key, value)
|
322
|
-
if exists(key)
|
329
|
+
if exists?(key)
|
323
330
|
false
|
324
331
|
else
|
325
332
|
set(key, value)
|
@@ -15,9 +15,9 @@ class MockRedis
|
|
15
15
|
@multi_block_given = false
|
16
16
|
end
|
17
17
|
|
18
|
-
def method_missing(method, *args, &block)
|
18
|
+
ruby2_keywords def method_missing(method, *args, &block)
|
19
19
|
if in_multi?
|
20
|
-
future = MockRedis::Future.new([method, *args])
|
20
|
+
future = MockRedis::Future.new([method, *args], block)
|
21
21
|
@transaction_futures << future
|
22
22
|
|
23
23
|
if @multi_block_given
|
@@ -60,7 +60,7 @@ class MockRedis
|
|
60
60
|
begin
|
61
61
|
result = send(*future.command)
|
62
62
|
future.store_result(result)
|
63
|
-
|
63
|
+
future.value
|
64
64
|
rescue StandardError => e
|
65
65
|
e
|
66
66
|
end
|
data/lib/mock_redis/version.rb
CHANGED
@@ -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]
|
@@ -282,7 +282,7 @@ class MockRedis
|
|
282
282
|
raise Redis::CommandError, 'ERR syntax error'
|
283
283
|
end
|
284
284
|
|
285
|
-
with_zsets_at(*keys) do |*zsets|
|
285
|
+
with_zsets_at(*keys, coercible: true) do |*zsets|
|
286
286
|
zsets.zip(weights).map do |(zset, weight)|
|
287
287
|
zset.reduce(Zset.new) do |acc, (score, member)|
|
288
288
|
acc.add(score * weight, member)
|
@@ -293,16 +293,30 @@ class MockRedis
|
|
293
293
|
end
|
294
294
|
end
|
295
295
|
|
296
|
-
def
|
297
|
-
|
296
|
+
def coerce_to_zset(set)
|
297
|
+
zset = Zset.new
|
298
|
+
set.each do |member|
|
299
|
+
zset.add(1.0, member)
|
300
|
+
end
|
301
|
+
zset
|
298
302
|
end
|
299
303
|
|
300
|
-
def
|
304
|
+
def with_zset_at(key, coercible: false, &blk)
|
305
|
+
if coercible
|
306
|
+
with_thing_at(key, :assert_coercible_zsety, proc { Zset.new }) do |value|
|
307
|
+
blk.call value.is_a?(Set) ? coerce_to_zset(value) : value
|
308
|
+
end
|
309
|
+
else
|
310
|
+
with_thing_at(key, :assert_zsety, proc { Zset.new }, &blk)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def with_zsets_at(*keys, coercible: false, &blk)
|
301
315
|
if keys.length == 1
|
302
|
-
with_zset_at(keys.first, &blk)
|
316
|
+
with_zset_at(keys.first, coercible: coercible, &blk)
|
303
317
|
else
|
304
|
-
with_zset_at(keys.first) do |set|
|
305
|
-
with_zsets_at(*(keys[1..-1])) do |*sets|
|
318
|
+
with_zset_at(keys.first, coercible: coercible) do |set|
|
319
|
+
with_zsets_at(*(keys[1..-1]), coercible: coercible) do |*sets|
|
306
320
|
yield(*([set] + sets))
|
307
321
|
end
|
308
322
|
end
|
@@ -313,6 +327,10 @@ class MockRedis
|
|
313
327
|
data[key].nil? || data[key].is_a?(Zset)
|
314
328
|
end
|
315
329
|
|
330
|
+
def coercible_zsety?(key)
|
331
|
+
zsety?(key) || data[key].is_a?(Set)
|
332
|
+
end
|
333
|
+
|
316
334
|
def assert_zsety(key)
|
317
335
|
unless zsety?(key)
|
318
336
|
raise Redis::CommandError,
|
@@ -320,13 +338,20 @@ class MockRedis
|
|
320
338
|
end
|
321
339
|
end
|
322
340
|
|
341
|
+
def assert_coercible_zsety(key)
|
342
|
+
unless coercible_zsety?(key)
|
343
|
+
raise Redis::CommandError,
|
344
|
+
'WRONGTYPE Operation against a key holding the wrong kind of value'
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
323
348
|
def looks_like_float?(x)
|
324
349
|
# ugh, exceptions for flow control.
|
325
350
|
!!Float(x) rescue false
|
326
351
|
end
|
327
352
|
|
328
353
|
def assert_scorey(value, message = 'ERR value is not a valid float')
|
329
|
-
return if value =~ /\(?(\-|\+)inf/
|
354
|
+
return if value.to_s =~ /\(?(\-|\+)inf/
|
330
355
|
|
331
356
|
value = $1 if value.to_s =~ /\((.*)/
|
332
357
|
unless looks_like_float?(value)
|
data/mock_redis.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
|
24
24
|
s.required_ruby_version = '>= 2.4'
|
25
25
|
|
26
|
-
s.add_development_dependency 'redis', '~> 4.
|
26
|
+
s.add_development_dependency 'redis', '~> 4.2.0'
|
27
27
|
s.add_development_dependency 'rspec', '~> 3.0'
|
28
28
|
s.add_development_dependency 'rspec-its', '~> 1.0'
|
29
29
|
s.add_development_dependency 'timecop', '~> 0.9.1'
|
data/spec/commands/blpop_spec.rb
CHANGED
@@ -27,12 +27,6 @@ describe '#blpop(key [, key, ...,], timeout)' do
|
|
27
27
|
[@list1, 'one']
|
28
28
|
end
|
29
29
|
|
30
|
-
it 'raises an error on subsecond timeouts' do
|
31
|
-
lambda do
|
32
|
-
@redises.blpop(@list1, @list2, :timeout => 0.5)
|
33
|
-
end.should raise_error(Redis::CommandError)
|
34
|
-
end
|
35
|
-
|
36
30
|
it 'raises an error on negative timeout' do
|
37
31
|
lambda do
|
38
32
|
@redises.blpop(@list1, @list2, :timeout => -1)
|
data/spec/commands/brpop_spec.rb
CHANGED
@@ -26,11 +26,12 @@ describe '#brpop(key [, key, ...,], timeout)' do
|
|
26
26
|
[@list1, 'two']
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
29
|
+
# TODO: Not sure how redis-rb is handling this but they're not raising an error
|
30
|
+
# it 'raises an error on subsecond timeouts' do
|
31
|
+
# lambda do
|
32
|
+
# @redises.brpop(@list1, @list2, :timeout => 0.5)
|
33
|
+
# end.should raise_error(Redis::CommandError)
|
34
|
+
# end
|
34
35
|
|
35
36
|
it 'raises an error on negative timeout' do
|
36
37
|
lambda do
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe '#connection' do
|
4
|
+
let(:redis) { @redises.mock }
|
5
|
+
|
6
|
+
it 'returns the correct values' do
|
7
|
+
redis.connection.should == {
|
8
|
+
:host => '127.0.0.1',
|
9
|
+
:port => 6379,
|
10
|
+
:db => 0,
|
11
|
+
:id => 'redis://127.0.0.1:6379/0',
|
12
|
+
:location => '127.0.0.1:6379'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
data/spec/commands/del_spec.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe '#del(key [, key, ...])' do
|
4
|
+
before :all do
|
5
|
+
sleep 1 - (Time.now.to_f % 1)
|
6
|
+
end
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
# TODO: Redis appears to be returning a timestamp a few seconds in the future
|
10
|
+
# so we're ignoring the last 5 digits (time in milliseconds)
|
11
|
+
@redises._gsub(/\d{5}-\d/, '...-.')
|
12
|
+
end
|
13
|
+
|
4
14
|
it 'returns the number of keys deleted' do
|
5
15
|
@redises.set('mock-redis-test:1', 1)
|
6
16
|
@redises.set('mock-redis-test:2', 1)
|
@@ -32,4 +42,11 @@ describe '#del(key [, key, ...])' do
|
|
32
42
|
it 'raises an error if an empty array is given' do
|
33
43
|
expect { @redises.del [] }.to raise_error Redis::CommandError
|
34
44
|
end
|
45
|
+
|
46
|
+
it 'removes a stream key' do
|
47
|
+
@redises.xadd('mock-redis-stream', { key: 'value' }, maxlen: 0)
|
48
|
+
expect(@redises.exists?('mock-redis-stream')).to eq true
|
49
|
+
@redises.del('mock-redis-stream')
|
50
|
+
expect(@redises.exists?('mock-redis-stream')).to eq false
|
51
|
+
end
|
35
52
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe '#dump(key)' do
|
4
|
+
before do
|
5
|
+
@key = 'mock-redis-test:45794'
|
6
|
+
# These are mock-only, since our dump/restore implementations
|
7
|
+
# aren't compatible with real redis.
|
8
|
+
@mock = @redises.mock
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns nil for keys that do not exist' do
|
12
|
+
@mock.dump(@key).should be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns a serialized value for keys that do exist' do
|
16
|
+
@mock.set(@key, '2')
|
17
|
+
@mock.dump(@key).should == Marshal.dump('2')
|
18
|
+
end
|
19
|
+
end
|
@@ -1,14 +1,43 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe '#exists(
|
4
|
-
before { @
|
3
|
+
describe '#exists(*keys)' do
|
4
|
+
before { @key1 = 'mock-redis-test:exists1' }
|
5
|
+
before { @key2 = 'mock-redis-test:exists2' }
|
6
|
+
|
7
|
+
it 'returns 0 for keys that do not exist' do
|
8
|
+
@redises.exists(@key1).should == 0
|
9
|
+
@redises.exists(@key1, @key2).should == 0
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns 1 for keys that do exist' do
|
13
|
+
@redises.set(@key1, 1)
|
14
|
+
@redises.exists(@key1).should == 1
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns the count of all keys that exist' do
|
18
|
+
@redises.set(@key1, 1)
|
19
|
+
@redises.set(@key2, 1)
|
20
|
+
@redises.exists(@key1, @key2).should == 2
|
21
|
+
@redises.exists(@key1, @key2, 'does-not-exist').should == 2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#exists?(*keys)' do
|
26
|
+
before { @key1 = 'mock-redis-test:exists1' }
|
27
|
+
before { @key2 = 'mock-redis-test:exists2' }
|
5
28
|
|
6
29
|
it 'returns false for keys that do not exist' do
|
7
|
-
@redises.exists(@
|
30
|
+
@redises.exists?(@key1).should == false
|
31
|
+
@redises.exists?(@key1, @key2).should == false
|
8
32
|
end
|
9
33
|
|
10
34
|
it 'returns true for keys that do exist' do
|
11
|
-
@redises.set(@
|
12
|
-
@redises.exists(@
|
35
|
+
@redises.set(@key1, 1)
|
36
|
+
@redises.exists?(@key1).should == true
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns true if any keys exist' do
|
40
|
+
@redises.set(@key2, 1)
|
41
|
+
@redises.exists?(@key1, @key2).should == true
|
13
42
|
end
|
14
43
|
end
|