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
@@ -5,6 +5,13 @@ require 'mock_redis/list_methods'
5
5
  require 'mock_redis/set_methods'
6
6
  require 'mock_redis/string_methods'
7
7
  require 'mock_redis/zset_methods'
8
+ require 'mock_redis/sort_method'
9
+ require 'mock_redis/indifferent_hash'
10
+ require 'mock_redis/info_method'
11
+ require 'mock_redis/utility_methods'
12
+ require 'mock_redis/geospatial_methods'
13
+ require 'mock_redis/stream_methods'
14
+ require 'mock_redis/connection_method'
8
15
 
9
16
  class MockRedis
10
17
  class Database
@@ -13,277 +20,157 @@ class MockRedis
13
20
  include SetMethods
14
21
  include StringMethods
15
22
  include ZsetMethods
23
+ include SortMethod
24
+ include InfoMethod
25
+ include UtilityMethods
26
+ include GeospatialMethods
27
+ include StreamMethods
28
+ include ConnectionMethod
16
29
 
17
30
  attr_reader :data, :expire_times
18
31
 
19
- def initialize(*args)
20
- @data = {}
32
+ def initialize(base, *_args)
33
+ @base = base
34
+ @data = MockRedis::IndifferentHash.new
21
35
  @expire_times = []
22
36
  end
23
37
 
24
- def initialize_copy(source)
38
+ def initialize_copy(_source)
25
39
  @data = @data.clone
26
- @data.keys.each {|k| @data[k] = @data[k].clone}
27
- @expire_times = @expire_times.map{|x| x.clone}
40
+ @data.each_key { |k| @data[k] = @data[k].clone }
41
+ @expire_times = @expire_times.map(&:clone)
28
42
  end
29
43
 
30
44
  # Redis commands go below this line and above 'private'
31
45
 
32
- def auth(_) 'OK' end
46
+ def auth(_)
47
+ 'OK'
48
+ end
49
+
50
+ def bgrewriteaof
51
+ 'Background append only file rewriting started'
52
+ end
33
53
 
34
- def bgrewriteaof() "Background append only file rewriting started" end
54
+ def bgsave
55
+ 'Background saving started'
56
+ end
57
+
58
+ def disconnect
59
+ nil
60
+ end
61
+ alias close disconnect
62
+ alias disconnect! close
35
63
 
36
- def bgsave() "Background saving started" end
64
+ def connected?
65
+ true
66
+ end
37
67
 
38
68
  def dbsize
39
69
  data.keys.length
40
70
  end
41
71
 
42
72
  def del(*keys)
73
+ keys = keys.flatten.map(&:to_s)
74
+ assert_has_args(keys, 'del')
75
+
43
76
  keys.
44
- find_all{|key| data[key]}.
45
- each {|k| persist(k)}.
46
- each {|k| data.delete(k)}.
77
+ find_all { |key| data[key] }.
78
+ each { |k| persist(k) }.
79
+ each { |k| data.delete(k) }.
47
80
  length
48
81
  end
82
+ alias unlink del
49
83
 
50
84
  def echo(msg)
51
85
  msg.to_s
52
86
  end
53
87
 
54
88
  def expire(key, seconds)
55
- expireat(key, Time.now.to_i + seconds.to_i)
89
+ assert_valid_integer(seconds)
90
+
91
+ pexpire(key, seconds.to_i * 1000)
92
+ end
93
+
94
+ def pexpire(key, ms)
95
+ assert_valid_integer(ms)
96
+
97
+ now, miliseconds = @base.now
98
+ now_ms = (now * 1000) + miliseconds
99
+ pexpireat(key, now_ms + ms.to_i)
56
100
  end
57
101
 
58
102
  def expireat(key, timestamp)
59
- unless looks_like_integer?(timestamp.to_s)
60
- raise Redis::CommandError, "ERR value is not an integer or out of range"
61
- end
103
+ assert_valid_integer(timestamp)
62
104
 
63
- if exists(key)
64
- set_expiration(key, Time.at(timestamp.to_i))
105
+ pexpireat(key, timestamp.to_i * 1000)
106
+ end
107
+
108
+ def pexpireat(key, timestamp_ms)
109
+ assert_valid_integer(timestamp_ms)
110
+
111
+ if exists?(key)
112
+ timestamp = Rational(timestamp_ms.to_i, 1000)
113
+ set_expiration(key, @base.time_at(timestamp))
65
114
  true
66
115
  else
67
116
  false
68
117
  end
69
118
  end
70
119
 
71
- def exists(key)
72
- data.has_key?(key)
120
+ def exists(*keys)
121
+ keys.count { |key| data.key?(key) }
122
+ end
123
+
124
+ def exists?(*keys)
125
+ keys.each { |key| return true if data.key?(key) }
126
+ false
73
127
  end
74
128
 
75
129
  def flushdb
76
- data.keys.each {|k| del(k)}
130
+ data.each_key { |k| del(k) }
131
+ 'OK'
132
+ end
133
+
134
+ def dump(key)
135
+ value = data[key]
136
+ value ? Marshal.dump(value) : nil
137
+ end
138
+
139
+ def restore(key, ttl, value, replace: false)
140
+ if !replace && exists?(key)
141
+ raise Redis::CommandError, 'BUSYKEY Target key name already exists.'
142
+ end
143
+ data[key] = Marshal.load(value) # rubocop:disable Security/MarshalLoad
144
+ if ttl > 0
145
+ pexpire(key, ttl)
146
+ end
77
147
  'OK'
78
148
  end
79
149
 
80
- def info
81
- astats = [
82
- ["2", "2699"],
83
- ["6", "1"],
84
- ["7", "1"],
85
- ["8", "17197"],
86
- ["9", "109875"],
87
- ["10", "94348"],
88
- ["11", "32580"],
89
- ["12", "52347"],
90
- ["13", "86475"],
91
- ["14", "58175"],
92
- ["15", "53408"],
93
- ["16", "876949"],
94
- ["17", "71157"],
95
- ["18", "5104"],
96
- ["19", "2705"],
97
- ["20", "2903"],
98
- ["21", "1024"],
99
- ["22", "2546"],
100
- ["23", "952"],
101
- ["24", "186080"],
102
- ["25", "611"],
103
- ["26", "40936"],
104
- ["27", "960"],
105
- ["28", "1323"],
106
- ["29", "14216"],
107
- ["30", "52412"],
108
- ["31", "21130"],
109
- ["32", "47959"],
110
- ["33", "6891"],
111
- ["34", "9712"],
112
- ["35", "3366"],
113
- ["36", "5737"],
114
- ["37", "11274"],
115
- ["38", "8057"],
116
- ["39", "2957"],
117
- ["40", "51200"],
118
- ["42", "8220"],
119
- ["43", "8278"],
120
- ["44", "6539"],
121
- ["45", "764"],
122
- ["47", "1018"],
123
- ["48", "19250"],
124
- ["49", "713"],
125
- ["51", "51"],
126
- ["53", "2"],
127
- ["55", "3922"],
128
- ["56", "153"],
129
- ["57", "614"],
130
- ["58", "1"],
131
- ["59", "1775"],
132
- ["61", "32865"],
133
- ["63", "2530"],
134
- ["64", "565"],
135
- ["65", "1322"],
136
- ["67", "1572"],
137
- ["69", "1421"],
138
- ["71", "1220"],
139
- ["72", "241"],
140
- ["73", "5432"],
141
- ["74", "1122"],
142
- ["75", "2555"],
143
- ["77", "1539"],
144
- ["78", "612"],
145
- ["79", "902"],
146
- ["81", "1678"],
147
- ["83", "51"],
148
- ["84", "612"],
149
- ["85", "706"],
150
- ["87", "410"],
151
- ["88", "5435"],
152
- ["89", "813"],
153
- ["90", "612"],
154
- ["93", "153"],
155
- ["94", "612"],
156
- ["96", "159"],
157
- ["97", "306"],
158
- ["99", "153"],
159
- ["101", "456"],
160
- ["103", "741"],
161
- ["105", "447"],
162
- ["107", "754"],
163
- ["109", "414"],
164
- ["111", "475"],
165
- ["113", "757"],
166
- ["115", "287"],
167
- ["117", "420"],
168
- ["118", "765"],
169
- ["119", "642"],
170
- ["120", "159"],
171
- ["121", "926"],
172
- ["122", "612"],
173
- ["123", "251"],
174
- ["125", "390"],
175
- ["127", "354"],
176
- ["128", "617"],
177
- ["129", "528"],
178
- ["131", "298"],
179
- ["132", "612"],
180
- ["133", "809"],
181
- ["135", "244"],
182
- ["136", "306"],
183
- ["137", "504"],
184
- ["139", "201"],
185
- ["141", "1124"],
186
- ["143", "139"],
187
- ["144", "159"],
188
- ["145", "1322"],
189
- ["147", "410"],
190
- ["149", "253"],
191
- ["151", "304"],
192
- ["153", "312"],
193
- ["155", "249"],
194
- ["157", "306"],
195
- ["159", "348"],
196
- ["161", "255"],
197
- ["163", "458"],
198
- ["165", "5"],
199
- ["167", "306"],
200
- ["168", "47"],
201
- ["169", "214"],
202
- ["171", "250"],
203
- ["173", "5"],
204
- ["177", "10"],
205
- ["179", "158"],
206
- ["181", "5"],
207
- ["183", "10"],
208
- ["185", "51"],
209
- ["187", "49"],
210
- ["191", "5"],
211
- ["192", "47"],
212
- ["193", "51"],
213
- ["197", "112"],
214
- ["199", "5"],
215
- ["201", "5"],
216
- ["203", "5"],
217
- ["209", "5"],
218
- ["213", "51"],
219
- ["217", "102"],
220
- ["225", "357"],
221
- ["229", "51"],
222
- ["233", "204"],
223
- ["237", "51"],
224
- ["239", "1"],
225
- ["247", "46"],
226
- ["255", "102"],
227
- [">=256", "6201"],
228
- ]
229
-
230
- {
231
- "allocation_stats" => astats.map {|(a,b)| "#{a}=#{b}"}.join(','),
232
- "aof_enabled" => "0",
233
- "arch_bits" => "64",
234
- "bgrewriteaof_in_progress" => "0",
235
- "bgsave_in_progress" => "0",
236
- "blocked_clients" => "0",
237
- "changes_since_last_save" => "0",
238
- "client_biggest_input_buf" => "0",
239
- "client_longest_output_list" => "0",
240
- "connected_clients" => "1",
241
- "connected_slaves" => "0",
242
- "db0" => "keys=8,expires=0",
243
- "evicted_keys" => "0",
244
- "expired_keys" => "0",
245
- "hash_max_zipmap_entries" => "512",
246
- "hash_max_zipmap_value" => "64",
247
- "keyspace_hits" => "62645",
248
- "keyspace_misses" => "29757",
249
- "last_save_time" => "1310596333",
250
- "loading" => "0",
251
- "lru_clock" => "1036434",
252
- "mem_fragmentation_ratio" => "2.04",
253
- "multiplexing_api" => "kqueue",
254
- "process_id" => "14508",
255
- "pubsub_channels" => "0",
256
- "pubsub_patterns" => "0",
257
- "redis_git_dirty" => "0",
258
- "redis_git_sha1" => "00000000",
259
- "redis_version" => "2.2.11",
260
- "role" => "master",
261
- "total_commands_processed" => "196800",
262
- "total_connections_received" => "4359",
263
- "uptime_in_days" => "0",
264
- "uptime_in_seconds" => "84215",
265
- "use_tcmalloc" => "0",
266
- "used_cpu_sys" => "5.54",
267
- "used_cpu_sys_childrens" => "0.00",
268
- "used_cpu_user" => "7.65",
269
- "used_cpu_user_childrens" => "0.02",
270
- "used_memory" => "931456",
271
- "used_memory_human" => "909.62K",
272
- "used_memory_rss" => "1904640",
273
- "vm_enabled" => "0",
274
- }
275
- end
276
-
277
- def keys(format)
150
+ def keys(format = '*')
278
151
  data.keys.grep(redis_pattern_to_ruby_regex(format))
279
152
  end
280
153
 
154
+ def scan(cursor, opts = {})
155
+ common_scan(data.keys, cursor, opts)
156
+ end
157
+
158
+ def scan_each(opts = {}, &block)
159
+ return to_enum(:scan_each, opts) unless block_given?
160
+ cursor = 0
161
+ loop do
162
+ cursor, keys = scan(cursor, opts)
163
+ keys.each(&block)
164
+ break if cursor == '0'
165
+ end
166
+ end
167
+
281
168
  def lastsave
282
- Time.now.to_i
169
+ now.first
283
170
  end
284
171
 
285
172
  def persist(key)
286
- if exists(key) && has_expiration?(key)
173
+ if exists?(key) && has_expiration?(key)
287
174
  remove_expiration(key)
288
175
  true
289
176
  else
@@ -291,8 +178,8 @@ class MockRedis
291
178
  end
292
179
  end
293
180
 
294
- def ping
295
- 'PONG'
181
+ def ping(response = 'PONG')
182
+ response
296
183
  end
297
184
 
298
185
  def quit
@@ -304,22 +191,27 @@ class MockRedis
304
191
  end
305
192
 
306
193
  def rename(key, newkey)
307
- if !data.include?(key)
308
- raise Redis::CommandError, "ERR no such key"
309
- elsif key == newkey
310
- raise Redis::CommandError, "ERR source and destination objects are the same"
194
+ unless data.include?(key)
195
+ raise Redis::CommandError, 'ERR no such key'
311
196
  end
312
- data[newkey] = data.delete(key)
197
+
198
+ if key != newkey
199
+ data[newkey] = data.delete(key)
200
+ if has_expiration?(key)
201
+ set_expiration(newkey, expiration(key))
202
+ remove_expiration(key)
203
+ end
204
+ end
205
+
313
206
  'OK'
314
207
  end
315
208
 
316
209
  def renamenx(key, newkey)
317
- if !data.include?(key)
318
- raise Redis::CommandError, "ERR no such key"
319
- elsif key == newkey
320
- raise Redis::CommandError, "ERR source and destination objects are the same"
210
+ unless data.include?(key)
211
+ raise Redis::CommandError, 'ERR no such key'
321
212
  end
322
- if exists(newkey)
213
+
214
+ if exists?(newkey)
323
215
  false
324
216
  else
325
217
  rename(key, newkey)
@@ -332,15 +224,38 @@ class MockRedis
332
224
  end
333
225
 
334
226
  def ttl(key)
335
- if has_expiration?(key)
336
- expiration(key).to_i - Time.now.to_i
227
+ if !exists?(key)
228
+ -2
229
+ elsif has_expiration?(key)
230
+ now, = @base.now
231
+ expiration(key).to_i - now
337
232
  else
338
233
  -1
339
234
  end
340
235
  end
341
236
 
237
+ def pttl(key)
238
+ now, miliseconds = @base.now
239
+ now_ms = now * 1000 + miliseconds
240
+
241
+ if !exists?(key)
242
+ -2
243
+ elsif has_expiration?(key)
244
+ (expiration(key).to_r * 1000).to_i - now_ms
245
+ else
246
+ -1
247
+ end
248
+ end
249
+
250
+ def now
251
+ current_time = @base.options[:time_class].now
252
+ miliseconds = (current_time.to_r - current_time.to_i) * 1_000
253
+ [current_time.to_i, miliseconds.to_i]
254
+ end
255
+ alias time now
256
+
342
257
  def type(key)
343
- if !exists(key)
258
+ if !exists?(key)
344
259
  'none'
345
260
  elsif hashy?(key)
346
261
  'hash'
@@ -357,13 +272,26 @@ class MockRedis
357
272
  end
358
273
  end
359
274
 
275
+ def script(subcommand, *args); end
276
+
277
+ def evalsha(*args); end
278
+
279
+ def eval(*args); end
280
+
360
281
  private
361
282
 
283
+ def assert_valid_integer(integer)
284
+ unless looks_like_integer?(integer.to_s)
285
+ raise Redis::CommandError, 'ERR value is not an integer or out of range'
286
+ end
287
+ integer
288
+ end
289
+
362
290
  def assert_valid_timeout(timeout)
363
- if !looks_like_float?(timeout.to_s)
364
- raise Redis::CommandError, "ERR timeout is not a float or out of range"
291
+ if !looks_like_integer?(timeout.to_s)
292
+ raise Redis::CommandError, 'ERR timeout is not an integer or out of range'
365
293
  elsif timeout < 0
366
- raise Redis::CommandError, "ERR timeout is negative"
294
+ raise Redis::CommandError, 'ERR timeout is negative'
367
295
  end
368
296
  timeout
369
297
  end
@@ -377,16 +305,24 @@ class MockRedis
377
305
  end
378
306
 
379
307
  def extract_timeout(arglist)
380
- timeout = assert_valid_timeout(arglist.last)
381
- [arglist[0..-2], arglist.last]
308
+ options = arglist.last
309
+ if options.is_a?(Hash) && options[:timeout]
310
+ timeout = assert_valid_timeout(options[:timeout])
311
+ [arglist[0..-2], timeout]
312
+ elsif options.is_a?(Integer)
313
+ timeout = assert_valid_timeout(options)
314
+ [arglist[0..-2], timeout]
315
+ else
316
+ [arglist, 0]
317
+ end
382
318
  end
383
319
 
384
320
  def expiration(key)
385
- expire_times.find {|(_,k)| k == key}.first
321
+ expire_times.find { |(_, k)| k == key.to_s }.first
386
322
  end
387
323
 
388
324
  def has_expiration?(key)
389
- expire_times.any? {|(_,k)| k == key}
325
+ expire_times.any? { |(_, k)| k == key.to_s }
390
326
  end
391
327
 
392
328
  def looks_like_integer?(str)
@@ -400,23 +336,23 @@ class MockRedis
400
336
  def redis_pattern_to_ruby_regex(pattern)
401
337
  Regexp.new(
402
338
  "^#{pattern}$".
403
- gsub(/([^\\])\?/, "\\1.").
404
- gsub(/([^\\])\*/, "\\1.+"))
339
+ gsub(/([+|()])/, '\\\\\1').
340
+ gsub(/(?<!\\)\?/, '\\1.').
341
+ gsub(/([^\\])\*/, '\\1.*')
342
+ )
405
343
  end
406
344
 
407
345
  def remove_expiration(key)
408
- expire_times.delete_if do |(t, k)|
409
- key == k
346
+ expire_times.delete_if do |(_t, k)|
347
+ key.to_s == k
410
348
  end
411
349
  end
412
350
 
413
351
  def set_expiration(key, time)
414
352
  remove_expiration(key)
415
-
416
- expire_times << [time, key]
417
- expire_times.sort! do |a, b|
418
- a.first <=> b.first
419
- end
353
+ found = expire_times.each_with_index.to_a.bsearch { |item, _| item.first >= time }
354
+ index = found ? found.last : -1
355
+ expire_times.insert(index, [time, key.to_s])
420
356
  end
421
357
 
422
358
  def zero_pad(string, desired_length)
@@ -425,20 +361,20 @@ class MockRedis
425
361
  end
426
362
 
427
363
  public
364
+
428
365
  # This method isn't private, but it also isn't a Redis command, so
429
366
  # it doesn't belong up above with all the Redis commands.
430
367
  def expire_keys
431
- now = Time.now
368
+ now_sec, miliseconds = now
369
+ now_ms = now_sec * 1_000 + miliseconds
432
370
 
433
- to_delete = expire_times.take_while do |(time, key)|
434
- time <= now
371
+ to_delete = expire_times.take_while do |(time, _key)|
372
+ (time.to_r * 1_000).to_i <= now_ms
435
373
  end
436
374
 
437
- to_delete.each do |(time, key)|
375
+ to_delete.each do |(_time, key)|
438
376
  del(key)
439
377
  end
440
-
441
- expire_times.slice!(0, to_delete.length)
442
378
  end
443
379
  end
444
380
  end
@@ -4,7 +4,7 @@ class MockRedis
4
4
  class ExpireWrapper
5
5
  include UndefRedisMethods
6
6
 
7
- def respond_to?(method, include_private=false)
7
+ def respond_to?(method, include_private = false)
8
8
  super || @db.respond_to?(method)
9
9
  end
10
10
 
@@ -12,7 +12,7 @@ class MockRedis
12
12
  @db = db
13
13
  end
14
14
 
15
- def method_missing(method, *args, &block)
15
+ ruby2_keywords def method_missing(method, *args, &block)
16
16
  @db.expire_keys
17
17
  @db.send(method, *args, &block)
18
18
  end
@@ -0,0 +1,23 @@
1
+ class MockRedis
2
+ class FutureNotReady < RuntimeError; end
3
+
4
+ class Future
5
+ attr_reader :command, :block
6
+
7
+ def initialize(command, block = nil)
8
+ @command = command
9
+ @block = block
10
+ @result_set = false
11
+ end
12
+
13
+ def value
14
+ raise FutureNotReady unless @result_set
15
+ @result
16
+ end
17
+
18
+ def store_result(result)
19
+ @result_set = true
20
+ @result = @block ? @block.call(result) : result
21
+ end
22
+ end
23
+ end