mock_redis 0.13.2 → 0.14.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/.travis.yml +3 -0
  4. data/CHANGELOG.md +10 -0
  5. data/Gemfile +0 -5
  6. data/README.md +2 -2
  7. data/lib/mock_redis.rb +1 -0
  8. data/lib/mock_redis/database.rb +11 -2
  9. data/lib/mock_redis/future.rb +22 -0
  10. data/lib/mock_redis/pipelined_wrapper.rb +0 -21
  11. data/lib/mock_redis/set_methods.rb +1 -0
  12. data/lib/mock_redis/string_methods.rb +13 -5
  13. data/lib/mock_redis/transaction_wrapper.rb +22 -8
  14. data/lib/mock_redis/version.rb +1 -1
  15. data/lib/mock_redis/zset_methods.rb +12 -9
  16. data/mock_redis.gemspec +2 -1
  17. data/spec/commands/connected_spec.rb +1 -1
  18. data/spec/commands/exists_spec.rb +2 -2
  19. data/spec/commands/expire_spec.rb +18 -2
  20. data/spec/commands/expireat_spec.rb +2 -2
  21. data/spec/commands/hexists_spec.rb +4 -4
  22. data/spec/commands/hsetnx_spec.rb +2 -2
  23. data/spec/commands/move_spec.rb +8 -8
  24. data/spec/commands/msetnx_spec.rb +2 -2
  25. data/spec/commands/persist_spec.rb +4 -4
  26. data/spec/commands/pexpire_spec.rb +2 -2
  27. data/spec/commands/pexpireat_spec.rb +2 -2
  28. data/spec/commands/renamenx_spec.rb +2 -2
  29. data/spec/commands/sadd_spec.rb +2 -2
  30. data/spec/commands/scan_spec.rb +51 -0
  31. data/spec/commands/set_spec.rb +6 -6
  32. data/spec/commands/setbit_spec.rb +7 -0
  33. data/spec/commands/setnx_spec.rb +2 -2
  34. data/spec/commands/sismember_spec.rb +5 -5
  35. data/spec/commands/smove_spec.rb +6 -6
  36. data/spec/commands/srem_spec.rb +3 -3
  37. data/spec/commands/sunion_spec.rb +4 -0
  38. data/spec/commands/zadd_spec.rb +2 -2
  39. data/spec/commands/zrange_spec.rb +1 -1
  40. data/spec/commands/zrangebyscore_spec.rb +1 -1
  41. data/spec/commands/zrem_spec.rb +3 -3
  42. data/spec/commands/zremrangebyscore_spec.rb +1 -0
  43. data/spec/commands/zrevrange_spec.rb +1 -1
  44. data/spec/commands/zrevrangebyscore_spec.rb +1 -1
  45. data/spec/commands/zscore_spec.rb +2 -2
  46. data/spec/spec_helper.rb +11 -2
  47. data/spec/support/redis_multiplexer.rb +6 -6
  48. data/spec/support/shared_examples/only_operates_on_hashes.rb +2 -2
  49. data/spec/support/shared_examples/only_operates_on_lists.rb +2 -2
  50. data/spec/support/shared_examples/only_operates_on_sets.rb +2 -2
  51. data/spec/support/shared_examples/only_operates_on_strings.rb +2 -2
  52. data/spec/support/shared_examples/only_operates_on_zsets.rb +4 -4
  53. data/spec/transactions_spec.rb +23 -0
  54. metadata +36 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3d7978080e5d75386c90cbae717fee17d6194c7
4
- data.tar.gz: abfc802fe90cf8e2a9a996311b0859451a8abff0
3
+ metadata.gz: 994ce4cc7000897238012b0a9f11a8417b31358d
4
+ data.tar.gz: 44f03b45a1310b977d0bf996fe042a7b32c70b36
5
5
  SHA512:
6
- metadata.gz: 19b2a107cbb896b8fd5435a83e84c45e64a5b0603e1e130611fcf4655e4f20a68427d230c4cd6c4133925a1bbf8bd49a783a0f94b676c5999df7f1f94bdb8b96
7
- data.tar.gz: a74a10f31f47764a4c09ed3a838587b384771f3a72130ea57dc8e09cb82556fa0974fc3e12285d78c4374580b0f7f5bd3686badea137fee95aa20231c2558f5f
6
+ metadata.gz: 84bd359ec9c9eff9b5b40cacc7ac65f206bdd8a720d00445fcb53cd0729abcd003c3fe66f96e220728575f305e00495c0fa75a55290a268ba304f4cf92597035
7
+ data.tar.gz: e09387fa284e38847e8ebec0d8ed1a7c3681ebfad0b661a239c54b976adada92ffb08661f736abdde1686aaba5ed394e53d6620326979b84811c42d65d63ba2c
data/.rspec CHANGED
@@ -1 +1 @@
1
- -c -d
1
+ -c
data/.travis.yml CHANGED
@@ -7,6 +7,9 @@ rvm:
7
7
  - 2.1.1
8
8
  - jruby-19mode
9
9
 
10
+ script:
11
+ - bundle exec rspec
12
+
10
13
  matrix:
11
14
  allow_failures:
12
15
  - rvm: 2.1.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.14.0
4
+
5
+ * Fix bug where SETBIT command would not correctly unset a bit
6
+ * Fix bug where a key that expired would cause another key that expired later
7
+ to prematurely expire
8
+ * Add support to set methods to take array as argument
9
+ * Evaluate futures at the end of `#multi` blocks
10
+ * Add support for the SCAN command
11
+ * Add support for `[+/-]inf` values for min/max in ordered set commands
12
+
3
13
  ### 0.13.2
4
14
 
5
15
  * Fix SMEMBERS command to not return frozen elements
data/Gemfile CHANGED
@@ -2,8 +2,3 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in mock_redis.gemspec
4
4
  gemspec
5
-
6
- group :test do
7
- gem 'debugger', platforms: [:mri, :mswin]
8
- gem 'ruby-debug', platforms: [:jruby]
9
- end
data/README.md CHANGED
@@ -8,7 +8,7 @@ MockRedis provides the same interface as `redis-rb`, but it stores its
8
8
  data in memory instead of talking to a Redis server. It is intended
9
9
  for use in tests.
10
10
 
11
- The current implementation is tested against *Redis 2.8.4*. Older versions
11
+ The current implementation is tested against *Redis 2.8.17*. Older versions
12
12
  of Redis may return different results or not support some commands.
13
13
 
14
14
  ## Getting Started
@@ -98,7 +98,7 @@ supported.
98
98
 
99
99
  If you want to work on this, you'll probably want to run the
100
100
  tests. (Just kidding! There's no probably about it.) These tests were
101
- written with Redis `2.8.4` running on localhost without any passwords
101
+ written with Redis running on `localhost` without any passwords
102
102
  required. If you're using a different version of Redis, you may see
103
103
  failures due to error message text being different. If you're running
104
104
  a really old version of Redis, you'll definitely see failures due to
data/lib/mock_redis.rb CHANGED
@@ -3,6 +3,7 @@ require 'set'
3
3
  require 'mock_redis/assertions'
4
4
  require 'mock_redis/database'
5
5
  require 'mock_redis/expire_wrapper'
6
+ require 'mock_redis/future'
6
7
  require 'mock_redis/multi_db_wrapper'
7
8
  require 'mock_redis/pipelined_wrapper'
8
9
  require 'mock_redis/transaction_wrapper'
@@ -112,6 +112,17 @@ class MockRedis
112
112
  data.keys.grep(redis_pattern_to_ruby_regex(format))
113
113
  end
114
114
 
115
+ def scan(cursor, opts = {})
116
+ count = (opts[:count] || 10).to_i
117
+ match = opts[:match] || '*'
118
+
119
+ keys = data.keys
120
+ limit = cursor + count
121
+ next_cursor = limit >= keys.length ? '0' : limit.to_s
122
+
123
+ [next_cursor, keys[cursor..limit].grep(redis_pattern_to_ruby_regex(match))]
124
+ end
125
+
115
126
  def lastsave
116
127
  @base.now.to_i
117
128
  end
@@ -296,8 +307,6 @@ class MockRedis
296
307
  to_delete.each do |(time, key)|
297
308
  del(key)
298
309
  end
299
-
300
- expire_times.slice!(0, to_delete.length)
301
310
  end
302
311
  end
303
312
  end
@@ -0,0 +1,22 @@
1
+ class MockRedis
2
+ class FutureNotReady < RuntimeError; end
3
+
4
+ class Future
5
+ attr_reader :command
6
+
7
+ def initialize(command)
8
+ @command = command
9
+ @result_set = false
10
+ end
11
+
12
+ def value
13
+ raise FutureNotReady unless @result_set
14
+ @result
15
+ end
16
+
17
+ def set_result(result)
18
+ @result_set = true
19
+ @result = result
20
+ end
21
+ end
22
+ end
@@ -1,25 +1,4 @@
1
1
  class MockRedis
2
- class FutureNotReady < RuntimeError; end
3
-
4
- class Future
5
- attr_reader :command
6
-
7
- def initialize(command)
8
- @command = command
9
- @result_set = false
10
- end
11
-
12
- def value
13
- raise FutureNotReady unless @result_set
14
- @result
15
- end
16
-
17
- def set_result(result)
18
- @result_set = true
19
- @result = result
20
- end
21
- end
22
-
23
2
  class PipelinedWrapper
24
3
  include UndefRedisMethods
25
4
 
@@ -127,6 +127,7 @@ class MockRedis
127
127
  end
128
128
 
129
129
  def with_sets_at(*keys, &blk)
130
+ keys = keys.flatten
130
131
  if keys.length == 1
131
132
  with_set_at(keys.first, &blk)
132
133
  else
@@ -123,7 +123,7 @@ class MockRedis
123
123
  def msetnx(*kvpairs)
124
124
  assert_has_args(kvpairs, 'msetnx')
125
125
 
126
- if kvpairs.each_slice(2).any? {|(k,v)| exists(k)}
126
+ if kvpairs.each_slice(2).any? {|(k,_)| exists(k)}
127
127
  false
128
128
  else
129
129
  mset(*kvpairs)
@@ -159,7 +159,7 @@ class MockRedis
159
159
  if expire_option
160
160
  type, duration = expire_option
161
161
  if duration == 0
162
- raise Redis::CommandError, 'ERR invalid expire time in SETEX'
162
+ raise Redis::CommandError, 'ERR invalid expire time in set'
163
163
  end
164
164
  expire(key, type.to_sym == :ex ? duration : duration / 1000.0)
165
165
  end
@@ -199,9 +199,17 @@ class MockRedis
199
199
  char_as_number = char.each_byte.reduce(0) do |a, byte|
200
200
  (a << 8) + byte
201
201
  end
202
- char_as_number ^=
203
- (2**((char.bytesize * 8)-1) >>
204
- (offset_within_char * 8 + offset_within_byte))
202
+
203
+ bitmask_length = (char.bytesize * 8 - offset_within_char * 8 - offset_within_byte - 1)
204
+ bitmask = 1 << bitmask_length
205
+
206
+ if value.zero?
207
+ bitmask ^= 2**(char.bytesize * 8) - 1
208
+ char_as_number &= bitmask
209
+ else
210
+ char_as_number |= bitmask
211
+ end
212
+
205
213
  str[char_index] = char_as_number.chr
206
214
 
207
215
  data[key] = str
@@ -10,14 +10,21 @@ class MockRedis
10
10
 
11
11
  def initialize(db)
12
12
  @db = db
13
- @queued_commands = []
13
+ @transaction_futures = []
14
14
  @in_multi = false
15
+ @multi_block_given = false
15
16
  end
16
17
 
17
18
  def method_missing(method, *args, &block)
18
19
  if @in_multi
19
- @queued_commands << [method, *args]
20
- 'QUEUED'
20
+ future = MockRedis::Future.new([method, *args])
21
+ @transaction_futures << future
22
+
23
+ if @multi_block_given
24
+ future
25
+ else
26
+ 'QUEUED'
27
+ end
21
28
  else
22
29
  @db.expire_keys
23
30
  @db.send(method, *args, &block)
@@ -27,7 +34,7 @@ class MockRedis
27
34
  def initialize_copy(source)
28
35
  super
29
36
  @db = @db.clone
30
- @queued_commands = @queued_commands.clone
37
+ @transaction_futures = @transaction_futures.clone
31
38
  end
32
39
 
33
40
  def discard
@@ -35,7 +42,8 @@ class MockRedis
35
42
  raise Redis::CommandError, "ERR DISCARD without MULTI"
36
43
  end
37
44
  @in_multi = false
38
- @queued_commands = []
45
+ @multi_block_given = false
46
+ @transaction_futures = []
39
47
  'OK'
40
48
  end
41
49
 
@@ -44,14 +52,19 @@ class MockRedis
44
52
  raise Redis::CommandError, "ERR EXEC without MULTI"
45
53
  end
46
54
  @in_multi = false
47
- responses = @queued_commands.map do |cmd|
55
+ @multi_block_given = false
56
+
57
+ responses = @transaction_futures.map do |future|
48
58
  begin
49
- send(*cmd)
59
+ result = send(*future.command)
60
+ future.set_result(result)
61
+ result
50
62
  rescue => e
51
63
  e
52
64
  end
53
65
  end
54
- @queued_commands = []
66
+
67
+ @transaction_futures = []
55
68
  responses
56
69
  end
57
70
 
@@ -61,6 +74,7 @@ class MockRedis
61
74
  end
62
75
  @in_multi = true
63
76
  if block_given?
77
+ @multi_block_given = true
64
78
  begin
65
79
  yield(self)
66
80
  self.exec
@@ -1,4 +1,4 @@
1
1
  # Defines the gem version.
2
2
  class MockRedis
3
- VERSION = '0.13.2'
3
+ VERSION = '0.14.0'
4
4
  end
@@ -19,7 +19,7 @@ class MockRedis
19
19
  end
20
20
  elsif args.size == 2
21
21
  score, member = args
22
- assert_scorey(score)
22
+ assert_scorey(score) unless score =~ /(\+|\-)inf/
23
23
  retval = !zscore(key, member)
24
24
  with_zset_at(key) {|z| z.add(score, member.to_s)}
25
25
  else
@@ -34,8 +34,7 @@ class MockRedis
34
34
  end
35
35
 
36
36
  def zcount(key, min, max)
37
- assert_scorey(min, 'ERR min or max is not a float') unless min == '-inf'
38
- assert_scorey(max, 'ERR min or max is not a float') unless max == '+inf'
37
+ assert_range_args(min, max)
39
38
 
40
39
  with_zset_at(key) do |zset|
41
40
  zset.in_range(min, max).size
@@ -67,8 +66,7 @@ class MockRedis
67
66
  end
68
67
 
69
68
  def zrangebyscore(key, min, max, options={})
70
- assert_scorey(min, 'ERR min or max is not a float') unless min == '-inf'
71
- assert_scorey(max, 'ERR min or max is not a float') unless max == '+inf'
69
+ assert_range_args(min, max)
72
70
 
73
71
  with_zset_at(key) do |zset|
74
72
  all_results = zset.in_range(min, max)
@@ -107,8 +105,7 @@ class MockRedis
107
105
  end
108
106
 
109
107
  def zremrangebyscore(key, min, max)
110
- assert_scorey(min, 'ERR min or max is not a float') unless min == '-inf'
111
- assert_scorey(max, 'ERR min or max is not a float') unless max == '+inf'
108
+ assert_range_args(min, max)
112
109
 
113
110
  zrangebyscore(key, min, max).
114
111
  each {|member| zrem(key, member)}.
@@ -116,8 +113,7 @@ class MockRedis
116
113
  end
117
114
 
118
115
  def zrevrangebyscore(key, max, min, options={})
119
- assert_scorey(min, 'ERR min or max is not a float') unless min == '-inf'
120
- assert_scorey(max, 'ERR min or max is not a float') unless max == '+inf'
116
+ assert_range_args(min, max)
121
117
 
122
118
  with_zset_at(key) do |zset|
123
119
  to_response(
@@ -232,11 +228,18 @@ class MockRedis
232
228
  end
233
229
 
234
230
  def assert_scorey(value, message = "ERR value is not a valid float")
231
+ return if value =~ /\(?(\-|\+)inf/
232
+
235
233
  value = $1 if value.to_s.match(/\((.*)/)
236
234
  unless looks_like_float?(value)
237
235
  raise Redis::CommandError, message
238
236
  end
239
237
  end
240
238
 
239
+ def assert_range_args(min, max)
240
+ [min, max].each do |value|
241
+ assert_scorey(value, 'ERR min or max is not a float')
242
+ end
243
+ end
241
244
  end
242
245
  end
data/mock_redis.gemspec CHANGED
@@ -23,5 +23,6 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.add_development_dependency 'rake', '~> 10.0'
25
25
  s.add_development_dependency 'redis', '~> 3.0.0'
26
- s.add_development_dependency 'rspec', '~> 2.14.0'
26
+ s.add_development_dependency 'rspec', '~> 3.1.0'
27
+ s.add_development_dependency 'rspec-its', '~> 1.0'
27
28
  end
@@ -2,6 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  describe "#connected? [mock only]" do
4
4
  it "returns true" do
5
- @redises.mock.connected?.should be_true
5
+ @redises.mock.connected?.should == true
6
6
  end
7
7
  end
@@ -4,11 +4,11 @@ describe "#exists(key)" do
4
4
  before { @key = 'mock-redis-test:45794' }
5
5
 
6
6
  it "returns false for keys that do not exist" do
7
- @redises.exists(@key).should be_false
7
+ @redises.exists(@key).should == false
8
8
  end
9
9
 
10
10
  it "returns true for keys that do exist" do
11
11
  @redises.set(@key, 1)
12
- @redises.exists(@key).should be_true
12
+ @redises.exists(@key).should == true
13
13
  end
14
14
  end
@@ -7,11 +7,11 @@ describe "#expire(key, seconds)" do
7
7
  end
8
8
 
9
9
  it "returns true for a key that exists" do
10
- @redises.expire(@key, 1).should be_true
10
+ @redises.expire(@key, 1).should == true
11
11
  end
12
12
 
13
13
  it "returns false for a key that does not exist" do
14
- @redises.expire('mock-redis-test:nonesuch', 1).should be_false
14
+ @redises.expire('mock-redis-test:nonesuch', 1).should == false
15
15
  end
16
16
 
17
17
  it "removes a key immediately when seconds==0" do
@@ -91,5 +91,21 @@ describe "#expire(key, seconds)" do
91
91
  end
92
92
  end
93
93
 
94
+ context 'with two key expirations' do
95
+ let(:other_key) { 'mock-redis-test:expire-other' }
96
+
97
+ before { @redises.set(other_key, 'spork-other') }
98
+
99
+ it 'removes keys after enough time has passed' do
100
+ @mock.expire(@key, 5)
101
+ @mock.expire(other_key, 10)
102
+
103
+ Time.stub(:now).and_return(@now + 5)
104
+ @mock.get(@key).should be_nil
105
+
106
+ Time.stub(:now).and_return(@now + 10)
107
+ @mock.get(other_key).should be_nil
108
+ end
109
+ end
94
110
  end
95
111
  end
@@ -7,11 +7,11 @@ describe "#expireat(key, timestamp)" do
7
7
  end
8
8
 
9
9
  it "returns true for a key that exists" do
10
- @redises.expireat(@key, Time.now.to_i + 1).should be_true
10
+ @redises.expireat(@key, Time.now.to_i + 1).should == true
11
11
  end
12
12
 
13
13
  it "returns false for a key that does not exist" do
14
- @redises.expireat('mock-redis-test:nonesuch', Time.now.to_i + 1).should be_false
14
+ @redises.expireat('mock-redis-test:nonesuch', Time.now.to_i + 1).should == false
15
15
  end
16
16
 
17
17
  it "removes a key immediately when timestamp is now" do
@@ -7,20 +7,20 @@ describe "#hexists(key, field)" do
7
7
  end
8
8
 
9
9
  it "returns true if the hash has that field" do
10
- @redises.hexists(@key, 'field').should be_true
10
+ @redises.hexists(@key, 'field').should == true
11
11
  end
12
12
 
13
13
  it "returns false if the hash lacks that field" do
14
- @redises.hexists(@key, 'nonesuch').should be_false
14
+ @redises.hexists(@key, 'nonesuch').should == false
15
15
  end
16
16
 
17
17
  it "treats the field as a string" do
18
18
  @redises.hset(@key, 1, 'one')
19
- @redises.hexists(@key, 1).should be_true
19
+ @redises.hexists(@key, 1).should == true
20
20
  end
21
21
 
22
22
  it "returns nil when there is no such key" do
23
- @redises.hexists('mock-redis-test:nonesuch', 'key').should be_false
23
+ @redises.hexists('mock-redis-test:nonesuch', 'key').should == false
24
24
  end
25
25
 
26
26
  it_should_behave_like "a hash-only command"