mock_redis 0.19.0 → 0.44.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|