mock_redis 0.0.1
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.
- 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,75 @@
|
|
|
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
|
+
'OK'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def unwatch
|
|
67
|
+
'OK'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def watch(_)
|
|
71
|
+
'OK'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
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,22 @@
|
|
|
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
|
+
yield data[key]
|
|
10
|
+
ensure
|
|
11
|
+
clean_up_empties_at(key)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def clean_up_empties_at(key)
|
|
16
|
+
if data[key] && data[key].empty?
|
|
17
|
+
del(key)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
class MockRedis
|
|
5
|
+
class Zset
|
|
6
|
+
include Enumerable
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
attr_reader :members, :scores
|
|
10
|
+
|
|
11
|
+
def_delegators :members, :empty?, :include?, :size
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@members = Set.new
|
|
15
|
+
@scores = Hash.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize_copy(source)
|
|
19
|
+
super
|
|
20
|
+
@members = @members.clone
|
|
21
|
+
@scores = @scores.clone
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add(score, member)
|
|
25
|
+
members.add(member)
|
|
26
|
+
if score.to_f.to_i == score.to_f
|
|
27
|
+
scores[member] = score.to_f.to_i
|
|
28
|
+
else
|
|
29
|
+
scores[member] = score.to_f
|
|
30
|
+
end
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete?(member)
|
|
35
|
+
scores.delete(member)
|
|
36
|
+
members.delete?(member) and self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def each
|
|
40
|
+
members.each {|m| yield score(m), m}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def intersection(other)
|
|
44
|
+
if !block_given?
|
|
45
|
+
intersection(other, &:+)
|
|
46
|
+
else
|
|
47
|
+
self.members.intersection(other.members).reduce(self.class.new) do |acc, m|
|
|
48
|
+
new_score = yield(self.score(m), other.score(m))
|
|
49
|
+
acc.add(new_score, m)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def score(member)
|
|
55
|
+
scores[member]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def sorted
|
|
59
|
+
members.map do |m|
|
|
60
|
+
[score(m), m]
|
|
61
|
+
end.sort_by(&:first)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def sorted_members
|
|
65
|
+
sorted.map(&:last)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def union(other)
|
|
69
|
+
if !block_given?
|
|
70
|
+
union(other, &:+)
|
|
71
|
+
else
|
|
72
|
+
self.members.union(other.members).reduce(self.class.new) do |acc, m|
|
|
73
|
+
new_score = yield(self.score(m), other.score(m))
|
|
74
|
+
acc.add(new_score, m)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
require 'mock_redis/assertions'
|
|
2
|
+
require 'mock_redis/utility_methods'
|
|
3
|
+
require 'mock_redis/zset'
|
|
4
|
+
|
|
5
|
+
class MockRedis
|
|
6
|
+
module ZsetMethods
|
|
7
|
+
include Assertions
|
|
8
|
+
include UtilityMethods
|
|
9
|
+
|
|
10
|
+
def zadd(key, score, member)
|
|
11
|
+
assert_scorey(score)
|
|
12
|
+
|
|
13
|
+
retval = !zscore(key, member)
|
|
14
|
+
with_zset_at(key) {|z| z.add(score, member)}
|
|
15
|
+
retval
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def zcard(key)
|
|
19
|
+
with_zset_at(key, &:size)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def zcount(key, min, max)
|
|
23
|
+
assert_scorey(min, 'min or max')
|
|
24
|
+
assert_scorey(max, 'min or max')
|
|
25
|
+
|
|
26
|
+
with_zset_at(key) do |z|
|
|
27
|
+
z.count do |score, _|
|
|
28
|
+
score >= min && score <= max
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def zincrby(key, increment, member)
|
|
34
|
+
assert_scorey(increment)
|
|
35
|
+
with_zset_at(key) do |z|
|
|
36
|
+
old_score = z.include?(member) ? z.score(member) : 0
|
|
37
|
+
new_score = old_score + increment
|
|
38
|
+
z.add(new_score, member)
|
|
39
|
+
new_score.to_s
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def zinterstore(destination, keys, options={})
|
|
44
|
+
assert_has_args(keys, 'zinterstore')
|
|
45
|
+
|
|
46
|
+
data[destination] = combine_weighted_zsets(keys, options, :intersection)
|
|
47
|
+
zcard(destination)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def zrange(key, start, stop, options={})
|
|
51
|
+
with_zset_at(key) do |z|
|
|
52
|
+
to_response(z.sorted[start..stop], options)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def zrangebyscore(key, min, max, options={})
|
|
57
|
+
with_zset_at(key) do |zset|
|
|
58
|
+
in_range = zset.sorted.find_all do |(score, member)|
|
|
59
|
+
min <= score && score <= max
|
|
60
|
+
end
|
|
61
|
+
to_response(apply_limit(in_range, options[:limit]), options)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def zrank(key, member)
|
|
66
|
+
with_zset_at(key) {|z| z.sorted_members.index(member) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def zrem(key, member)
|
|
70
|
+
with_zset_at(key) {|z| !!z.delete?(member)}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def zrevrange(key, start, stop, options={})
|
|
74
|
+
with_zset_at(key) do |z|
|
|
75
|
+
to_response(z.sorted.reverse[start..stop], options)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def zremrangebyrank(key, start, stop)
|
|
80
|
+
zrange(key, start, stop).
|
|
81
|
+
each {|member| zrem(key, member)}.
|
|
82
|
+
size
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def zremrangebyscore(key, min, max)
|
|
86
|
+
zrangebyscore(key, min, max).
|
|
87
|
+
each {|member| zrem(key, member)}.
|
|
88
|
+
size
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def zrevrangebyscore(key, max, min, options={})
|
|
92
|
+
with_zset_at(key) do |zset|
|
|
93
|
+
in_range = zset.sorted.reverse.find_all do |(score, member)|
|
|
94
|
+
min <= score && score <= max
|
|
95
|
+
end
|
|
96
|
+
to_response(apply_limit(in_range, options[:limit]), options)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def zrevrank(key, member)
|
|
101
|
+
with_zset_at(key) {|z| z.sorted_members.reverse.index(member) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def zscore(key, member)
|
|
105
|
+
with_zset_at(key) do |z|
|
|
106
|
+
score = z.score(member)
|
|
107
|
+
score.to_s if score
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def zunionstore(destination, keys, options={})
|
|
112
|
+
assert_has_args(keys, 'zunionstore')
|
|
113
|
+
|
|
114
|
+
data[destination] = combine_weighted_zsets(keys, options, :union)
|
|
115
|
+
zcard(destination)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
def apply_limit(collection, limit)
|
|
120
|
+
if limit
|
|
121
|
+
if limit.is_a?(Array) && limit.length == 2
|
|
122
|
+
offset, count = limit
|
|
123
|
+
collection.drop(offset).take(count)
|
|
124
|
+
else
|
|
125
|
+
raise RuntimeError, "ERR syntax error"
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
collection
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def to_response(score_member_pairs, options)
|
|
133
|
+
score_member_pairs.map do |(score,member)|
|
|
134
|
+
if options[:with_scores] || options[:withscores]
|
|
135
|
+
[member, score.to_s]
|
|
136
|
+
else
|
|
137
|
+
member
|
|
138
|
+
end
|
|
139
|
+
end.flatten
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def combine_weighted_zsets(keys, options, how)
|
|
143
|
+
weights = options.fetch(:weights, keys.map { 1 })
|
|
144
|
+
if weights.length != keys.length
|
|
145
|
+
raise RuntimeError, "ERR syntax error"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
aggregator = case options.fetch(:aggregate, :sum).to_s.downcase.to_sym
|
|
149
|
+
when :sum
|
|
150
|
+
proc {|a,b| [a,b].compact.reduce(&:+)}
|
|
151
|
+
when :min
|
|
152
|
+
proc {|a,b| [a,b].compact.min}
|
|
153
|
+
when :max
|
|
154
|
+
proc {|a,b| [a,b].compact.max}
|
|
155
|
+
else
|
|
156
|
+
raise RuntimeError, "ERR syntax error"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
with_zsets_at(*keys) do |*zsets|
|
|
160
|
+
zsets.zip(weights).map do |(zset, weight)|
|
|
161
|
+
zset.reduce(Zset.new) do |acc, (score, member)|
|
|
162
|
+
acc.add(score * weight, member)
|
|
163
|
+
end
|
|
164
|
+
end.reduce do |za, zb|
|
|
165
|
+
za.send(how, zb, &aggregator)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def with_zset_at(key, &blk)
|
|
172
|
+
with_thing_at(key, :assert_zsety, proc {Zset.new}, &blk)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def with_zsets_at(*keys, &blk)
|
|
176
|
+
if keys.length == 1
|
|
177
|
+
with_zset_at(keys.first, &blk)
|
|
178
|
+
else
|
|
179
|
+
with_zset_at(keys.first) do |set|
|
|
180
|
+
with_zsets_at(*(keys[1..-1])) do |*sets|
|
|
181
|
+
blk.call(*([set] + sets))
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def zsety?(key)
|
|
188
|
+
data[key].nil? || data[key].kind_of?(Zset)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def assert_zsety(key)
|
|
192
|
+
unless zsety?(key)
|
|
193
|
+
raise RuntimeError,
|
|
194
|
+
"ERR Operation against a key holding the wrong kind of value"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def looks_like_float?(x)
|
|
199
|
+
# ugh, exceptions for flow control.
|
|
200
|
+
!!Float(x) rescue false
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def assert_scorey(value, what='value')
|
|
204
|
+
unless looks_like_float?(value)
|
|
205
|
+
raise RuntimeError, "ERR #{what} is not a double"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
end
|
data/lib/mock_redis.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
require 'mock_redis/assertions'
|
|
4
|
+
require 'mock_redis/database'
|
|
5
|
+
require 'mock_redis/expire_wrapper'
|
|
6
|
+
require 'mock_redis/multi_db_wrapper'
|
|
7
|
+
require 'mock_redis/transaction_wrapper'
|
|
8
|
+
require 'mock_redis/undef_redis_methods'
|
|
9
|
+
|
|
10
|
+
class MockRedis
|
|
11
|
+
include UndefRedisMethods
|
|
12
|
+
|
|
13
|
+
def initialize(*args)
|
|
14
|
+
@db = TransactionWrapper.new(
|
|
15
|
+
ExpireWrapper.new(
|
|
16
|
+
MultiDbWrapper.new(
|
|
17
|
+
Database.new(*args))))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def respond_to?(method, include_private=false)
|
|
21
|
+
super || @db.respond_to?(method, include_private)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def method_missing(method, *args)
|
|
25
|
+
@db.send(method, *args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize_copy(source)
|
|
29
|
+
super
|
|
30
|
+
@db = @db.clone
|
|
31
|
+
end
|
|
32
|
+
end
|
data/mock_redis.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "mock_redis/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "mock_redis"
|
|
7
|
+
s.version = MockRedis::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ["Samuel Merritt"]
|
|
10
|
+
s.email = ["spam@andcheese.org"]
|
|
11
|
+
s.homepage = "https://github.com/smerritt/mock_redis"
|
|
12
|
+
s.summary = %q{Redis mock that just lives in memory; useful for testing.}
|
|
13
|
+
|
|
14
|
+
s.description = %q{Instantiate one with `redis = MockRedis.new` and treat it like you would a normal Redis object. It supports all the usual Redis operations.}
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
s.add_development_dependency "redis", "~> 2.2.1"
|
|
22
|
+
s.add_development_dependency "rspec", "~> 2.6.0"
|
|
23
|
+
s.add_development_dependency "ZenTest"
|
|
24
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "MockRedis#clone" do
|
|
4
|
+
before do
|
|
5
|
+
@mock = MockRedis.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
context "the stored data" do
|
|
9
|
+
before do
|
|
10
|
+
@mock.set('foo', 'bar')
|
|
11
|
+
@mock.hset('foohash', 'bar', 'baz')
|
|
12
|
+
@mock.lpush('foolist', 'bar')
|
|
13
|
+
@mock.sadd('fooset', 'bar')
|
|
14
|
+
@mock.zadd('foozset', 1, 'bar')
|
|
15
|
+
|
|
16
|
+
@clone = @mock.clone
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "copies the stored data to the clone" do
|
|
20
|
+
@clone.get('foo').should == 'bar'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "performs a deep copy (string values)" do
|
|
24
|
+
@mock.del('foo')
|
|
25
|
+
@clone.get('foo').should == 'bar'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "performs a deep copy (list values)" do
|
|
29
|
+
@mock.lpop('foolist')
|
|
30
|
+
@clone.lrange('foolist', 0, 1).should == ['bar']
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "performs a deep copy (hash values)" do
|
|
34
|
+
@mock.hset('foohash', 'bar', 'quux')
|
|
35
|
+
@clone.hgetall('foohash').should == {'bar' => 'baz'}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "performs a deep copy (set values)" do
|
|
39
|
+
@mock.srem('fooset', 'bar')
|
|
40
|
+
@clone.smembers('fooset').should == ['bar']
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "performs a deep copy (zset values)" do
|
|
44
|
+
@mock.zadd('foozset', 2, 'bar')
|
|
45
|
+
@clone.zscore('foozset', 'bar').should == "1"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "expiration times" do
|
|
50
|
+
before do
|
|
51
|
+
@mock.set('foo', 1)
|
|
52
|
+
@mock.expire('foo', 60026)
|
|
53
|
+
|
|
54
|
+
@clone = @mock.clone
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "copies the expiration times" do
|
|
58
|
+
@clone.ttl('foo').should > 0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "deep-copies the expiration times" do
|
|
62
|
+
@mock.persist('foo')
|
|
63
|
+
@clone.ttl('foo').should > 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "deep-copies the expiration times" do
|
|
67
|
+
@clone.persist('foo')
|
|
68
|
+
@mock.ttl('foo').should > 0
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "transactional info" do
|
|
73
|
+
before do
|
|
74
|
+
@mock.multi
|
|
75
|
+
@mock.incr('foo')
|
|
76
|
+
@mock.incrby('foo', 2)
|
|
77
|
+
@mock.incrby('foo', 4)
|
|
78
|
+
|
|
79
|
+
@clone = @mock.clone
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "makes sure the clone is in a transaction" do
|
|
83
|
+
lambda do
|
|
84
|
+
@clone.exec
|
|
85
|
+
end.should_not raise_error
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "deep-copies the queued commands" do
|
|
89
|
+
@clone.incrby('foo', 8)
|
|
90
|
+
@clone.exec.should == [1, 3, 7, 15]
|
|
91
|
+
|
|
92
|
+
@mock.exec.should == [1, 3, 7]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe '#append(key, value)' do
|
|
4
|
+
before { @key = 'mock-redis-test:append' }
|
|
5
|
+
|
|
6
|
+
it "returns the new length of the string" do
|
|
7
|
+
@redises.set(@key, 'porkchop')
|
|
8
|
+
@redises.append(@key, 'sandwiches').should == 18
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "appends value to the previously-stored value" do
|
|
12
|
+
@redises.set(@key, 'porkchop')
|
|
13
|
+
@redises.append(@key, 'sandwiches')
|
|
14
|
+
|
|
15
|
+
@redises.get(@key).should == 'porkchopsandwiches'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "treats a missing key as an empty string" do
|
|
19
|
+
@redises.append(@key, 'foo')
|
|
20
|
+
@redises.get(@key).should == 'foo'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it_should_behave_like "a string-only command"
|
|
24
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe '#blpop(key [, key, ...,], timeout)' do
|
|
4
|
+
before do
|
|
5
|
+
@list1 = 'mock-redis-test:blpop1'
|
|
6
|
+
@list2 = 'mock-redis-test:blpop2'
|
|
7
|
+
|
|
8
|
+
@redises.rpush(@list1, 'one')
|
|
9
|
+
@redises.rpush(@list1, 'two')
|
|
10
|
+
@redises.rpush(@list2, 'ten')
|
|
11
|
+
@redises.rpush(@list2, 'eleven')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "returns [first-nonempty-list, popped-value]" do
|
|
15
|
+
@redises.blpop(@list1, @list2, 1).should == [@list1, 'one']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "pops that value off the list" do
|
|
19
|
+
@redises.blpop(@list1, @list2, 1)
|
|
20
|
+
@redises.blpop(@list1, @list2, 1)
|
|
21
|
+
|
|
22
|
+
@redises.blpop(@list1, @list2, 1).should == [@list2, 'ten']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "ignores empty keys" do
|
|
26
|
+
@redises.blpop('mock-redis-test:not-here', @list1, 1).should ==
|
|
27
|
+
[@list1, 'one']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "raises an error on non-integer timeout" do
|
|
31
|
+
lambda do
|
|
32
|
+
@redises.blpop(@list1, @list2, 0.5)
|
|
33
|
+
end.should raise_error(RuntimeError)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "raises an error on negative timeout" do
|
|
37
|
+
lambda do
|
|
38
|
+
@redises.blpop(@list1, @list2, -1)
|
|
39
|
+
end.should raise_error(RuntimeError)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it_should_behave_like "a list-only command"
|
|
43
|
+
|
|
44
|
+
context "[mock only]" do
|
|
45
|
+
it "ignores positive timeouts and returns nil" do
|
|
46
|
+
@redises.mock.blpop('mock-redis-test:not-here', 1).should be_nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "raises WouldBlock on zero timeout (no blocking in the mock)" do
|
|
50
|
+
lambda do
|
|
51
|
+
@redises.mock.blpop('mock-redis-test:not-here', 0)
|
|
52
|
+
end.should raise_error(MockRedis::WouldBlock)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|