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.
Files changed (136) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE +19 -0
  5. data/README.md +88 -0
  6. data/Rakefile +10 -0
  7. data/lib/mock_redis/assertions.rb +13 -0
  8. data/lib/mock_redis/database.rb +432 -0
  9. data/lib/mock_redis/exceptions.rb +3 -0
  10. data/lib/mock_redis/expire_wrapper.rb +25 -0
  11. data/lib/mock_redis/hash_methods.rb +102 -0
  12. data/lib/mock_redis/list_methods.rb +187 -0
  13. data/lib/mock_redis/multi_db_wrapper.rb +48 -0
  14. data/lib/mock_redis/set_methods.rb +125 -0
  15. data/lib/mock_redis/string_methods.rb +195 -0
  16. data/lib/mock_redis/transaction_wrapper.rb +75 -0
  17. data/lib/mock_redis/undef_redis_methods.rb +11 -0
  18. data/lib/mock_redis/utility_methods.rb +22 -0
  19. data/lib/mock_redis/version.rb +3 -0
  20. data/lib/mock_redis/zset.rb +80 -0
  21. data/lib/mock_redis/zset_methods.rb +210 -0
  22. data/lib/mock_redis.rb +32 -0
  23. data/mock_redis.gemspec +24 -0
  24. data/spec/cloning_spec.rb +96 -0
  25. data/spec/commands/append_spec.rb +24 -0
  26. data/spec/commands/auth_spec.rb +7 -0
  27. data/spec/commands/bgrewriteaof_spec.rb +7 -0
  28. data/spec/commands/bgsave_spec.rb +7 -0
  29. data/spec/commands/blpop_spec.rb +55 -0
  30. data/spec/commands/brpop_spec.rb +54 -0
  31. data/spec/commands/brpoplpush_spec.rb +53 -0
  32. data/spec/commands/dbsize_spec.rb +18 -0
  33. data/spec/commands/decr_spec.rb +34 -0
  34. data/spec/commands/decrby_spec.rb +34 -0
  35. data/spec/commands/del_spec.rb +20 -0
  36. data/spec/commands/echo_spec.rb +11 -0
  37. data/spec/commands/exists_spec.rb +14 -0
  38. data/spec/commands/expire_spec.rb +83 -0
  39. data/spec/commands/expireat_spec.rb +48 -0
  40. data/spec/commands/flushall_spec.rb +38 -0
  41. data/spec/commands/flushdb_spec.rb +38 -0
  42. data/spec/commands/get_spec.rb +23 -0
  43. data/spec/commands/getbit_spec.rb +34 -0
  44. data/spec/commands/getrange_spec.rb +22 -0
  45. data/spec/commands/getset_spec.rb +23 -0
  46. data/spec/commands/hdel_spec.rb +35 -0
  47. data/spec/commands/hexists_spec.rb +22 -0
  48. data/spec/commands/hget_spec.rb +23 -0
  49. data/spec/commands/hgetall_spec.rb +22 -0
  50. data/spec/commands/hincrby_spec.rb +52 -0
  51. data/spec/commands/hkeys_spec.rb +19 -0
  52. data/spec/commands/hlen_spec.rb +19 -0
  53. data/spec/commands/hmget_spec.rb +30 -0
  54. data/spec/commands/hmset_spec.rb +43 -0
  55. data/spec/commands/hset_spec.rb +23 -0
  56. data/spec/commands/hsetnx_spec.rb +39 -0
  57. data/spec/commands/hvals_spec.rb +19 -0
  58. data/spec/commands/incr_spec.rb +34 -0
  59. data/spec/commands/incrby_spec.rb +44 -0
  60. data/spec/commands/info_spec.rb +13 -0
  61. data/spec/commands/keys_spec.rb +87 -0
  62. data/spec/commands/lastsave_spec.rb +8 -0
  63. data/spec/commands/lindex_spec.rb +39 -0
  64. data/spec/commands/linsert_spec.rb +68 -0
  65. data/spec/commands/llen_spec.rb +16 -0
  66. data/spec/commands/lpop_spec.rb +34 -0
  67. data/spec/commands/lpush_spec.rb +30 -0
  68. data/spec/commands/lpushx_spec.rb +33 -0
  69. data/spec/commands/lrange_spec.rb +35 -0
  70. data/spec/commands/lrem_spec.rb +79 -0
  71. data/spec/commands/lset_spec.rb +38 -0
  72. data/spec/commands/ltrim_spec.rb +35 -0
  73. data/spec/commands/mget_spec.rb +34 -0
  74. data/spec/commands/mset_spec.rb +29 -0
  75. data/spec/commands/msetnx_spec.rb +40 -0
  76. data/spec/commands/persist_spec.rb +49 -0
  77. data/spec/commands/ping_spec.rb +7 -0
  78. data/spec/commands/quit_spec.rb +7 -0
  79. data/spec/commands/randomkey_spec.rb +20 -0
  80. data/spec/commands/rename_spec.rb +31 -0
  81. data/spec/commands/renamenx_spec.rb +36 -0
  82. data/spec/commands/rpop_spec.rb +34 -0
  83. data/spec/commands/rpoplpush_spec.rb +45 -0
  84. data/spec/commands/rpush_spec.rb +30 -0
  85. data/spec/commands/rpushx_spec.rb +33 -0
  86. data/spec/commands/sadd_spec.rb +22 -0
  87. data/spec/commands/save_spec.rb +7 -0
  88. data/spec/commands/scard_spec.rb +18 -0
  89. data/spec/commands/sdiff_spec.rb +47 -0
  90. data/spec/commands/sdiffstore_spec.rb +51 -0
  91. data/spec/commands/select_spec.rb +53 -0
  92. data/spec/commands/set_spec.rb +7 -0
  93. data/spec/commands/setbit_spec.rb +46 -0
  94. data/spec/commands/setex_spec.rb +22 -0
  95. data/spec/commands/setnx_spec.rb +25 -0
  96. data/spec/commands/setrange_spec.rb +30 -0
  97. data/spec/commands/sinter_spec.rb +41 -0
  98. data/spec/commands/sinterstore_spec.rb +46 -0
  99. data/spec/commands/sismember_spec.rb +29 -0
  100. data/spec/commands/smembers_spec.rb +18 -0
  101. data/spec/commands/smove_spec.rb +41 -0
  102. data/spec/commands/spop_spec.rb +25 -0
  103. data/spec/commands/srandmember_spec.rb +25 -0
  104. data/spec/commands/srem_spec.rb +35 -0
  105. data/spec/commands/strlen_spec.rb +19 -0
  106. data/spec/commands/sunion_spec.rb +40 -0
  107. data/spec/commands/sunionstore_spec.rb +46 -0
  108. data/spec/commands/ttl_spec.rb +36 -0
  109. data/spec/commands/type_spec.rb +36 -0
  110. data/spec/commands/unwatch_spec.rb +7 -0
  111. data/spec/commands/watch_spec.rb +7 -0
  112. data/spec/commands/zadd_spec.rb +29 -0
  113. data/spec/commands/zcard_spec.rb +19 -0
  114. data/spec/commands/zcount_spec.rb +23 -0
  115. data/spec/commands/zincrby_spec.rb +24 -0
  116. data/spec/commands/zinterstore_spec.rb +88 -0
  117. data/spec/commands/zrange_spec.rb +31 -0
  118. data/spec/commands/zrangebyscore_spec.rb +42 -0
  119. data/spec/commands/zrank_spec.rb +23 -0
  120. data/spec/commands/zrem_spec.rb +25 -0
  121. data/spec/commands/zremrangebyrank_spec.rb +22 -0
  122. data/spec/commands/zremrangebyscore_spec.rb +21 -0
  123. data/spec/commands/zrevrange_spec.rb +31 -0
  124. data/spec/commands/zrevrangebyscore_spec.rb +42 -0
  125. data/spec/commands/zrevrank_spec.rb +23 -0
  126. data/spec/commands/zscore_spec.rb +16 -0
  127. data/spec/commands/zunionstore_spec.rb +96 -0
  128. data/spec/spec_helper.rb +44 -0
  129. data/spec/support/redis_multiplexer.rb +91 -0
  130. data/spec/support/shared_examples/only_operates_on_hashes.rb +13 -0
  131. data/spec/support/shared_examples/only_operates_on_lists.rb +13 -0
  132. data/spec/support/shared_examples/only_operates_on_sets.rb +13 -0
  133. data/spec/support/shared_examples/only_operates_on_strings.rb +13 -0
  134. data/spec/support/shared_examples/only_operates_on_zsets.rb +57 -0
  135. data/spec/transactions_spec.rb +73 -0
  136. 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,3 @@
1
+ class MockRedis
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#auth(password) [mock only]" do
4
+ it "just returns 'OK'" do
5
+ @redises.mock.auth('foo').should == 'OK'
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#bgrewriteaof [mock only]" do
4
+ it "just returns a canned string" do
5
+ @redises.mock.bgrewriteaof.should =~ /append/
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#bgsave [mock only]" do
4
+ it "just returns a canned string" do
5
+ @redises.mock.bgsave.should =~ /saving/
6
+ end
7
+ 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