mock_redis 0.5.4 → 0.31.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/lint.yml +31 -0
- data/.github/workflows/tests.yml +63 -0
- data/.gitignore +1 -1
- data/.overcommit.yml +21 -0
- data/.rspec +1 -1
- data/.rubocop.yml +148 -0
- data/.rubocop_todo.yml +35 -0
- data/.simplecov +4 -0
- data/CHANGELOG.md +278 -0
- data/Gemfile +9 -5
- data/LICENSE.md +21 -0
- data/README.md +52 -16
- data/Rakefile +0 -8
- data/lib/mock_redis/assertions.rb +0 -1
- data/lib/mock_redis/connection_method.rb +13 -0
- data/lib/mock_redis/database.rb +193 -257
- data/lib/mock_redis/expire_wrapper.rb +2 -2
- data/lib/mock_redis/future.rb +23 -0
- data/lib/mock_redis/geospatial_methods.rb +240 -0
- data/lib/mock_redis/hash_methods.rb +83 -24
- data/lib/mock_redis/indifferent_hash.rb +11 -0
- data/lib/mock_redis/info_method.rb +160 -0
- data/lib/mock_redis/list_methods.rb +34 -19
- data/lib/mock_redis/multi_db_wrapper.rb +8 -7
- data/lib/mock_redis/pipelined_wrapper.rb +42 -16
- data/lib/mock_redis/set_methods.rb +62 -19
- data/lib/mock_redis/sort_method.rb +81 -0
- 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 +235 -42
- data/lib/mock_redis/transaction_wrapper.rb +62 -28
- data/lib/mock_redis/utility_methods.rb +62 -11
- data/lib/mock_redis/version.rb +4 -1
- data/lib/mock_redis/zset.rb +24 -29
- data/lib/mock_redis/zset_methods.rb +187 -59
- data/lib/mock_redis.rb +77 -27
- data/mock_redis.gemspec +23 -15
- data/spec/client_spec.rb +29 -0
- data/spec/cloning_spec.rb +17 -18
- data/spec/commands/append_spec.rb +4 -4
- data/spec/commands/auth_spec.rb +1 -1
- data/spec/commands/bgrewriteaof_spec.rb +2 -2
- data/spec/commands/bgsave_spec.rb +2 -2
- data/spec/commands/bitcount_spec.rb +25 -0
- data/spec/commands/bitfield_spec.rb +169 -0
- data/spec/commands/blpop_spec.rb +19 -21
- data/spec/commands/brpop_spec.rb +25 -20
- data/spec/commands/brpoplpush_spec.rb +16 -17
- data/spec/commands/connected_spec.rb +7 -0
- data/spec/commands/connection_spec.rb +15 -0
- data/spec/commands/dbsize_spec.rb +3 -3
- data/spec/commands/decr_spec.rb +8 -8
- data/spec/commands/decrby_spec.rb +8 -8
- data/spec/commands/del_spec.rb +35 -3
- data/spec/commands/disconnect_spec.rb +7 -0
- data/spec/commands/dump_spec.rb +19 -0
- data/spec/commands/echo_spec.rb +4 -4
- data/spec/commands/eval_spec.rb +7 -0
- data/spec/commands/evalsha_spec.rb +10 -0
- data/spec/commands/exists_spec.rb +36 -7
- data/spec/commands/expire_spec.rb +48 -20
- data/spec/commands/expireat_spec.rb +12 -13
- data/spec/commands/flushall_spec.rb +5 -5
- data/spec/commands/flushdb_spec.rb +5 -5
- data/spec/commands/future_spec.rb +30 -0
- data/spec/commands/geoadd_spec.rb +58 -0
- data/spec/commands/geodist_spec.rb +118 -0
- data/spec/commands/geohash_spec.rb +52 -0
- data/spec/commands/geopos_spec.rb +55 -0
- data/spec/commands/get_spec.rb +14 -6
- data/spec/commands/getbit_spec.rb +7 -7
- data/spec/commands/getrange_spec.rb +9 -9
- data/spec/commands/getset_spec.rb +7 -7
- data/spec/commands/hdel_spec.rb +41 -11
- data/spec/commands/hexists_spec.rb +11 -11
- data/spec/commands/hget_spec.rb +7 -7
- data/spec/commands/hgetall_spec.rb +15 -5
- data/spec/commands/hincrby_spec.rb +16 -16
- data/spec/commands/hincrbyfloat_spec.rb +58 -0
- data/spec/commands/hkeys_spec.rb +5 -5
- data/spec/commands/hlen_spec.rb +5 -5
- data/spec/commands/hmget_spec.rb +19 -9
- data/spec/commands/hmset_spec.rb +38 -12
- data/spec/commands/hscan_each_spec.rb +48 -0
- data/spec/commands/hscan_spec.rb +27 -0
- data/spec/commands/hset_spec.rb +26 -12
- data/spec/commands/hsetnx_spec.rb +16 -16
- data/spec/commands/hvals_spec.rb +5 -5
- data/spec/commands/incr_spec.rb +8 -8
- data/spec/commands/incrby_spec.rb +13 -13
- data/spec/commands/incrbyfloat_spec.rb +13 -13
- data/spec/commands/info_spec.rb +54 -5
- data/spec/commands/keys_spec.rb +83 -31
- data/spec/commands/lastsave_spec.rb +2 -2
- data/spec/commands/lindex_spec.rb +20 -10
- data/spec/commands/linsert_spec.rb +14 -14
- data/spec/commands/llen_spec.rb +4 -4
- data/spec/commands/lpop_spec.rb +6 -6
- data/spec/commands/lpush_spec.rb +21 -15
- data/spec/commands/lpushx_spec.rb +24 -11
- data/spec/commands/lrange_spec.rb +24 -8
- data/spec/commands/lrem_spec.rb +16 -16
- data/spec/commands/lset_spec.rb +17 -12
- data/spec/commands/ltrim_spec.rb +17 -7
- data/spec/commands/mapped_hmget_spec.rb +13 -9
- data/spec/commands/mapped_hmset_spec.rb +12 -12
- data/spec/commands/mapped_mget_spec.rb +22 -0
- data/spec/commands/mapped_mset_spec.rb +19 -0
- data/spec/commands/mapped_msetnx_spec.rb +26 -0
- data/spec/commands/mget_spec.rb +48 -17
- data/spec/commands/move_spec.rb +37 -37
- data/spec/commands/mset_spec.rb +20 -6
- data/spec/commands/msetnx_spec.rb +14 -14
- data/spec/commands/persist_spec.rb +15 -16
- data/spec/commands/pexpire_spec.rb +86 -0
- data/spec/commands/pexpireat_spec.rb +48 -0
- data/spec/commands/ping_spec.rb +6 -2
- data/spec/commands/pipelined_spec.rb +98 -7
- data/spec/commands/pttl_spec.rb +41 -0
- data/spec/commands/randomkey_spec.rb +3 -3
- data/spec/commands/rename_spec.rb +16 -12
- data/spec/commands/renamenx_spec.rb +13 -15
- data/spec/commands/restore_spec.rb +47 -0
- data/spec/commands/rpop_spec.rb +6 -6
- data/spec/commands/rpoplpush_spec.rb +13 -8
- data/spec/commands/rpush_spec.rb +21 -15
- data/spec/commands/rpushx_spec.rb +24 -11
- data/spec/commands/sadd_spec.rb +14 -10
- data/spec/commands/scan_each_spec.rb +39 -0
- data/spec/commands/scan_spec.rb +64 -0
- data/spec/commands/scard_spec.rb +3 -3
- data/spec/commands/script_spec.rb +9 -0
- data/spec/commands/sdiff_spec.rb +13 -13
- data/spec/commands/sdiffstore_spec.rb +13 -13
- data/spec/commands/select_spec.rb +13 -5
- data/spec/commands/set_spec.rb +112 -0
- data/spec/commands/setbit_spec.rb +25 -16
- data/spec/commands/setex_spec.rb +20 -4
- data/spec/commands/setnx_spec.rb +6 -6
- data/spec/commands/setrange_spec.rb +12 -12
- data/spec/commands/sinter_spec.rb +11 -13
- data/spec/commands/sinterstore_spec.rb +12 -12
- data/spec/commands/sismember_spec.rb +10 -10
- data/spec/commands/smembers_spec.rb +15 -5
- data/spec/commands/smove_spec.rb +13 -13
- data/spec/commands/sort_list_spec.rb +21 -0
- data/spec/commands/sort_set_spec.rb +21 -0
- data/spec/commands/sort_zset_spec.rb +21 -0
- data/spec/commands/spop_spec.rb +19 -4
- data/spec/commands/srandmember_spec.rb +28 -4
- data/spec/commands/srem_spec.rb +17 -12
- data/spec/commands/sscan_each_spec.rb +48 -0
- data/spec/commands/sscan_spec.rb +39 -0
- data/spec/commands/strlen_spec.rb +4 -5
- data/spec/commands/sunion_spec.rb +13 -11
- data/spec/commands/sunionstore_spec.rb +12 -12
- data/spec/commands/ttl_spec.rb +11 -6
- data/spec/commands/type_spec.rb +1 -1
- data/spec/commands/watch_spec.rb +9 -4
- data/spec/commands/xadd_spec.rb +122 -0
- data/spec/commands/xlen_spec.rb +22 -0
- data/spec/commands/xrange_spec.rb +164 -0
- data/spec/commands/xread_spec.rb +66 -0
- data/spec/commands/xrevrange_spec.rb +130 -0
- data/spec/commands/xtrim_spec.rb +36 -0
- data/spec/commands/zadd_spec.rb +100 -11
- data/spec/commands/zcard_spec.rb +4 -4
- data/spec/commands/zcount_spec.rb +18 -10
- data/spec/commands/zincrby_spec.rb +6 -6
- data/spec/commands/zinterstore_spec.rb +54 -20
- data/spec/commands/zpopmax_spec.rb +60 -0
- data/spec/commands/zpopmin_spec.rb +60 -0
- data/spec/commands/zrange_spec.rb +54 -13
- data/spec/commands/zrangebyscore_spec.rb +42 -27
- data/spec/commands/zrank_spec.rb +4 -4
- data/spec/commands/zrem_spec.rb +18 -12
- data/spec/commands/zremrangebyrank_spec.rb +5 -5
- data/spec/commands/zremrangebyscore_spec.rb +12 -5
- data/spec/commands/zrevrange_spec.rb +35 -10
- data/spec/commands/zrevrangebyscore_spec.rb +26 -15
- data/spec/commands/zrevrank_spec.rb +4 -4
- data/spec/commands/zscan_each_spec.rb +48 -0
- data/spec/commands/zscan_spec.rb +26 -0
- data/spec/commands/zscore_spec.rb +7 -7
- data/spec/commands/zunionstore_spec.rb +54 -21
- data/spec/mock_redis_spec.rb +61 -0
- data/spec/spec_helper.rb +35 -8
- data/spec/support/redis_multiplexer.rb +62 -37
- data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +5 -3
- data/spec/support/shared_examples/only_operates_on_lists.rb +5 -3
- data/spec/support/shared_examples/only_operates_on_sets.rb +5 -3
- data/spec/support/shared_examples/only_operates_on_strings.rb +4 -4
- data/spec/support/shared_examples/only_operates_on_zsets.rb +18 -16
- data/spec/support/shared_examples/sorts_enumerables.rb +56 -0
- data/spec/transactions_spec.rb +79 -29
- metadata +162 -42
- data/LICENSE +0 -19
- data/spec/commands/hash_operator_spec.rb +0 -21
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'mock_redis/assertions'
|
2
|
+
require 'mock_redis/utility_methods'
|
3
|
+
require 'mock_redis/stream'
|
4
|
+
|
5
|
+
# TODO: Implement the following commands
|
6
|
+
#
|
7
|
+
# * xgroup
|
8
|
+
# * xreadgroup
|
9
|
+
# * xack
|
10
|
+
# * xpending
|
11
|
+
# * xclaim
|
12
|
+
# * xinfo
|
13
|
+
# * xtrim
|
14
|
+
# * xdel
|
15
|
+
#
|
16
|
+
# TODO: Complete support for
|
17
|
+
#
|
18
|
+
# * xtrim
|
19
|
+
# - `approximate: true` argument is currently ignored
|
20
|
+
# * xadd
|
21
|
+
# - `approximate: true` argument (for capped streams) is currently ignored
|
22
|
+
#
|
23
|
+
# For details of these commands see
|
24
|
+
# * https://redis.io/topics/streams-intro
|
25
|
+
# * https://redis.io/commands#stream
|
26
|
+
|
27
|
+
class MockRedis
|
28
|
+
module StreamMethods
|
29
|
+
include Assertions
|
30
|
+
include UtilityMethods
|
31
|
+
|
32
|
+
def xadd(key, entry, opts = {})
|
33
|
+
id = opts[:id] || '*'
|
34
|
+
with_stream_at(key) do |stream|
|
35
|
+
stream.add id, entry
|
36
|
+
stream.trim opts[:maxlen] if opts[:maxlen]
|
37
|
+
return stream.last_id
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def xtrim(key, count)
|
42
|
+
with_stream_at(key) do |stream|
|
43
|
+
stream.trim count
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def xlen(key)
|
48
|
+
with_stream_at(key) do |stream|
|
49
|
+
return stream.count
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def xrange(key, first = '-', last = '+', count: nil)
|
54
|
+
args = [first, last, false]
|
55
|
+
args += ['COUNT', count] if count
|
56
|
+
with_stream_at(key) do |stream|
|
57
|
+
return stream.range(*args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def xrevrange(key, last = '+', first = '-', count: nil)
|
62
|
+
args = [first, last, true]
|
63
|
+
args += ['COUNT', count] if count
|
64
|
+
with_stream_at(key) do |stream|
|
65
|
+
return stream.range(*args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def xread(keys, ids, count: nil, block: nil)
|
70
|
+
args = []
|
71
|
+
args += ['COUNT', count] if count
|
72
|
+
args += ['BLOCK', block.to_i] if block
|
73
|
+
result = {}
|
74
|
+
keys = keys.is_a?(Array) ? keys : [keys]
|
75
|
+
ids = ids.is_a?(Array) ? ids : [ids]
|
76
|
+
keys.each_with_index do |key, index|
|
77
|
+
with_stream_at(key) do |stream|
|
78
|
+
data = stream.read(ids[index], *args)
|
79
|
+
result[key] = data unless data.empty?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def with_stream_at(key, &blk)
|
88
|
+
with_thing_at(key, :assert_streamy, proc { Stream.new }, &blk)
|
89
|
+
end
|
90
|
+
|
91
|
+
def streamy?(key)
|
92
|
+
data[key].nil? || data[key].is_a?(Stream)
|
93
|
+
end
|
94
|
+
|
95
|
+
def assert_streamy(key)
|
96
|
+
unless streamy?(key)
|
97
|
+
raise Redis::CommandError,
|
98
|
+
'WRONGTYPE Operation against a key holding the wrong kind of value'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -3,14 +3,79 @@ require 'mock_redis/assertions'
|
|
3
3
|
class MockRedis
|
4
4
|
module StringMethods
|
5
5
|
include Assertions
|
6
|
+
include UtilityMethods
|
6
7
|
|
7
8
|
def append(key, value)
|
8
9
|
assert_stringy(key)
|
9
|
-
data[key] ||=
|
10
|
+
data[key] ||= ''
|
10
11
|
data[key] << value
|
11
12
|
data[key].length
|
12
13
|
end
|
13
14
|
|
15
|
+
def bitfield(*args)
|
16
|
+
if args.length < 4
|
17
|
+
raise Redis::CommandError, 'ERR wrong number of arguments for BITFIELD'
|
18
|
+
end
|
19
|
+
|
20
|
+
key = args.shift
|
21
|
+
output = []
|
22
|
+
overflow_method = 'wrap'
|
23
|
+
|
24
|
+
until args.empty?
|
25
|
+
command = args.shift.to_s
|
26
|
+
|
27
|
+
if command == 'overflow'
|
28
|
+
new_overflow_method = args.shift.to_s.downcase
|
29
|
+
|
30
|
+
unless %w[wrap sat fail].include? new_overflow_method
|
31
|
+
raise Redis::CommandError, 'ERR Invalid OVERFLOW type specified'
|
32
|
+
end
|
33
|
+
|
34
|
+
overflow_method = new_overflow_method
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
type, offset = args.shift(2)
|
39
|
+
|
40
|
+
is_signed = type.slice(0) == 'i'
|
41
|
+
type_size = type[1..-1].to_i
|
42
|
+
|
43
|
+
if (type_size > 64 && is_signed) || (type_size >= 64 && !is_signed)
|
44
|
+
raise Redis::CommandError,
|
45
|
+
'ERR Invalid bitfield type. Use something like i16 u8. ' \
|
46
|
+
'Note that u64 is not supported but i64 is.'
|
47
|
+
end
|
48
|
+
|
49
|
+
if offset.to_s[0] == '#'
|
50
|
+
offset = offset[1..-1].to_i * type_size
|
51
|
+
end
|
52
|
+
|
53
|
+
bits = []
|
54
|
+
|
55
|
+
type_size.times do |i|
|
56
|
+
bits.push(getbit(key, offset + i))
|
57
|
+
end
|
58
|
+
|
59
|
+
val = is_signed ? twos_complement_decode(bits) : bits.join('').to_i(2)
|
60
|
+
|
61
|
+
case command
|
62
|
+
when 'get'
|
63
|
+
output.push(val)
|
64
|
+
when 'set'
|
65
|
+
output.push(val)
|
66
|
+
|
67
|
+
set_bitfield(key, args.shift.to_i, is_signed, type_size, offset)
|
68
|
+
when 'incrby'
|
69
|
+
new_val = incr_bitfield(val, args.shift.to_i, is_signed, type_size, overflow_method)
|
70
|
+
|
71
|
+
set_bitfield(key, new_val, is_signed, type_size, offset) if new_val
|
72
|
+
output.push(new_val)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
output
|
77
|
+
end
|
78
|
+
|
14
79
|
def decr(key)
|
15
80
|
decrby(key, 1)
|
16
81
|
end
|
@@ -20,22 +85,20 @@ class MockRedis
|
|
20
85
|
end
|
21
86
|
|
22
87
|
def get(key)
|
88
|
+
key = key.to_s
|
23
89
|
assert_stringy(key)
|
24
90
|
data[key]
|
25
91
|
end
|
26
92
|
|
27
|
-
def [](key)
|
28
|
-
get(key)
|
29
|
-
end
|
30
|
-
|
31
93
|
def getbit(key, offset)
|
32
94
|
assert_stringy(key)
|
33
95
|
|
96
|
+
offset = offset.to_i
|
34
97
|
offset_of_byte = offset / 8
|
35
98
|
offset_within_byte = offset % 8
|
36
99
|
|
37
100
|
# String#getbyte would be lovely, but it's not in 1.8.7.
|
38
|
-
byte = (data[key] ||
|
101
|
+
byte = (data[key] || '').each_byte.drop(offset_of_byte).first
|
39
102
|
|
40
103
|
if byte
|
41
104
|
(byte & (2**7 >> offset_within_byte)) > 0 ? 1 : 0
|
@@ -46,7 +109,7 @@ class MockRedis
|
|
46
109
|
|
47
110
|
def getrange(key, start, stop)
|
48
111
|
assert_stringy(key)
|
49
|
-
(data[key] ||
|
112
|
+
(data[key] || '')[start..stop]
|
50
113
|
end
|
51
114
|
|
52
115
|
def getset(key, value)
|
@@ -62,11 +125,11 @@ class MockRedis
|
|
62
125
|
def incrby(key, n)
|
63
126
|
assert_stringy(key)
|
64
127
|
unless can_incr?(data[key])
|
65
|
-
raise Redis::CommandError,
|
128
|
+
raise Redis::CommandError, 'ERR value is not an integer or out of range'
|
66
129
|
end
|
67
130
|
|
68
131
|
unless looks_like_integer?(n.to_s)
|
69
|
-
raise Redis::CommandError,
|
132
|
+
raise Redis::CommandError, 'ERR value is not an integer or out of range'
|
70
133
|
end
|
71
134
|
|
72
135
|
new_value = data[key].to_i + n.to_i
|
@@ -78,11 +141,11 @@ class MockRedis
|
|
78
141
|
def incrbyfloat(key, n)
|
79
142
|
assert_stringy(key)
|
80
143
|
unless can_incr_float?(data[key])
|
81
|
-
raise Redis::CommandError,
|
144
|
+
raise Redis::CommandError, 'ERR value is not a valid float'
|
82
145
|
end
|
83
146
|
|
84
147
|
unless looks_like_float?(n.to_s)
|
85
|
-
raise Redis::CommandError,
|
148
|
+
raise Redis::CommandError, 'ERR value is not a valid float'
|
86
149
|
end
|
87
150
|
|
88
151
|
new_value = data[key].to_f + n.to_f
|
@@ -91,31 +154,45 @@ class MockRedis
|
|
91
154
|
new_value
|
92
155
|
end
|
93
156
|
|
94
|
-
def mget(*keys)
|
157
|
+
def mget(*keys, &blk)
|
158
|
+
keys.flatten!
|
159
|
+
|
95
160
|
assert_has_args(keys, 'mget')
|
96
161
|
|
97
|
-
keys.map do |key|
|
162
|
+
data = keys.map do |key|
|
98
163
|
get(key) if stringy?(key)
|
99
164
|
end
|
165
|
+
|
166
|
+
blk ? blk.call(data) : data
|
167
|
+
end
|
168
|
+
|
169
|
+
def mapped_mget(*keys)
|
170
|
+
Hash[keys.zip(mget(*keys))]
|
100
171
|
end
|
101
172
|
|
102
173
|
def mset(*kvpairs)
|
103
174
|
assert_has_args(kvpairs, 'mset')
|
175
|
+
kvpairs = kvpairs.first if kvpairs.size == 1 && kvpairs.first.is_a?(Enumerable)
|
176
|
+
|
104
177
|
if kvpairs.length.odd?
|
105
|
-
raise Redis::CommandError,
|
178
|
+
raise Redis::CommandError, 'ERR wrong number of arguments for MSET'
|
106
179
|
end
|
107
180
|
|
108
|
-
kvpairs.each_slice(2) do |(k,v)|
|
109
|
-
set(k,v)
|
181
|
+
kvpairs.each_slice(2) do |(k, v)|
|
182
|
+
set(k, v)
|
110
183
|
end
|
111
184
|
|
112
|
-
|
185
|
+
'OK'
|
186
|
+
end
|
187
|
+
|
188
|
+
def mapped_mset(hash)
|
189
|
+
mset(*hash.to_a.flatten)
|
113
190
|
end
|
114
191
|
|
115
192
|
def msetnx(*kvpairs)
|
116
193
|
assert_has_args(kvpairs, 'msetnx')
|
117
194
|
|
118
|
-
if kvpairs.each_slice(2).any? {|(k,
|
195
|
+
if kvpairs.each_slice(2).any? { |(k, _)| exists?(k) }
|
119
196
|
false
|
120
197
|
else
|
121
198
|
mset(*kvpairs)
|
@@ -123,26 +200,62 @@ class MockRedis
|
|
123
200
|
end
|
124
201
|
end
|
125
202
|
|
126
|
-
def
|
127
|
-
|
128
|
-
'OK'
|
203
|
+
def mapped_msetnx(hash)
|
204
|
+
msetnx(*hash.to_a.flatten)
|
129
205
|
end
|
130
206
|
|
131
|
-
|
132
|
-
|
207
|
+
# Parameer list required to ensure the ArgumentError is returned correctly
|
208
|
+
# rubocop:disable Metrics/ParameterLists
|
209
|
+
def set(key, value, ex: nil, px: nil, nx: nil, xx: nil, keepttl: nil)
|
210
|
+
key = key.to_s
|
211
|
+
return_true = false
|
212
|
+
if nx
|
213
|
+
if exists?(key)
|
214
|
+
return false
|
215
|
+
else
|
216
|
+
return_true = true
|
217
|
+
end
|
218
|
+
end
|
219
|
+
if xx
|
220
|
+
if exists?(key)
|
221
|
+
return_true = true
|
222
|
+
else
|
223
|
+
return false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
data[key] = value.to_s
|
227
|
+
|
228
|
+
remove_expiration(key) unless keepttl
|
229
|
+
if ex
|
230
|
+
if ex == 0
|
231
|
+
raise Redis::CommandError, 'ERR invalid expire time in set'
|
232
|
+
end
|
233
|
+
expire(key, ex)
|
234
|
+
end
|
235
|
+
|
236
|
+
if px
|
237
|
+
if px == 0
|
238
|
+
raise Redis::CommandError, 'ERR invalid expire time in set'
|
239
|
+
end
|
240
|
+
pexpire(key, px)
|
241
|
+
end
|
242
|
+
|
243
|
+
return_true ? true : 'OK'
|
133
244
|
end
|
245
|
+
# rubocop:enable Metrics/ParameterLists
|
134
246
|
|
135
247
|
def setbit(key, offset, value)
|
136
|
-
assert_stringy(key,
|
248
|
+
assert_stringy(key, 'ERR bit is not an integer or out of range')
|
137
249
|
retval = getbit(key, offset)
|
138
250
|
|
139
|
-
str = data[key] ||
|
251
|
+
str = data[key] || ''
|
140
252
|
|
253
|
+
offset = offset.to_i
|
141
254
|
offset_of_byte = offset / 8
|
142
255
|
offset_within_byte = offset % 8
|
143
256
|
|
144
257
|
if offset_of_byte >= str.bytesize
|
145
|
-
str = zero_pad(str, offset_of_byte+1)
|
258
|
+
str = zero_pad(str, offset_of_byte + 1)
|
146
259
|
end
|
147
260
|
|
148
261
|
char_index = byte_index = offset_within_char = 0
|
@@ -157,27 +270,66 @@ class MockRedis
|
|
157
270
|
end
|
158
271
|
|
159
272
|
char = str[char_index]
|
160
|
-
char = char.chr if char.respond_to?(:chr)
|
273
|
+
char = char.chr if char.respond_to?(:chr) # ruby 1.8 vs 1.9
|
161
274
|
char_as_number = char.each_byte.reduce(0) do |a, byte|
|
162
275
|
(a << 8) + byte
|
163
276
|
end
|
164
|
-
|
165
|
-
|
166
|
-
|
277
|
+
|
278
|
+
bitmask_length = (char.bytesize * 8 - offset_within_char * 8 - offset_within_byte - 1)
|
279
|
+
bitmask = 1 << bitmask_length
|
280
|
+
|
281
|
+
if value.zero?
|
282
|
+
bitmask ^= 2**(char.bytesize * 8) - 1
|
283
|
+
char_as_number &= bitmask
|
284
|
+
else
|
285
|
+
char_as_number |= bitmask
|
286
|
+
end
|
287
|
+
|
167
288
|
str[char_index] = char_as_number.chr
|
168
289
|
|
169
290
|
data[key] = str
|
170
291
|
retval
|
171
292
|
end
|
172
293
|
|
294
|
+
def bitcount(key, start = 0, stop = -1)
|
295
|
+
assert_stringy(key)
|
296
|
+
|
297
|
+
str = data[key] || ''
|
298
|
+
count = 0
|
299
|
+
m1 = 0x5555555555555555
|
300
|
+
m2 = 0x3333333333333333
|
301
|
+
m4 = 0x0f0f0f0f0f0f0f0f
|
302
|
+
m8 = 0x00ff00ff00ff00ff
|
303
|
+
m16 = 0x0000ffff0000ffff
|
304
|
+
m32 = 0x00000000ffffffff
|
305
|
+
|
306
|
+
str.bytes.to_a[start..stop].each do |byte|
|
307
|
+
# Naive Hamming weight
|
308
|
+
c = byte
|
309
|
+
c = (c & m1) + ((c >> 1) & m1)
|
310
|
+
c = (c & m2) + ((c >> 2) & m2)
|
311
|
+
c = (c & m4) + ((c >> 4) & m4)
|
312
|
+
c = (c & m8) + ((c >> 8) & m8)
|
313
|
+
c = (c & m16) + ((c >> 16) & m16)
|
314
|
+
c = (c & m32) + ((c >> 32) & m32)
|
315
|
+
count += c
|
316
|
+
end
|
317
|
+
|
318
|
+
count
|
319
|
+
end
|
320
|
+
|
173
321
|
def setex(key, seconds, value)
|
174
|
-
|
175
|
-
|
176
|
-
|
322
|
+
if seconds <= 0
|
323
|
+
raise Redis::CommandError, 'ERR invalid expire time in setex'
|
324
|
+
else
|
325
|
+
set(key, value)
|
326
|
+
expire(key, seconds)
|
327
|
+
'OK'
|
328
|
+
end
|
177
329
|
end
|
178
330
|
|
179
331
|
def setnx(key, value)
|
180
|
-
if exists(key)
|
332
|
+
if exists?(key)
|
181
333
|
false
|
182
334
|
else
|
183
335
|
set(key, value)
|
@@ -188,32 +340,73 @@ class MockRedis
|
|
188
340
|
def setrange(key, offset, value)
|
189
341
|
assert_stringy(key)
|
190
342
|
value = value.to_s
|
191
|
-
old_value = (data[key] ||
|
343
|
+
old_value = (data[key] || '')
|
192
344
|
|
193
345
|
prefix = zero_pad(old_value[0...offset], offset)
|
194
|
-
data[key] = prefix + value + (old_value[(offset + value.length)..-1] ||
|
346
|
+
data[key] = prefix + value + (old_value[(offset + value.length)..-1] || '')
|
195
347
|
data[key].length
|
196
348
|
end
|
197
349
|
|
198
350
|
def strlen(key)
|
199
351
|
assert_stringy(key)
|
200
|
-
(data[key] ||
|
352
|
+
(data[key] || '').bytesize
|
201
353
|
end
|
202
354
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
355
|
private
|
356
|
+
|
207
357
|
def stringy?(key)
|
208
|
-
data[key].nil? || data[key].
|
358
|
+
data[key].nil? || data[key].is_a?(String)
|
209
359
|
end
|
210
360
|
|
211
361
|
def assert_stringy(key,
|
212
|
-
message=
|
362
|
+
message = 'WRONGTYPE Operation against a key holding the wrong kind of value')
|
213
363
|
unless stringy?(key)
|
214
364
|
raise Redis::CommandError, message
|
215
365
|
end
|
216
366
|
end
|
217
367
|
|
368
|
+
def set_bitfield(key, value, is_signed, type_size, offset)
|
369
|
+
if is_signed
|
370
|
+
val_array = twos_complement_encode(value, type_size)
|
371
|
+
else
|
372
|
+
str = left_pad(value.to_i.abs.to_s(2), type_size)
|
373
|
+
val_array = str.split('').map(&:to_i)
|
374
|
+
end
|
375
|
+
|
376
|
+
val_array.each_with_index do |bit, i|
|
377
|
+
setbit(key, offset + i, bit)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def incr_bitfield(val, incrby, is_signed, type_size, overflow_method)
|
382
|
+
new_val = val + incrby
|
383
|
+
|
384
|
+
max = is_signed ? (2**(type_size - 1)) - 1 : (2**type_size) - 1
|
385
|
+
min = is_signed ? (-2**(type_size - 1)) : 0
|
386
|
+
size = 2**type_size
|
387
|
+
|
388
|
+
return new_val if (min..max).cover?(new_val)
|
389
|
+
|
390
|
+
case overflow_method
|
391
|
+
when 'fail'
|
392
|
+
new_val = nil
|
393
|
+
when 'sat'
|
394
|
+
new_val = new_val > max ? max : min
|
395
|
+
when 'wrap'
|
396
|
+
if is_signed
|
397
|
+
if new_val > max
|
398
|
+
remainder = new_val - (max + 1)
|
399
|
+
new_val = min + remainder.abs
|
400
|
+
else
|
401
|
+
remainder = new_val - (min - 1)
|
402
|
+
new_val = max - remainder.abs
|
403
|
+
end
|
404
|
+
else
|
405
|
+
new_val = new_val > max ? new_val % size : size - new_val.abs
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
new_val
|
410
|
+
end
|
218
411
|
end
|
219
412
|
end
|
@@ -4,20 +4,27 @@ class MockRedis
|
|
4
4
|
class TransactionWrapper
|
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
|
|
11
11
|
def initialize(db)
|
12
12
|
@db = db
|
13
|
-
@
|
14
|
-
@
|
13
|
+
@transaction_futures = []
|
14
|
+
@multi_stack = []
|
15
|
+
@multi_block_given = false
|
15
16
|
end
|
16
17
|
|
17
|
-
def method_missing(method, *args, &block)
|
18
|
-
if
|
19
|
-
|
20
|
-
|
18
|
+
ruby2_keywords def method_missing(method, *args, &block)
|
19
|
+
if in_multi?
|
20
|
+
future = MockRedis::Future.new([method, *args], block)
|
21
|
+
@transaction_futures << future
|
22
|
+
|
23
|
+
if @multi_block_given
|
24
|
+
future
|
25
|
+
else
|
26
|
+
'QUEUED'
|
27
|
+
end
|
21
28
|
else
|
22
29
|
@db.expire_keys
|
23
30
|
@db.send(method, *args, &block)
|
@@ -27,59 +34,86 @@ class MockRedis
|
|
27
34
|
def initialize_copy(source)
|
28
35
|
super
|
29
36
|
@db = @db.clone
|
30
|
-
@
|
37
|
+
@transaction_futures = @transaction_futures.clone
|
38
|
+
@multi_stack = @multi_stack.clone
|
31
39
|
end
|
32
40
|
|
33
41
|
def discard
|
34
|
-
unless
|
35
|
-
raise Redis::CommandError,
|
42
|
+
unless in_multi?
|
43
|
+
raise Redis::CommandError, 'ERR DISCARD without MULTI'
|
36
44
|
end
|
37
|
-
|
38
|
-
|
45
|
+
pop_multi
|
46
|
+
|
47
|
+
@transaction_futures = []
|
39
48
|
'OK'
|
40
49
|
end
|
41
50
|
|
42
51
|
def exec
|
43
|
-
unless
|
44
|
-
raise Redis::CommandError,
|
52
|
+
unless in_multi?
|
53
|
+
raise Redis::CommandError, 'ERR EXEC without MULTI'
|
45
54
|
end
|
46
|
-
|
47
|
-
|
55
|
+
pop_multi
|
56
|
+
return if in_multi?
|
57
|
+
@multi_block_given = false
|
58
|
+
|
59
|
+
responses = @transaction_futures.map do |future|
|
48
60
|
begin
|
49
|
-
send(*
|
50
|
-
|
61
|
+
result = send(*future.command)
|
62
|
+
future.store_result(result)
|
63
|
+
future.value
|
64
|
+
rescue StandardError => e
|
51
65
|
e
|
52
66
|
end
|
53
67
|
end
|
54
|
-
|
68
|
+
|
69
|
+
@transaction_futures = []
|
55
70
|
responses
|
56
71
|
end
|
57
72
|
|
73
|
+
def in_multi?
|
74
|
+
@multi_stack.any?
|
75
|
+
end
|
76
|
+
|
77
|
+
def push_multi
|
78
|
+
@multi_stack.push(@multi_stack.size + 1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def pop_multi
|
82
|
+
@multi_stack.pop
|
83
|
+
end
|
84
|
+
|
58
85
|
def multi
|
59
|
-
if @in_multi
|
60
|
-
raise Redis::CommandError, "ERR MULTI calls can not be nested"
|
61
|
-
end
|
62
|
-
@in_multi = true
|
63
86
|
if block_given?
|
87
|
+
push_multi
|
88
|
+
@multi_block_given = true
|
64
89
|
begin
|
65
90
|
yield(self)
|
66
|
-
|
91
|
+
exec
|
67
92
|
rescue StandardError => e
|
68
|
-
|
93
|
+
discard
|
69
94
|
raise e
|
70
95
|
end
|
71
96
|
else
|
97
|
+
raise Redis::CommandError, 'ERR MULTI calls can not be nested' if in_multi?
|
98
|
+
push_multi
|
72
99
|
'OK'
|
73
100
|
end
|
74
101
|
end
|
75
102
|
|
103
|
+
def pipelined
|
104
|
+
yield(self) if block_given?
|
105
|
+
end
|
106
|
+
|
76
107
|
def unwatch
|
77
108
|
'OK'
|
78
109
|
end
|
79
110
|
|
80
|
-
def watch(_)
|
81
|
-
|
111
|
+
def watch(*_)
|
112
|
+
if block_given?
|
113
|
+
yield self
|
114
|
+
else
|
115
|
+
'OK'
|
116
|
+
end
|
82
117
|
end
|
83
|
-
|
84
118
|
end
|
85
119
|
end
|