mock_redis 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/LICENSE +19 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/lib/mock_redis/assertions.rb +13 -0
- data/lib/mock_redis/database.rb +432 -0
- data/lib/mock_redis/exceptions.rb +3 -0
- data/lib/mock_redis/expire_wrapper.rb +25 -0
- data/lib/mock_redis/hash_methods.rb +102 -0
- data/lib/mock_redis/list_methods.rb +187 -0
- data/lib/mock_redis/multi_db_wrapper.rb +48 -0
- data/lib/mock_redis/set_methods.rb +125 -0
- data/lib/mock_redis/string_methods.rb +195 -0
- data/lib/mock_redis/transaction_wrapper.rb +75 -0
- data/lib/mock_redis/undef_redis_methods.rb +11 -0
- data/lib/mock_redis/utility_methods.rb +22 -0
- data/lib/mock_redis/version.rb +3 -0
- data/lib/mock_redis/zset.rb +80 -0
- data/lib/mock_redis/zset_methods.rb +210 -0
- data/lib/mock_redis.rb +32 -0
- data/mock_redis.gemspec +24 -0
- data/spec/cloning_spec.rb +96 -0
- data/spec/commands/append_spec.rb +24 -0
- data/spec/commands/auth_spec.rb +7 -0
- data/spec/commands/bgrewriteaof_spec.rb +7 -0
- data/spec/commands/bgsave_spec.rb +7 -0
- data/spec/commands/blpop_spec.rb +55 -0
- data/spec/commands/brpop_spec.rb +54 -0
- data/spec/commands/brpoplpush_spec.rb +53 -0
- data/spec/commands/dbsize_spec.rb +18 -0
- data/spec/commands/decr_spec.rb +34 -0
- data/spec/commands/decrby_spec.rb +34 -0
- data/spec/commands/del_spec.rb +20 -0
- data/spec/commands/echo_spec.rb +11 -0
- data/spec/commands/exists_spec.rb +14 -0
- data/spec/commands/expire_spec.rb +83 -0
- data/spec/commands/expireat_spec.rb +48 -0
- data/spec/commands/flushall_spec.rb +38 -0
- data/spec/commands/flushdb_spec.rb +38 -0
- data/spec/commands/get_spec.rb +23 -0
- data/spec/commands/getbit_spec.rb +34 -0
- data/spec/commands/getrange_spec.rb +22 -0
- data/spec/commands/getset_spec.rb +23 -0
- data/spec/commands/hdel_spec.rb +35 -0
- data/spec/commands/hexists_spec.rb +22 -0
- data/spec/commands/hget_spec.rb +23 -0
- data/spec/commands/hgetall_spec.rb +22 -0
- data/spec/commands/hincrby_spec.rb +52 -0
- data/spec/commands/hkeys_spec.rb +19 -0
- data/spec/commands/hlen_spec.rb +19 -0
- data/spec/commands/hmget_spec.rb +30 -0
- data/spec/commands/hmset_spec.rb +43 -0
- data/spec/commands/hset_spec.rb +23 -0
- data/spec/commands/hsetnx_spec.rb +39 -0
- data/spec/commands/hvals_spec.rb +19 -0
- data/spec/commands/incr_spec.rb +34 -0
- data/spec/commands/incrby_spec.rb +44 -0
- data/spec/commands/info_spec.rb +13 -0
- data/spec/commands/keys_spec.rb +87 -0
- data/spec/commands/lastsave_spec.rb +8 -0
- data/spec/commands/lindex_spec.rb +39 -0
- data/spec/commands/linsert_spec.rb +68 -0
- data/spec/commands/llen_spec.rb +16 -0
- data/spec/commands/lpop_spec.rb +34 -0
- data/spec/commands/lpush_spec.rb +30 -0
- data/spec/commands/lpushx_spec.rb +33 -0
- data/spec/commands/lrange_spec.rb +35 -0
- data/spec/commands/lrem_spec.rb +79 -0
- data/spec/commands/lset_spec.rb +38 -0
- data/spec/commands/ltrim_spec.rb +35 -0
- data/spec/commands/mget_spec.rb +34 -0
- data/spec/commands/mset_spec.rb +29 -0
- data/spec/commands/msetnx_spec.rb +40 -0
- data/spec/commands/persist_spec.rb +49 -0
- data/spec/commands/ping_spec.rb +7 -0
- data/spec/commands/quit_spec.rb +7 -0
- data/spec/commands/randomkey_spec.rb +20 -0
- data/spec/commands/rename_spec.rb +31 -0
- data/spec/commands/renamenx_spec.rb +36 -0
- data/spec/commands/rpop_spec.rb +34 -0
- data/spec/commands/rpoplpush_spec.rb +45 -0
- data/spec/commands/rpush_spec.rb +30 -0
- data/spec/commands/rpushx_spec.rb +33 -0
- data/spec/commands/sadd_spec.rb +22 -0
- data/spec/commands/save_spec.rb +7 -0
- data/spec/commands/scard_spec.rb +18 -0
- data/spec/commands/sdiff_spec.rb +47 -0
- data/spec/commands/sdiffstore_spec.rb +51 -0
- data/spec/commands/select_spec.rb +53 -0
- data/spec/commands/set_spec.rb +7 -0
- data/spec/commands/setbit_spec.rb +46 -0
- data/spec/commands/setex_spec.rb +22 -0
- data/spec/commands/setnx_spec.rb +25 -0
- data/spec/commands/setrange_spec.rb +30 -0
- data/spec/commands/sinter_spec.rb +41 -0
- data/spec/commands/sinterstore_spec.rb +46 -0
- data/spec/commands/sismember_spec.rb +29 -0
- data/spec/commands/smembers_spec.rb +18 -0
- data/spec/commands/smove_spec.rb +41 -0
- data/spec/commands/spop_spec.rb +25 -0
- data/spec/commands/srandmember_spec.rb +25 -0
- data/spec/commands/srem_spec.rb +35 -0
- data/spec/commands/strlen_spec.rb +19 -0
- data/spec/commands/sunion_spec.rb +40 -0
- data/spec/commands/sunionstore_spec.rb +46 -0
- data/spec/commands/ttl_spec.rb +36 -0
- data/spec/commands/type_spec.rb +36 -0
- data/spec/commands/unwatch_spec.rb +7 -0
- data/spec/commands/watch_spec.rb +7 -0
- data/spec/commands/zadd_spec.rb +29 -0
- data/spec/commands/zcard_spec.rb +19 -0
- data/spec/commands/zcount_spec.rb +23 -0
- data/spec/commands/zincrby_spec.rb +24 -0
- data/spec/commands/zinterstore_spec.rb +88 -0
- data/spec/commands/zrange_spec.rb +31 -0
- data/spec/commands/zrangebyscore_spec.rb +42 -0
- data/spec/commands/zrank_spec.rb +23 -0
- data/spec/commands/zrem_spec.rb +25 -0
- data/spec/commands/zremrangebyrank_spec.rb +22 -0
- data/spec/commands/zremrangebyscore_spec.rb +21 -0
- data/spec/commands/zrevrange_spec.rb +31 -0
- data/spec/commands/zrevrangebyscore_spec.rb +42 -0
- data/spec/commands/zrevrank_spec.rb +23 -0
- data/spec/commands/zscore_spec.rb +16 -0
- data/spec/commands/zunionstore_spec.rb +96 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/redis_multiplexer.rb +91 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_lists.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_sets.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_strings.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_zsets.rb +57 -0
- data/spec/transactions_spec.rb +73 -0
- metadata +358 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'mock_redis/assertions'
|
2
|
+
require 'mock_redis/utility_methods'
|
3
|
+
|
4
|
+
class MockRedis
|
5
|
+
module HashMethods
|
6
|
+
include Assertions
|
7
|
+
include UtilityMethods
|
8
|
+
|
9
|
+
def hdel(key, field)
|
10
|
+
with_hash_at(key) do |hash|
|
11
|
+
hash.delete(field) ? 1 : 0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def hexists(key, field)
|
16
|
+
with_hash_at(key) {|h| h.has_key?(field)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def hget(key, field)
|
20
|
+
with_hash_at(key) {|h| h[field]}
|
21
|
+
end
|
22
|
+
|
23
|
+
def hgetall(key)
|
24
|
+
with_hash_at(key) {|h| h}
|
25
|
+
end
|
26
|
+
|
27
|
+
def hincrby(key, field, increment)
|
28
|
+
with_hash_at(key) do |hash|
|
29
|
+
unless can_incr?(data[key][field])
|
30
|
+
raise RuntimeError, "ERR hash value is not an integer"
|
31
|
+
end
|
32
|
+
unless looks_like_integer?(increment.to_s)
|
33
|
+
raise RuntimeError, "ERR value is not an integer or out of range"
|
34
|
+
end
|
35
|
+
|
36
|
+
new_value = (hash[field] || "0").to_i + increment.to_i
|
37
|
+
hash[field] = new_value.to_s
|
38
|
+
new_value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def hkeys(key)
|
43
|
+
with_hash_at(key, &:keys)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hlen(key)
|
47
|
+
hkeys(key).length
|
48
|
+
end
|
49
|
+
|
50
|
+
def hmget(key, *fields)
|
51
|
+
assert_has_args(fields, 'hmget')
|
52
|
+
fields.map{|f| hget(key, f)}
|
53
|
+
end
|
54
|
+
|
55
|
+
def hmset(key, *kvpairs)
|
56
|
+
assert_has_args(kvpairs, 'hmset')
|
57
|
+
if kvpairs.length.odd?
|
58
|
+
raise RuntimeError, "ERR wrong number of arguments for HMSET"
|
59
|
+
end
|
60
|
+
|
61
|
+
kvpairs.each_slice(2) do |(k,v)|
|
62
|
+
hset(key, k, v)
|
63
|
+
end
|
64
|
+
'OK'
|
65
|
+
end
|
66
|
+
|
67
|
+
def hset(key, field, value)
|
68
|
+
with_hash_at(key) {|h| h[field] = value.to_s}
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def hsetnx(key, field, value)
|
73
|
+
if hget(key, field)
|
74
|
+
false
|
75
|
+
else
|
76
|
+
hset(key, field, value)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def hvals(key)
|
82
|
+
with_hash_at(key, &:values)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def with_hash_at(key, &blk)
|
88
|
+
with_thing_at(key, :assert_hashy, proc {{}}, &blk)
|
89
|
+
end
|
90
|
+
|
91
|
+
def hashy?(key)
|
92
|
+
data[key].nil? || data[key].kind_of?(Hash)
|
93
|
+
end
|
94
|
+
|
95
|
+
def assert_hashy(key)
|
96
|
+
unless hashy?(key)
|
97
|
+
raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'mock_redis/assertions'
|
2
|
+
require 'mock_redis/utility_methods'
|
3
|
+
|
4
|
+
class MockRedis
|
5
|
+
module ListMethods
|
6
|
+
include Assertions
|
7
|
+
include UtilityMethods
|
8
|
+
|
9
|
+
def blpop(*args)
|
10
|
+
lists, timeout = extract_timeout(args)
|
11
|
+
nonempty_list = first_nonempty_list(lists)
|
12
|
+
|
13
|
+
if nonempty_list
|
14
|
+
[nonempty_list, lpop(nonempty_list)]
|
15
|
+
elsif timeout > 0
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
raise MockRedis::WouldBlock, "Can't block forever"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def brpop(*args)
|
23
|
+
lists, timeout = extract_timeout(args)
|
24
|
+
nonempty_list = first_nonempty_list(lists)
|
25
|
+
|
26
|
+
if nonempty_list
|
27
|
+
[nonempty_list, rpop(nonempty_list)]
|
28
|
+
elsif timeout > 0
|
29
|
+
nil
|
30
|
+
else
|
31
|
+
raise MockRedis::WouldBlock, "Can't block forever"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def brpoplpush(source, destination, timeout)
|
36
|
+
assert_valid_timeout(timeout)
|
37
|
+
|
38
|
+
if llen(source) > 0
|
39
|
+
rpoplpush(source, destination)
|
40
|
+
elsif timeout > 0
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
raise MockRedis::WouldBlock, "Can't block forever"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def lindex(key, index)
|
48
|
+
with_list_at(key) {|l| l[index]}
|
49
|
+
end
|
50
|
+
|
51
|
+
def linsert(key, position, pivot, value)
|
52
|
+
unless %w[before after].include?(position.to_s)
|
53
|
+
raise RuntimeError, "ERR syntax error"
|
54
|
+
end
|
55
|
+
|
56
|
+
assert_listy(key)
|
57
|
+
return 0 unless data[key]
|
58
|
+
|
59
|
+
pivot_position = (0..llen(key) - 1).find do |i|
|
60
|
+
data[key][i] == pivot.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
return -1 unless pivot_position
|
64
|
+
|
65
|
+
insertion_index = if position.to_s == 'before'
|
66
|
+
pivot_position
|
67
|
+
else
|
68
|
+
pivot_position + 1
|
69
|
+
end
|
70
|
+
|
71
|
+
data[key].insert(insertion_index, value.to_s)
|
72
|
+
llen(key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def llen(key)
|
76
|
+
with_list_at(key, &:length)
|
77
|
+
end
|
78
|
+
|
79
|
+
def lpop(key)
|
80
|
+
with_list_at(key, &:shift)
|
81
|
+
end
|
82
|
+
|
83
|
+
def lpush(key, value)
|
84
|
+
with_list_at(key) {|l| l.unshift(value.to_s)}
|
85
|
+
llen(key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def lpushx(key, value)
|
89
|
+
assert_listy(key)
|
90
|
+
return 0 unless list_at?(key)
|
91
|
+
lpush(key, value)
|
92
|
+
end
|
93
|
+
|
94
|
+
def lrange(key, start, stop)
|
95
|
+
with_list_at(key) {|l| l[start..stop]}
|
96
|
+
end
|
97
|
+
|
98
|
+
def lrem(key, count, value)
|
99
|
+
count = count.to_i
|
100
|
+
value = value.to_s
|
101
|
+
|
102
|
+
with_list_at(key) do |list|
|
103
|
+
indices_with_value = (0..(llen(key) - 1)).find_all do |i|
|
104
|
+
list[i] == value
|
105
|
+
end
|
106
|
+
|
107
|
+
indices_to_delete = if count == 0
|
108
|
+
indices_with_value.reverse
|
109
|
+
elsif count > 0
|
110
|
+
indices_with_value.take(count).reverse
|
111
|
+
else
|
112
|
+
indices_with_value.reverse.take(-count)
|
113
|
+
end
|
114
|
+
|
115
|
+
indices_to_delete.each {|i| list.delete_at(i)}.length
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def lset(key, index, value)
|
120
|
+
assert_listy(key)
|
121
|
+
|
122
|
+
unless list_at?(key)
|
123
|
+
raise RuntimeError, "ERR no such key"
|
124
|
+
end
|
125
|
+
|
126
|
+
unless (0...llen(key)).include?(index)
|
127
|
+
raise RuntimeError, "ERR index out of range"
|
128
|
+
end
|
129
|
+
|
130
|
+
data[key][index] = value.to_s
|
131
|
+
'OK'
|
132
|
+
end
|
133
|
+
|
134
|
+
def ltrim(key, start, stop)
|
135
|
+
with_list_at(key) do |list|
|
136
|
+
list.replace(list[start..stop] || []) if list
|
137
|
+
'OK'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def rpop(key)
|
142
|
+
with_list_at(key) {|list| list.pop if list}
|
143
|
+
end
|
144
|
+
|
145
|
+
def rpoplpush(source, destination)
|
146
|
+
value = rpop(source)
|
147
|
+
lpush(destination, value)
|
148
|
+
value
|
149
|
+
end
|
150
|
+
|
151
|
+
def rpush(key, value)
|
152
|
+
with_list_at(key) {|l| l.push(value.to_s)}
|
153
|
+
llen(key)
|
154
|
+
end
|
155
|
+
|
156
|
+
def rpushx(key, value)
|
157
|
+
assert_listy(key)
|
158
|
+
return 0 unless list_at?(key)
|
159
|
+
rpush(key, value)
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def list_at?(key)
|
164
|
+
data[key] && listy?(key)
|
165
|
+
end
|
166
|
+
|
167
|
+
def with_list_at(key, &blk)
|
168
|
+
with_thing_at(key, :assert_listy, proc {[]}, &blk)
|
169
|
+
end
|
170
|
+
|
171
|
+
def listy?(key)
|
172
|
+
data[key].nil? || data[key].kind_of?(Array)
|
173
|
+
end
|
174
|
+
|
175
|
+
def assert_listy(key)
|
176
|
+
unless listy?(key)
|
177
|
+
# Not the most helpful error, but it's what redis-rb barfs up
|
178
|
+
raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def first_nonempty_list(keys)
|
183
|
+
keys.find{|k| llen(k) > 0}
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'mock_redis/undef_redis_methods'
|
2
|
+
|
3
|
+
class MockRedis
|
4
|
+
class MultiDbWrapper
|
5
|
+
include UndefRedisMethods
|
6
|
+
|
7
|
+
def initialize(db)
|
8
|
+
@db_index = 0
|
9
|
+
|
10
|
+
@prototype_db = db.clone
|
11
|
+
|
12
|
+
@databases = Hash.new {|h,k| h[k] = @prototype_db.clone}
|
13
|
+
@databases[@db_index] = db
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to?(method, include_private=false)
|
17
|
+
super || current_db.respond_to?(method, include_private)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args)
|
21
|
+
current_db.send(method, *args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize_copy(source)
|
25
|
+
super
|
26
|
+
@databases = @databases.clone
|
27
|
+
@databases.keys.each do |k|
|
28
|
+
@databases[k] = @databases[k].clone
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Redis commands
|
33
|
+
def flushall
|
34
|
+
@databases.values.each(&:flushdb)
|
35
|
+
'OK'
|
36
|
+
end
|
37
|
+
|
38
|
+
def select(db_index)
|
39
|
+
@db_index = db_index.to_i
|
40
|
+
'OK'
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def current_db
|
45
|
+
@databases[@db_index]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'mock_redis/assertions'
|
2
|
+
require 'mock_redis/utility_methods'
|
3
|
+
|
4
|
+
class MockRedis
|
5
|
+
module SetMethods
|
6
|
+
include Assertions
|
7
|
+
include UtilityMethods
|
8
|
+
|
9
|
+
def sadd(key, member)
|
10
|
+
with_set_at(key) {|s| !!s.add?(member.to_s)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def scard(key)
|
14
|
+
with_set_at(key) {|s| s.length}
|
15
|
+
end
|
16
|
+
|
17
|
+
def sdiff(*keys)
|
18
|
+
assert_has_args(keys, 'sdiff')
|
19
|
+
with_sets_at(*keys) {|*sets| sets.reduce(&:-)}.to_a
|
20
|
+
end
|
21
|
+
|
22
|
+
def sdiffstore(destination, *keys)
|
23
|
+
assert_has_args(keys, 'sdiffstore')
|
24
|
+
with_set_at(destination) do |set|
|
25
|
+
set.merge(sdiff(*keys))
|
26
|
+
end
|
27
|
+
scard(destination)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sinter(*keys)
|
31
|
+
assert_has_args(keys, 'sinter')
|
32
|
+
|
33
|
+
with_sets_at(*keys) do |*sets|
|
34
|
+
sets.reduce(&:&).to_a
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def sinterstore(destination, *keys)
|
39
|
+
assert_has_args(keys, 'sinterstore')
|
40
|
+
with_set_at(destination) do |set|
|
41
|
+
set.merge(sinter(*keys))
|
42
|
+
end
|
43
|
+
scard(destination)
|
44
|
+
end
|
45
|
+
|
46
|
+
def sismember(key, member)
|
47
|
+
with_set_at(key) {|s| s.include?(member.to_s)}
|
48
|
+
end
|
49
|
+
|
50
|
+
def smembers(key)
|
51
|
+
with_set_at(key, &:to_a)
|
52
|
+
end
|
53
|
+
|
54
|
+
def smove(src, dest, member)
|
55
|
+
member = member.to_s
|
56
|
+
|
57
|
+
with_sets_at(src, dest) do |src_set, dest_set|
|
58
|
+
if src_set.delete?(member)
|
59
|
+
dest_set.add(member)
|
60
|
+
true
|
61
|
+
else
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def spop(key)
|
68
|
+
with_set_at(key) do |set|
|
69
|
+
member = set.first
|
70
|
+
set.delete(member)
|
71
|
+
member
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def srandmember(key)
|
76
|
+
with_set_at(key, &:first)
|
77
|
+
end
|
78
|
+
|
79
|
+
def srem(key, member)
|
80
|
+
with_set_at(key) {|s| !!s.delete?(member.to_s)}
|
81
|
+
end
|
82
|
+
|
83
|
+
def sunion(*keys)
|
84
|
+
assert_has_args(keys, 'sunion')
|
85
|
+
with_sets_at(*keys) {|*sets| sets.reduce(&:+).to_a}
|
86
|
+
end
|
87
|
+
|
88
|
+
def sunionstore(destination, *keys)
|
89
|
+
assert_has_args(keys, 'sunionstore')
|
90
|
+
with_set_at(destination) do |dest_set|
|
91
|
+
dest_set.merge(sunion(*keys))
|
92
|
+
end
|
93
|
+
scard(destination)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def with_set_at(key, &blk)
|
98
|
+
with_thing_at(key, :assert_sety, proc {Set.new}, &blk)
|
99
|
+
end
|
100
|
+
|
101
|
+
def with_sets_at(*keys, &blk)
|
102
|
+
if keys.length == 1
|
103
|
+
with_set_at(keys.first, &blk)
|
104
|
+
else
|
105
|
+
with_set_at(keys.first) do |set|
|
106
|
+
with_sets_at(*(keys[1..-1])) do |*sets|
|
107
|
+
blk.call(*([set] + sets))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def sety?(key)
|
114
|
+
data[key].nil? || data[key].kind_of?(Set)
|
115
|
+
end
|
116
|
+
|
117
|
+
def assert_sety(key)
|
118
|
+
unless sety?(key)
|
119
|
+
# Not the most helpful error, but it's what redis-rb barfs up
|
120
|
+
raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'mock_redis/assertions'
|
2
|
+
|
3
|
+
class MockRedis
|
4
|
+
module StringMethods
|
5
|
+
include Assertions
|
6
|
+
|
7
|
+
def append(key, value)
|
8
|
+
assert_stringy(key)
|
9
|
+
data[key] ||= ""
|
10
|
+
data[key] << value
|
11
|
+
data[key].length
|
12
|
+
end
|
13
|
+
|
14
|
+
def decr(key)
|
15
|
+
decrby(key, 1)
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrby(key, n)
|
19
|
+
incrby(key, -n)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(key)
|
23
|
+
assert_stringy(key)
|
24
|
+
data[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def getbit(key, offset)
|
28
|
+
assert_stringy(key)
|
29
|
+
|
30
|
+
offset_of_byte = offset / 8
|
31
|
+
offset_within_byte = offset % 8
|
32
|
+
|
33
|
+
# String#getbyte would be lovely, but it's not in 1.8.7.
|
34
|
+
byte = (data[key] || "").each_byte.drop(offset_of_byte).first
|
35
|
+
|
36
|
+
if byte
|
37
|
+
(byte & (2**7 >> offset_within_byte)) > 0 ? 1 : 0
|
38
|
+
else
|
39
|
+
0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def getrange(key, start, stop)
|
44
|
+
assert_stringy(key)
|
45
|
+
(data[key] || "")[start..stop]
|
46
|
+
end
|
47
|
+
|
48
|
+
def getset(key, value)
|
49
|
+
retval = get(key)
|
50
|
+
set(key, value)
|
51
|
+
retval
|
52
|
+
end
|
53
|
+
|
54
|
+
def incr(key)
|
55
|
+
incrby(key, 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
def incrby(key, n)
|
59
|
+
assert_stringy(key)
|
60
|
+
unless can_incr?(data[key])
|
61
|
+
raise RuntimeError, "ERR value is not an integer or out of range"
|
62
|
+
end
|
63
|
+
|
64
|
+
unless looks_like_integer?(n.to_s)
|
65
|
+
raise RuntimeError, "ERR value is not an integer or out of range"
|
66
|
+
end
|
67
|
+
|
68
|
+
new_value = data[key].to_i + n.to_i
|
69
|
+
data[key] = new_value.to_s
|
70
|
+
# for some reason, redis-rb doesn't return this as a string.
|
71
|
+
new_value
|
72
|
+
end
|
73
|
+
|
74
|
+
def mget(*keys)
|
75
|
+
assert_has_args(keys, 'mget')
|
76
|
+
|
77
|
+
keys.map do |key|
|
78
|
+
get(key) if stringy?(key)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def mset(*kvpairs)
|
83
|
+
assert_has_args(kvpairs, 'mset')
|
84
|
+
if kvpairs.length.odd?
|
85
|
+
raise RuntimeError, "ERR wrong number of arguments for MSET"
|
86
|
+
end
|
87
|
+
|
88
|
+
kvpairs.each_slice(2) do |(k,v)|
|
89
|
+
set(k,v)
|
90
|
+
end
|
91
|
+
|
92
|
+
"OK"
|
93
|
+
end
|
94
|
+
|
95
|
+
def msetnx(*kvpairs)
|
96
|
+
assert_has_args(kvpairs, 'msetnx')
|
97
|
+
|
98
|
+
if kvpairs.each_slice(2).any? {|(k,v)| exists(k)}
|
99
|
+
0
|
100
|
+
else
|
101
|
+
mset(*kvpairs)
|
102
|
+
1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def set(key, value)
|
107
|
+
data[key] = value.to_s
|
108
|
+
'OK'
|
109
|
+
end
|
110
|
+
|
111
|
+
def setbit(key, offset, value)
|
112
|
+
assert_stringy(key, "ERR bit is not an integer or out of range")
|
113
|
+
retval = getbit(key, offset)
|
114
|
+
|
115
|
+
str = data[key] || ""
|
116
|
+
|
117
|
+
offset_of_byte = offset / 8
|
118
|
+
offset_within_byte = offset % 8
|
119
|
+
|
120
|
+
if offset_of_byte >= str.bytesize
|
121
|
+
str = zero_pad(str, offset_of_byte+1)
|
122
|
+
end
|
123
|
+
|
124
|
+
char_index = byte_index = offset_within_char = 0
|
125
|
+
str.each_char do |c|
|
126
|
+
if byte_index < offset_of_byte
|
127
|
+
char_index += 1
|
128
|
+
byte_index += c.bytesize
|
129
|
+
else
|
130
|
+
offset_within_char = byte_index - offset_of_byte
|
131
|
+
break
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
char = str[char_index]
|
136
|
+
char = char.chr if char.respond_to?(:chr) # ruby 1.8 vs 1.9
|
137
|
+
char_as_number = char.each_byte.reduce(0) do |a, byte|
|
138
|
+
(a << 8) + byte
|
139
|
+
end
|
140
|
+
char_as_number |=
|
141
|
+
(2**((char.bytesize * 8)-1) >>
|
142
|
+
(offset_within_char * 8 + offset_within_byte))
|
143
|
+
str[char_index] = char_as_number.chr
|
144
|
+
|
145
|
+
data[key] = str
|
146
|
+
retval
|
147
|
+
end
|
148
|
+
|
149
|
+
def setex(key, seconds, value)
|
150
|
+
set(key, value)
|
151
|
+
expire(key, seconds)
|
152
|
+
'OK'
|
153
|
+
end
|
154
|
+
|
155
|
+
def setnx(key, value)
|
156
|
+
if exists(key)
|
157
|
+
false
|
158
|
+
else
|
159
|
+
set(key, value)
|
160
|
+
true
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def setrange(key, offset, value)
|
165
|
+
assert_stringy(key)
|
166
|
+
value = value.to_s
|
167
|
+
old_value = (data[key] || "")
|
168
|
+
|
169
|
+
prefix = zero_pad(old_value[0...offset], offset)
|
170
|
+
data[key] = prefix + value + (old_value[(offset + value.length)..-1] || "")
|
171
|
+
data[key].length
|
172
|
+
end
|
173
|
+
|
174
|
+
def strlen(key)
|
175
|
+
assert_stringy(key)
|
176
|
+
(data[key] || "").bytesize
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
private
|
183
|
+
def stringy?(key)
|
184
|
+
data[key].nil? || data[key].kind_of?(String)
|
185
|
+
end
|
186
|
+
|
187
|
+
def assert_stringy(key,
|
188
|
+
message="ERR Operation against a key holding the wrong kind of value")
|
189
|
+
unless stringy?(key)
|
190
|
+
raise RuntimeError, message
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|