mock_redis 0.19.0 → 0.24.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/.rubocop.yml +32 -5
- data/.rubocop_todo.yml +1 -1
- data/.travis.yml +9 -10
- data/CHANGELOG.md +46 -0
- data/Gemfile +2 -2
- data/LICENSE.md +21 -0
- data/README.md +39 -15
- data/lib/mock_redis.rb +0 -5
- data/lib/mock_redis/database.rb +59 -22
- data/lib/mock_redis/future.rb +1 -1
- data/lib/mock_redis/geospatial_methods.rb +14 -22
- data/lib/mock_redis/hash_methods.rb +23 -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 +2 -2
- data/lib/mock_redis/multi_db_wrapper.rb +2 -2
- data/lib/mock_redis/pipelined_wrapper.rb +25 -6
- data/lib/mock_redis/set_methods.rb +16 -4
- data/lib/mock_redis/stream.rb +62 -0
- data/lib/mock_redis/stream/id.rb +60 -0
- data/lib/mock_redis/stream_methods.rb +87 -0
- data/lib/mock_redis/string_methods.rb +31 -20
- data/lib/mock_redis/transaction_wrapper.rb +27 -14
- data/lib/mock_redis/utility_methods.rb +6 -3
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset_methods.rb +54 -11
- data/mock_redis.gemspec +6 -6
- data/spec/client_spec.rb +12 -0
- data/spec/commands/blpop_spec.rb +0 -6
- data/spec/commands/brpop_spec.rb +6 -5
- data/spec/commands/dump_spec.rb +19 -0
- data/spec/commands/exists_spec.rb +34 -5
- data/spec/commands/future_spec.rb +11 -1
- data/spec/commands/geoadd_spec.rb +1 -1
- data/spec/commands/geodist_spec.rb +8 -4
- data/spec/commands/geohash_spec.rb +4 -4
- data/spec/commands/geopos_spec.rb +4 -4
- data/spec/commands/get_spec.rb +1 -0
- data/spec/commands/hdel_spec.rb +18 -2
- data/spec/commands/hmset_spec.rb +26 -0
- data/spec/commands/hset_spec.rb +6 -6
- data/spec/commands/keys_spec.rb +17 -0
- data/spec/commands/mget_spec.rb +34 -15
- data/spec/commands/move_spec.rb +5 -5
- data/spec/commands/mset_spec.rb +14 -0
- data/spec/commands/pipelined_spec.rb +72 -0
- data/spec/commands/restore_spec.rb +47 -0
- data/spec/commands/scan_spec.rb +9 -0
- data/spec/commands/set_spec.rb +12 -2
- data/spec/commands/setbit_spec.rb +1 -0
- data/spec/commands/setex_spec.rb +16 -0
- data/spec/commands/spop_spec.rb +15 -0
- data/spec/commands/srandmember_spec.rb +1 -1
- data/spec/commands/srem_spec.rb +5 -0
- data/spec/commands/watch_spec.rb +8 -3
- data/spec/commands/xadd_spec.rb +104 -0
- data/spec/commands/xlen_spec.rb +20 -0
- data/spec/commands/xrange_spec.rb +141 -0
- data/spec/commands/xrevrange_spec.rb +130 -0
- data/spec/commands/xtrim_spec.rb +30 -0
- data/spec/commands/zinterstore_spec.rb +34 -0
- data/spec/commands/zpopmax_spec.rb +60 -0
- data/spec/commands/zpopmin_spec.rb +60 -0
- data/spec/commands/zrange_spec.rb +1 -1
- data/spec/commands/zrangebyscore_spec.rb +1 -1
- data/spec/commands/zrevrange_spec.rb +1 -1
- data/spec/commands/zrevrangebyscore_spec.rb +1 -1
- data/spec/commands/zunionstore_spec.rb +33 -0
- data/spec/mock_redis_spec.rb +4 -6
- data/spec/spec_helper.rb +6 -2
- data/spec/support/redis_multiplexer.rb +18 -1
- data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_lists.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_sets.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_zsets.rb +2 -0
- data/spec/transactions_spec.rb +30 -26
- metadata +45 -31
- data/LICENSE +0 -19
- data/spec/commands/hash_operator_spec.rb +0 -21
data/lib/mock_redis/future.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class MockRedis
|
2
2
|
module GeospatialMethods
|
3
|
-
LNG_RANGE = (-180..180)
|
4
|
-
LAT_RANGE = (-85.05112878..85.05112878)
|
3
|
+
LNG_RANGE = (-180..180).freeze
|
4
|
+
LAT_RANGE = (-85.05112878..85.05112878).freeze
|
5
5
|
STEP = 26
|
6
6
|
UNITS = {
|
7
7
|
m: 1,
|
@@ -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,12 @@ 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
|
-
orig_size - hash.size
|
16
|
-
else
|
17
|
-
hash.delete(field.to_s) ? 1 : 0
|
18
|
-
end
|
11
|
+
orig_size = hash.size
|
12
|
+
fields = Array(fields).flatten.map(&:to_s)
|
13
|
+
hash.delete_if { |k, _v| fields.include?(k) }
|
14
|
+
orig_size - hash.size
|
19
15
|
end
|
20
16
|
end
|
21
17
|
|
@@ -88,10 +84,17 @@ class MockRedis
|
|
88
84
|
end
|
89
85
|
|
90
86
|
def hmset(key, *kvpairs)
|
87
|
+
if key.is_a? Array
|
88
|
+
err_msg = 'ERR wrong number of arguments for \'hmset\' command'
|
89
|
+
kvpairs = key[1..-1]
|
90
|
+
key = key[0]
|
91
|
+
end
|
92
|
+
|
91
93
|
kvpairs.flatten!
|
92
94
|
assert_has_args(kvpairs, 'hmset')
|
95
|
+
|
93
96
|
if kvpairs.length.odd?
|
94
|
-
raise Redis::CommandError, 'ERR wrong number of arguments for HMSET'
|
97
|
+
raise Redis::CommandError, err_msg || 'ERR wrong number of arguments for HMSET'
|
95
98
|
end
|
96
99
|
|
97
100
|
kvpairs.each_slice(2) do |(k, v)|
|
@@ -125,10 +128,15 @@ class MockRedis
|
|
125
128
|
end
|
126
129
|
end
|
127
130
|
|
128
|
-
def hset(key,
|
129
|
-
|
130
|
-
with_hash_at(key)
|
131
|
-
|
131
|
+
def hset(key, *args)
|
132
|
+
added = 0
|
133
|
+
with_hash_at(key) do |hash|
|
134
|
+
args.each_slice(2) do |field, value|
|
135
|
+
added += 1 unless hash.key?(field.to_s)
|
136
|
+
hash[field.to_s] = value.to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
added
|
132
140
|
end
|
133
141
|
|
134
142
|
def hsetnx(key, field, value)
|
@@ -147,7 +155,7 @@ class MockRedis
|
|
147
155
|
private
|
148
156
|
|
149
157
|
def with_hash_at(key, &blk)
|
150
|
-
with_thing_at(key, :assert_hashy, proc { {} }, &blk)
|
158
|
+
with_thing_at(key.to_s, :assert_hashy, proc { {} }, &blk)
|
151
159
|
end
|
152
160
|
|
153
161
|
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,
|
@@ -146,13 +146,13 @@ class MockRedis
|
|
146
146
|
|
147
147
|
def ltrim(key, start, stop)
|
148
148
|
with_list_at(key) do |list|
|
149
|
-
list
|
149
|
+
list&.replace(list[[start.to_i, -list.length].max..stop.to_i] || [])
|
150
150
|
'OK'
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
154
154
|
def rpop(key)
|
155
|
-
with_list_at(key) { |list| list
|
155
|
+
with_list_at(key) { |list| list&.pop }
|
156
156
|
end
|
157
157
|
|
158
158
|
def rpoplpush(source, destination)
|
@@ -24,7 +24,7 @@ class MockRedis
|
|
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
|
@@ -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)
|
@@ -19,7 +19,7 @@ class MockRedis
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def method_missing(method, *args, &block)
|
22
|
-
if
|
22
|
+
if in_pipeline?
|
23
23
|
future = MockRedis::Future.new([method, *args], block)
|
24
24
|
@pipelined_futures << future
|
25
25
|
future
|
@@ -29,9 +29,17 @@ 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
44
|
begin
|
37
45
|
result = if future.block
|
@@ -40,7 +48,12 @@ class MockRedis
|
|
40
48
|
send(*future.command)
|
41
49
|
end
|
42
50
|
future.store_result(result)
|
43
|
-
|
51
|
+
|
52
|
+
if future.block
|
53
|
+
result
|
54
|
+
else
|
55
|
+
[result]
|
56
|
+
end
|
44
57
|
rescue StandardError => e
|
45
58
|
e
|
46
59
|
end
|
@@ -48,5 +61,11 @@ class MockRedis
|
|
48
61
|
@pipelined_futures = []
|
49
62
|
responses
|
50
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def in_pipeline?
|
68
|
+
@nesting_level > 0
|
69
|
+
end
|
51
70
|
end
|
52
71
|
end
|
@@ -81,11 +81,22 @@ class MockRedis
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
def spop(key)
|
84
|
+
def spop(key, count = nil)
|
85
85
|
with_set_at(key) do |set|
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
if count.nil?
|
87
|
+
member = set.first
|
88
|
+
set.delete(member)
|
89
|
+
member
|
90
|
+
else
|
91
|
+
members = []
|
92
|
+
count.times do
|
93
|
+
member = set.first
|
94
|
+
break if member.nil?
|
95
|
+
set.delete(member)
|
96
|
+
members << member
|
97
|
+
end
|
98
|
+
members
|
99
|
+
end
|
89
100
|
end
|
90
101
|
end
|
91
102
|
|
@@ -106,6 +117,7 @@ class MockRedis
|
|
106
117
|
with_set_at(key) do |s|
|
107
118
|
if members.is_a?(Array)
|
108
119
|
orig_size = s.size
|
120
|
+
members = members.map(&:to_s)
|
109
121
|
s.delete_if { |m| members.include?(m) }
|
110
122
|
orig_size - s.size
|
111
123
|
else
|
@@ -0,0 +1,62 @@
|
|
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
|
+
members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]]
|
27
|
+
@last_id.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def trim(count)
|
31
|
+
deleted = @members.size - count
|
32
|
+
@members = @members.to_a[-count..-1].to_set
|
33
|
+
deleted
|
34
|
+
end
|
35
|
+
|
36
|
+
def range(start, finish, reversed, *opts_in)
|
37
|
+
opts = options opts_in, ['count']
|
38
|
+
start_id = MockRedis::Stream::Id.new(start)
|
39
|
+
finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY)
|
40
|
+
items = members
|
41
|
+
.select { |m| (start_id <= m[0]) && (finish_id >= m[0]) }
|
42
|
+
.map { |m| [m[0].to_s, m[1]] }
|
43
|
+
items.reverse! if reversed
|
44
|
+
return items.first(opts['count'].to_i) if opts.key?('count')
|
45
|
+
items
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
members.each { |m| yield m }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def options(opts_in, permitted)
|
55
|
+
opts_out = {}
|
56
|
+
raise Redis::CommandError, 'ERR syntax error' unless (opts_in.length % 2).zero?
|
57
|
+
opts_in.each_slice(2).map { |pair| opts_out[pair[0].downcase] = pair[1] }
|
58
|
+
raise Redis::CommandError, 'ERR syntax error' unless (opts_out.keys - permitted).empty?
|
59
|
+
opts_out
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class MockRedis
|
2
|
+
class Stream
|
3
|
+
class Id
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_accessor :timestamp, :sequence
|
7
|
+
|
8
|
+
def initialize(id, min: nil, sequence: 0)
|
9
|
+
case id
|
10
|
+
when '*'
|
11
|
+
@timestamp = (Time.now.to_f * 1000).to_i
|
12
|
+
@sequence = 0
|
13
|
+
if self <= min
|
14
|
+
@timestamp = min.timestamp
|
15
|
+
@sequence = min.sequence + 1
|
16
|
+
end
|
17
|
+
when '-'
|
18
|
+
@timestamp = @sequence = 0
|
19
|
+
when '+'
|
20
|
+
@timestamp = @sequence = Float::INFINITY
|
21
|
+
else
|
22
|
+
if id.is_a? String
|
23
|
+
(_, @timestamp, @sequence) = id.match(/^(\d+)-?(\d+)?$/)
|
24
|
+
.to_a
|
25
|
+
if @timestamp.nil?
|
26
|
+
raise Redis::CommandError,
|
27
|
+
'ERR Invalid stream ID specified as stream command argument'
|
28
|
+
end
|
29
|
+
@timestamp = @timestamp.to_i
|
30
|
+
else
|
31
|
+
@timestamp = id
|
32
|
+
end
|
33
|
+
@sequence = @sequence.nil? ? sequence : @sequence.to_i
|
34
|
+
if @timestamp == 0 && @sequence == 0
|
35
|
+
raise Redis::CommandError,
|
36
|
+
'ERR The ID specified in XADD is equal or smaller than ' \
|
37
|
+
'the target stream top item'
|
38
|
+
# TOOD: Redis version 6.0.4, w redis 4.2.1 generates the following error message:
|
39
|
+
# 'ERR The ID specified in XADD must be greater than 0-0'
|
40
|
+
end
|
41
|
+
if self <= min
|
42
|
+
raise Redis::CommandError,
|
43
|
+
'ERR The ID specified in XADD is equal or smaller than ' \
|
44
|
+
'the target stream top item'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"#{@timestamp}-#{@sequence}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def <=>(other)
|
54
|
+
return 1 if other.nil?
|
55
|
+
return @sequence <=> other.sequence if @timestamp == other.timestamp
|
56
|
+
@timestamp <=> other.timestamp
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|