mock_redis 0.5.4 → 0.31.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 +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
|