mock_redis 0.5.4 → 0.31.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 (201) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/lint.yml +31 -0
  3. data/.github/workflows/tests.yml +63 -0
  4. data/.gitignore +1 -1
  5. data/.overcommit.yml +21 -0
  6. data/.rspec +1 -1
  7. data/.rubocop.yml +148 -0
  8. data/.rubocop_todo.yml +35 -0
  9. data/.simplecov +4 -0
  10. data/CHANGELOG.md +278 -0
  11. data/Gemfile +9 -5
  12. data/LICENSE.md +21 -0
  13. data/README.md +52 -16
  14. data/Rakefile +0 -8
  15. data/lib/mock_redis/assertions.rb +0 -1
  16. data/lib/mock_redis/connection_method.rb +13 -0
  17. data/lib/mock_redis/database.rb +193 -257
  18. data/lib/mock_redis/expire_wrapper.rb +2 -2
  19. data/lib/mock_redis/future.rb +23 -0
  20. data/lib/mock_redis/geospatial_methods.rb +240 -0
  21. data/lib/mock_redis/hash_methods.rb +83 -24
  22. data/lib/mock_redis/indifferent_hash.rb +11 -0
  23. data/lib/mock_redis/info_method.rb +160 -0
  24. data/lib/mock_redis/list_methods.rb +34 -19
  25. data/lib/mock_redis/multi_db_wrapper.rb +8 -7
  26. data/lib/mock_redis/pipelined_wrapper.rb +42 -16
  27. data/lib/mock_redis/set_methods.rb +62 -19
  28. data/lib/mock_redis/sort_method.rb +81 -0
  29. data/lib/mock_redis/stream/id.rb +58 -0
  30. data/lib/mock_redis/stream.rb +88 -0
  31. data/lib/mock_redis/stream_methods.rb +102 -0
  32. data/lib/mock_redis/string_methods.rb +235 -42
  33. data/lib/mock_redis/transaction_wrapper.rb +62 -28
  34. data/lib/mock_redis/utility_methods.rb +62 -11
  35. data/lib/mock_redis/version.rb +4 -1
  36. data/lib/mock_redis/zset.rb +24 -29
  37. data/lib/mock_redis/zset_methods.rb +187 -59
  38. data/lib/mock_redis.rb +77 -27
  39. data/mock_redis.gemspec +23 -15
  40. data/spec/client_spec.rb +29 -0
  41. data/spec/cloning_spec.rb +17 -18
  42. data/spec/commands/append_spec.rb +4 -4
  43. data/spec/commands/auth_spec.rb +1 -1
  44. data/spec/commands/bgrewriteaof_spec.rb +2 -2
  45. data/spec/commands/bgsave_spec.rb +2 -2
  46. data/spec/commands/bitcount_spec.rb +25 -0
  47. data/spec/commands/bitfield_spec.rb +169 -0
  48. data/spec/commands/blpop_spec.rb +19 -21
  49. data/spec/commands/brpop_spec.rb +25 -20
  50. data/spec/commands/brpoplpush_spec.rb +16 -17
  51. data/spec/commands/connected_spec.rb +7 -0
  52. data/spec/commands/connection_spec.rb +15 -0
  53. data/spec/commands/dbsize_spec.rb +3 -3
  54. data/spec/commands/decr_spec.rb +8 -8
  55. data/spec/commands/decrby_spec.rb +8 -8
  56. data/spec/commands/del_spec.rb +35 -3
  57. data/spec/commands/disconnect_spec.rb +7 -0
  58. data/spec/commands/dump_spec.rb +19 -0
  59. data/spec/commands/echo_spec.rb +4 -4
  60. data/spec/commands/eval_spec.rb +7 -0
  61. data/spec/commands/evalsha_spec.rb +10 -0
  62. data/spec/commands/exists_spec.rb +36 -7
  63. data/spec/commands/expire_spec.rb +48 -20
  64. data/spec/commands/expireat_spec.rb +12 -13
  65. data/spec/commands/flushall_spec.rb +5 -5
  66. data/spec/commands/flushdb_spec.rb +5 -5
  67. data/spec/commands/future_spec.rb +30 -0
  68. data/spec/commands/geoadd_spec.rb +58 -0
  69. data/spec/commands/geodist_spec.rb +118 -0
  70. data/spec/commands/geohash_spec.rb +52 -0
  71. data/spec/commands/geopos_spec.rb +55 -0
  72. data/spec/commands/get_spec.rb +14 -6
  73. data/spec/commands/getbit_spec.rb +7 -7
  74. data/spec/commands/getrange_spec.rb +9 -9
  75. data/spec/commands/getset_spec.rb +7 -7
  76. data/spec/commands/hdel_spec.rb +41 -11
  77. data/spec/commands/hexists_spec.rb +11 -11
  78. data/spec/commands/hget_spec.rb +7 -7
  79. data/spec/commands/hgetall_spec.rb +15 -5
  80. data/spec/commands/hincrby_spec.rb +16 -16
  81. data/spec/commands/hincrbyfloat_spec.rb +58 -0
  82. data/spec/commands/hkeys_spec.rb +5 -5
  83. data/spec/commands/hlen_spec.rb +5 -5
  84. data/spec/commands/hmget_spec.rb +19 -9
  85. data/spec/commands/hmset_spec.rb +38 -12
  86. data/spec/commands/hscan_each_spec.rb +48 -0
  87. data/spec/commands/hscan_spec.rb +27 -0
  88. data/spec/commands/hset_spec.rb +26 -12
  89. data/spec/commands/hsetnx_spec.rb +16 -16
  90. data/spec/commands/hvals_spec.rb +5 -5
  91. data/spec/commands/incr_spec.rb +8 -8
  92. data/spec/commands/incrby_spec.rb +13 -13
  93. data/spec/commands/incrbyfloat_spec.rb +13 -13
  94. data/spec/commands/info_spec.rb +54 -5
  95. data/spec/commands/keys_spec.rb +83 -31
  96. data/spec/commands/lastsave_spec.rb +2 -2
  97. data/spec/commands/lindex_spec.rb +20 -10
  98. data/spec/commands/linsert_spec.rb +14 -14
  99. data/spec/commands/llen_spec.rb +4 -4
  100. data/spec/commands/lpop_spec.rb +6 -6
  101. data/spec/commands/lpush_spec.rb +21 -15
  102. data/spec/commands/lpushx_spec.rb +24 -11
  103. data/spec/commands/lrange_spec.rb +24 -8
  104. data/spec/commands/lrem_spec.rb +16 -16
  105. data/spec/commands/lset_spec.rb +17 -12
  106. data/spec/commands/ltrim_spec.rb +17 -7
  107. data/spec/commands/mapped_hmget_spec.rb +13 -9
  108. data/spec/commands/mapped_hmset_spec.rb +12 -12
  109. data/spec/commands/mapped_mget_spec.rb +22 -0
  110. data/spec/commands/mapped_mset_spec.rb +19 -0
  111. data/spec/commands/mapped_msetnx_spec.rb +26 -0
  112. data/spec/commands/mget_spec.rb +48 -17
  113. data/spec/commands/move_spec.rb +37 -37
  114. data/spec/commands/mset_spec.rb +20 -6
  115. data/spec/commands/msetnx_spec.rb +14 -14
  116. data/spec/commands/persist_spec.rb +15 -16
  117. data/spec/commands/pexpire_spec.rb +86 -0
  118. data/spec/commands/pexpireat_spec.rb +48 -0
  119. data/spec/commands/ping_spec.rb +6 -2
  120. data/spec/commands/pipelined_spec.rb +98 -7
  121. data/spec/commands/pttl_spec.rb +41 -0
  122. data/spec/commands/randomkey_spec.rb +3 -3
  123. data/spec/commands/rename_spec.rb +16 -12
  124. data/spec/commands/renamenx_spec.rb +13 -15
  125. data/spec/commands/restore_spec.rb +47 -0
  126. data/spec/commands/rpop_spec.rb +6 -6
  127. data/spec/commands/rpoplpush_spec.rb +13 -8
  128. data/spec/commands/rpush_spec.rb +21 -15
  129. data/spec/commands/rpushx_spec.rb +24 -11
  130. data/spec/commands/sadd_spec.rb +14 -10
  131. data/spec/commands/scan_each_spec.rb +39 -0
  132. data/spec/commands/scan_spec.rb +64 -0
  133. data/spec/commands/scard_spec.rb +3 -3
  134. data/spec/commands/script_spec.rb +9 -0
  135. data/spec/commands/sdiff_spec.rb +13 -13
  136. data/spec/commands/sdiffstore_spec.rb +13 -13
  137. data/spec/commands/select_spec.rb +13 -5
  138. data/spec/commands/set_spec.rb +112 -0
  139. data/spec/commands/setbit_spec.rb +25 -16
  140. data/spec/commands/setex_spec.rb +20 -4
  141. data/spec/commands/setnx_spec.rb +6 -6
  142. data/spec/commands/setrange_spec.rb +12 -12
  143. data/spec/commands/sinter_spec.rb +11 -13
  144. data/spec/commands/sinterstore_spec.rb +12 -12
  145. data/spec/commands/sismember_spec.rb +10 -10
  146. data/spec/commands/smembers_spec.rb +15 -5
  147. data/spec/commands/smove_spec.rb +13 -13
  148. data/spec/commands/sort_list_spec.rb +21 -0
  149. data/spec/commands/sort_set_spec.rb +21 -0
  150. data/spec/commands/sort_zset_spec.rb +21 -0
  151. data/spec/commands/spop_spec.rb +19 -4
  152. data/spec/commands/srandmember_spec.rb +28 -4
  153. data/spec/commands/srem_spec.rb +17 -12
  154. data/spec/commands/sscan_each_spec.rb +48 -0
  155. data/spec/commands/sscan_spec.rb +39 -0
  156. data/spec/commands/strlen_spec.rb +4 -5
  157. data/spec/commands/sunion_spec.rb +13 -11
  158. data/spec/commands/sunionstore_spec.rb +12 -12
  159. data/spec/commands/ttl_spec.rb +11 -6
  160. data/spec/commands/type_spec.rb +1 -1
  161. data/spec/commands/watch_spec.rb +9 -4
  162. data/spec/commands/xadd_spec.rb +122 -0
  163. data/spec/commands/xlen_spec.rb +22 -0
  164. data/spec/commands/xrange_spec.rb +164 -0
  165. data/spec/commands/xread_spec.rb +66 -0
  166. data/spec/commands/xrevrange_spec.rb +130 -0
  167. data/spec/commands/xtrim_spec.rb +36 -0
  168. data/spec/commands/zadd_spec.rb +100 -11
  169. data/spec/commands/zcard_spec.rb +4 -4
  170. data/spec/commands/zcount_spec.rb +18 -10
  171. data/spec/commands/zincrby_spec.rb +6 -6
  172. data/spec/commands/zinterstore_spec.rb +54 -20
  173. data/spec/commands/zpopmax_spec.rb +60 -0
  174. data/spec/commands/zpopmin_spec.rb +60 -0
  175. data/spec/commands/zrange_spec.rb +54 -13
  176. data/spec/commands/zrangebyscore_spec.rb +42 -27
  177. data/spec/commands/zrank_spec.rb +4 -4
  178. data/spec/commands/zrem_spec.rb +18 -12
  179. data/spec/commands/zremrangebyrank_spec.rb +5 -5
  180. data/spec/commands/zremrangebyscore_spec.rb +12 -5
  181. data/spec/commands/zrevrange_spec.rb +35 -10
  182. data/spec/commands/zrevrangebyscore_spec.rb +26 -15
  183. data/spec/commands/zrevrank_spec.rb +4 -4
  184. data/spec/commands/zscan_each_spec.rb +48 -0
  185. data/spec/commands/zscan_spec.rb +26 -0
  186. data/spec/commands/zscore_spec.rb +7 -7
  187. data/spec/commands/zunionstore_spec.rb +54 -21
  188. data/spec/mock_redis_spec.rb +61 -0
  189. data/spec/spec_helper.rb +35 -8
  190. data/spec/support/redis_multiplexer.rb +62 -37
  191. data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
  192. data/spec/support/shared_examples/only_operates_on_hashes.rb +5 -3
  193. data/spec/support/shared_examples/only_operates_on_lists.rb +5 -3
  194. data/spec/support/shared_examples/only_operates_on_sets.rb +5 -3
  195. data/spec/support/shared_examples/only_operates_on_strings.rb +4 -4
  196. data/spec/support/shared_examples/only_operates_on_zsets.rb +18 -16
  197. data/spec/support/shared_examples/sorts_enumerables.rb +56 -0
  198. data/spec/transactions_spec.rb +79 -29
  199. metadata +162 -42
  200. data/LICENSE +0 -19
  201. data/spec/commands/hash_operator_spec.rb +0 -21
@@ -3,23 +3,74 @@ class MockRedis
3
3
  private
4
4
 
5
5
  def with_thing_at(key, assertion, empty_thing_generator)
6
- begin
7
- send(assertion, key)
8
- data[key] ||= empty_thing_generator.call
9
- data_key_ref = data[key]
10
- ret = yield data[key]
11
- data[key] = data_key_ref if data[key].nil?
12
- ret
13
- ensure
14
- clean_up_empties_at(key)
15
- end
6
+ send(assertion, key)
7
+ data[key] ||= empty_thing_generator.call
8
+ data_key_ref = data[key]
9
+ ret = yield data[key]
10
+ data[key] = data_key_ref if data[key].nil?
11
+ primitive?(ret) ? ret.dup : ret
12
+ ensure
13
+ clean_up_empties_at(key)
14
+ end
15
+
16
+ def primitive?(value)
17
+ value.is_a?(::Array) || value.is_a?(::Hash) || value.is_a?(::String)
16
18
  end
17
19
 
18
20
  def clean_up_empties_at(key)
19
- if data[key] && data[key].empty?
21
+ if data[key]&.empty? && data[key] != '' && !data[key].is_a?(Stream)
20
22
  del(key)
21
23
  end
22
24
  end
23
25
 
26
+ def common_scan(values, cursor, opts = {})
27
+ count = (opts[:count] || 10).to_i
28
+ cursor = cursor.to_i
29
+ match = opts[:match] || '*'
30
+ key = opts[:key] || lambda { |x| x }
31
+ filtered_values = []
32
+
33
+ limit = cursor + count
34
+ next_cursor = limit >= values.length ? '0' : limit.to_s
35
+
36
+ unless values[cursor...limit].nil?
37
+ filtered_values = values[cursor...limit].select do |val|
38
+ redis_pattern_to_ruby_regex(match).match(key.call(val))
39
+ end
40
+ end
41
+
42
+ [next_cursor, filtered_values]
43
+ end
44
+
45
+ def twos_complement_encode(n, size)
46
+ if n < 0
47
+ str = (n + 1).abs.to_s(2)
48
+
49
+ binary = left_pad(str, size - 1).chars.map { |c| c == '0' ? 1 : 0 }
50
+ binary.unshift(1)
51
+ else
52
+ binary = left_pad(n.abs.to_s(2), size - 1).chars.map(&:to_i)
53
+ binary.unshift(0)
54
+ end
55
+
56
+ binary
57
+ end
58
+
59
+ def twos_complement_decode(array)
60
+ total = 0
61
+
62
+ array.each.with_index do |bit, index|
63
+ total += 2**(array.length - index - 1) if bit == 1
64
+ total = -total if index == 0
65
+ end
66
+
67
+ total
68
+ end
69
+
70
+ def left_pad(str, size)
71
+ str = '0' + str while str.length < size
72
+
73
+ str
74
+ end
24
75
  end
25
76
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Defines the gem version.
1
4
  class MockRedis
2
- VERSION = '0.5.4'
5
+ VERSION = '0.31.0'
3
6
  end
@@ -12,7 +12,7 @@ class MockRedis
12
12
 
13
13
  def initialize
14
14
  @members = Set.new
15
- @scores = Hash.new
15
+ @scores = {}
16
16
  end
17
17
 
18
18
  def initialize_copy(source)
@@ -23,49 +23,45 @@ class MockRedis
23
23
 
24
24
  def add(score, member)
25
25
  members.add(member)
26
- if score.to_f.to_i == score.to_f
27
- scores[member] = score.to_f.to_i
28
- else
29
- scores[member] = score.to_f
30
- end
26
+ scores[member] = score.to_f
31
27
  self
32
28
  end
33
29
 
34
30
  def delete?(member)
35
31
  scores.delete(member)
36
- members.delete?(member) and self
32
+ members.delete?(member) && self
37
33
  end
38
34
 
39
35
  def each
40
- members.each {|m| yield score(m), m}
36
+ members.each { |m| yield score(m), m }
41
37
  end
42
38
 
43
39
  def in_range(min, max)
44
40
  in_from_the_left = case min
45
- when "-inf"
46
- lambda {|_| true }
47
- when "+inf"
48
- lambda {|_| false }
41
+ when '-inf'
42
+ lambda { |_| true }
43
+ when '+inf'
44
+ lambda { |_| false }
49
45
  when /\((.*)$/
50
- val = $1.to_f
51
- lambda {|x| x.to_f > val }
46
+ left_val = $1.to_f
47
+ lambda { |x| x.to_f > left_val }
52
48
  else
53
- lambda {|x| x.to_f >= min.to_f }
49
+ lambda { |x| x.to_f >= min.to_f }
54
50
  end
55
51
 
56
52
  in_from_the_right = case max
57
- when "-inf"
58
- lambda {|_| false }
59
- when "+inf"
60
- lambda {|_| true }
53
+ when '-inf'
54
+ lambda { |_| false }
55
+ when '+inf'
56
+ lambda { |_| true }
61
57
  when /\((.*)$/
62
- val = $1.to_f
63
- lambda {|x| x.to_f < val }
58
+ right_val = $1.to_f
59
+ lambda { |x| x.to_f < right_val }
64
60
  else
65
- lambda {|x| x.to_f <= max.to_f }
61
+ lambda { |x| x.to_f <= max.to_f }
66
62
  end
67
63
 
68
- sorted.find_all do |(score, member)|
64
+ sorted.find_all do |(score, _member)|
69
65
  in_from_the_left[score] && in_from_the_right[score]
70
66
  end
71
67
  end
@@ -74,8 +70,8 @@ class MockRedis
74
70
  if !block_given?
75
71
  intersection(other, &:+)
76
72
  else
77
- self.members.intersection(other.members).reduce(self.class.new) do |acc, m|
78
- new_score = yield(self.score(m), other.score(m))
73
+ members.intersection(other.members).reduce(self.class.new) do |acc, m|
74
+ new_score = yield(score(m), other.score(m))
79
75
  acc.add(new_score, m)
80
76
  end
81
77
  end
@@ -88,7 +84,7 @@ class MockRedis
88
84
  def sorted
89
85
  members.map do |m|
90
86
  [score(m), m]
91
- end.sort_by(&:first)
87
+ end.sort
92
88
  end
93
89
 
94
90
  def sorted_members
@@ -99,12 +95,11 @@ class MockRedis
99
95
  if !block_given?
100
96
  union(other, &:+)
101
97
  else
102
- self.members.union(other.members).reduce(self.class.new) do |acc, m|
103
- new_score = yield(self.score(m), other.score(m))
98
+ members.union(other.members).reduce(self.class.new) do |acc, m|
99
+ new_score = yield(score(m), other.score(m))
104
100
  acc.add(new_score, m)
105
101
  end
106
102
  end
107
103
  end
108
-
109
104
  end
110
105
  end
@@ -8,42 +8,90 @@ class MockRedis
8
8
  include UtilityMethods
9
9
 
10
10
  def zadd(key, *args)
11
- if !args.first.is_a?(Array)
12
- if args.size < 2
13
- raise Redis::CommandError, "ERR wrong number of arguments for 'zadd' command"
14
- elsif args.size.odd?
15
- raise Redis::CommandError, "ERR syntax error"
16
- end
17
- else
18
- unless args.all? {|pair| pair.size == 2 }
19
- raise(Redis::CommandError, "ERR syntax error")
20
- end
11
+ zadd_options = {}
12
+ zadd_options = args.pop if args.last.is_a?(Hash)
13
+
14
+ if zadd_options&.include?(:nx) && zadd_options&.include?(:xx)
15
+ raise Redis::CommandError, 'ERR XX and NX options at the same time are not compatible'
21
16
  end
22
17
 
23
- if args.size == 2
18
+ if args.size == 1 && args[0].is_a?(Array)
19
+ zadd_multiple_members(key, args.first, zadd_options)
20
+ elsif args.size == 2
24
21
  score, member = args
25
- assert_scorey(score)
26
- retval = !zscore(key, member)
27
- with_zset_at(key) {|z| z.add(score, member.to_s)}
22
+ zadd_one_member(key, score, member, zadd_options)
28
23
  else
29
- args = args.first
30
- args = args.each_slice(2).to_a unless args.first.is_a?(Array)
31
- retval = args.map(&:last).map { |member| !!zscore(key, member.to_s) }.count(false)
32
- with_zset_at(key) do |z|
33
- args.each { |score, member| z.add(score, member.to_s) }
24
+ raise Redis::CommandError, 'ERR wrong number of arguments'
25
+ end
26
+ end
27
+
28
+ def zadd_one_member(key, score, member, zadd_options = {})
29
+ assert_scorey(score) unless score.to_s =~ /(\+|\-)inf/
30
+
31
+ with_zset_at(key) do |zset|
32
+ if zadd_options[:incr]
33
+ if zadd_options[:xx]
34
+ member_present = zset.include?(member)
35
+ return member_present ? zincrby(key, score, member) : nil
36
+ end
37
+
38
+ if zadd_options[:nx]
39
+ member_present = zset.include?(member)
40
+ return member_present ? nil : zincrby(key, score, member)
41
+ end
42
+
43
+ zincrby(key, score, member)
44
+ elsif zadd_options[:xx]
45
+ zset.add(score, member.to_s) if zset.include?(member)
46
+ false
47
+ elsif zadd_options[:nx]
48
+ !zset.include?(member) && !!zset.add(score, member.to_s)
49
+ else
50
+ retval = !zscore(key, member)
51
+ zset.add(score, member.to_s)
52
+ retval
34
53
  end
35
54
  end
55
+ end
36
56
 
37
- retval
57
+ private :zadd_one_member
58
+
59
+ def zadd_multiple_members(key, args, zadd_options = {})
60
+ assert_has_args(args, 'zadd')
61
+
62
+ args = args.each_slice(2).to_a unless args.first.is_a?(Array)
63
+ with_zset_at(key) do |zset|
64
+ if zadd_options[:incr]
65
+ raise Redis::CommandError, 'ERR INCR option supports a single increment-element pair'
66
+ elsif zadd_options[:xx]
67
+ args.each { |score, member| zset.include?(member) && zset.add(score, member.to_s) }
68
+ 0
69
+ elsif zadd_options[:nx]
70
+ args.reduce(0) do |retval, (score, member)|
71
+ unless zset.include?(member)
72
+ zset.add(score, member.to_s)
73
+ retval += 1
74
+ end
75
+ retval
76
+ end
77
+ else
78
+ args.reduce(0) do |retval, (score, member)|
79
+ retval += 1 unless zset.include?(member)
80
+ zset.add(score, member.to_s)
81
+ retval
82
+ end
83
+ end
84
+ end
38
85
  end
39
86
 
87
+ private :zadd_multiple_members
88
+
40
89
  def zcard(key)
41
90
  with_zset_at(key, &:size)
42
91
  end
43
92
 
44
93
  def zcount(key, min, max)
45
- assert_scorey(min, 'ERR min or max is not a float') unless min == '-inf'
46
- assert_scorey(max, 'ERR min or max is not a float') unless max == '+inf'
94
+ assert_range_args(min, max)
47
95
 
48
96
  with_zset_at(key) do |zset|
49
97
  zset.in_range(min, max).size
@@ -61,20 +109,24 @@ class MockRedis
61
109
  end
62
110
  end
63
111
 
64
- def zinterstore(destination, keys, options={})
112
+ def zinterstore(destination, keys, options = {})
65
113
  assert_has_args(keys, 'zinterstore')
66
114
 
67
115
  data[destination] = combine_weighted_zsets(keys, options, :intersection)
68
116
  zcard(destination)
69
117
  end
70
118
 
71
- def zrange(key, start, stop, options={})
119
+ def zrange(key, start, stop, options = {})
72
120
  with_zset_at(key) do |z|
73
- to_response(z.sorted[start.to_i..stop.to_i] || [], options)
121
+ start = [start.to_i, -z.sorted.size].max
122
+ stop = stop.to_i
123
+ to_response(z.sorted[start..stop] || [], options)
74
124
  end
75
125
  end
76
126
 
77
- def zrangebyscore(key, min, max, options={})
127
+ def zrangebyscore(key, min, max, options = {})
128
+ assert_range_args(min, max)
129
+
78
130
  with_zset_at(key) do |zset|
79
131
  all_results = zset.in_range(min, max)
80
132
  to_response(apply_limit(all_results, options[:limit]), options)
@@ -82,63 +134,106 @@ class MockRedis
82
134
  end
83
135
 
84
136
  def zrank(key, member)
85
- with_zset_at(key) {|z| z.sorted_members.index(member.to_s) }
137
+ with_zset_at(key) { |z| z.sorted_members.index(member.to_s) }
86
138
  end
87
139
 
88
140
  def zrem(key, *args)
89
141
  if !args.first.is_a?(Array)
90
- retval = with_zset_at(key) {|z| !!z.delete?(args.first.to_s)}
142
+ retval = with_zset_at(key) { |z| !!z.delete?(args.first.to_s) }
91
143
  else
92
144
  args = args.first
93
- retval = args.map { |member| !!zscore(key, member.to_s) }.count(true)
94
- with_zset_at(key) do |z|
95
- args.each { |member| z.delete?(member) }
145
+ if args.empty?
146
+ raise Redis::CommandError, "ERR wrong number of arguments for 'zrem' command"
147
+ else
148
+ retval = args.map { |member| !!zscore(key, member.to_s) }.count(true)
149
+ with_zset_at(key) do |z|
150
+ args.each { |member| z.delete?(member) }
151
+ end
96
152
  end
97
153
  end
98
154
 
99
155
  retval
100
156
  end
101
157
 
102
- def zrevrange(key, start, stop, options={})
158
+ def zpopmin(key, count = 1)
103
159
  with_zset_at(key) do |z|
104
- to_response(z.sorted.reverse[start..stop], options)
160
+ pairs = z.sorted.first(count)
161
+ pairs.each { |pair| z.delete?(pair.last) }
162
+ retval = to_response(pairs, with_scores: true)
163
+ count == 1 ? retval.first : retval
164
+ end
165
+ end
166
+
167
+ def zpopmax(key, count = 1)
168
+ with_zset_at(key) do |z|
169
+ pairs = z.sorted.reverse.first(count)
170
+ pairs.each { |pair| z.delete?(pair.last) }
171
+ retval = to_response(pairs, with_scores: true)
172
+ count == 1 ? retval.first : retval
173
+ end
174
+ end
175
+
176
+ def zrevrange(key, start, stop, options = {})
177
+ with_zset_at(key) do |z|
178
+ to_response(z.sorted.reverse[start..stop] || [], options)
105
179
  end
106
180
  end
107
181
 
108
182
  def zremrangebyrank(key, start, stop)
109
183
  zrange(key, start, stop).
110
- each {|member| zrem(key, member)}.
184
+ each { |member| zrem(key, member) }.
111
185
  size
112
186
  end
113
187
 
114
188
  def zremrangebyscore(key, min, max)
189
+ assert_range_args(min, max)
190
+
115
191
  zrangebyscore(key, min, max).
116
- each {|member| zrem(key, member)}.
192
+ each { |member| zrem(key, member) }.
117
193
  size
118
194
  end
119
195
 
120
- def zrevrangebyscore(key, max, min, options={})
196
+ def zrevrangebyscore(key, max, min, options = {})
197
+ assert_range_args(min, max)
198
+
121
199
  with_zset_at(key) do |zset|
122
200
  to_response(
123
201
  apply_limit(
124
202
  zset.in_range(min, max).reverse,
125
- options[:limit]),
126
- options)
203
+ options[:limit]
204
+ ),
205
+ options
206
+ )
127
207
  end
128
208
  end
129
209
 
130
210
  def zrevrank(key, member)
131
- with_zset_at(key) {|z| z.sorted_members.reverse.index(member.to_s) }
211
+ with_zset_at(key) { |z| z.sorted_members.reverse.index(member.to_s) }
212
+ end
213
+
214
+ def zscan(key, cursor, opts = {})
215
+ opts = opts.merge(key: lambda { |x| x[0] })
216
+ common_scan(zrange(key, 0, -1, withscores: true), cursor, opts)
217
+ end
218
+
219
+ def zscan_each(key, opts = {}, &block)
220
+ return to_enum(:zscan_each, key, opts) unless block_given?
221
+ cursor = 0
222
+ loop do
223
+ cursor, values = zscan(key, cursor, opts)
224
+ values.each(&block)
225
+ break if cursor == '0'
226
+ end
132
227
  end
133
228
 
134
229
  def zscore(key, member)
135
230
  with_zset_at(key) do |z|
136
231
  score = z.score(member.to_s)
137
- score.to_f if score
232
+ score&.to_f
138
233
  end
139
234
  end
140
235
 
141
- def zunionstore(destination, keys, options={})
236
+ def zunionstore(destination, keys, options = {})
142
237
  assert_has_args(keys, 'zunionstore')
143
238
 
144
239
  data[destination] = combine_weighted_zsets(keys, options, :union)
@@ -146,13 +241,14 @@ class MockRedis
146
241
  end
147
242
 
148
243
  private
244
+
149
245
  def apply_limit(collection, limit)
150
246
  if limit
151
247
  if limit.is_a?(Array) && limit.length == 2
152
248
  offset, count = limit
153
249
  collection.drop(offset).take(count)
154
250
  else
155
- raise Redis::CommandError, "ERR syntax error"
251
+ raise Redis::CommandError, 'ERR syntax error'
156
252
  end
157
253
  else
158
254
  collection
@@ -160,7 +256,7 @@ class MockRedis
160
256
  end
161
257
 
162
258
  def to_response(score_member_pairs, options)
163
- score_member_pairs.map do |(score,member)|
259
+ score_member_pairs.map do |(score, member)|
164
260
  if options[:with_scores] || options[:withscores]
165
261
  [member, score.to_f]
166
262
  else
@@ -172,21 +268,21 @@ class MockRedis
172
268
  def combine_weighted_zsets(keys, options, how)
173
269
  weights = options.fetch(:weights, keys.map { 1 })
174
270
  if weights.length != keys.length
175
- raise Redis::CommandError, "ERR syntax error"
271
+ raise Redis::CommandError, 'ERR syntax error'
176
272
  end
177
273
 
178
274
  aggregator = case options.fetch(:aggregate, :sum).to_s.downcase.to_sym
179
275
  when :sum
180
- proc {|a,b| [a,b].compact.reduce(&:+)}
276
+ proc { |a, b| [a, b].compact.reduce(&:+) }
181
277
  when :min
182
- proc {|a,b| [a,b].compact.min}
278
+ proc { |a, b| [a, b].compact.min }
183
279
  when :max
184
- proc {|a,b| [a,b].compact.max}
280
+ proc { |a, b| [a, b].compact.max }
185
281
  else
186
- raise Redis::CommandError, "ERR syntax error"
282
+ raise Redis::CommandError, 'ERR syntax error'
187
283
  end
188
284
 
189
- with_zsets_at(*keys) do |*zsets|
285
+ with_zsets_at(*keys, coercible: true) do |*zsets|
190
286
  zsets.zip(weights).map do |(zset, weight)|
191
287
  zset.reduce(Zset.new) do |acc, (score, member)|
192
288
  acc.add(score * weight, member)
@@ -195,33 +291,57 @@ class MockRedis
195
291
  za.send(how, zb, &aggregator)
196
292
  end
197
293
  end
294
+ end
198
295
 
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
199
302
  end
200
303
 
201
- def with_zset_at(key, &blk)
202
- with_thing_at(key, :assert_zsety, proc {Zset.new}, &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
203
312
  end
204
313
 
205
- def with_zsets_at(*keys, &blk)
314
+ def with_zsets_at(*keys, coercible: false, &blk)
206
315
  if keys.length == 1
207
- with_zset_at(keys.first, &blk)
316
+ with_zset_at(keys.first, coercible: coercible, &blk)
208
317
  else
209
- with_zset_at(keys.first) do |set|
210
- with_zsets_at(*(keys[1..-1])) do |*sets|
211
- blk.call(*([set] + sets))
318
+ with_zset_at(keys.first, coercible: coercible) do |set|
319
+ with_zsets_at(*(keys[1..-1]), coercible: coercible) do |*sets|
320
+ yield(*([set] + sets))
212
321
  end
213
322
  end
214
323
  end
215
324
  end
216
325
 
217
326
  def zsety?(key)
218
- data[key].nil? || data[key].kind_of?(Zset)
327
+ data[key].nil? || data[key].is_a?(Zset)
328
+ end
329
+
330
+ def coercible_zsety?(key)
331
+ zsety?(key) || data[key].is_a?(Set)
219
332
  end
220
333
 
221
334
  def assert_zsety(key)
222
335
  unless zsety?(key)
223
336
  raise Redis::CommandError,
224
- "ERR Operation against a key holding the wrong kind of value"
337
+ 'WRONGTYPE Operation against a key holding the wrong kind of value'
338
+ end
339
+ end
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'
225
345
  end
226
346
  end
227
347
 
@@ -230,11 +350,19 @@ class MockRedis
230
350
  !!Float(x) rescue false
231
351
  end
232
352
 
233
- def assert_scorey(value, message = "ERR value is not a valid float")
353
+ def assert_scorey(value, message = 'ERR value is not a valid float')
354
+ return if value.to_s =~ /\(?(\-|\+)inf/
355
+
356
+ value = $1 if value.to_s =~ /\((.*)/
234
357
  unless looks_like_float?(value)
235
358
  raise Redis::CommandError, message
236
359
  end
237
360
  end
238
361
 
362
+ def assert_range_args(min, max)
363
+ [min, max].each do |value|
364
+ assert_scorey(value, 'ERR min or max is not a float')
365
+ end
366
+ end
239
367
  end
240
368
  end