mock_redis 0.23.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -5
  3. data/.rubocop_todo.yml +1 -1
  4. data/CHANGELOG.md +12 -0
  5. data/Gemfile +2 -2
  6. data/lib/mock_redis/database.rb +19 -14
  7. data/lib/mock_redis/future.rb +1 -1
  8. data/lib/mock_redis/geospatial_methods.rb +4 -4
  9. data/lib/mock_redis/hash_methods.rb +9 -4
  10. data/lib/mock_redis/info_method.rb +2 -2
  11. data/lib/mock_redis/multi_db_wrapper.rb +2 -2
  12. data/lib/mock_redis/stream.rb +22 -2
  13. data/lib/mock_redis/stream/id.rb +1 -1
  14. data/lib/mock_redis/stream_methods.rb +14 -1
  15. data/lib/mock_redis/string_methods.rb +9 -5
  16. data/lib/mock_redis/transaction_wrapper.rb +2 -2
  17. data/lib/mock_redis/utility_methods.rb +1 -1
  18. data/lib/mock_redis/version.rb +1 -1
  19. data/mock_redis.gemspec +1 -1
  20. data/spec/commands/blpop_spec.rb +0 -6
  21. data/spec/commands/brpop_spec.rb +6 -5
  22. data/spec/commands/del_spec.rb +15 -0
  23. data/spec/commands/exists_spec.rb +34 -5
  24. data/spec/commands/future_spec.rb +11 -1
  25. data/spec/commands/geoadd_spec.rb +1 -1
  26. data/spec/commands/hset_spec.rb +6 -6
  27. data/spec/commands/keys_spec.rb +17 -0
  28. data/spec/commands/mget_spec.rb +6 -0
  29. data/spec/commands/move_spec.rb +5 -5
  30. data/spec/commands/set_spec.rb +4 -2
  31. data/spec/commands/setbit_spec.rb +1 -0
  32. data/spec/commands/srandmember_spec.rb +1 -1
  33. data/spec/commands/xadd_spec.rb +20 -0
  34. data/spec/commands/xrange_spec.rb +13 -0
  35. data/spec/commands/xread_spec.rb +50 -0
  36. data/spec/commands/xtrim_spec.rb +6 -0
  37. data/spec/commands/zrange_spec.rb +1 -1
  38. data/spec/commands/zrangebyscore_spec.rb +1 -1
  39. data/spec/commands/zrevrange_spec.rb +1 -1
  40. data/spec/commands/zrevrangebyscore_spec.rb +1 -1
  41. data/spec/spec_helper.rb +3 -1
  42. data/spec/support/redis_multiplexer.rb +1 -0
  43. data/spec/transactions_spec.rb +16 -0
  44. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd9fd0d5b4ccad106eee03697d49082baa7e34065ff76f67b7e18da9991847a9
4
- data.tar.gz: 11f940cdee40f2646c3e6087faac7ac1c70e0aae5eaff7ddfc8839f0fb50e1b6
3
+ metadata.gz: e4a2d7e6a155059cb97120f7f95c1e992a87a3933be5fe337a443dc0a9ac30df
4
+ data.tar.gz: 135172a723e9d5bfb058eec85863387093d56e3a6d7729f28da425f2b239e4bb
5
5
  SHA512:
6
- metadata.gz: 347930a11a59575cd3a4bbc48cb689d46d25b27c6cfbdc28c28b6bfdfa1d833c317dd5ce7566ba016aae7b13b909629d078486bd95dee68f4e7e6b5cfc7e1e44
7
- data.tar.gz: 689b8dc84b58c38201bf3220641f467cd56b3ef2d6c4efc12829ec08c3a84f6dbc1e2c8207fdb18808c10bdb71f878cc571d21df2548a9d240881fff8b9ddd68
6
+ metadata.gz: 1e1b5cba39d01b11b2c85442c5d9485285bf661316e5196448484bc78dd9d866c3829648cd547ac0996d454f113d054c3bc43728f917740870f5fe191c97b15c
7
+ data.tar.gz: bf5d20de1111be19c03cacb2639f303d0ced578656bfc5b4dae4e8cd6e28991be003e7a20702ab6825526fbee6f91ebcd676fbb27c9010792abd37a2e7eab218
@@ -3,10 +3,10 @@ inherit_from: .rubocop_todo.yml
3
3
  AllCops:
4
4
  TargetRubyVersion: 2.4
5
5
 
6
- Layout/AlignArguments:
6
+ Layout/ArgumentAlignment:
7
7
  Enabled: false
8
8
 
9
- Layout/AlignParameters:
9
+ Layout/ParameterAlignment:
10
10
  Enabled: false
11
11
 
12
12
  Layout/DotPosition:
@@ -15,9 +15,21 @@ Layout/DotPosition:
15
15
  Layout/EmptyLineAfterGuardClause:
16
16
  Enabled: false
17
17
 
18
+ Layout/LineLength:
19
+ Max: 100
20
+
21
+ Layout/SpaceAroundMethodCallOperator:
22
+ Enabled: true
23
+
18
24
  Lint/AssignmentInCondition:
19
25
  Enabled: false
20
26
 
27
+ Lint/RaiseException:
28
+ Enabled: true
29
+
30
+ Lint/StructNewOverride:
31
+ Enabled: true
32
+
21
33
  # We use this a lot in specs where it's perfectly valid
22
34
  Lint/Void:
23
35
  Exclude:
@@ -35,9 +47,6 @@ Metrics/CyclomaticComplexity:
35
47
  Metrics/ClassLength:
36
48
  Enabled: false
37
49
 
38
- Metrics/LineLength:
39
- Max: 100
40
-
41
50
  Metrics/MethodLength:
42
51
  Enabled: false
43
52
 
@@ -57,6 +66,18 @@ Style/Documentation:
57
66
  Style/DoubleNegation:
58
67
  Enabled: false
59
68
 
69
+ Style/ExponentialNotation:
70
+ Enabled: true
71
+
72
+ Style/HashEachMethods:
73
+ Enabled: true
74
+
75
+ Style/HashTransformKeys:
76
+ Enabled: true
77
+
78
+ Style/HashTransformValues:
79
+ Enabled: true
80
+
60
81
  # We have too much code that relies on modifying strings
61
82
  Style/FrozenStringLiteralComment:
62
83
  Enabled: false
@@ -9,7 +9,7 @@
9
9
  # Offense count: 17
10
10
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
11
11
  # AllowedNames: io, id, to, by, on, in, at, ip
12
- Naming/UncommunicativeMethodParamName:
12
+ Naming/MethodParameterName:
13
13
  Exclude:
14
14
  - 'lib/mock_redis/database.rb'
15
15
  - 'lib/mock_redis/expire_wrapper.rb'
@@ -1,5 +1,17 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.25.0
4
+
5
+ * Add support for `xread` command ([#190](https://github.com/sds/mock_redis/pull/190))
6
+ * Fix `mget` to raise error when passing empty array ([#191](https://github.com/sds/mock_redis/pull/191))
7
+ * Fix `xadd` when `maxlen` is zero ([#192](https://github.com/sds/mock_redis/pull/192))
8
+
9
+ ### 0.24.0
10
+
11
+ * Fix handling of blocks within `multi` blocks ([#185](https://github.com/sds/mock_redis/pull/185))
12
+ * Fix handling of multiple consecutive `?` characters in key pattern matching ([#186](https://github.com/sds/mock_redis/pull/186))
13
+ * Change `exists` to return an integer and add `exists?` ([#188](https://github.com/sds/mock_redis/pull/188))
14
+
3
15
  ### 0.23.0
4
16
 
5
17
  * Raise error when `setex` called with negative timeout ([#174](https://github.com/sds/mock_redis/pull/174))
data/Gemfile CHANGED
@@ -4,9 +4,9 @@ source 'http://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  # Run all pre-commit hooks via Overcommit during CI runs
7
- gem 'overcommit', '0.48.0'
7
+ gem 'overcommit', '0.53.0'
8
8
 
9
9
  # Pin tool versions (which are executed by Overcommit) for Travis builds
10
- gem 'rubocop', '0.68.1'
10
+ gem 'rubocop', '0.82.0'
11
11
 
12
12
  gem 'coveralls', require: false
@@ -35,7 +35,7 @@ class MockRedis
35
35
 
36
36
  def initialize_copy(_source)
37
37
  @data = @data.clone
38
- @data.keys.each { |k| @data[k] = @data[k].clone }
38
+ @data.each_key { |k| @data[k] = @data[k].clone }
39
39
  @expire_times = @expire_times.map(&:clone)
40
40
  end
41
41
 
@@ -106,7 +106,7 @@ class MockRedis
106
106
  raise Redis::CommandError, 'ERR value is not an integer or out of range'
107
107
  end
108
108
 
109
- if exists(key)
109
+ if exists?(key)
110
110
  timestamp = Rational(timestamp_ms.to_i, 1000)
111
111
  set_expiration(key, @base.time_at(timestamp))
112
112
  true
@@ -115,12 +115,17 @@ class MockRedis
115
115
  end
116
116
  end
117
117
 
118
- def exists(key)
119
- data.key?(key)
118
+ def exists(*keys)
119
+ keys.count { |key| data.key?(key) }
120
+ end
121
+
122
+ def exists?(*keys)
123
+ keys.each { |key| return true if data.key?(key) }
124
+ false
120
125
  end
121
126
 
122
127
  def flushdb
123
- data.keys.each { |k| del(k) }
128
+ data.each_key { |k| del(k) }
124
129
  'OK'
125
130
  end
126
131
 
@@ -130,7 +135,7 @@ class MockRedis
130
135
  end
131
136
 
132
137
  def restore(key, ttl, value, replace: false)
133
- if !replace && exists(key)
138
+ if !replace && exists?(key)
134
139
  raise Redis::CommandError, 'BUSYKEY Target key name already exists.'
135
140
  end
136
141
  data[key] = Marshal.load(value) # rubocop:disable Security/MarshalLoad
@@ -163,7 +168,7 @@ class MockRedis
163
168
  end
164
169
 
165
170
  def persist(key)
166
- if exists(key) && has_expiration?(key)
171
+ if exists?(key) && has_expiration?(key)
167
172
  remove_expiration(key)
168
173
  true
169
174
  else
@@ -204,7 +209,7 @@ class MockRedis
204
209
  raise Redis::CommandError, 'ERR no such key'
205
210
  end
206
211
 
207
- if exists(newkey)
212
+ if exists?(newkey)
208
213
  false
209
214
  else
210
215
  rename(key, newkey)
@@ -217,7 +222,7 @@ class MockRedis
217
222
  end
218
223
 
219
224
  def ttl(key)
220
- if !exists(key)
225
+ if !exists?(key)
221
226
  -2
222
227
  elsif has_expiration?(key)
223
228
  now, = @base.now
@@ -231,7 +236,7 @@ class MockRedis
231
236
  now, miliseconds = @base.now
232
237
  now_ms = now * 1000 + miliseconds
233
238
 
234
- if !exists(key)
239
+ if !exists?(key)
235
240
  -2
236
241
  elsif has_expiration?(key)
237
242
  (expiration(key).to_r * 1000).to_i - now_ms
@@ -248,7 +253,7 @@ class MockRedis
248
253
  alias time now
249
254
 
250
255
  def type(key)
251
- if !exists(key)
256
+ if !exists?(key)
252
257
  'none'
253
258
  elsif hashy?(key)
254
259
  'hash'
@@ -323,7 +328,7 @@ class MockRedis
323
328
  Regexp.new(
324
329
  "^#{pattern}$".
325
330
  gsub(/([+|()])/, '\\\\\1').
326
- gsub(/([^\\])\?/, '\\1.').
331
+ gsub(/(?<!\\)\?/, '\\1.').
327
332
  gsub(/([^\\])\*/, '\\1.*')
328
333
  )
329
334
  end
@@ -351,8 +356,8 @@ class MockRedis
351
356
  # This method isn't private, but it also isn't a Redis command, so
352
357
  # it doesn't belong up above with all the Redis commands.
353
358
  def expire_keys
354
- now, miliseconds = self.now
355
- now_ms = now * 1_000 + miliseconds
359
+ now_sec, miliseconds = now
360
+ now_ms = now_sec * 1_000 + miliseconds
356
361
 
357
362
  to_delete = expire_times.take_while do |(time, _key)|
358
363
  (time.to_r * 1_000).to_i <= now_ms
@@ -17,7 +17,7 @@ class MockRedis
17
17
 
18
18
  def store_result(result)
19
19
  @result_set = true
20
- @result = result
20
+ @result = @block ? @block.call(result) : result
21
21
  end
22
22
  end
23
23
  end
@@ -38,7 +38,7 @@ class MockRedis
38
38
  lng2, lat2 = geohash_decode(hash2)
39
39
 
40
40
  distance = geohash_distance(lng1, lat1, lng2, lat2) / to_meter
41
- format('%.4f', distance)
41
+ format('%<distance>.4f', distance: distance)
42
42
  end
43
43
 
44
44
  def geohash(key, members)
@@ -95,8 +95,8 @@ class MockRedis
95
95
  lat = Float(point[1])
96
96
 
97
97
  unless LNG_RANGE.include?(lng) && LAT_RANGE.include?(lat)
98
- lng = format('%.6f', lng)
99
- lat = format('%.6f', lat)
98
+ lng = format('%<long>.6f', long: lng)
99
+ lat = format('%<lat>.6f', lat: lat)
100
100
  raise Redis::CommandError,
101
101
  "ERR invalid longitude,latitude pair #{lng},#{lat}"
102
102
  end
@@ -201,7 +201,7 @@ class MockRedis
201
201
  end
202
202
 
203
203
  def format_decoded_coord(coord)
204
- coord = format('%.17f', coord)
204
+ coord = format('%<coord>.17f', coord: coord)
205
205
  l = 1
206
206
  l += 1 while coord[-l] == '0'
207
207
  coord = coord[0..-l]
@@ -128,10 +128,15 @@ class MockRedis
128
128
  end
129
129
  end
130
130
 
131
- def hset(key, field, value)
132
- field_exists = hexists(key, field)
133
- with_hash_at(key) { |h| h[field.to_s] = value.to_s }
134
- !field_exists
131
+ def hset(key, *args)
132
+ added = 0
133
+ with_hash_at(key) do |hash|
134
+ args.each_slice(2) do |field, value|
135
+ added += 1 unless hash.key?(field.to_s)
136
+ hash[field.to_s] = value.to_s
137
+ end
138
+ end
139
+ added
135
140
  end
136
141
 
137
142
  def hsetnx(key, field, value)
@@ -83,7 +83,7 @@ class MockRedis
83
83
 
84
84
  # The Ruby Redis client returns commandstats differently when it's called as
85
85
  # "INFO commandstats".
86
- # rubocop:disable Metrics/LineLength
86
+ # rubocop:disable Layout/LineLength
87
87
  COMMAND_STATS_SOLO_INFO = {
88
88
  'auth' => { 'calls' => '572501', 'usec' => '2353163', 'usec_per_call' => '4.11' },
89
89
  'client' => { 'calls' => '1', 'usec' => '80', 'usec_per_call' => '80.00' },
@@ -123,7 +123,7 @@ class MockRedis
123
123
  'cmdstat_smembers' => 'calls=58,usec=231,usec_per_call=3.98',
124
124
  'cmdstat_sunionstore' => 'calls=4185027,usec=11762454022,usec_per_call=2810.60',
125
125
  }.freeze
126
- # rubocop:enable Metrics/LineLength
126
+ # rubocop:enable Layout/LineLength
127
127
 
128
128
  DEFAULT_INFO = [
129
129
  SERVER_INFO,
@@ -24,7 +24,7 @@ class MockRedis
24
24
  def initialize_copy(source)
25
25
  super
26
26
  @databases = @databases.clone
27
- @databases.keys.each do |k|
27
+ @databases.each_key do |k|
28
28
  @databases[k] = @databases[k].clone
29
29
  end
30
30
  end
@@ -39,7 +39,7 @@ class MockRedis
39
39
  src = current_db
40
40
  dest = db(db_index)
41
41
 
42
- if !src.exists(key) || dest.exists(key)
42
+ if !src.exists?(key) || dest.exists?(key)
43
43
  false
44
44
  else
45
45
  case current_db.type(key)
@@ -23,14 +23,29 @@ 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 is equal or smaller than ' \
29
+ 'the target stream top item'
30
+ # TOOD: Redis version 6.0.4, w redis 4.2.1 generates the following error message:
31
+ # 'ERR The ID specified in XADD must be greater than 0-0'
32
+ end
26
33
  members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]]
27
34
  @last_id.to_s
28
35
  end
29
36
 
30
37
  def trim(count)
31
38
  deleted = @members.size - count
32
- @members = @members.to_a[-count..-1].to_set
33
- deleted
39
+ if deleted > 0
40
+ @members = if count == 0
41
+ Set.new
42
+ else
43
+ @members.to_a[-count..-1].to_set
44
+ end
45
+ deleted
46
+ else
47
+ 0
48
+ end
34
49
  end
35
50
 
36
51
  def range(start, finish, reversed, *opts_in)
@@ -45,6 +60,11 @@ class MockRedis
45
60
  items
46
61
  end
47
62
 
63
+ def read(id)
64
+ stream_id = MockRedis::Stream::Id.new(id)
65
+ members.select { |m| (stream_id < m[0]) }.map { |m| [m[0].to_s, m[1]] }
66
+ end
67
+
48
68
  def each
49
69
  members.each { |m| yield m }
50
70
  end
@@ -31,7 +31,7 @@ class MockRedis
31
31
  @timestamp = id
32
32
  end
33
33
  @sequence = @sequence.nil? ? sequence : @sequence.to_i
34
- if (@timestamp == 0 && @sequence == 0) || self <= min
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,20 @@ class MockRedis
67
66
  end
68
67
  end
69
68
 
69
+ # TODO: Implement count and block parameters
70
+ def xread(keys, ids)
71
+ result = {}
72
+ keys = keys.is_a?(Array) ? keys : [keys]
73
+ ids = ids.is_a?(Array) ? ids : [ids]
74
+ keys.each_with_index do |key, index|
75
+ with_stream_at(key) do |stream|
76
+ data = stream.read(ids[index])
77
+ result[key] = data unless data.empty?
78
+ end
79
+ end
80
+ result
81
+ end
82
+
70
83
  private
71
84
 
72
85
  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)
@@ -205,14 +206,14 @@ class MockRedis
205
206
  return_true = false
206
207
  options = options.dup
207
208
  if options.delete(:nx)
208
- if exists(key)
209
+ if exists?(key)
209
210
  return false
210
211
  else
211
212
  return_true = true
212
213
  end
213
214
  end
214
215
  if options.delete(:xx)
215
- if exists(key)
216
+ if exists?(key)
216
217
  return_true = true
217
218
  else
218
219
  return false
@@ -235,6 +236,9 @@ class MockRedis
235
236
  end
236
237
  pexpire(key, duration)
237
238
  end
239
+ unless options.empty?
240
+ raise ArgumentError, "unknown keyword: #{options.keys[0]}"
241
+ end
238
242
 
239
243
  return_true ? true : 'OK'
240
244
  end
@@ -323,7 +327,7 @@ class MockRedis
323
327
  end
324
328
 
325
329
  def setnx(key, value)
326
- if exists(key)
330
+ if exists?(key)
327
331
  false
328
332
  else
329
333
  set(key, value)
@@ -17,7 +17,7 @@ class MockRedis
17
17
 
18
18
  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
- result
63
+ future.value
64
64
  rescue StandardError => e
65
65
  e
66
66
  end
@@ -18,7 +18,7 @@ class MockRedis
18
18
  end
19
19
 
20
20
  def clean_up_empties_at(key)
21
- if data[key]&.empty? && data[key] != ''
21
+ if data[key]&.empty? && data[key] != '' && !data[key].is_a?(Stream)
22
22
  del(key)
23
23
  end
24
24
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  class MockRedis
5
- VERSION = '0.23.0'
5
+ VERSION = '0.25.0'
6
6
  end
@@ -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.1.0'
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'
@@ -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)
@@ -26,11 +26,12 @@ describe '#brpop(key [, key, ...,], timeout)' do
26
26
  [@list1, 'two']
27
27
  end
28
28
 
29
- it 'raises an error on subsecond timeouts' do
30
- lambda do
31
- @redises.brpop(@list1, @list2, :timeout => 0.5)
32
- end.should raise_error(Redis::CommandError)
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
@@ -1,6 +1,14 @@
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
+ @redises._gsub(/\d{3}-\d/, '...-.')
10
+ end
11
+
4
12
  it 'returns the number of keys deleted' do
5
13
  @redises.set('mock-redis-test:1', 1)
6
14
  @redises.set('mock-redis-test:2', 1)
@@ -32,4 +40,11 @@ describe '#del(key [, key, ...])' do
32
40
  it 'raises an error if an empty array is given' do
33
41
  expect { @redises.del [] }.to raise_error Redis::CommandError
34
42
  end
43
+
44
+ it 'removes a stream key' do
45
+ @redises.xadd('mock-redis-stream', { key: 'value' }, maxlen: 0)
46
+ expect(@redises.exists?('mock-redis-stream')).to eq true
47
+ @redises.del('mock-redis-stream')
48
+ expect(@redises.exists?('mock-redis-stream')).to eq false
49
+ end
35
50
  end
@@ -1,14 +1,43 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe '#exists(key)' do
4
- before { @key = 'mock-redis-test:45794' }
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(@key).should == false
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(@key, 1)
12
- @redises.exists(@key).should == true
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
@@ -3,7 +3,12 @@ require 'spec_helper'
3
3
  describe MockRedis::Future do
4
4
  let(:command) { [:get, 'foo'] }
5
5
  let(:result) { 'bar' }
6
- before { @future = MockRedis::Future.new(command) }
6
+ let(:block) { ->(value) { value.upcase } }
7
+
8
+ before do
9
+ @future = MockRedis::Future.new(command)
10
+ @future2 = MockRedis::Future.new(command, block)
11
+ end
7
12
 
8
13
  it 'remembers the command' do
9
14
  @future.command.should eq(command)
@@ -17,4 +22,9 @@ describe MockRedis::Future do
17
22
  @future.store_result(result)
18
23
  @future.value.should eq(result)
19
24
  end
25
+
26
+ it 'executes the block on the value if block is passed in' do
27
+ @future2.store_result(result)
28
+ @future2.value.should eq('BAR')
29
+ end
20
30
  end
@@ -33,7 +33,7 @@ describe '#geoadd' do
33
33
  context 'when coordinates are not in allowed range' do
34
34
  let(:coords) { [181, 86] }
35
35
  let(:message) do
36
- formatted_coords = coords.map { |c| format('%.6f', c) }
36
+ formatted_coords = coords.map { |c| format('%<coords>.6f', coords: c) }
37
37
  "ERR invalid longitude,latitude pair #{formatted_coords.join(',')}"
38
38
  end
39
39
 
@@ -5,18 +5,18 @@ describe '#hset(key, field)' do
5
5
  @key = 'mock-redis-test:hset'
6
6
  end
7
7
 
8
- it 'returns true if the key does not exist' do
9
- @redises.hset(@key, 'k1', 'v1').should == true
8
+ it 'returns 1 if the key does not exist' do
9
+ @redises.hset(@key, 'k1', 'v1').should == 1
10
10
  end
11
11
 
12
- it 'returns true if the key exists but the field does not' do
12
+ it 'returns 1 if the key exists but the field does not' do
13
13
  @redises.hset(@key, 'k1', 'v1')
14
- @redises.hset(@key, 'k2', 'v2').should == true
14
+ @redises.hset(@key, 'k2', 'v2').should == 1
15
15
  end
16
16
 
17
- it 'returns false if the field already exists' do
17
+ it 'returns 0 if the field already exists' do
18
18
  @redises.hset(@key, 'k1', 'v1')
19
- @redises.hset(@key, 'k1', 'v1').should == false
19
+ @redises.hset(@key, 'k1', 'v1').should == 0
20
20
  end
21
21
 
22
22
  it 'creates a hash there is no such field' do
@@ -29,6 +29,7 @@ describe '#keys()' do
29
29
 
30
30
  @redises.set('mock-redis-test:special-key?', 'true')
31
31
  @redises.set('mock-redis-test:special-key*', 'true')
32
+ @redises.set('mock-redis-test:special-key-!?*', 'true')
32
33
  end
33
34
 
34
35
  describe 'the ? character' do
@@ -53,6 +54,22 @@ describe '#keys()' do
53
54
  'mock-redis-test:special-key?',
54
55
  ]
55
56
  end
57
+
58
+ context 'multiple ? characters' do
59
+ it "properly handles multiple consequtive '?' characters" do
60
+ @redises.keys('mock-redis-test:special-key-???').sort.should == [
61
+ 'mock-redis-test:special-key-!?*',
62
+ ]
63
+ end
64
+
65
+ context '\\? as a literal ' do
66
+ it 'handles multiple ? as both literal and special character' do
67
+ @redises.keys('mock-redis-test:special-key-?\??').sort.should == [
68
+ 'mock-redis-test:special-key-!?*',
69
+ ]
70
+ end
71
+ end
72
+ end
56
73
  end
57
74
 
58
75
  describe 'the * character' do
@@ -49,5 +49,11 @@ describe '#mget(key [, key, ...])' do
49
49
  @redises.mget
50
50
  end.should raise_error(Redis::CommandError)
51
51
  end
52
+
53
+ it 'raises an error if you pass it empty array' do
54
+ lambda do
55
+ @redises.mget([])
56
+ end.should raise_error(Redis::CommandError)
57
+ end
52
58
  end
53
59
  end
@@ -64,7 +64,7 @@ describe '#move(key, db)' do
64
64
  end
65
65
 
66
66
  it 'removes key from srcdb' do
67
- @redises.exists(@key).should == false
67
+ @redises.exists?(@key).should == false
68
68
  end
69
69
 
70
70
  it 'copies key to destdb' do
@@ -81,7 +81,7 @@ describe '#move(key, db)' do
81
81
  end
82
82
 
83
83
  it 'removes key from srcdb' do
84
- @redises.exists(@key).should == false
84
+ @redises.exists?(@key).should == false
85
85
  end
86
86
 
87
87
  it 'copies key to destdb' do
@@ -99,7 +99,7 @@ describe '#move(key, db)' do
99
99
  end
100
100
 
101
101
  it 'removes key from srcdb' do
102
- @redises.exists(@key).should == false
102
+ @redises.exists?(@key).should == false
103
103
  end
104
104
 
105
105
  it 'copies key to destdb' do
@@ -117,7 +117,7 @@ describe '#move(key, db)' do
117
117
  end
118
118
 
119
119
  it 'removes key from srcdb' do
120
- @redises.exists(@key).should == false
120
+ @redises.exists?(@key).should == false
121
121
  end
122
122
 
123
123
  it 'copies key to destdb' do
@@ -135,7 +135,7 @@ describe '#move(key, db)' do
135
135
  end
136
136
 
137
137
  it 'removes key from srcdb' do
138
- @redises.exists(@key).should == false
138
+ @redises.exists?(@key).should == false
139
139
  end
140
140
 
141
141
  it 'copies key to destdb' do
@@ -33,10 +33,12 @@ describe '#set(key, value)' do
33
33
  @redises.set(key, 1, xx: true).should == true
34
34
  end
35
35
 
36
- it 'ignores other options' do
36
+ it 'raises on unknown options' do
37
37
  key = 'mock-redis-test'
38
38
  @redises.del(key)
39
- @redises.set(key, 1, logger: :something).should == 'OK'
39
+ expect do
40
+ @redises.set(key, 1, logger: :something)
41
+ end.to raise_error(ArgumentError, 'unknown keyword: logger')
40
42
  end
41
43
 
42
44
  context '[mock only]' do
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe '#setbit(key, offset)' do
4
4
  before do
5
+ Encoding.default_external = 'UTF-8'
5
6
  @key = 'mock-redis-test:setbit'
6
7
  @redises.set(@key, 'h') # ASCII 0x68
7
8
  end
@@ -37,7 +37,7 @@ describe '#srandmember(key)' do
37
37
  @redises.send_without_checking(:srandmember, @key, 2).size.should == 2
38
38
  end
39
39
 
40
- it 'returns random members up to count from the set when count is negative even if count.abs is greater than the set size' do # rubocop:disable Metrics/LineLength
40
+ it 'returns random members up to count from the set when count is negative even if count.abs is greater than the set size' do # rubocop:disable Layout/LineLength
41
41
  @redises.send_without_checking(:srandmember, @key, -5).size.should == 5
42
42
  end
43
43
 
@@ -54,6 +54,8 @@ describe '#xadd("mystream", { f1: "v1", f2: "v2" }, id: "0-0", maxlen: 1000, app
54
54
  Redis::CommandError,
55
55
  'ERR The ID specified in XADD is equal or smaller than the target ' \
56
56
  'stream top item'
57
+ # TOOD: Redis version 6.0.4, w redis 4.2.1 generates the following error message:
58
+ # 'ERR The ID specified in XADD must be greater than 0-0'
57
59
  )
58
60
  end
59
61
 
@@ -99,4 +101,22 @@ describe '#xadd("mystream", { f1: "v1", f2: "v2" }, id: "0-0", maxlen: 1000, app
99
101
  ]
100
102
  )
101
103
  end
104
+
105
+ it 'supports a maxlen greater than the current size' do
106
+ @redises.xadd(@key, { key1: 'value1' }, id: '1234567891234-0')
107
+ @redises.xadd(@key, { key2: 'value2' }, id: '1234567891245-0', maxlen: 1000)
108
+ expect(@redises.xrange(@key, '-', '+')).to eq(
109
+ [
110
+ ['1234567891234-0', { 'key1' => 'value1' }],
111
+ ['1234567891245-0', { 'key2' => 'value2' }],
112
+ ]
113
+ )
114
+ end
115
+
116
+ it 'creates an empty stream with maxlen of 0' do
117
+ @redises.xadd(@key, { key: 'value' }, maxlen: 0)
118
+ expect(@redises.xlen(@key)).to eq 0
119
+ expect(@redises.xrange(@key, '-', '+')).to eq([])
120
+ expect(@redises.exists?(@key)).to eq true
121
+ end
102
122
  end
@@ -54,6 +54,19 @@ describe '#xrange("mystream", first: "0-1", last: "0-3", count: 10)' do
54
54
  )
55
55
  end
56
56
 
57
+ it 'returns all entries with a lower limit of 0-0' do
58
+ expect(@redises.xrange(@key, '0-0', '+')).to eq(
59
+ [
60
+ ['1234567891234-0', { 'key1' => 'value1' }],
61
+ ['1234567891245-0', { 'key2' => 'value2' }],
62
+ ['1234567891245-1', { 'key3' => 'value3' }],
63
+ ['1234567891278-0', { 'key4' => 'value4' }],
64
+ ['1234567891278-1', { 'key5' => 'value5' }],
65
+ ['1234567891299-0', { 'key6' => 'value6' }]
66
+ ]
67
+ )
68
+ end
69
+
57
70
  it 'returns entries with an upper limit' do
58
71
  expect(@redises.xrange(@key, '-', '1234567891285-0')).to eq(
59
72
  [
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#xread(keys, ids)' do
4
+ before :all do
5
+ sleep 1 - (Time.now.to_f % 1)
6
+ @key = 'mock-redis-test:xread'
7
+ @key1 = 'mock-redis-test:xread1'
8
+ end
9
+
10
+ it 'reads a single entry' do
11
+ @redises.xadd(@key, { key: 'value' }, id: '1234567891234-0')
12
+ expect(@redises.xread(@key, '0-0'))
13
+ .to eq({ @key => [['1234567891234-0', { 'key' => 'value' }]] })
14
+ end
15
+
16
+ it 'reads multiple entries from the beginning of the stream' do
17
+ @redises.xadd(@key, { key0: 'value0' }, id: '1234567891234-0')
18
+ @redises.xadd(@key, { key1: 'value1' }, id: '1234567891234-1')
19
+ expect(@redises.xread(@key, '0-0'))
20
+ .to eq({ @key => [['1234567891234-0', { 'key0' => 'value0' }],
21
+ ['1234567891234-1', { 'key1' => 'value1' }]] })
22
+ end
23
+
24
+ it 'reads entries greater than the ID passed' do
25
+ @redises.xadd(@key, { key0: 'value0' }, id: '1234567891234-0')
26
+ @redises.xadd(@key, { key1: 'value1' }, id: '1234567891234-1')
27
+ expect(@redises.xread(@key, '1234567891234-0'))
28
+ .to eq({ @key => [['1234567891234-1', { 'key1' => 'value1' }]] })
29
+ end
30
+
31
+ it 'reads from multiple streams' do
32
+ @redises.xadd(@key, { key: 'value' }, id: '1234567891234-0')
33
+ @redises.xadd(@key1, { key1: 'value1' }, id: '1234567891234-0')
34
+ expect(@redises.xread([@key, @key1], %w[0-0 0-0]))
35
+ .to eq({ @key => [['1234567891234-0', { 'key' => 'value' }]],
36
+ @key1 => [['1234567891234-0', { 'key1' => 'value1' }]] })
37
+ end
38
+
39
+ it 'reads from multiple streams at the given IDs' do
40
+ @redises.xadd(@key, { key: 'value0' }, id: '1234567891234-0')
41
+ @redises.xadd(@key, { key: 'value1' }, id: '1234567891234-1')
42
+ @redises.xadd(@key, { key: 'value2' }, id: '1234567891234-2')
43
+ @redises.xadd(@key1, { key1: 'value0' }, id: '1234567891234-0')
44
+ @redises.xadd(@key1, { key1: 'value1' }, id: '1234567891234-1')
45
+ @redises.xadd(@key1, { key1: 'value2' }, id: '1234567891234-2')
46
+ # The first stream won't return anything since we specify the last ID
47
+ expect(@redises.xread([@key, @key1], %w[1234567891234-2 1234567891234-1]))
48
+ .to eq({ @key1 => [['1234567891234-2', { 'key1' => 'value2' }]] })
49
+ end
50
+ end
@@ -16,6 +16,12 @@ describe '#xtrim("mystream", 1000, approximate: true)' do
16
16
  expect(@redises.xtrim(@key, 4)).to eq 2
17
17
  end
18
18
 
19
+ it 'returns 0 if count is greater than size' do
20
+ initial = @redises.xrange(@key, '-', '+')
21
+ expect(@redises.xtrim(@key, 1000)).to eq 0
22
+ expect(@redises.xrange(@key, '-', '+')).to eql(initial)
23
+ end
24
+
19
25
  it 'deletes the oldes elements' do
20
26
  @redises.xtrim(@key, 4)
21
27
  expect(@redises.xrange(@key, '-', '+')).to eq(
@@ -15,7 +15,7 @@ describe '#zrange(key, start, stop [, :with_scores => true])' do
15
15
  end
16
16
 
17
17
  it 'should return an empty array' do
18
- @redises.exists(@key).should == false
18
+ @redises.exists?(@key).should == false
19
19
  @redises.zrange(@key, 0, 4).should == []
20
20
  end
21
21
  end
@@ -15,7 +15,7 @@ describe '#zrangebyscore(key, start, stop [:with_scores => true] [:limit => [off
15
15
  end
16
16
 
17
17
  it 'should return an empty array' do
18
- @redises.exists(@key).should == false
18
+ @redises.exists?(@key).should == false
19
19
  @redises.zrangebyscore(@key, 0, 4).should == []
20
20
  end
21
21
  end
@@ -15,7 +15,7 @@ describe '#zrevrange(key, start, stop [, :with_scores => true])' do
15
15
  end
16
16
 
17
17
  it 'should return an empty array' do
18
- @redises.exists(@key).should == false
18
+ @redises.exists?(@key).should == false
19
19
  @redises.zrevrange(@key, 0, 4).should == []
20
20
  end
21
21
  end
@@ -15,7 +15,7 @@ describe '#zrevrangebyscore(key, start, stop [:with_scores => true] [:limit => [
15
15
  end
16
16
 
17
17
  it 'should return an empty array' do
18
- @redises.exists(@key).should == false
18
+ @redises.exists?(@key).should == false
19
19
  @redises.zrevrangebyscore(@key, 0, 4).should == []
20
20
  end
21
21
  end
@@ -15,7 +15,7 @@ require 'mock_redis'
15
15
  require 'timecop'
16
16
 
17
17
  $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..')))
18
- Dir['spec/support/**/*.rb'].each { |x| require x }
18
+ Dir['spec/support/**/*.rb'].sort.each { |x| require x }
19
19
 
20
20
  module TypeCheckingHelper
21
21
  def method_from_description(example)
@@ -40,6 +40,8 @@ end
40
40
  RSpec.configure do |config|
41
41
  config.expect_with :rspec do |c|
42
42
  c.syntax = [:expect, :should]
43
+ # Allow for a large output so we can debug error messages
44
+ c.max_formatted_output_length = 1_000_000
43
45
  end
44
46
 
45
47
  config.mock_with :rspec do |c|
@@ -9,6 +9,7 @@ class RedisMultiplexer < BlankSlate
9
9
 
10
10
  def initialize(*a)
11
11
  @mock_redis = MockRedis.new(*a)
12
+ Redis.exists_returns_integer = true
12
13
  @real_redis = Redis.new(*a)
13
14
  _gsub_clear
14
15
  end
@@ -67,6 +67,22 @@ describe 'transactions (multi/exec/discard)' do
67
67
  @redises.get('counter').should eq '6'
68
68
  @redises.get('test').should eq '1'
69
69
  end
70
+
71
+ it 'allows blocks within multi blocks' do
72
+ @redises.set('foo', 'bar')
73
+ @redises.set('fuu', 'baz')
74
+
75
+ result = nil
76
+
77
+ @redises.multi do |r|
78
+ result = r.mget('foo', 'fuu') { |reply| reply.map(&:upcase) }
79
+ r.del('foo', 'fuu')
80
+ end
81
+
82
+ result.value.should eq %w[BAR BAZ]
83
+ @redises.get('foo').should eq nil
84
+ @redises.get('fuu').should eq nil
85
+ end
70
86
  end
71
87
 
72
88
  context '#discard' do
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.23.0
4
+ version: 0.25.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: 2020-04-22 00:00:00.000000000 Z
12
+ date: 2020-06-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 4.1.0
20
+ version: 4.2.0
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 4.1.0
27
+ version: 4.2.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rspec
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -241,6 +241,7 @@ files:
241
241
  - spec/commands/xadd_spec.rb
242
242
  - spec/commands/xlen_spec.rb
243
243
  - spec/commands/xrange_spec.rb
244
+ - spec/commands/xread_spec.rb
244
245
  - spec/commands/xrevrange_spec.rb
245
246
  - spec/commands/xtrim_spec.rb
246
247
  - spec/commands/zadd_spec.rb
@@ -425,6 +426,7 @@ test_files:
425
426
  - spec/commands/xadd_spec.rb
426
427
  - spec/commands/xlen_spec.rb
427
428
  - spec/commands/xrange_spec.rb
429
+ - spec/commands/xread_spec.rb
428
430
  - spec/commands/xrevrange_spec.rb
429
431
  - spec/commands/xtrim_spec.rb
430
432
  - spec/commands/zadd_spec.rb