mock_redis 0.5.4 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
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