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
@@ -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