mock_redis 0.23.0 → 0.25.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 (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