mock_redis 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9dd8cc0a4f19edccfb6c7b3f1a82724f0da757ad7139f4a576eed1d0b815b8d
4
- data.tar.gz: 980f4326447c75afb034b6292baab71bb723ca05689b04aa7ed4933bd8bbf356
3
+ metadata.gz: dd9fd0d5b4ccad106eee03697d49082baa7e34065ff76f67b7e18da9991847a9
4
+ data.tar.gz: 11f940cdee40f2646c3e6087faac7ac1c70e0aae5eaff7ddfc8839f0fb50e1b6
5
5
  SHA512:
6
- metadata.gz: 53a8419ef9f62a4d276993714241f22f9da4087df9ce69158821ea42c735e4fc022f4ab9431bbef268eb562130cc139308fd46ad9eeef8941ef95d9b56afc083
7
- data.tar.gz: 266de40ac11c22fa449d709380cec9461a93cd18c6950d31aee538b295c613ab2a0ee2fb828420ff02ea2e2351e92e2d565a9bad630abfd21abf8b8eab7c144d
6
+ metadata.gz: 347930a11a59575cd3a4bbc48cb689d46d25b27c6cfbdc28c28b6bfdfa1d833c317dd5ce7566ba016aae7b13b909629d078486bd95dee68f4e7e6b5cfc7e1e44
7
+ data.tar.gz: 689b8dc84b58c38201bf3220641f467cd56b3ef2d6c4efc12829ec08c3a84f6dbc1e2c8207fdb18808c10bdb71f878cc571d21df2548a9d240881fff8b9ddd68
@@ -1,5 +1,14 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.23.0
4
+
5
+ * Raise error when `setex` called with negative timeout ([#174](https://github.com/sds/mock_redis/pull/174))
6
+ * Add support for `dump`/`restore` between MockRedis instances ([#176](https://github.com/sds/mock_redis/pull/176))
7
+ * Fix warnings for ZSET methods on Ruby 2.7 ([#177](https://github.com/sds/mock_redis/pull/177))
8
+ * Add support for returning time in pipelines ([#179](https://github.com/sds/mock_redis/pull/179))
9
+ * Fix SET methods to correct set milliseconds with `px` ([#180](https://github.com/sds/mock_redis/pull/180))
10
+ * Add support for unsorted sets within `zinterstore`/`zunionstore`([#182](https://github.com/sds/mock_redis/pull/182))
11
+
3
12
  ### 0.22.0
4
13
 
5
14
  * Gracefully handle cursors larger than the collection size in scan commands ([#171](https://github.com/sds/mock_redis/pull/171))
@@ -64,13 +64,6 @@ class MockRedis
64
64
  options[:db]
65
65
  end
66
66
 
67
- def now
68
- current_time = options[:time_class].now
69
- miliseconds = (current_time.to_r - current_time.to_i) * 1_000
70
- [current_time.to_i, miliseconds.to_i]
71
- end
72
- alias time now
73
-
74
67
  def time_at(timestamp)
75
68
  options[:time_class].at(timestamp)
76
69
  end
@@ -124,6 +124,22 @@ class MockRedis
124
124
  'OK'
125
125
  end
126
126
 
127
+ def dump(key)
128
+ value = data[key]
129
+ value ? Marshal.dump(value) : nil
130
+ end
131
+
132
+ def restore(key, ttl, value, replace: false)
133
+ if !replace && exists(key)
134
+ raise Redis::CommandError, 'BUSYKEY Target key name already exists.'
135
+ end
136
+ data[key] = Marshal.load(value) # rubocop:disable Security/MarshalLoad
137
+ if ttl > 0
138
+ pexpire(key, ttl)
139
+ end
140
+ 'OK'
141
+ end
142
+
127
143
  def keys(format = '*')
128
144
  data.keys.grep(redis_pattern_to_ruby_regex(format))
129
145
  end
@@ -143,7 +159,7 @@ class MockRedis
143
159
  end
144
160
 
145
161
  def lastsave
146
- @base.now.first
162
+ now.first
147
163
  end
148
164
 
149
165
  def persist(key)
@@ -224,6 +240,13 @@ class MockRedis
224
240
  end
225
241
  end
226
242
 
243
+ def now
244
+ current_time = @base.options[:time_class].now
245
+ miliseconds = (current_time.to_r - current_time.to_i) * 1_000
246
+ [current_time.to_i, miliseconds.to_i]
247
+ end
248
+ alias time now
249
+
227
250
  def type(key)
228
251
  if !exists(key)
229
252
  'none'
@@ -328,7 +351,7 @@ class MockRedis
328
351
  # This method isn't private, but it also isn't a Redis command, so
329
352
  # it doesn't belong up above with all the Redis commands.
330
353
  def expire_keys
331
- now, miliseconds = @base.now
354
+ now, miliseconds = self.now
332
355
  now_ms = now * 1_000 + miliseconds
333
356
 
334
357
  to_delete = expire_times.take_while do |(time, _key)|
@@ -233,7 +233,7 @@ class MockRedis
233
233
  if duration == 0
234
234
  raise Redis::CommandError, 'ERR invalid expire time in set'
235
235
  end
236
- expire(key, duration / 1000.0)
236
+ pexpire(key, duration)
237
237
  end
238
238
 
239
239
  return_true ? true : 'OK'
@@ -313,9 +313,13 @@ class MockRedis
313
313
  end
314
314
 
315
315
  def setex(key, seconds, value)
316
- set(key, value)
317
- expire(key, seconds)
318
- 'OK'
316
+ if seconds <= 0
317
+ raise Redis::CommandError, 'ERR invalid expire time in setex'
318
+ else
319
+ set(key, value)
320
+ expire(key, seconds)
321
+ 'OK'
322
+ end
319
323
  end
320
324
 
321
325
  def setnx(key, value)
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  class MockRedis
5
- VERSION = '0.22.0'
5
+ VERSION = '0.23.0'
6
6
  end
@@ -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 with_zset_at(key, &blk)
297
- with_thing_at(key, :assert_zsety, proc { Zset.new }, &blk)
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 with_zsets_at(*keys, &blk)
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)
@@ -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
@@ -66,6 +66,26 @@ describe '#pipelined' do
66
66
  end
67
67
  end
68
68
 
69
+ context 'with redis time return value' do
70
+ let(:time_stub) { double 'Time', :now => Time.new(2019, 1, 2, 3, 4, 6, '+00:00') }
71
+ let(:options) { { :time_class => time_stub } }
72
+
73
+ subject { MockRedis.new(options) }
74
+
75
+ it 'returns the time value' do
76
+ subject.set('foo', 'bar')
77
+
78
+ results = subject.pipelined do
79
+ subject.get('foo')
80
+ subject.host # defined on MockRedis, so not captured
81
+ subject.time
82
+ subject.echo('baz')
83
+ end
84
+
85
+ expect(results).to eq(['bar', [1_546_398_246, 0], 'baz'])
86
+ end
87
+ end
88
+
69
89
  context 'with nested pipelines' do
70
90
  let(:key1) { 'hello' }
71
91
  let(:key2) { 'world' }
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#restore(key, ttl, value)' do
4
+ before do
5
+ @key = 'mock-redis-test:45794'
6
+ @src = MockRedis.new
7
+ @src.set(@key, '123')
8
+ @dumped_value = @src.dump(@key)
9
+ @dst = MockRedis.new
10
+ @now = Time.now.round
11
+ Time.stub(:now).and_return(@now)
12
+ end
13
+
14
+ it 'allows dump/restoring values between two redis instances' do
15
+ expect(@dst.restore(@key, 0, @dumped_value)).to eq('OK')
16
+ expect(@dst.get(@key)).to eq('123')
17
+ expect(@dst.pttl(@key)).to eq(-1)
18
+ end
19
+
20
+ context 'when the key being restored to already exists' do
21
+ before do
22
+ @dst.set(@key, '456')
23
+ end
24
+
25
+ it 'raises an error by default' do
26
+ expect { @dst.restore(@key, 0, @dumped_value) }.to raise_error(Redis::CommandError)
27
+ expect(@dst.get(@key)).to eq('456')
28
+ end
29
+
30
+ it 'allows replacing the key if replace==true' do
31
+ expect(@dst.restore(@key, 0, @dumped_value, replace: true)).to eq('OK')
32
+ expect(@dst.get(@key)).to eq('123')
33
+ end
34
+ end
35
+
36
+ it 'sets ttl in ms' do
37
+ @dst.restore(@key, 500, @dumped_value)
38
+ expect(@dst.pttl(@key)).to eq(500)
39
+ end
40
+
41
+ it 'can dump/restore more complex data types' do
42
+ key = 'a_hash'
43
+ @src.mapped_hmset(key, foo: 'bar')
44
+ @dst.restore(key, 0, @src.dump(key))
45
+ expect(@dst.hgetall(key)).to eq('foo' => 'bar')
46
+ end
47
+ end
@@ -59,9 +59,11 @@ describe '#set(key, value)' do
59
59
 
60
60
  it 'accepts PX milliseconds' do
61
61
  key = 'mock-redis-test'
62
- @mock.set(key, 1, px: 1000).should == 'OK'
62
+ @mock.set(key, 1, px: 500).should == 'OK'
63
63
  @mock.get(key).should_not be_nil
64
- Time.stub(:now).and_return(@now + 2)
64
+ Time.stub(:now).and_return(@now + 300 / 1000.to_f)
65
+ @mock.get(key).should_not be_nil
66
+ Time.stub(:now).and_return(@now + 600 / 1000.to_f)
65
67
  @mock.get(key).should be_nil
66
68
  end
67
69
  end
@@ -19,4 +19,20 @@ describe '#setex(key, seconds, value)' do
19
19
  @redises.real.ttl(@key).should > 0
20
20
  @redises.mock.ttl(@key).should > 0
21
21
  end
22
+
23
+ context 'when expiration time is zero' do
24
+ it 'raises Redis::CommandError' do
25
+ expect do
26
+ @redises.setex(@key, 0, 'value')
27
+ end.to raise_error(Redis::CommandError, 'ERR invalid expire time in setex')
28
+ end
29
+ end
30
+
31
+ context 'when expiration time is negative' do
32
+ it 'raises Redis::CommandError' do
33
+ expect do
34
+ @redises.setex(@key, -2, 'value')
35
+ end.to raise_error(Redis::CommandError, 'ERR invalid expire time in setex')
36
+ end
37
+ end
22
38
  end
@@ -42,6 +42,40 @@ describe '#zinterstore(destination, keys, [:weights => [w,w,], [:aggregate => :s
42
42
  end.should raise_error(Redis::CommandError)
43
43
  end
44
44
 
45
+ context 'when used with a set' do
46
+ before do
47
+ @primes_text = 'mock-redis-test:zinterstore:primes-text'
48
+
49
+ @redises.sadd(@primes_text, 'two')
50
+ @redises.sadd(@primes_text, 'three')
51
+ @redises.sadd(@primes_text, 'five')
52
+ @redises.sadd(@primes_text, 'seven')
53
+ end
54
+
55
+ it 'returns the number of elements in the new set' do
56
+ @redises.zinterstore(@dest, [@odds, @primes_text]).should == 3
57
+ end
58
+
59
+ it 'sums the scores, substituting 1.0 for set values' do
60
+ @redises.zinterstore(@dest, [@odds, @primes_text])
61
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
62
+ [['three', 4.0], ['five', 6.0], ['seven', 8.0]]
63
+ end
64
+ end
65
+
66
+ context 'when used with a non-coercible structure' do
67
+ before do
68
+ @non_set = 'mock-redis-test:zinterstore:non-set'
69
+
70
+ @redises.set(@non_set, 'one')
71
+ end
72
+ it 'raises an error for wrong value type' do
73
+ lambda do
74
+ @redises.zinterstore(@dest, [@odds, @non_set])
75
+ end.should raise_error(Redis::CommandError)
76
+ end
77
+ end
78
+
45
79
  context 'the :weights argument' do
46
80
  it 'multiplies the scores by the weights while aggregating' do
47
81
  @redises.zinterstore(@dest, [@odds, @primes], :weights => [2, 3])
@@ -41,6 +41,39 @@ describe '#zunionstore(destination, keys, [:weights => [w,w,], [:aggregate => :s
41
41
  end.should raise_error(Redis::CommandError)
42
42
  end
43
43
 
44
+ context 'when used with a set' do
45
+ before do
46
+ @set4 = 'mock-redis-test:zunionstore4'
47
+
48
+ @redises.sadd(@set4, 'two')
49
+ @redises.sadd(@set4, 'three')
50
+ @redises.sadd(@set4, 'four')
51
+ end
52
+
53
+ it 'returns the number of elements in the new set' do
54
+ @redises.zunionstore(@dest, [@set3, @set4]).should == 4
55
+ end
56
+
57
+ it 'sums the scores, substituting 1.0 for set values' do
58
+ @redises.zunionstore(@dest, [@set3, @set4])
59
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
60
+ [['four', 1.0], ['one', 1.0], ['two', 3.0], ['three', 4.0]]
61
+ end
62
+ end
63
+
64
+ context 'when used with a non-coercible structure' do
65
+ before do
66
+ @non_set = 'mock-redis-test:zunionstore4'
67
+
68
+ @redises.set(@non_set, 'one')
69
+ end
70
+ it 'raises an error for wrong value type' do
71
+ lambda do
72
+ @redises.zunionstore(@dest, [@set1, @non_set])
73
+ end.should raise_error(Redis::CommandError)
74
+ end
75
+ end
76
+
44
77
  context 'the :weights argument' do
45
78
  it 'multiplies the scores by the weights while aggregating' do
46
79
  @redises.zunionstore(@dest, [@set1, @set2, @set3], :weights => [2, 3, 5])
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mock_redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-10 00:00:00.000000000 Z
12
+ date: 2020-04-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -131,6 +131,7 @@ files:
131
131
  - spec/commands/decrby_spec.rb
132
132
  - spec/commands/del_spec.rb
133
133
  - spec/commands/disconnect_spec.rb
134
+ - spec/commands/dump_spec.rb
134
135
  - spec/commands/echo_spec.rb
135
136
  - spec/commands/eval_spec.rb
136
137
  - spec/commands/evalsha_spec.rb
@@ -198,6 +199,7 @@ files:
198
199
  - spec/commands/randomkey_spec.rb
199
200
  - spec/commands/rename_spec.rb
200
201
  - spec/commands/renamenx_spec.rb
202
+ - spec/commands/restore_spec.rb
201
203
  - spec/commands/rpop_spec.rb
202
204
  - spec/commands/rpoplpush_spec.rb
203
205
  - spec/commands/rpush_spec.rb
@@ -291,7 +293,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
291
293
  - !ruby/object:Gem::Version
292
294
  version: '0'
293
295
  requirements: []
294
- rubygems_version: 3.0.3
296
+ rubygems_version: 3.1.1
295
297
  signing_key:
296
298
  specification_version: 4
297
299
  summary: Redis mock that just lives in memory; useful for testing.
@@ -313,6 +315,7 @@ test_files:
313
315
  - spec/commands/decrby_spec.rb
314
316
  - spec/commands/del_spec.rb
315
317
  - spec/commands/disconnect_spec.rb
318
+ - spec/commands/dump_spec.rb
316
319
  - spec/commands/echo_spec.rb
317
320
  - spec/commands/eval_spec.rb
318
321
  - spec/commands/evalsha_spec.rb
@@ -380,6 +383,7 @@ test_files:
380
383
  - spec/commands/randomkey_spec.rb
381
384
  - spec/commands/rename_spec.rb
382
385
  - spec/commands/renamenx_spec.rb
386
+ - spec/commands/restore_spec.rb
383
387
  - spec/commands/rpop_spec.rb
384
388
  - spec/commands/rpoplpush_spec.rb
385
389
  - spec/commands/rpush_spec.rb