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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +161 -0
- data/LICENSE.md +21 -0
- data/README.md +41 -17
- data/lib/mock_redis/connection_method.rb +13 -0
- data/lib/mock_redis/database.rb +116 -41
- data/lib/mock_redis/expire_wrapper.rb +1 -1
- data/lib/mock_redis/future.rb +1 -1
- data/lib/mock_redis/geospatial_methods.rb +13 -21
- data/lib/mock_redis/hash_methods.rb +34 -15
- data/lib/mock_redis/indifferent_hash.rb +0 -8
- data/lib/mock_redis/info_method.rb +2 -2
- data/lib/mock_redis/list_methods.rb +39 -4
- data/lib/mock_redis/memory_method.rb +11 -0
- data/lib/mock_redis/multi_db_wrapper.rb +4 -4
- data/lib/mock_redis/pipelined_wrapper.rb +32 -15
- data/lib/mock_redis/set_methods.rb +34 -6
- data/lib/mock_redis/stream/id.rb +58 -0
- data/lib/mock_redis/stream.rb +88 -0
- data/lib/mock_redis/stream_methods.rb +102 -0
- data/lib/mock_redis/string_methods.rb +81 -30
- data/lib/mock_redis/transaction_wrapper.rb +32 -21
- data/lib/mock_redis/utility_methods.rb +9 -4
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset.rb +5 -8
- data/lib/mock_redis/zset_methods.rb +64 -12
- data/lib/mock_redis.rb +38 -10
- metadata +40 -336
- data/.gitignore +0 -5
- data/.mailmap +0 -2
- data/.overcommit.yml +0 -21
- data/.rspec +0 -1
- data/.rubocop.yml +0 -121
- data/.rubocop_todo.yml +0 -35
- data/.simplecov +0 -4
- data/.travis.yml +0 -33
- data/Gemfile +0 -12
- data/LICENSE +0 -19
- data/Rakefile +0 -2
- data/mock_redis.gemspec +0 -30
- data/spec/client_spec.rb +0 -17
- data/spec/cloning_spec.rb +0 -95
- data/spec/commands/append_spec.rb +0 -24
- data/spec/commands/auth_spec.rb +0 -7
- data/spec/commands/bgrewriteaof_spec.rb +0 -7
- data/spec/commands/bgsave_spec.rb +0 -7
- data/spec/commands/bitcount_spec.rb +0 -25
- data/spec/commands/bitfield_spec.rb +0 -169
- data/spec/commands/blpop_spec.rb +0 -59
- data/spec/commands/brpop_spec.rb +0 -58
- data/spec/commands/brpoplpush_spec.rb +0 -52
- data/spec/commands/connected_spec.rb +0 -7
- data/spec/commands/dbsize_spec.rb +0 -18
- data/spec/commands/decr_spec.rb +0 -34
- data/spec/commands/decrby_spec.rb +0 -34
- data/spec/commands/del_spec.rb +0 -35
- data/spec/commands/disconnect_spec.rb +0 -7
- data/spec/commands/echo_spec.rb +0 -11
- data/spec/commands/eval_spec.rb +0 -7
- data/spec/commands/evalsha_spec.rb +0 -10
- data/spec/commands/exists_spec.rb +0 -14
- data/spec/commands/expire_spec.rb +0 -111
- data/spec/commands/expireat_spec.rb +0 -47
- data/spec/commands/flushall_spec.rb +0 -38
- data/spec/commands/flushdb_spec.rb +0 -38
- data/spec/commands/future_spec.rb +0 -20
- data/spec/commands/geoadd_spec.rb +0 -58
- data/spec/commands/geodist_spec.rb +0 -114
- data/spec/commands/geohash_spec.rb +0 -52
- data/spec/commands/geopos_spec.rb +0 -55
- data/spec/commands/get_spec.rb +0 -30
- data/spec/commands/getbit_spec.rb +0 -34
- data/spec/commands/getrange_spec.rb +0 -22
- data/spec/commands/getset_spec.rb +0 -23
- data/spec/commands/hash_operator_spec.rb +0 -21
- data/spec/commands/hdel_spec.rb +0 -54
- data/spec/commands/hexists_spec.rb +0 -27
- data/spec/commands/hget_spec.rb +0 -28
- data/spec/commands/hgetall_spec.rb +0 -32
- data/spec/commands/hincrby_spec.rb +0 -58
- data/spec/commands/hincrbyfloat_spec.rb +0 -58
- data/spec/commands/hkeys_spec.rb +0 -19
- data/spec/commands/hlen_spec.rb +0 -19
- data/spec/commands/hmget_spec.rb +0 -40
- data/spec/commands/hmset_spec.rb +0 -43
- data/spec/commands/hscan_each_spec.rb +0 -48
- data/spec/commands/hscan_spec.rb +0 -27
- data/spec/commands/hset_spec.rb +0 -38
- data/spec/commands/hsetnx_spec.rb +0 -44
- data/spec/commands/hvals_spec.rb +0 -19
- data/spec/commands/incr_spec.rb +0 -34
- data/spec/commands/incrby_spec.rb +0 -44
- data/spec/commands/incrbyfloat_spec.rb +0 -44
- data/spec/commands/info_spec.rb +0 -62
- data/spec/commands/keys_spec.rb +0 -122
- data/spec/commands/lastsave_spec.rb +0 -8
- data/spec/commands/lindex_spec.rb +0 -49
- data/spec/commands/linsert_spec.rb +0 -68
- data/spec/commands/llen_spec.rb +0 -16
- data/spec/commands/lpop_spec.rb +0 -34
- data/spec/commands/lpush_spec.rb +0 -43
- data/spec/commands/lpushx_spec.rb +0 -46
- data/spec/commands/lrange_spec.rb +0 -51
- data/spec/commands/lrem_spec.rb +0 -80
- data/spec/commands/lset_spec.rb +0 -43
- data/spec/commands/ltrim_spec.rb +0 -45
- data/spec/commands/mapped_hmget_spec.rb +0 -29
- data/spec/commands/mapped_hmset_spec.rb +0 -47
- data/spec/commands/mapped_mget_spec.rb +0 -22
- data/spec/commands/mapped_mset_spec.rb +0 -19
- data/spec/commands/mapped_msetnx_spec.rb +0 -26
- data/spec/commands/mget_spec.rb +0 -34
- data/spec/commands/move_spec.rb +0 -147
- data/spec/commands/mset_spec.rb +0 -29
- data/spec/commands/msetnx_spec.rb +0 -40
- data/spec/commands/persist_spec.rb +0 -48
- data/spec/commands/pexpire_spec.rb +0 -86
- data/spec/commands/pexpireat_spec.rb +0 -48
- data/spec/commands/ping_spec.rb +0 -7
- data/spec/commands/pipelined_spec.rb +0 -42
- data/spec/commands/pttl_spec.rb +0 -41
- data/spec/commands/quit_spec.rb +0 -7
- data/spec/commands/randomkey_spec.rb +0 -20
- data/spec/commands/rename_spec.rb +0 -42
- data/spec/commands/renamenx_spec.rb +0 -41
- data/spec/commands/rpop_spec.rb +0 -34
- data/spec/commands/rpoplpush_spec.rb +0 -50
- data/spec/commands/rpush_spec.rb +0 -43
- data/spec/commands/rpushx_spec.rb +0 -46
- data/spec/commands/sadd_spec.rb +0 -45
- data/spec/commands/save_spec.rb +0 -7
- data/spec/commands/scan_each_spec.rb +0 -39
- data/spec/commands/scan_spec.rb +0 -55
- data/spec/commands/scard_spec.rb +0 -18
- data/spec/commands/script_spec.rb +0 -9
- data/spec/commands/sdiff_spec.rb +0 -47
- data/spec/commands/sdiffstore_spec.rb +0 -58
- data/spec/commands/select_spec.rb +0 -61
- data/spec/commands/set_spec.rb +0 -63
- data/spec/commands/setbit_spec.rb +0 -54
- data/spec/commands/setex_spec.rb +0 -22
- data/spec/commands/setnx_spec.rb +0 -25
- data/spec/commands/setrange_spec.rb +0 -30
- data/spec/commands/sinter_spec.rb +0 -39
- data/spec/commands/sinterstore_spec.rb +0 -53
- data/spec/commands/sismember_spec.rb +0 -29
- data/spec/commands/smembers_spec.rb +0 -28
- data/spec/commands/smove_spec.rb +0 -41
- data/spec/commands/sort_list_spec.rb +0 -21
- data/spec/commands/sort_set_spec.rb +0 -21
- data/spec/commands/sort_zset_spec.rb +0 -21
- data/spec/commands/spop_spec.rb +0 -25
- data/spec/commands/srandmember_spec.rb +0 -49
- data/spec/commands/srem_spec.rb +0 -40
- data/spec/commands/sscan_each_spec.rb +0 -48
- data/spec/commands/sscan_spec.rb +0 -39
- data/spec/commands/strlen_spec.rb +0 -18
- data/spec/commands/sunion_spec.rb +0 -42
- data/spec/commands/sunionstore_spec.rb +0 -59
- data/spec/commands/ttl_spec.rb +0 -40
- data/spec/commands/type_spec.rb +0 -36
- data/spec/commands/unwatch_spec.rb +0 -7
- data/spec/commands/watch_spec.rb +0 -16
- data/spec/commands/zadd_spec.rb +0 -123
- data/spec/commands/zcard_spec.rb +0 -19
- data/spec/commands/zcount_spec.rb +0 -39
- data/spec/commands/zincrby_spec.rb +0 -31
- data/spec/commands/zinterstore_spec.rb +0 -96
- data/spec/commands/zrange_spec.rb +0 -80
- data/spec/commands/zrangebyscore_spec.rb +0 -83
- data/spec/commands/zrank_spec.rb +0 -29
- data/spec/commands/zrem_spec.rb +0 -43
- data/spec/commands/zremrangebyrank_spec.rb +0 -27
- data/spec/commands/zremrangebyscore_spec.rb +0 -35
- data/spec/commands/zrevrange_spec.rb +0 -56
- data/spec/commands/zrevrangebyscore_spec.rb +0 -58
- data/spec/commands/zrevrank_spec.rb +0 -29
- data/spec/commands/zscan_each_spec.rb +0 -48
- data/spec/commands/zscan_spec.rb +0 -26
- data/spec/commands/zscore_spec.rb +0 -22
- data/spec/commands/zunionstore_spec.rb +0 -104
- data/spec/mock_redis_spec.rb +0 -86
- data/spec/spec_helper.rb +0 -63
- data/spec/support/redis_multiplexer.rb +0 -106
- data/spec/support/shared_examples/only_operates_on_hashes.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_lists.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_sets.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_strings.rb +0 -13
- data/spec/support/shared_examples/only_operates_on_zsets.rb +0 -57
- data/spec/support/shared_examples/sorts_enumerables.rb +0 -56
- 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,
|
27
|
-
|
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,
|
40
|
-
score2 = zscore(key,
|
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('
|
41
|
+
format('%<distance>.4f', distance: distance)
|
50
42
|
end
|
51
43
|
|
52
|
-
def geohash(key,
|
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,
|
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('
|
107
|
-
lat = format('
|
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('
|
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,
|
9
|
+
def hdel(key, *fields)
|
10
10
|
with_hash_at(key) do |hash|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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.
|
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
|
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,
|
129
|
-
|
130
|
-
with_hash_at(key)
|
131
|
-
|
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)
|
@@ -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
|
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
|
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
|
82
|
-
|
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
|
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
|
190
|
+
with_list_at(key) { |list| list&.pop }
|
156
191
|
end
|
157
192
|
|
158
193
|
def rpoplpush(source, destination)
|
@@ -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.
|
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.
|
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
|
-
@
|
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
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
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 =
|
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
|
-
|
87
|
-
|
88
|
-
|
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
|
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
|