mock_redis 0.19.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +32 -5
  3. data/.rubocop_todo.yml +1 -1
  4. data/.travis.yml +9 -10
  5. data/CHANGELOG.md +46 -0
  6. data/Gemfile +2 -2
  7. data/LICENSE.md +21 -0
  8. data/README.md +39 -15
  9. data/lib/mock_redis.rb +0 -5
  10. data/lib/mock_redis/database.rb +59 -22
  11. data/lib/mock_redis/future.rb +1 -1
  12. data/lib/mock_redis/geospatial_methods.rb +14 -22
  13. data/lib/mock_redis/hash_methods.rb +23 -15
  14. data/lib/mock_redis/indifferent_hash.rb +0 -8
  15. data/lib/mock_redis/info_method.rb +2 -2
  16. data/lib/mock_redis/list_methods.rb +2 -2
  17. data/lib/mock_redis/multi_db_wrapper.rb +2 -2
  18. data/lib/mock_redis/pipelined_wrapper.rb +25 -6
  19. data/lib/mock_redis/set_methods.rb +16 -4
  20. data/lib/mock_redis/stream.rb +62 -0
  21. data/lib/mock_redis/stream/id.rb +60 -0
  22. data/lib/mock_redis/stream_methods.rb +87 -0
  23. data/lib/mock_redis/string_methods.rb +31 -20
  24. data/lib/mock_redis/transaction_wrapper.rb +27 -14
  25. data/lib/mock_redis/utility_methods.rb +6 -3
  26. data/lib/mock_redis/version.rb +1 -1
  27. data/lib/mock_redis/zset_methods.rb +54 -11
  28. data/mock_redis.gemspec +6 -6
  29. data/spec/client_spec.rb +12 -0
  30. data/spec/commands/blpop_spec.rb +0 -6
  31. data/spec/commands/brpop_spec.rb +6 -5
  32. data/spec/commands/dump_spec.rb +19 -0
  33. data/spec/commands/exists_spec.rb +34 -5
  34. data/spec/commands/future_spec.rb +11 -1
  35. data/spec/commands/geoadd_spec.rb +1 -1
  36. data/spec/commands/geodist_spec.rb +8 -4
  37. data/spec/commands/geohash_spec.rb +4 -4
  38. data/spec/commands/geopos_spec.rb +4 -4
  39. data/spec/commands/get_spec.rb +1 -0
  40. data/spec/commands/hdel_spec.rb +18 -2
  41. data/spec/commands/hmset_spec.rb +26 -0
  42. data/spec/commands/hset_spec.rb +6 -6
  43. data/spec/commands/keys_spec.rb +17 -0
  44. data/spec/commands/mget_spec.rb +34 -15
  45. data/spec/commands/move_spec.rb +5 -5
  46. data/spec/commands/mset_spec.rb +14 -0
  47. data/spec/commands/pipelined_spec.rb +72 -0
  48. data/spec/commands/restore_spec.rb +47 -0
  49. data/spec/commands/scan_spec.rb +9 -0
  50. data/spec/commands/set_spec.rb +12 -2
  51. data/spec/commands/setbit_spec.rb +1 -0
  52. data/spec/commands/setex_spec.rb +16 -0
  53. data/spec/commands/spop_spec.rb +15 -0
  54. data/spec/commands/srandmember_spec.rb +1 -1
  55. data/spec/commands/srem_spec.rb +5 -0
  56. data/spec/commands/watch_spec.rb +8 -3
  57. data/spec/commands/xadd_spec.rb +104 -0
  58. data/spec/commands/xlen_spec.rb +20 -0
  59. data/spec/commands/xrange_spec.rb +141 -0
  60. data/spec/commands/xrevrange_spec.rb +130 -0
  61. data/spec/commands/xtrim_spec.rb +30 -0
  62. data/spec/commands/zinterstore_spec.rb +34 -0
  63. data/spec/commands/zpopmax_spec.rb +60 -0
  64. data/spec/commands/zpopmin_spec.rb +60 -0
  65. data/spec/commands/zrange_spec.rb +1 -1
  66. data/spec/commands/zrangebyscore_spec.rb +1 -1
  67. data/spec/commands/zrevrange_spec.rb +1 -1
  68. data/spec/commands/zrevrangebyscore_spec.rb +1 -1
  69. data/spec/commands/zunionstore_spec.rb +33 -0
  70. data/spec/mock_redis_spec.rb +4 -6
  71. data/spec/spec_helper.rb +6 -2
  72. data/spec/support/redis_multiplexer.rb +18 -1
  73. data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
  74. data/spec/support/shared_examples/only_operates_on_hashes.rb +2 -0
  75. data/spec/support/shared_examples/only_operates_on_lists.rb +2 -0
  76. data/spec/support/shared_examples/only_operates_on_sets.rb +2 -0
  77. data/spec/support/shared_examples/only_operates_on_zsets.rb +2 -0
  78. data/spec/transactions_spec.rb +30 -26
  79. metadata +45 -31
  80. data/LICENSE +0 -19
  81. data/spec/commands/hash_operator_spec.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da2c2693b445e51b487e52f7c9b3986960417686792f9a5e554ca4d215c323e2
4
- data.tar.gz: 8da2170fcc49e768133a61deb716d72cc18d66291567008d96287b8d2104b15c
3
+ metadata.gz: 0d399a835c1f41a8eb0e5fc42a366e64fc399b5d76ebc23f0d40f73c9db4888e
4
+ data.tar.gz: 1442920d2b12e46d2d45ff9d8854e4ccf3665b5a5f9a4aef6d29669c9110d023
5
5
  SHA512:
6
- metadata.gz: 15b8b59cff1120de0befaee6cf08facfb120e239d3fbc1b6b55f40bb60c526f57e5cbb61f1d9f6fd5538067122e49b80e9e782590cd98070a3d49943ec8bb929
7
- data.tar.gz: 52a09f0896a405137be0075db930bec09898dd96e3d5366a95ef6a189268348f91244c051dbe45db5b4b37023a12f87552868fcaf2a5383316bebe9bdaf44997
6
+ metadata.gz: 62b218977c75b5c7ffc80e3762bf0f7791c8f17dfe4f875a8874118583124736ab782e1c985dc92363d62e4366170306d9d0211b19cead2982f16cbff2c13557
7
+ data.tar.gz: 97b5eeb1c20e597d2ce35e9d109f62aaf3a2271f42c86515be1d7e0d627ad628173aee7d74062b119cf954538116f0b199d06eee5d1fe71b552dea543859a69e
@@ -1,17 +1,35 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.2
4
+ TargetRubyVersion: 2.4
5
5
 
6
- Layout/AlignParameters:
6
+ Layout/ArgumentAlignment:
7
+ Enabled: false
8
+
9
+ Layout/ParameterAlignment:
7
10
  Enabled: false
8
11
 
9
12
  Layout/DotPosition:
10
13
  Enabled: false
11
14
 
15
+ Layout/EmptyLineAfterGuardClause:
16
+ Enabled: false
17
+
18
+ Layout/LineLength:
19
+ Max: 100
20
+
21
+ Layout/SpaceAroundMethodCallOperator:
22
+ Enabled: true
23
+
12
24
  Lint/AssignmentInCondition:
13
25
  Enabled: false
14
26
 
27
+ Lint/RaiseException:
28
+ Enabled: true
29
+
30
+ Lint/StructNewOverride:
31
+ Enabled: true
32
+
15
33
  # We use this a lot in specs where it's perfectly valid
16
34
  Lint/Void:
17
35
  Exclude:
@@ -29,9 +47,6 @@ Metrics/CyclomaticComplexity:
29
47
  Metrics/ClassLength:
30
48
  Enabled: false
31
49
 
32
- Metrics/LineLength:
33
- Max: 100
34
-
35
50
  Metrics/MethodLength:
36
51
  Enabled: false
37
52
 
@@ -51,6 +66,18 @@ Style/Documentation:
51
66
  Style/DoubleNegation:
52
67
  Enabled: false
53
68
 
69
+ Style/ExponentialNotation:
70
+ Enabled: true
71
+
72
+ Style/HashEachMethods:
73
+ Enabled: true
74
+
75
+ Style/HashTransformKeys:
76
+ Enabled: true
77
+
78
+ Style/HashTransformValues:
79
+ Enabled: true
80
+
54
81
  # We have too much code that relies on modifying strings
55
82
  Style/FrozenStringLiteralComment:
56
83
  Enabled: false
@@ -9,7 +9,7 @@
9
9
  # Offense count: 17
10
10
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
11
11
  # AllowedNames: io, id, to, by, on, in, at, ip
12
- Naming/UncommunicativeMethodParamName:
12
+ Naming/MethodParameterName:
13
13
  Exclude:
14
14
  - 'lib/mock_redis/database.rb'
15
15
  - 'lib/mock_redis/expire_wrapper.rb'
@@ -1,7 +1,5 @@
1
1
  language: ruby
2
2
 
3
- sudo: false
4
-
5
3
  cache: bundler
6
4
 
7
5
  addons:
@@ -12,11 +10,16 @@ addons:
12
10
  services:
13
11
  - redis-server
14
12
 
13
+ before_install:
14
+ - sudo sed -e 's/^bind.*/bind 127.0.0.1/' /etc/redis/redis.conf > redis.conf
15
+ - sudo mv redis.conf /etc/redis
16
+ - sudo service redis-server start
17
+ - echo PING | nc localhost 6379
18
+
15
19
  rvm:
16
- - 2.2
17
- - 2.3.7
18
- - 2.4.4
19
- - 2.5.1
20
+ - 2.4
21
+ - 2.5
22
+ - 2.6
20
23
 
21
24
  before_script:
22
25
  - git config --local user.email "travis@travis.ci"
@@ -27,7 +30,3 @@ script:
27
30
  - bundle exec rspec
28
31
  - bundle exec overcommit --sign
29
32
  - bundle exec overcommit --run
30
-
31
- matrix:
32
- allow_failures:
33
- - rvm: 2.1.0
@@ -1,5 +1,51 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.24.0
4
+
5
+ * Fix handling of blocks within `multi` blocks ([#185](https://github.com/sds/mock_redis/pull/185))
6
+ * Fix handling of multiple consecutive `?` characters in key pattern matching ([#186](https://github.com/sds/mock_redis/pull/186))
7
+ * Change `exists` to return an integer and add `exists?` ([#188](https://github.com/sds/mock_redis/pull/188))
8
+
9
+ ### 0.23.0
10
+
11
+ * Raise error when `setex` called with negative timeout ([#174](https://github.com/sds/mock_redis/pull/174))
12
+ * Add support for `dump`/`restore` between MockRedis instances ([#176](https://github.com/sds/mock_redis/pull/176))
13
+ * Fix warnings for ZSET methods on Ruby 2.7 ([#177](https://github.com/sds/mock_redis/pull/177))
14
+ * Add support for returning time in pipelines ([#179](https://github.com/sds/mock_redis/pull/179))
15
+ * Fix SET methods to correct set milliseconds with `px` ([#180](https://github.com/sds/mock_redis/pull/180))
16
+ * Add support for unsorted sets within `zinterstore`/`zunionstore`([#182](https://github.com/sds/mock_redis/pull/182))
17
+
18
+ ### 0.22.0
19
+
20
+ * Gracefully handle cursors larger than the collection size in scan commands ([#171](https://github.com/sds/mock_redis/pull/171))
21
+ * Add `zpopmin` and `zpopmax` commands ([#172](https://github.com/sds/mock_redis/pull/172))
22
+ * Fix `hmset` to support array arguments ([#173](https://github.com/sds/mock_redis/pull/173))
23
+ * Fix `hmset` to always treat keys as strings ([#173](https://github.com/sds/mock_redis/pull/173))
24
+ * Remove unnecessary dependency on `rake` gem
25
+
26
+ ### 0.21.0
27
+
28
+ * Fix behavior of `time` to return array of two integers ([#161](https://github.com/sds/mock_redis/pull/161))
29
+ * Add support for `close` and `disconnect!` ([#163](https://github.com/sds/mock_redis/pull/163))
30
+ * Fix `set` to properly handle (and ignore) other options ([#164](https://github.com/sds/mock_redis/pull/163))
31
+ * Fix `srem` to allow array of integers as argument ([#166](https://github.com/sds/mock_redis/pull/166))
32
+ * Fix `hdel` to allow array as argument ([#168](https://github.com/sds/mock_redis/pull/168))
33
+
34
+ ### 0.20.0
35
+
36
+ * Add support for `count` parameter of `spop`
37
+ * Fix `mget` and `mset` to accept array as parameters
38
+ * Fix pipelined array replies
39
+ * Fix nested pipelining
40
+ * Allow nested multi
41
+ * Require Redis gem 4.0.1 or newer
42
+ * Add support for stream commands on Redis 5
43
+ * Keep empty strings on type mismatch
44
+ * Improve performance of `set_expiration`
45
+ * Fix `watch` to allow multiple keys
46
+ * Add `unlink` alias for `del`
47
+ * Drop support for Ruby 2.3 or older
48
+
3
49
  ### 0.19.0
4
50
 
5
51
  * Require Ruby 2.2+
data/Gemfile CHANGED
@@ -4,9 +4,9 @@ source 'http://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  # Run all pre-commit hooks via Overcommit during CI runs
7
- gem 'overcommit', '0.45.0'
7
+ gem 'overcommit', '0.53.0'
8
8
 
9
9
  # Pin tool versions (which are executed by Overcommit) for Travis builds
10
- gem 'rubocop', '0.58.2'
10
+ gem 'rubocop', '0.82.0'
11
11
 
12
12
  gem 'coveralls', require: false
@@ -0,0 +1,21 @@
1
+ MockRedis released under the MIT license.
2
+
3
+ > Copyright (c) Shane da Silva. http://shane.io
4
+ >
5
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ > of this software and associated documentation files (the "Software"), to deal
7
+ > in the Software without restriction, including without limitation the rights
8
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ > copies of the Software, and to permit persons to whom the Software is
10
+ > furnished to do so, subject to the following conditions:
11
+ >
12
+ > The above copyright notice and this permission notice shall be included in
13
+ > all copies or substantial portions of the Software.
14
+ >
15
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ > SOFTWARE.
data/README.md CHANGED
@@ -1,18 +1,27 @@
1
1
  # MockRedis
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/mock_redis.svg)](http://badge.fury.io/rb/mock_redis)
4
- [![Build Status](https://travis-ci.org/brigade/mock_redis.svg)](https://travis-ci.org/brigade/mock_redis)
5
- [![Coverage Status](https://coveralls.io/repos/brigade/mock_redis/badge.svg)](https://coveralls.io/r/brigade/mock_redis)
4
+ [![Build Status](https://travis-ci.org/sds/mock_redis.svg)](https://travis-ci.org/sds/mock_redis)
5
+ [![Coverage Status](https://coveralls.io/repos/sds/mock_redis/badge.svg)](https://coveralls.io/r/sds/mock_redis)
6
6
 
7
7
  MockRedis provides the same interface as `redis-rb`, but it stores its
8
8
  data in memory instead of talking to a Redis server. It is intended
9
9
  for use in tests.
10
10
 
11
- The current implementation is tested against *Redis 4*. Older versions
12
- of Redis may return different results or not support some commands.
11
+ ## Requirements
12
+
13
+ * Ruby 2.4+
14
+
15
+ The current implementation is tested against **Redis 5**. Older versions may work, but can also return different results or not support some commands.
13
16
 
14
17
  ## Getting Started
15
18
 
19
+ Install the gem:
20
+
21
+ ```bash
22
+ gem install mock_redis
23
+ ```
24
+
16
25
  It's as easy as `require 'mock_redis'; mr = MockRedis.new`. Then you can
17
26
  call the same methods on it as you can call on a real `Redis` object.
18
27
 
@@ -90,17 +99,32 @@ please submit a pull request with your (tested!) implementation.
90
99
  * `#config(:get|:set|:resetstat)` isn't done. They can just return
91
100
  canned values.
92
101
 
93
- ## Compatibility
102
+ ## Running tests
103
+
104
+ We recommend running Redis within a Docker container to make development as simple as possible, but as long as you have a Redis instance listening on `localhost:6379` you should be good to go.
105
+
106
+ 1. Start Redis.
107
+ ```bash
108
+ docker run --rm -p 6379:6379 redis
109
+ ```
110
+ 2. Install dependencies.
111
+ ```bash
112
+ bundle install
113
+ ```
114
+ 3. Run tests.
115
+ ```bash
116
+ bundle exec rspec
117
+ ```
118
+
119
+ These tests were written with Redis running on `localhost` without any
120
+ passwords required. If you're using a different version of Redis, you
121
+ may see failures due to error message text being different and other
122
+ breaking changes over time.
123
+
124
+ ## Changelog
94
125
 
95
- As of version `0.19.0`, Ruby 2.2 and above are supported. For
96
- older versions of Ruby, use `0.18.0` or older.
126
+ If you're interested in seeing the changes and bug fixes between each version of `mock_redis`, read the [MockRedis Changelog](CHANGELOG.md).
97
127
 
98
- ## Running the Tests
128
+ ## License
99
129
 
100
- If you want to work on this, you'll probably want to run the
101
- tests. (Just kidding! There's no probably about it.) These tests were
102
- written with Redis running on `localhost` without any passwords
103
- required. If you're using a different version of Redis, you may see
104
- failures due to error message text being different. If you're running
105
- a really old version of Redis, you'll definitely see failures due to
106
- stuff that doesn't work!
130
+ This project is released under the [MIT license](LICENSE.md).
@@ -64,11 +64,6 @@ class MockRedis
64
64
  options[:db]
65
65
  end
66
66
 
67
- def now
68
- options[:time_class].now
69
- end
70
- alias time now
71
-
72
67
  def time_at(timestamp)
73
68
  options[:time_class].at(timestamp)
74
69
  end
@@ -10,6 +10,7 @@ require 'mock_redis/indifferent_hash'
10
10
  require 'mock_redis/info_method'
11
11
  require 'mock_redis/utility_methods'
12
12
  require 'mock_redis/geospatial_methods'
13
+ require 'mock_redis/stream_methods'
13
14
 
14
15
  class MockRedis
15
16
  class Database
@@ -22,6 +23,7 @@ class MockRedis
22
23
  include InfoMethod
23
24
  include UtilityMethods
24
25
  include GeospatialMethods
26
+ include StreamMethods
25
27
 
26
28
  attr_reader :data, :expire_times
27
29
 
@@ -33,7 +35,7 @@ class MockRedis
33
35
 
34
36
  def initialize_copy(_source)
35
37
  @data = @data.clone
36
- @data.keys.each { |k| @data[k] = @data[k].clone }
38
+ @data.each_key { |k| @data[k] = @data[k].clone }
37
39
  @expire_times = @expire_times.map(&:clone)
38
40
  end
39
41
 
@@ -54,6 +56,8 @@ class MockRedis
54
56
  def disconnect
55
57
  nil
56
58
  end
59
+ alias close disconnect
60
+ alias disconnect! close
57
61
 
58
62
  def connected?
59
63
  true
@@ -73,6 +77,7 @@ class MockRedis
73
77
  each { |k| data.delete(k) }.
74
78
  length
75
79
  end
80
+ alias unlink del
76
81
 
77
82
  def echo(msg)
78
83
  msg.to_s
@@ -83,7 +88,8 @@ class MockRedis
83
88
  end
84
89
 
85
90
  def pexpire(key, ms)
86
- now_ms = (@base.now.to_r * 1000).to_i
91
+ now, miliseconds = @base.now
92
+ now_ms = (now * 1000) + miliseconds
87
93
  pexpireat(key, now_ms + ms.to_i)
88
94
  end
89
95
 
@@ -100,7 +106,7 @@ class MockRedis
100
106
  raise Redis::CommandError, 'ERR value is not an integer or out of range'
101
107
  end
102
108
 
103
- if exists(key)
109
+ if exists?(key)
104
110
  timestamp = Rational(timestamp_ms.to_i, 1000)
105
111
  set_expiration(key, @base.time_at(timestamp))
106
112
  true
@@ -109,12 +115,33 @@ class MockRedis
109
115
  end
110
116
  end
111
117
 
112
- def exists(key)
113
- data.key?(key)
118
+ def exists(*keys)
119
+ keys.count { |key| data.key?(key) }
120
+ end
121
+
122
+ def exists?(*keys)
123
+ keys.each { |key| return true if data.key?(key) }
124
+ false
114
125
  end
115
126
 
116
127
  def flushdb
117
- data.keys.each { |k| del(k) }
128
+ data.each_key { |k| del(k) }
129
+ 'OK'
130
+ end
131
+
132
+ def dump(key)
133
+ value = data[key]
134
+ value ? Marshal.dump(value) : nil
135
+ end
136
+
137
+ def restore(key, ttl, value, replace: false)
138
+ if !replace && exists?(key)
139
+ raise Redis::CommandError, 'BUSYKEY Target key name already exists.'
140
+ end
141
+ data[key] = Marshal.load(value) # rubocop:disable Security/MarshalLoad
142
+ if ttl > 0
143
+ pexpire(key, ttl)
144
+ end
118
145
  'OK'
119
146
  end
120
147
 
@@ -137,11 +164,11 @@ class MockRedis
137
164
  end
138
165
 
139
166
  def lastsave
140
- @base.now.to_i
167
+ now.first
141
168
  end
142
169
 
143
170
  def persist(key)
144
- if exists(key) && has_expiration?(key)
171
+ if exists?(key) && has_expiration?(key)
145
172
  remove_expiration(key)
146
173
  true
147
174
  else
@@ -182,7 +209,7 @@ class MockRedis
182
209
  raise Redis::CommandError, 'ERR no such key'
183
210
  end
184
211
 
185
- if exists(newkey)
212
+ if exists?(newkey)
186
213
  false
187
214
  else
188
215
  rename(key, newkey)
@@ -195,27 +222,38 @@ class MockRedis
195
222
  end
196
223
 
197
224
  def ttl(key)
198
- if !exists(key)
225
+ if !exists?(key)
199
226
  -2
200
227
  elsif has_expiration?(key)
201
- expiration(key).to_i - @base.now.to_i
228
+ now, = @base.now
229
+ expiration(key).to_i - now
202
230
  else
203
231
  -1
204
232
  end
205
233
  end
206
234
 
207
235
  def pttl(key)
208
- if !exists(key)
236
+ now, miliseconds = @base.now
237
+ now_ms = now * 1000 + miliseconds
238
+
239
+ if !exists?(key)
209
240
  -2
210
241
  elsif has_expiration?(key)
211
- (expiration(key).to_r * 1000).to_i - (@base.now.to_r * 1000).to_i
242
+ (expiration(key).to_r * 1000).to_i - now_ms
212
243
  else
213
244
  -1
214
245
  end
215
246
  end
216
247
 
248
+ def now
249
+ current_time = @base.options[:time_class].now
250
+ miliseconds = (current_time.to_r - current_time.to_i) * 1_000
251
+ [current_time.to_i, miliseconds.to_i]
252
+ end
253
+ alias time now
254
+
217
255
  def type(key)
218
- if !exists(key)
256
+ if !exists?(key)
219
257
  'none'
220
258
  elsif hashy?(key)
221
259
  'hash'
@@ -290,7 +328,7 @@ class MockRedis
290
328
  Regexp.new(
291
329
  "^#{pattern}$".
292
330
  gsub(/([+|()])/, '\\\\\1').
293
- gsub(/([^\\])\?/, '\\1.').
331
+ gsub(/(?<!\\)\?/, '\\1.').
294
332
  gsub(/([^\\])\*/, '\\1.*')
295
333
  )
296
334
  end
@@ -303,11 +341,9 @@ class MockRedis
303
341
 
304
342
  def set_expiration(key, time)
305
343
  remove_expiration(key)
306
-
307
- expire_times << [time, key.to_s]
308
- expire_times.sort! do |a, b|
309
- a.first <=> b.first
310
- end
344
+ found = expire_times.each_with_index.to_a.bsearch { |item, _| item.first >= time }
345
+ index = found ? found.last : -1
346
+ expire_times.insert(index, [time, key.to_s])
311
347
  end
312
348
 
313
349
  def zero_pad(string, desired_length)
@@ -320,10 +356,11 @@ class MockRedis
320
356
  # This method isn't private, but it also isn't a Redis command, so
321
357
  # it doesn't belong up above with all the Redis commands.
322
358
  def expire_keys
323
- now = @base.now
359
+ now_sec, miliseconds = now
360
+ now_ms = now_sec * 1_000 + miliseconds
324
361
 
325
362
  to_delete = expire_times.take_while do |(time, _key)|
326
- (time.to_r * 1_000).to_i <= (now.to_r * 1_000).to_i
363
+ (time.to_r * 1_000).to_i <= now_ms
327
364
  end
328
365
 
329
366
  to_delete.each do |(_time, key)|