mock_redis 0.19.0 → 0.44.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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +161 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +41 -17
  5. data/lib/mock_redis/connection_method.rb +13 -0
  6. data/lib/mock_redis/database.rb +116 -41
  7. data/lib/mock_redis/expire_wrapper.rb +1 -1
  8. data/lib/mock_redis/future.rb +1 -1
  9. data/lib/mock_redis/geospatial_methods.rb +13 -21
  10. data/lib/mock_redis/hash_methods.rb +34 -15
  11. data/lib/mock_redis/indifferent_hash.rb +0 -8
  12. data/lib/mock_redis/info_method.rb +2 -2
  13. data/lib/mock_redis/list_methods.rb +39 -4
  14. data/lib/mock_redis/memory_method.rb +11 -0
  15. data/lib/mock_redis/multi_db_wrapper.rb +4 -4
  16. data/lib/mock_redis/pipelined_wrapper.rb +32 -15
  17. data/lib/mock_redis/set_methods.rb +34 -6
  18. data/lib/mock_redis/stream/id.rb +58 -0
  19. data/lib/mock_redis/stream.rb +88 -0
  20. data/lib/mock_redis/stream_methods.rb +102 -0
  21. data/lib/mock_redis/string_methods.rb +81 -30
  22. data/lib/mock_redis/transaction_wrapper.rb +32 -21
  23. data/lib/mock_redis/utility_methods.rb +9 -4
  24. data/lib/mock_redis/version.rb +1 -1
  25. data/lib/mock_redis/zset.rb +5 -8
  26. data/lib/mock_redis/zset_methods.rb +64 -12
  27. data/lib/mock_redis.rb +38 -10
  28. metadata +40 -336
  29. data/.gitignore +0 -5
  30. data/.mailmap +0 -2
  31. data/.overcommit.yml +0 -21
  32. data/.rspec +0 -1
  33. data/.rubocop.yml +0 -121
  34. data/.rubocop_todo.yml +0 -35
  35. data/.simplecov +0 -4
  36. data/.travis.yml +0 -33
  37. data/Gemfile +0 -12
  38. data/LICENSE +0 -19
  39. data/Rakefile +0 -2
  40. data/mock_redis.gemspec +0 -30
  41. data/spec/client_spec.rb +0 -17
  42. data/spec/cloning_spec.rb +0 -95
  43. data/spec/commands/append_spec.rb +0 -24
  44. data/spec/commands/auth_spec.rb +0 -7
  45. data/spec/commands/bgrewriteaof_spec.rb +0 -7
  46. data/spec/commands/bgsave_spec.rb +0 -7
  47. data/spec/commands/bitcount_spec.rb +0 -25
  48. data/spec/commands/bitfield_spec.rb +0 -169
  49. data/spec/commands/blpop_spec.rb +0 -59
  50. data/spec/commands/brpop_spec.rb +0 -58
  51. data/spec/commands/brpoplpush_spec.rb +0 -52
  52. data/spec/commands/connected_spec.rb +0 -7
  53. data/spec/commands/dbsize_spec.rb +0 -18
  54. data/spec/commands/decr_spec.rb +0 -34
  55. data/spec/commands/decrby_spec.rb +0 -34
  56. data/spec/commands/del_spec.rb +0 -35
  57. data/spec/commands/disconnect_spec.rb +0 -7
  58. data/spec/commands/echo_spec.rb +0 -11
  59. data/spec/commands/eval_spec.rb +0 -7
  60. data/spec/commands/evalsha_spec.rb +0 -10
  61. data/spec/commands/exists_spec.rb +0 -14
  62. data/spec/commands/expire_spec.rb +0 -111
  63. data/spec/commands/expireat_spec.rb +0 -47
  64. data/spec/commands/flushall_spec.rb +0 -38
  65. data/spec/commands/flushdb_spec.rb +0 -38
  66. data/spec/commands/future_spec.rb +0 -20
  67. data/spec/commands/geoadd_spec.rb +0 -58
  68. data/spec/commands/geodist_spec.rb +0 -114
  69. data/spec/commands/geohash_spec.rb +0 -52
  70. data/spec/commands/geopos_spec.rb +0 -55
  71. data/spec/commands/get_spec.rb +0 -30
  72. data/spec/commands/getbit_spec.rb +0 -34
  73. data/spec/commands/getrange_spec.rb +0 -22
  74. data/spec/commands/getset_spec.rb +0 -23
  75. data/spec/commands/hash_operator_spec.rb +0 -21
  76. data/spec/commands/hdel_spec.rb +0 -54
  77. data/spec/commands/hexists_spec.rb +0 -27
  78. data/spec/commands/hget_spec.rb +0 -28
  79. data/spec/commands/hgetall_spec.rb +0 -32
  80. data/spec/commands/hincrby_spec.rb +0 -58
  81. data/spec/commands/hincrbyfloat_spec.rb +0 -58
  82. data/spec/commands/hkeys_spec.rb +0 -19
  83. data/spec/commands/hlen_spec.rb +0 -19
  84. data/spec/commands/hmget_spec.rb +0 -40
  85. data/spec/commands/hmset_spec.rb +0 -43
  86. data/spec/commands/hscan_each_spec.rb +0 -48
  87. data/spec/commands/hscan_spec.rb +0 -27
  88. data/spec/commands/hset_spec.rb +0 -38
  89. data/spec/commands/hsetnx_spec.rb +0 -44
  90. data/spec/commands/hvals_spec.rb +0 -19
  91. data/spec/commands/incr_spec.rb +0 -34
  92. data/spec/commands/incrby_spec.rb +0 -44
  93. data/spec/commands/incrbyfloat_spec.rb +0 -44
  94. data/spec/commands/info_spec.rb +0 -62
  95. data/spec/commands/keys_spec.rb +0 -122
  96. data/spec/commands/lastsave_spec.rb +0 -8
  97. data/spec/commands/lindex_spec.rb +0 -49
  98. data/spec/commands/linsert_spec.rb +0 -68
  99. data/spec/commands/llen_spec.rb +0 -16
  100. data/spec/commands/lpop_spec.rb +0 -34
  101. data/spec/commands/lpush_spec.rb +0 -43
  102. data/spec/commands/lpushx_spec.rb +0 -46
  103. data/spec/commands/lrange_spec.rb +0 -51
  104. data/spec/commands/lrem_spec.rb +0 -80
  105. data/spec/commands/lset_spec.rb +0 -43
  106. data/spec/commands/ltrim_spec.rb +0 -45
  107. data/spec/commands/mapped_hmget_spec.rb +0 -29
  108. data/spec/commands/mapped_hmset_spec.rb +0 -47
  109. data/spec/commands/mapped_mget_spec.rb +0 -22
  110. data/spec/commands/mapped_mset_spec.rb +0 -19
  111. data/spec/commands/mapped_msetnx_spec.rb +0 -26
  112. data/spec/commands/mget_spec.rb +0 -34
  113. data/spec/commands/move_spec.rb +0 -147
  114. data/spec/commands/mset_spec.rb +0 -29
  115. data/spec/commands/msetnx_spec.rb +0 -40
  116. data/spec/commands/persist_spec.rb +0 -48
  117. data/spec/commands/pexpire_spec.rb +0 -86
  118. data/spec/commands/pexpireat_spec.rb +0 -48
  119. data/spec/commands/ping_spec.rb +0 -7
  120. data/spec/commands/pipelined_spec.rb +0 -42
  121. data/spec/commands/pttl_spec.rb +0 -41
  122. data/spec/commands/quit_spec.rb +0 -7
  123. data/spec/commands/randomkey_spec.rb +0 -20
  124. data/spec/commands/rename_spec.rb +0 -42
  125. data/spec/commands/renamenx_spec.rb +0 -41
  126. data/spec/commands/rpop_spec.rb +0 -34
  127. data/spec/commands/rpoplpush_spec.rb +0 -50
  128. data/spec/commands/rpush_spec.rb +0 -43
  129. data/spec/commands/rpushx_spec.rb +0 -46
  130. data/spec/commands/sadd_spec.rb +0 -45
  131. data/spec/commands/save_spec.rb +0 -7
  132. data/spec/commands/scan_each_spec.rb +0 -39
  133. data/spec/commands/scan_spec.rb +0 -55
  134. data/spec/commands/scard_spec.rb +0 -18
  135. data/spec/commands/script_spec.rb +0 -9
  136. data/spec/commands/sdiff_spec.rb +0 -47
  137. data/spec/commands/sdiffstore_spec.rb +0 -58
  138. data/spec/commands/select_spec.rb +0 -61
  139. data/spec/commands/set_spec.rb +0 -63
  140. data/spec/commands/setbit_spec.rb +0 -54
  141. data/spec/commands/setex_spec.rb +0 -22
  142. data/spec/commands/setnx_spec.rb +0 -25
  143. data/spec/commands/setrange_spec.rb +0 -30
  144. data/spec/commands/sinter_spec.rb +0 -39
  145. data/spec/commands/sinterstore_spec.rb +0 -53
  146. data/spec/commands/sismember_spec.rb +0 -29
  147. data/spec/commands/smembers_spec.rb +0 -28
  148. data/spec/commands/smove_spec.rb +0 -41
  149. data/spec/commands/sort_list_spec.rb +0 -21
  150. data/spec/commands/sort_set_spec.rb +0 -21
  151. data/spec/commands/sort_zset_spec.rb +0 -21
  152. data/spec/commands/spop_spec.rb +0 -25
  153. data/spec/commands/srandmember_spec.rb +0 -49
  154. data/spec/commands/srem_spec.rb +0 -40
  155. data/spec/commands/sscan_each_spec.rb +0 -48
  156. data/spec/commands/sscan_spec.rb +0 -39
  157. data/spec/commands/strlen_spec.rb +0 -18
  158. data/spec/commands/sunion_spec.rb +0 -42
  159. data/spec/commands/sunionstore_spec.rb +0 -59
  160. data/spec/commands/ttl_spec.rb +0 -40
  161. data/spec/commands/type_spec.rb +0 -36
  162. data/spec/commands/unwatch_spec.rb +0 -7
  163. data/spec/commands/watch_spec.rb +0 -16
  164. data/spec/commands/zadd_spec.rb +0 -123
  165. data/spec/commands/zcard_spec.rb +0 -19
  166. data/spec/commands/zcount_spec.rb +0 -39
  167. data/spec/commands/zincrby_spec.rb +0 -31
  168. data/spec/commands/zinterstore_spec.rb +0 -96
  169. data/spec/commands/zrange_spec.rb +0 -80
  170. data/spec/commands/zrangebyscore_spec.rb +0 -83
  171. data/spec/commands/zrank_spec.rb +0 -29
  172. data/spec/commands/zrem_spec.rb +0 -43
  173. data/spec/commands/zremrangebyrank_spec.rb +0 -27
  174. data/spec/commands/zremrangebyscore_spec.rb +0 -35
  175. data/spec/commands/zrevrange_spec.rb +0 -56
  176. data/spec/commands/zrevrangebyscore_spec.rb +0 -58
  177. data/spec/commands/zrevrank_spec.rb +0 -29
  178. data/spec/commands/zscan_each_spec.rb +0 -48
  179. data/spec/commands/zscan_spec.rb +0 -26
  180. data/spec/commands/zscore_spec.rb +0 -22
  181. data/spec/commands/zunionstore_spec.rb +0 -104
  182. data/spec/mock_redis_spec.rb +0 -86
  183. data/spec/spec_helper.rb +0 -63
  184. data/spec/support/redis_multiplexer.rb +0 -106
  185. data/spec/support/shared_examples/only_operates_on_hashes.rb +0 -13
  186. data/spec/support/shared_examples/only_operates_on_lists.rb +0 -13
  187. data/spec/support/shared_examples/only_operates_on_sets.rb +0 -13
  188. data/spec/support/shared_examples/only_operates_on_strings.rb +0 -13
  189. data/spec/support/shared_examples/only_operates_on_zsets.rb +0 -57
  190. data/spec/support/shared_examples/sorts_enumerables.rb +0 -56
  191. data/spec/transactions_spec.rb +0 -159
@@ -12,7 +12,7 @@ class MockRedis
12
12
  D_R = Math::PI / 180.0
13
13
  EARTH_RADIUS_IN_METERS = 6_372_797.560856
14
14
 
15
- def geoadd(key, *args)
15
+ ruby2_keywords def geoadd(key, *args)
16
16
  points = parse_points(args)
17
17
 
18
18
  scored_points = points.map do |point|
@@ -23,21 +23,13 @@ class MockRedis
23
23
  zadd(key, scored_points)
24
24
  end
25
25
 
26
- def geodist(key, *args)
27
- if args.length < 2
28
- raise Redis::CommandError,
29
- "ERR wrong number of arguments for 'geodist' command"
30
- end
31
-
32
- raise Redis::CommandError, 'ERR syntax error' if args.length > 3
33
-
34
- to_meter = 1
35
- to_meter = parse_unit(args[2]) if args.length == 3
26
+ def geodist(key, member1, member2, unit = 'm')
27
+ to_meter = parse_unit(unit)
36
28
 
37
29
  return nil if zcard(key).zero?
38
30
 
39
- score1 = zscore(key, args[0])
40
- score2 = zscore(key, args[1])
31
+ score1 = zscore(key, member1)
32
+ score2 = zscore(key, member2)
41
33
  return nil if score1.nil? || score2.nil?
42
34
  hash1 = { bits: score1.to_i, step: STEP }
43
35
  hash2 = { bits: score2.to_i, step: STEP }
@@ -46,15 +38,15 @@ class MockRedis
46
38
  lng2, lat2 = geohash_decode(hash2)
47
39
 
48
40
  distance = geohash_distance(lng1, lat1, lng2, lat2) / to_meter
49
- format('%.4f', distance)
41
+ format('%<distance>.4f', distance: distance)
50
42
  end
51
43
 
52
- def geohash(key, *members)
44
+ def geohash(key, members)
53
45
  lng_range = (-180..180)
54
46
  lat_range = (-90..90)
55
47
  geoalphabet = '0123456789bcdefghjkmnpqrstuvwxyz'
56
48
 
57
- members.map do |member|
49
+ Array(members).map do |member|
58
50
  score = zscore(key, member)
59
51
  next nil unless score
60
52
  score = score.to_i
@@ -71,8 +63,8 @@ class MockRedis
71
63
  end
72
64
  end
73
65
 
74
- def geopos(key, *members)
75
- members.map do |member|
66
+ def geopos(key, members)
67
+ Array(members).map do |member|
76
68
  score = zscore(key, member)
77
69
  next nil unless score
78
70
  hash = { bits: score.to_i, step: STEP }
@@ -103,8 +95,8 @@ class MockRedis
103
95
  lat = Float(point[1])
104
96
 
105
97
  unless LNG_RANGE.include?(lng) && LAT_RANGE.include?(lat)
106
- lng = format('%.6f', lng)
107
- lat = format('%.6f', lat)
98
+ lng = format('%<long>.6f', long: lng)
99
+ lat = format('%<lat>.6f', lat: lat)
108
100
  raise Redis::CommandError,
109
101
  "ERR invalid longitude,latitude pair #{lng},#{lat}"
110
102
  end
@@ -209,7 +201,7 @@ class MockRedis
209
201
  end
210
202
 
211
203
  def format_decoded_coord(coord)
212
- coord = format('%.17f', coord)
204
+ coord = format('%<coord>.17f', coord: coord)
213
205
  l = 1
214
206
  l += 1 while coord[-l] == '0'
215
207
  coord = coord[0..-l]
@@ -6,16 +6,17 @@ class MockRedis
6
6
  include Assertions
7
7
  include UtilityMethods
8
8
 
9
- def hdel(key, field)
9
+ def hdel(key, *fields)
10
10
  with_hash_at(key) do |hash|
11
- if field.is_a?(Array)
12
- orig_size = hash.size
13
- fields = field.map(&:to_s)
14
- hash.delete_if { |k, _v| fields.include?(k) }
15
- orig_size - hash.size
16
- else
17
- hash.delete(field.to_s) ? 1 : 0
11
+ orig_size = hash.size
12
+ fields = Array(fields).flatten.map(&:to_s)
13
+
14
+ if fields.empty?
15
+ raise Redis::CommandError, "ERR wrong number of arguments for 'hdel' command"
18
16
  end
17
+
18
+ hash.delete_if { |k, _v| fields.include?(k) }
19
+ orig_size - hash.size
19
20
  end
20
21
  end
21
22
 
@@ -74,8 +75,10 @@ class MockRedis
74
75
  end
75
76
 
76
77
  def hmget(key, *fields)
78
+ fields.flatten!
79
+
77
80
  assert_has_args(fields, 'hmget')
78
- fields.flatten.map { |f| hget(key, f) }
81
+ fields.map { |f| hget(key, f) }
79
82
  end
80
83
 
81
84
  def mapped_hmget(key, *fields)
@@ -88,10 +91,17 @@ class MockRedis
88
91
  end
89
92
 
90
93
  def hmset(key, *kvpairs)
94
+ if key.is_a? Array
95
+ err_msg = 'ERR wrong number of arguments for \'hmset\' command'
96
+ kvpairs = key[1..]
97
+ key = key[0]
98
+ end
99
+
91
100
  kvpairs.flatten!
92
101
  assert_has_args(kvpairs, 'hmset')
102
+
93
103
  if kvpairs.length.odd?
94
- raise Redis::CommandError, 'ERR wrong number of arguments for HMSET'
104
+ raise Redis::CommandError, err_msg || 'ERR wrong number of arguments for \'hmset\' command'
95
105
  end
96
106
 
97
107
  kvpairs.each_slice(2) do |(k, v)|
@@ -125,10 +135,19 @@ class MockRedis
125
135
  end
126
136
  end
127
137
 
128
- def hset(key, field, value)
129
- field_exists = hexists(key, field)
130
- with_hash_at(key) { |h| h[field.to_s] = value.to_s }
131
- !field_exists
138
+ def hset(key, *args)
139
+ added = 0
140
+ with_hash_at(key) do |hash|
141
+ if args.length == 1 && args[0].is_a?(Hash)
142
+ args = args[0].to_a.flatten
143
+ end
144
+
145
+ args.each_slice(2) do |field, value|
146
+ added += 1 unless hash.key?(field.to_s)
147
+ hash[field.to_s] = value.to_s
148
+ end
149
+ end
150
+ added
132
151
  end
133
152
 
134
153
  def hsetnx(key, field, value)
@@ -147,7 +166,7 @@ class MockRedis
147
166
  private
148
167
 
149
168
  def with_hash_at(key, &blk)
150
- with_thing_at(key, :assert_hashy, proc { {} }, &blk)
169
+ with_thing_at(key.to_s, :assert_hashy, proc { {} }, &blk)
151
170
  end
152
171
 
153
172
  def hashy?(key)
@@ -1,13 +1,5 @@
1
1
  class MockRedis
2
2
  class IndifferentHash < Hash
3
- def [](key)
4
- super(key.to_s)
5
- end
6
-
7
- def []=(key, value)
8
- super(key.to_s, value)
9
- end
10
-
11
3
  def has_key?(key)
12
4
  super(key.to_s)
13
5
  end
@@ -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,
@@ -6,6 +6,20 @@ class MockRedis
6
6
  include Assertions
7
7
  include UtilityMethods
8
8
 
9
+ def blmove(source, destination, wherefrom, whereto, options = {})
10
+ options = { :timeout => options } if options.is_a?(Integer)
11
+ timeout = options.is_a?(Hash) && options[:timeout] || 0
12
+ assert_valid_timeout(timeout)
13
+
14
+ if llen(source) > 0
15
+ lmove(source, destination, wherefrom, whereto)
16
+ elsif timeout > 0
17
+ nil
18
+ else
19
+ raise MockRedis::WouldBlock, "Can't block forever"
20
+ end
21
+ end
22
+
9
23
  def blpop(*args)
10
24
  lists, timeout = extract_timeout(args)
11
25
  nonempty_list = first_nonempty_list(lists)
@@ -78,8 +92,29 @@ class MockRedis
78
92
  with_list_at(key, &:length)
79
93
  end
80
94
 
81
- def lpop(key)
82
- with_list_at(key, &:shift)
95
+ def lmove(source, destination, wherefrom, whereto)
96
+ assert_listy(source)
97
+ assert_listy(destination)
98
+
99
+ wherefrom = wherefrom.to_s.downcase
100
+ whereto = whereto.to_s.downcase
101
+
102
+ unless %w[left right].include?(wherefrom) && %w[left right].include?(whereto)
103
+ raise Redis::CommandError, 'ERR syntax error'
104
+ end
105
+
106
+ value = wherefrom == 'left' ? lpop(source) : rpop(source)
107
+ (whereto == 'left' ? lpush(destination, value) : rpush(destination, value)) unless value.nil?
108
+ value
109
+ end
110
+
111
+ def lpop(key, count = nil)
112
+ return with_list_at(key, &:shift) if count.nil?
113
+
114
+ record_count = llen(key)
115
+ return nil if record_count.zero?
116
+
117
+ [record_count, count].min.times.map { with_list_at(key, &:shift) }
83
118
  end
84
119
 
85
120
  def lpush(key, values)
@@ -146,13 +181,13 @@ class MockRedis
146
181
 
147
182
  def ltrim(key, start, stop)
148
183
  with_list_at(key) do |list|
149
- list.replace(list[[start.to_i, -list.length].max..stop.to_i] || []) if list
184
+ list&.replace(list[[start.to_i, -list.length].max..stop.to_i] || [])
150
185
  'OK'
151
186
  end
152
187
  end
153
188
 
154
189
  def rpop(key)
155
- with_list_at(key) { |list| list.pop if list }
190
+ with_list_at(key) { |list| list&.pop }
156
191
  end
157
192
 
158
193
  def rpoplpush(source, destination)
@@ -0,0 +1,11 @@
1
+ class MockRedis
2
+ module MemoryMethod
3
+ def memory(usage, key = nil, *_options)
4
+ raise ArgumentError, "unhandled command `memory #{usage}`" if usage != 'usage'
5
+
6
+ return nil unless @data.key?(key)
7
+
8
+ 160
9
+ end
10
+ end
11
+ end
@@ -17,21 +17,21 @@ class MockRedis
17
17
  super || current_db.respond_to?(method, include_private)
18
18
  end
19
19
 
20
- def method_missing(method, *args, &block)
20
+ ruby2_keywords def method_missing(method, *args, &block)
21
21
  current_db.send(method, *args, &block)
22
22
  end
23
23
 
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
31
31
 
32
32
  # Redis commands
33
33
  def flushall
34
- @databases.values.each(&:flushdb)
34
+ @databases.each_value(&:flushdb)
35
35
  'OK'
36
36
  end
37
37
 
@@ -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)
@@ -9,7 +9,7 @@ class MockRedis
9
9
  def initialize(db)
10
10
  @db = db
11
11
  @pipelined_futures = []
12
- @in_pipeline = false
12
+ @nesting_level = 0
13
13
  end
14
14
 
15
15
  def initialize_copy(source)
@@ -18,8 +18,8 @@ class MockRedis
18
18
  @pipelined_futures = @pipelined_futures.clone
19
19
  end
20
20
 
21
- def method_missing(method, *args, &block)
22
- if @in_pipeline
21
+ ruby2_keywords def method_missing(method, *args, &block)
22
+ if in_pipeline?
23
23
  future = MockRedis::Future.new([method, *args], block)
24
24
  @pipelined_futures << future
25
25
  future
@@ -29,24 +29,41 @@ class MockRedis
29
29
  end
30
30
 
31
31
  def pipelined(_options = {})
32
- @in_pipeline = true
33
- yield self
34
- @in_pipeline = false
32
+ begin
33
+ @nesting_level += 1
34
+ yield self
35
+ ensure
36
+ @nesting_level -= 1
37
+ end
38
+
39
+ if in_pipeline?
40
+ return
41
+ end
42
+
35
43
  responses = @pipelined_futures.flat_map do |future|
36
- begin
37
- result = if future.block
38
- send(*future.command, &future.block)
39
- else
40
- send(*future.command)
41
- end
42
- future.store_result(result)
44
+ result = if future.block
45
+ send(*future.command, &future.block)
46
+ else
47
+ send(*future.command)
48
+ end
49
+ future.store_result(result)
50
+
51
+ if future.block
43
52
  result
44
- rescue StandardError => e
45
- e
53
+ else
54
+ [result]
46
55
  end
56
+ rescue StandardError => e
57
+ e
47
58
  end
48
59
  @pipelined_futures = []
49
60
  responses
50
61
  end
62
+
63
+ private
64
+
65
+ def in_pipeline?
66
+ @nesting_level > 0
67
+ end
51
68
  end
52
69
  end
@@ -8,7 +8,7 @@ class MockRedis
8
8
 
9
9
  def sadd(key, members)
10
10
  members_class = members.class
11
- members = [members].flatten.map(&:to_s)
11
+ members = Array(members).map(&:to_s)
12
12
  assert_has_args(members, 'sadd')
13
13
 
14
14
  with_set_at(key) do |s|
@@ -27,6 +27,11 @@ class MockRedis
27
27
  end
28
28
  end
29
29
 
30
+ def sadd?(key, members)
31
+ res = sadd(key, members)
32
+ res.is_a?(Numeric) ? res > 0 : res
33
+ end
34
+
30
35
  def scard(key)
31
36
  with_set_at(key, &:length)
32
37
  end
@@ -64,6 +69,12 @@ class MockRedis
64
69
  with_set_at(key) { |s| s.include?(member.to_s) }
65
70
  end
66
71
 
72
+ def smismember(key, *members)
73
+ with_set_at(key) do |set|
74
+ members.flatten.map { |m| set.include?(m.to_s) }
75
+ end
76
+ end
77
+
67
78
  def smembers(key)
68
79
  with_set_at(key, &:to_a).map(&:dup).reverse
69
80
  end
@@ -81,11 +92,22 @@ class MockRedis
81
92
  end
82
93
  end
83
94
 
84
- def spop(key)
95
+ def spop(key, count = nil)
85
96
  with_set_at(key) do |set|
86
- member = set.first
87
- set.delete(member)
88
- member
97
+ if count.nil?
98
+ member = set.first
99
+ set.delete(member)
100
+ member
101
+ else
102
+ members = []
103
+ count.times do
104
+ member = set.first
105
+ break if member.nil?
106
+ set.delete(member)
107
+ members << member
108
+ end
109
+ members
110
+ end
89
111
  end
90
112
  end
91
113
 
@@ -106,6 +128,7 @@ class MockRedis
106
128
  with_set_at(key) do |s|
107
129
  if members.is_a?(Array)
108
130
  orig_size = s.size
131
+ members = members.map(&:to_s)
109
132
  s.delete_if { |m| members.include?(m) }
110
133
  orig_size - s.size
111
134
  else
@@ -114,6 +137,11 @@ class MockRedis
114
137
  end
115
138
  end
116
139
 
140
+ def srem?(key, members)
141
+ res = srem(key, members)
142
+ res.is_a?(Numeric) ? res > 0 : res
143
+ end
144
+
117
145
  def sscan(key, cursor, opts = {})
118
146
  common_scan(smembers(key), cursor, opts)
119
147
  end
@@ -153,7 +181,7 @@ class MockRedis
153
181
  with_set_at(keys.first, &blk)
154
182
  else
155
183
  with_set_at(keys.first) do |set|
156
- with_sets_at(*(keys[1..-1])) do |*sets|
184
+ with_sets_at(*(keys[1..])) do |*sets|
157
185
  yield(*([set] + sets))
158
186
  end
159
187
  end
@@ -0,0 +1,58 @@
1
+ class MockRedis
2
+ class Stream
3
+ class Id
4
+ include Comparable
5
+
6
+ attr_accessor :timestamp, :sequence, :exclusive
7
+
8
+ def initialize(id, min: nil, sequence: 0)
9
+ @exclusive = false
10
+ case id
11
+ when '*'
12
+ @timestamp = (Time.now.to_f * 1000).to_i
13
+ @sequence = 0
14
+ if self <= min
15
+ @timestamp = min.timestamp
16
+ @sequence = min.sequence + 1
17
+ end
18
+ when '-'
19
+ @timestamp = @sequence = 0
20
+ when '+'
21
+ @timestamp = @sequence = Float::INFINITY
22
+ else
23
+ if id.is_a? String
24
+ # See https://redis.io/topics/streams-intro
25
+ # Ids are a unix timestamp in milliseconds followed by an
26
+ # optional dash sequence number, e.g. -0. They can also optionally
27
+ # be prefixed with '(' to change the XRANGE to exclusive.
28
+ (_, @timestamp, @sequence) = id.match(/^\(?(\d+)-?(\d+)?$/).to_a
29
+ @exclusive = true if id[0] == '('
30
+ if @timestamp.nil?
31
+ raise Redis::CommandError,
32
+ 'ERR Invalid stream ID specified as stream command argument'
33
+ end
34
+ @timestamp = @timestamp.to_i
35
+ else
36
+ @timestamp = id
37
+ end
38
+ @sequence = @sequence.nil? ? sequence : @sequence.to_i
39
+ if self <= min
40
+ raise Redis::CommandError,
41
+ 'ERR The ID specified in XADD is equal or smaller than ' \
42
+ 'the target stream top item'
43
+ end
44
+ end
45
+ end
46
+
47
+ def to_s
48
+ "#{@timestamp}-#{@sequence}"
49
+ end
50
+
51
+ def <=>(other)
52
+ return 1 if other.nil?
53
+ return @sequence <=> other.sequence if @timestamp == other.timestamp
54
+ @timestamp <=> other.timestamp
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,88 @@
1
+ require 'forwardable'
2
+ require 'set'
3
+ require 'date'
4
+ require 'mock_redis/stream/id'
5
+
6
+ class MockRedis
7
+ class Stream
8
+ include Enumerable
9
+ extend Forwardable
10
+
11
+ attr_accessor :members
12
+
13
+ def_delegators :members, :empty?
14
+
15
+ def initialize
16
+ @members = Set.new
17
+ @last_id = nil
18
+ end
19
+
20
+ def last_id
21
+ @last_id.to_s
22
+ end
23
+
24
+ def add(id, values)
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 must be greater than 0-0'
29
+ end
30
+ members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]]
31
+ @last_id.to_s
32
+ end
33
+
34
+ def trim(count)
35
+ deleted = @members.size - count
36
+ if deleted > 0
37
+ @members = if count == 0
38
+ Set.new
39
+ else
40
+ @members.to_a[-count..].to_set
41
+ end
42
+ deleted
43
+ else
44
+ 0
45
+ end
46
+ end
47
+
48
+ def range(start, finish, reversed, *opts_in)
49
+ opts = options opts_in, ['count']
50
+ start_id = MockRedis::Stream::Id.new(start)
51
+ finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY)
52
+ items = if start_id.exclusive
53
+ members
54
+ .select { |m| (start_id < m[0]) && (finish_id >= m[0]) }
55
+ .map { |m| [m[0].to_s, m[1]] }
56
+ else
57
+ members
58
+ .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) }
59
+ .map { |m| [m[0].to_s, m[1]] }
60
+ end
61
+ items.reverse! if reversed
62
+ return items.first(opts['count'].to_i) if opts.key?('count')
63
+ items
64
+ end
65
+
66
+ def read(id, *opts_in)
67
+ opts = options opts_in, %w[count block]
68
+ stream_id = MockRedis::Stream::Id.new(id)
69
+ items = members.select { |m| (stream_id < m[0]) }.map { |m| [m[0].to_s, m[1]] }
70
+ return items.first(opts['count'].to_i) if opts.key?('count')
71
+ items
72
+ end
73
+
74
+ def each(&block)
75
+ members.each(&block)
76
+ end
77
+
78
+ private
79
+
80
+ def options(opts_in, permitted)
81
+ opts_out = {}
82
+ raise Redis::CommandError, 'ERR syntax error' unless (opts_in.length % 2).zero?
83
+ opts_in.each_slice(2).map { |pair| opts_out[pair[0].downcase] = pair[1] }
84
+ raise Redis::CommandError, 'ERR syntax error' unless (opts_out.keys - permitted).empty?
85
+ opts_out
86
+ end
87
+ end
88
+ end