redis_migrator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/README.md +35 -0
- data/lib/redis_migrator/redis_helper.rb +52 -0
- data/lib/redis_migrator/redis_populator.rb +63 -0
- data/lib/redis_migrator.rb +96 -0
- data/migrator_benchmark.rb +22 -0
- data/redis_migrator.gemspec +20 -0
- data/spec/migrator_spec.rb +63 -0
- data/spec/mock_redis/lib/mock_redis/assertions.rb +13 -0
- data/spec/mock_redis/lib/mock_redis/database.rb +432 -0
- data/spec/mock_redis/lib/mock_redis/distributed.rb +6 -0
- data/spec/mock_redis/lib/mock_redis/exceptions.rb +3 -0
- data/spec/mock_redis/lib/mock_redis/expire_wrapper.rb +25 -0
- data/spec/mock_redis/lib/mock_redis/hash_methods.rb +118 -0
- data/spec/mock_redis/lib/mock_redis/list_methods.rb +187 -0
- data/spec/mock_redis/lib/mock_redis/multi_db_wrapper.rb +86 -0
- data/spec/mock_redis/lib/mock_redis/set_methods.rb +126 -0
- data/spec/mock_redis/lib/mock_redis/string_methods.rb +203 -0
- data/spec/mock_redis/lib/mock_redis/transaction_wrapper.rb +80 -0
- data/spec/mock_redis/lib/mock_redis/undef_redis_methods.rb +11 -0
- data/spec/mock_redis/lib/mock_redis/utility_methods.rb +25 -0
- data/spec/mock_redis/lib/mock_redis/version.rb +3 -0
- data/spec/mock_redis/lib/mock_redis/zset.rb +110 -0
- data/spec/mock_redis/lib/mock_redis/zset_methods.rb +210 -0
- data/spec/mock_redis/lib/mock_redis.rb +119 -0
- data/spec/redis_helper_spec.rb +58 -0
- data/spec/spec_helper.rb +29 -0
- metadata +107 -0
@@ -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,86 @@
|
|
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 move(key, db_index)
|
39
|
+
src = current_db
|
40
|
+
dest = db(db_index)
|
41
|
+
|
42
|
+
if !src.exists(key) || dest.exists(key)
|
43
|
+
false
|
44
|
+
else
|
45
|
+
case current_db.type(key)
|
46
|
+
when 'hash'
|
47
|
+
dest.hmset(key, *(src.hgetall(key).map{|k,v| [k,v]}.flatten))
|
48
|
+
when 'list'
|
49
|
+
while value = src.rpop(key)
|
50
|
+
dest.lpush(key, value)
|
51
|
+
end
|
52
|
+
when 'set'
|
53
|
+
while value = src.spop(key)
|
54
|
+
dest.sadd(key, value)
|
55
|
+
end
|
56
|
+
when 'string'
|
57
|
+
dest.set(key, src.get(key))
|
58
|
+
when 'zset'
|
59
|
+
src.zrange(key, 0, -1, :with_scores => true).each_slice(2) do |(m,s)|
|
60
|
+
dest.zadd(key, s, m)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise ArgumentError,
|
64
|
+
"Can't move a key of type #{current_db.type(key).inspect}"
|
65
|
+
end
|
66
|
+
|
67
|
+
src.del(key)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def select(db_index)
|
73
|
+
@db_index = db_index.to_i
|
74
|
+
'OK'
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def current_db
|
79
|
+
@databases[@db_index]
|
80
|
+
end
|
81
|
+
|
82
|
+
def db(index)
|
83
|
+
@databases[index]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,126 @@
|
|
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.replace(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.replace(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
|
+
members = with_set_at(key, &:to_a)
|
77
|
+
members[rand(members.length)]
|
78
|
+
end
|
79
|
+
|
80
|
+
def srem(key, member)
|
81
|
+
with_set_at(key) {|s| !!s.delete?(member.to_s)}
|
82
|
+
end
|
83
|
+
|
84
|
+
def sunion(*keys)
|
85
|
+
assert_has_args(keys, 'sunion')
|
86
|
+
with_sets_at(*keys) {|*sets| sets.reduce(&:+).to_a}
|
87
|
+
end
|
88
|
+
|
89
|
+
def sunionstore(destination, *keys)
|
90
|
+
assert_has_args(keys, 'sunionstore')
|
91
|
+
with_set_at(destination) do |dest_set|
|
92
|
+
dest_set.replace(sunion(*keys))
|
93
|
+
end
|
94
|
+
scard(destination)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def with_set_at(key, &blk)
|
99
|
+
with_thing_at(key, :assert_sety, proc {Set.new}, &blk)
|
100
|
+
end
|
101
|
+
|
102
|
+
def with_sets_at(*keys, &blk)
|
103
|
+
if keys.length == 1
|
104
|
+
with_set_at(keys.first, &blk)
|
105
|
+
else
|
106
|
+
with_set_at(keys.first) do |set|
|
107
|
+
with_sets_at(*(keys[1..-1])) do |*sets|
|
108
|
+
blk.call(*([set] + sets))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def sety?(key)
|
115
|
+
data[key].nil? || data[key].kind_of?(Set)
|
116
|
+
end
|
117
|
+
|
118
|
+
def assert_sety(key)
|
119
|
+
unless sety?(key)
|
120
|
+
# Not the most helpful error, but it's what redis-rb barfs up
|
121
|
+
raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,203 @@
|
|
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 [](key)
|
28
|
+
get(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def getbit(key, offset)
|
32
|
+
assert_stringy(key)
|
33
|
+
|
34
|
+
offset_of_byte = offset / 8
|
35
|
+
offset_within_byte = offset % 8
|
36
|
+
|
37
|
+
# String#getbyte would be lovely, but it's not in 1.8.7.
|
38
|
+
byte = (data[key] || "").each_byte.drop(offset_of_byte).first
|
39
|
+
|
40
|
+
if byte
|
41
|
+
(byte & (2**7 >> offset_within_byte)) > 0 ? 1 : 0
|
42
|
+
else
|
43
|
+
0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def getrange(key, start, stop)
|
48
|
+
assert_stringy(key)
|
49
|
+
(data[key] || "")[start..stop]
|
50
|
+
end
|
51
|
+
|
52
|
+
def getset(key, value)
|
53
|
+
retval = get(key)
|
54
|
+
set(key, value)
|
55
|
+
retval
|
56
|
+
end
|
57
|
+
|
58
|
+
def incr(key)
|
59
|
+
incrby(key, 1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def incrby(key, n)
|
63
|
+
assert_stringy(key)
|
64
|
+
unless can_incr?(data[key])
|
65
|
+
raise RuntimeError, "ERR value is not an integer or out of range"
|
66
|
+
end
|
67
|
+
|
68
|
+
unless looks_like_integer?(n.to_s)
|
69
|
+
raise RuntimeError, "ERR value is not an integer or out of range"
|
70
|
+
end
|
71
|
+
|
72
|
+
new_value = data[key].to_i + n.to_i
|
73
|
+
data[key] = new_value.to_s
|
74
|
+
# for some reason, redis-rb doesn't return this as a string.
|
75
|
+
new_value
|
76
|
+
end
|
77
|
+
|
78
|
+
def mget(*keys)
|
79
|
+
assert_has_args(keys, 'mget')
|
80
|
+
|
81
|
+
keys.map do |key|
|
82
|
+
get(key) if stringy?(key)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def mset(*kvpairs)
|
87
|
+
assert_has_args(kvpairs, 'mset')
|
88
|
+
if kvpairs.length.odd?
|
89
|
+
raise RuntimeError, "ERR wrong number of arguments for MSET"
|
90
|
+
end
|
91
|
+
|
92
|
+
kvpairs.each_slice(2) do |(k,v)|
|
93
|
+
set(k,v)
|
94
|
+
end
|
95
|
+
|
96
|
+
"OK"
|
97
|
+
end
|
98
|
+
|
99
|
+
def msetnx(*kvpairs)
|
100
|
+
assert_has_args(kvpairs, 'msetnx')
|
101
|
+
|
102
|
+
if kvpairs.each_slice(2).any? {|(k,v)| exists(k)}
|
103
|
+
0
|
104
|
+
else
|
105
|
+
mset(*kvpairs)
|
106
|
+
1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def set(key, value)
|
111
|
+
data[key] = value.to_s
|
112
|
+
'OK'
|
113
|
+
end
|
114
|
+
|
115
|
+
def []=(key, value)
|
116
|
+
set(key, value)
|
117
|
+
end
|
118
|
+
|
119
|
+
def setbit(key, offset, value)
|
120
|
+
assert_stringy(key, "ERR bit is not an integer or out of range")
|
121
|
+
retval = getbit(key, offset)
|
122
|
+
|
123
|
+
str = data[key] || ""
|
124
|
+
|
125
|
+
offset_of_byte = offset / 8
|
126
|
+
offset_within_byte = offset % 8
|
127
|
+
|
128
|
+
if offset_of_byte >= str.bytesize
|
129
|
+
str = zero_pad(str, offset_of_byte+1)
|
130
|
+
end
|
131
|
+
|
132
|
+
char_index = byte_index = offset_within_char = 0
|
133
|
+
str.each_char do |c|
|
134
|
+
if byte_index < offset_of_byte
|
135
|
+
char_index += 1
|
136
|
+
byte_index += c.bytesize
|
137
|
+
else
|
138
|
+
offset_within_char = byte_index - offset_of_byte
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
char = str[char_index]
|
144
|
+
char = char.chr if char.respond_to?(:chr) # ruby 1.8 vs 1.9
|
145
|
+
char_as_number = char.each_byte.reduce(0) do |a, byte|
|
146
|
+
(a << 8) + byte
|
147
|
+
end
|
148
|
+
char_as_number |=
|
149
|
+
(2**((char.bytesize * 8)-1) >>
|
150
|
+
(offset_within_char * 8 + offset_within_byte))
|
151
|
+
str[char_index] = char_as_number.chr
|
152
|
+
|
153
|
+
data[key] = str
|
154
|
+
retval
|
155
|
+
end
|
156
|
+
|
157
|
+
def setex(key, seconds, value)
|
158
|
+
set(key, value)
|
159
|
+
expire(key, seconds)
|
160
|
+
'OK'
|
161
|
+
end
|
162
|
+
|
163
|
+
def setnx(key, value)
|
164
|
+
if exists(key)
|
165
|
+
false
|
166
|
+
else
|
167
|
+
set(key, value)
|
168
|
+
true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def setrange(key, offset, value)
|
173
|
+
assert_stringy(key)
|
174
|
+
value = value.to_s
|
175
|
+
old_value = (data[key] || "")
|
176
|
+
|
177
|
+
prefix = zero_pad(old_value[0...offset], offset)
|
178
|
+
data[key] = prefix + value + (old_value[(offset + value.length)..-1] || "")
|
179
|
+
data[key].length
|
180
|
+
end
|
181
|
+
|
182
|
+
def strlen(key)
|
183
|
+
assert_stringy(key)
|
184
|
+
(data[key] || "").bytesize
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
|
190
|
+
private
|
191
|
+
def stringy?(key)
|
192
|
+
data[key].nil? || data[key].kind_of?(String)
|
193
|
+
end
|
194
|
+
|
195
|
+
def assert_stringy(key,
|
196
|
+
message="ERR Operation against a key holding the wrong kind of value")
|
197
|
+
unless stringy?(key)
|
198
|
+
raise RuntimeError, message
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'mock_redis/undef_redis_methods'
|
2
|
+
|
3
|
+
class MockRedis
|
4
|
+
class TransactionWrapper
|
5
|
+
include UndefRedisMethods
|
6
|
+
|
7
|
+
def respond_to?(method, include_private=false)
|
8
|
+
super || @db.respond_to?(method)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(db)
|
12
|
+
@db = db
|
13
|
+
@queued_commands = []
|
14
|
+
@in_multi = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
if @in_multi
|
19
|
+
@queued_commands << [method, *args]
|
20
|
+
'QUEUED'
|
21
|
+
else
|
22
|
+
@db.expire_keys
|
23
|
+
@db.send(method, *args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_copy(source)
|
28
|
+
super
|
29
|
+
@db = @db.clone
|
30
|
+
@queued_commands = @queued_commands.clone
|
31
|
+
end
|
32
|
+
|
33
|
+
def discard
|
34
|
+
unless @in_multi
|
35
|
+
raise RuntimeError, "ERR DISCARD without MULTI"
|
36
|
+
end
|
37
|
+
@in_multi = false
|
38
|
+
@queued_commands = []
|
39
|
+
'OK'
|
40
|
+
end
|
41
|
+
|
42
|
+
def exec
|
43
|
+
unless @in_multi
|
44
|
+
raise RuntimeError, "ERR EXEC without MULTI"
|
45
|
+
end
|
46
|
+
@in_multi = false
|
47
|
+
responses = @queued_commands.map do |cmd|
|
48
|
+
begin
|
49
|
+
send(*cmd)
|
50
|
+
rescue => e
|
51
|
+
e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@queued_commands = []
|
55
|
+
responses
|
56
|
+
end
|
57
|
+
|
58
|
+
def multi
|
59
|
+
if @in_multi
|
60
|
+
raise RuntimeError, "ERR MULTI calls can not be nested"
|
61
|
+
end
|
62
|
+
@in_multi = true
|
63
|
+
if block_given?
|
64
|
+
yield(self)
|
65
|
+
self.exec
|
66
|
+
else
|
67
|
+
'OK'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def unwatch
|
72
|
+
'OK'
|
73
|
+
end
|
74
|
+
|
75
|
+
def watch(_)
|
76
|
+
'OK'
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class MockRedis
|
2
|
+
module UndefRedisMethods
|
3
|
+
def self.included(klass)
|
4
|
+
if klass.instance_methods.map(&:to_s).include?('type')
|
5
|
+
klass.send(:undef_method, 'type')
|
6
|
+
end
|
7
|
+
klass.send(:undef_method, 'exec')
|
8
|
+
klass.send(:undef_method, 'select')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class MockRedis
|
2
|
+
module UtilityMethods
|
3
|
+
private
|
4
|
+
|
5
|
+
def with_thing_at(key, assertion, empty_thing_generator)
|
6
|
+
begin
|
7
|
+
send(assertion, key)
|
8
|
+
data[key] ||= empty_thing_generator.call
|
9
|
+
data_key_ref = data[key]
|
10
|
+
ret = yield data[key]
|
11
|
+
data[key] = data_key_ref if data[key].nil?
|
12
|
+
ret
|
13
|
+
ensure
|
14
|
+
clean_up_empties_at(key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def clean_up_empties_at(key)
|
19
|
+
if data[key] && data[key].empty?
|
20
|
+
del(key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|