redis-store-pika 1.9.2.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +6 -0
- data/.github/auto-assign-issues.yml +2 -0
- data/.github/workflows/ci.yml +64 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +132 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +667 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +68 -0
- data/Rakefile +15 -0
- data/gemfiles/redis_4_0_x.gemfile +7 -0
- data/gemfiles/redis_4_1_x.gemfile +7 -0
- data/gemfiles/redis_4_6_x.gemfile +7 -0
- data/gemfiles/redis_4_x.gemfile +7 -0
- data/gemfiles/redis_5_x.gemfile +7 -0
- data/lib/redis/distributed_store.rb +68 -0
- data/lib/redis/store/factory.rb +111 -0
- data/lib/redis/store/interface.rb +29 -0
- data/lib/redis/store/namespace.rb +211 -0
- data/lib/redis/store/redis_version.rb +13 -0
- data/lib/redis/store/serialization.rb +67 -0
- data/lib/redis/store/ttl.rb +47 -0
- data/lib/redis/store/version.rb +13 -0
- data/lib/redis/store.rb +85 -0
- data/lib/redis-store-pika.rb +1 -0
- data/lib/redis-store.rb +1 -0
- data/redis-store.gemspec +35 -0
- data/test/redis/distributed_store_test.rb +111 -0
- data/test/redis/store/factory_test.rb +273 -0
- data/test/redis/store/interface_test.rb +27 -0
- data/test/redis/store/namespace_test.rb +316 -0
- data/test/redis/store/redis_version_test.rb +28 -0
- data/test/redis/store/serialization_test.rb +173 -0
- data/test/redis/store/ttl_test.rb +142 -0
- data/test/redis/store/version_test.rb +7 -0
- data/test/redis/store_test.rb +68 -0
- data/test/test_helper.rb +20 -0
- metadata +246 -0
@@ -0,0 +1,316 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "Redis::Store::Namespace" do
|
4
|
+
def setup
|
5
|
+
@namespace = "theplaylist"
|
6
|
+
@store = Redis::Store.new :namespace => @namespace, :serializer => nil
|
7
|
+
@client = @store.instance_variable_get(:@client)
|
8
|
+
@rabbit = "bunny"
|
9
|
+
@default_store = Redis::Store.new
|
10
|
+
@other_namespace = 'other'
|
11
|
+
@other_store = Redis::Store.new :namespace => @other_namespace
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
@store.flushdb
|
16
|
+
@store.quit
|
17
|
+
|
18
|
+
@default_store.flushdb
|
19
|
+
@default_store.quit
|
20
|
+
|
21
|
+
@other_store.flushdb
|
22
|
+
@other_store.quit
|
23
|
+
end
|
24
|
+
|
25
|
+
it "only decorates instances that need to be namespaced" do
|
26
|
+
store = Redis::Store.new
|
27
|
+
client = store.instance_variable_get(:@client)
|
28
|
+
# `call_v` used since redis-rb 5.0
|
29
|
+
client_call_method_name = client.respond_to?(:call_v) ? :call_v : :call
|
30
|
+
client.expects(client_call_method_name).with([:get, "rabbit"])
|
31
|
+
store.get("rabbit")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "doesn't namespace a key which is already namespaced" do
|
35
|
+
_(@store.send(:interpolate, "#{@namespace}:rabbit")).must_equal("#{@namespace}:rabbit")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should only delete namespaced keys" do
|
39
|
+
@default_store.set 'abc', 'cba'
|
40
|
+
@store.set 'def', 'fed'
|
41
|
+
|
42
|
+
@store.flushdb
|
43
|
+
_(@store.get('def')).must_be_nil
|
44
|
+
_(@default_store.get('abc')).must_equal('cba')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should allow to change namespace on the fly' do
|
48
|
+
@default_store.set 'abc', 'cba'
|
49
|
+
@other_store.set 'foo', 'bar'
|
50
|
+
|
51
|
+
_(@default_store.keys.sort).must_equal ['abc', 'other:foo']
|
52
|
+
|
53
|
+
@default_store.with_namespace(@other_namespace) do
|
54
|
+
_(@default_store.keys).must_equal ['foo']
|
55
|
+
_(@default_store.get('foo')).must_equal('bar')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not try to delete missing namespaced keys" do
|
60
|
+
empty_store = Redis::Store.new :namespace => 'empty'
|
61
|
+
empty_store.flushdb
|
62
|
+
_(empty_store.keys).must_be_empty
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should work with dynamic namespace" do
|
66
|
+
$ns = "ns1"
|
67
|
+
dyn_store = Redis::Store.new :namespace => -> { $ns }
|
68
|
+
dyn_store.set 'key', 'x'
|
69
|
+
$ns = "ns2"
|
70
|
+
dyn_store.set 'key', 'y'
|
71
|
+
$ns = "ns3"
|
72
|
+
dyn_store.set 'key', 'z'
|
73
|
+
dyn_store.flushdb
|
74
|
+
r3 = dyn_store.get 'key'
|
75
|
+
$ns = "ns2"
|
76
|
+
r2 = dyn_store.get 'key'
|
77
|
+
$ns = "ns1"
|
78
|
+
r1 = dyn_store.get 'key'
|
79
|
+
_(r1).must_equal('x') && _(r2).must_equal('y') && _(r3).must_be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it "namespaces setex and ttl" do
|
83
|
+
@store.flushdb
|
84
|
+
@other_store.flushdb
|
85
|
+
|
86
|
+
@store.setex('foo', 30, 'bar')
|
87
|
+
_(@store.ttl('foo')).must_be_close_to(30)
|
88
|
+
_(@store.get('foo')).must_equal('bar')
|
89
|
+
|
90
|
+
_(@other_store.ttl('foo')).must_equal(-2)
|
91
|
+
_(@other_store.get('foo')).must_be_nil
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'method calls' do
|
95
|
+
let(:store) { Redis::Store.new :namespace => @namespace, :serializer => nil }
|
96
|
+
let(:client) { store.instance_variable_get(:@client) }
|
97
|
+
let(:client_call_method_name) do
|
98
|
+
# `call_v` used since redis-rb 5.0
|
99
|
+
client.respond_to?(:call_v) ? :call_v : :call
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should namespace get" do
|
103
|
+
client.expects(client_call_method_name).with([:get, "#{@namespace}:rabbit"]).once
|
104
|
+
store.get("rabbit")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should namespace set" do
|
108
|
+
client.expects(client_call_method_name).with([:set, "#{@namespace}:rabbit", @rabbit])
|
109
|
+
store.set "rabbit", @rabbit
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should namespace setnx" do
|
113
|
+
client.expects(client_call_method_name).with([:setnx, "#{@namespace}:rabbit", @rabbit])
|
114
|
+
store.setnx "rabbit", @rabbit
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should namespace del with single key" do
|
118
|
+
client.expects(client_call_method_name).with([:del, "#{@namespace}:rabbit"])
|
119
|
+
store.del "rabbit"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should namespace del with multiple keys" do
|
123
|
+
client.expects(client_call_method_name).with([:del, "#{@namespace}:rabbit", "#{@namespace}:white_rabbit"])
|
124
|
+
store.del "rabbit", "white_rabbit"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should namespace keys" do
|
128
|
+
store.set "rabbit", @rabbit
|
129
|
+
_(store.keys("rabb*")).must_equal [ "rabbit" ]
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should namespace scan when a pattern is given" do
|
133
|
+
store.set "rabbit", @rabbit
|
134
|
+
cursor = "0"
|
135
|
+
keys = []
|
136
|
+
begin
|
137
|
+
cursor, matched_keys = store.scan(cursor, match: "rabb*")
|
138
|
+
keys = keys.concat(matched_keys) unless matched_keys.empty?
|
139
|
+
end until cursor == "0"
|
140
|
+
_(keys).must_equal [ "rabbit" ]
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should namespace exists" do
|
144
|
+
client.expects(client_call_method_name).with([:exists, "#{@namespace}:rabbit"])
|
145
|
+
store.exists "rabbit"
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should namespace incrby" do
|
149
|
+
client.expects(client_call_method_name).with([:incrby, "#{@namespace}:counter", 1])
|
150
|
+
store.incrby "counter", 1
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should namespace decrby" do
|
154
|
+
client.expects(client_call_method_name).with([:decrby, "#{@namespace}:counter", 1])
|
155
|
+
store.decrby "counter", 1
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should namespace mget" do
|
159
|
+
client.expects(client_call_method_name).with([:mget, "#{@namespace}:rabbit", "#{@namespace}:white_rabbit"]).returns(%w[ foo bar ])
|
160
|
+
store.mget "rabbit", "white_rabbit" do |result|
|
161
|
+
_(result).must_equal(%w[ foo bar ])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should namespace mapped_mget" do
|
166
|
+
if client.respond_to?(:process, true)
|
167
|
+
# Redis < 5.0 uses `#process`
|
168
|
+
client.expects(:process).with([[:mget, "#{@namespace}:rabbit", "#{@namespace}:white_rabbit"]]).returns(%w[ foo bar ])
|
169
|
+
else
|
170
|
+
# Redis 5.x calls `#ensure_connected` (private)
|
171
|
+
client.send(:ensure_connected).expects(:call).returns(%w[ foo bar ])
|
172
|
+
end
|
173
|
+
result = store.mapped_mget "rabbit", "white_rabbit"
|
174
|
+
_(result.keys).must_equal %w[ rabbit white_rabbit ]
|
175
|
+
_(result["rabbit"]).must_equal "foo"
|
176
|
+
_(result["white_rabbit"]).must_equal "bar"
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should namespace expire" do
|
180
|
+
client.expects(client_call_method_name).with([:expire, "#{@namespace}:rabbit", 60]).once
|
181
|
+
store.expire("rabbit", 60)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should namespace ttl" do
|
185
|
+
client.expects(client_call_method_name).with([:ttl, "#{@namespace}:rabbit"]).once
|
186
|
+
store.ttl("rabbit")
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should namespace watch" do
|
190
|
+
client.expects(client_call_method_name).with([:watch, "#{@namespace}:rabbit"]).once
|
191
|
+
store.watch("rabbit")
|
192
|
+
end
|
193
|
+
|
194
|
+
it "wraps flushdb with appropriate KEYS * calls" do
|
195
|
+
client.expects(client_call_method_name).with([:flushdb]).never
|
196
|
+
client.expects(client_call_method_name).with([:keys, "#{@namespace}:*"]).once.returns(["rabbit"])
|
197
|
+
client.expects(client_call_method_name).with([:del, "#{@namespace}:rabbit"]).once
|
198
|
+
store.flushdb
|
199
|
+
end
|
200
|
+
|
201
|
+
it "skips flushdb wrapping if the namespace is nil" do
|
202
|
+
client.expects(client_call_method_name).with([:flushdb])
|
203
|
+
client.expects(client_call_method_name).with([:keys]).never
|
204
|
+
store.with_namespace(nil) do
|
205
|
+
store.flushdb
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should namespace hdel" do
|
210
|
+
client.expects(client_call_method_name).with([:hdel, "#{@namespace}:rabbit", "key1", "key2"]).once
|
211
|
+
store.hdel("rabbit", "key1", "key2")
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should namespace hget" do
|
215
|
+
client.expects(client_call_method_name).with([:hget, "#{@namespace}:rabbit", "key"]).once
|
216
|
+
store.hget("rabbit", "key")
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should namespace hgetall" do
|
220
|
+
client.expects(client_call_method_name).with([:hgetall, "#{@namespace}:rabbit"]).once
|
221
|
+
store.hgetall("rabbit")
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should namespace hexists" do
|
225
|
+
client.expects(client_call_method_name).with([:hexists, "#{@namespace}:rabbit", "key"]).once
|
226
|
+
store.hexists("rabbit", "key")
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should namespace hincrby" do
|
230
|
+
client.expects(client_call_method_name).with([:hincrby, "#{@namespace}:rabbit", "key", 1]).once
|
231
|
+
store.hincrby("rabbit", "key", 1)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should namespace hincrbyfloat" do
|
235
|
+
client.expects(client_call_method_name).with([:hincrbyfloat, "#{@namespace}:rabbit", "key", 1.5]).once
|
236
|
+
store.hincrbyfloat("rabbit", "key", 1.5)
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should namespace hkeys" do
|
240
|
+
client.expects(client_call_method_name).with([:hkeys, "#{@namespace}:rabbit"])
|
241
|
+
store.hkeys("rabbit")
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should namespace hlen" do
|
245
|
+
client.expects(client_call_method_name).with([:hlen, "#{@namespace}:rabbit"])
|
246
|
+
store.hlen("rabbit")
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should namespace hmget" do
|
250
|
+
client.expects(client_call_method_name).with([:hmget, "#{@namespace}:rabbit", "key1", "key2"])
|
251
|
+
store.hmget("rabbit", "key1", "key2")
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should namespace hmset" do
|
255
|
+
client.expects(client_call_method_name).with([:hmset, "#{@namespace}:rabbit", "key", @rabbit])
|
256
|
+
store.hmset("rabbit", "key", @rabbit)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should namespace hset" do
|
260
|
+
client.expects(client_call_method_name).with([:hset, "#{@namespace}:rabbit", "key", @rabbit])
|
261
|
+
store.hset("rabbit", "key", @rabbit)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should namespace hsetnx" do
|
265
|
+
client.expects(client_call_method_name).with([:hsetnx, "#{@namespace}:rabbit", "key", @rabbit])
|
266
|
+
store.hsetnx("rabbit", "key", @rabbit)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should namespace hvals" do
|
270
|
+
client.expects(client_call_method_name).with([:hvals, "#{@namespace}:rabbit"])
|
271
|
+
store.hvals("rabbit")
|
272
|
+
end
|
273
|
+
|
274
|
+
it "should namespace hscan" do
|
275
|
+
client.expects(client_call_method_name).with([:hscan, "#{@namespace}:rabbit", 0])
|
276
|
+
store.hscan("rabbit", 0)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should namespace hscan_each with block" do
|
280
|
+
client.public_send(client_call_method_name, [:hset, "#{@namespace}:rabbit", "key1", @rabbit])
|
281
|
+
client.expects(client_call_method_name).with([:hscan, "#{@namespace}:rabbit", 0]).returns(["0", ["key1"]])
|
282
|
+
results = []
|
283
|
+
store.hscan_each("rabbit") do |key|
|
284
|
+
results << key
|
285
|
+
end
|
286
|
+
_(results).must_equal(["key1"])
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should namespace hscan_each without block" do
|
290
|
+
client.public_send(client_call_method_name, [:hset, "#{@namespace}:rabbit", "key1", @rabbit])
|
291
|
+
client.expects(client_call_method_name).with([:hscan, "#{@namespace}:rabbit", 0]).returns(["0", ["key1"]])
|
292
|
+
results = store.hscan_each("rabbit").to_a
|
293
|
+
_(results).must_equal(["key1"])
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should namespace zincrby" do
|
297
|
+
client.expects(client_call_method_name).with([:zincrby, "#{@namespace}:rabbit", 1.0, "member"])
|
298
|
+
store.zincrby("rabbit", 1.0, "member")
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should namespace zscore" do
|
302
|
+
client.expects(client_call_method_name).with([:zscore, "#{@namespace}:rabbit", "member"])
|
303
|
+
store.zscore("rabbit", "member")
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should namespace zadd" do
|
307
|
+
client.expects(client_call_method_name).with([:zadd, "#{@namespace}:rabbit", 1.0, "member"])
|
308
|
+
store.zadd("rabbit", 1.0, "member")
|
309
|
+
end
|
310
|
+
|
311
|
+
it "should namespace zrem" do
|
312
|
+
client.expects(client_call_method_name).with([:zrem, "#{@namespace}:rabbit", "member"])
|
313
|
+
store.zrem("rabbit", "member")
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "Redis::RedisVersion" do
|
4
|
+
def setup
|
5
|
+
@store = Redis::Store.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@store.quit
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#redis_version' do
|
13
|
+
it 'returns redis version' do
|
14
|
+
_(@store.redis_version.to_s).must_match(/^\d{1}\.\d{1,}\.\d{1,}$/)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#supports_redis_version?' do
|
19
|
+
it 'returns true if redis version is greater or equal to required version' do
|
20
|
+
@store.stubs(:redis_version).returns('2.8.19')
|
21
|
+
_(@store.supports_redis_version?('2.6.0')).must_equal(true)
|
22
|
+
_(@store.supports_redis_version?('2.8.19')).must_equal(true)
|
23
|
+
_(@store.supports_redis_version?('2.8.20')).must_equal(false)
|
24
|
+
_(@store.supports_redis_version?('2.9.0')).must_equal(false)
|
25
|
+
_(@store.supports_redis_version?('3.0.0')).must_equal(false)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "Redis::Serialization" do
|
4
|
+
def setup
|
5
|
+
@store = Redis::Store.new serializer: Marshal
|
6
|
+
@rabbit = OpenStruct.new :name => "bunny"
|
7
|
+
@white_rabbit = OpenStruct.new :color => "white"
|
8
|
+
@store.set "rabbit", @rabbit
|
9
|
+
@store.del "rabbit2"
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
@store.flushdb
|
14
|
+
@store.quit
|
15
|
+
end
|
16
|
+
|
17
|
+
it "unmarshals on get" do
|
18
|
+
_(@store.get("rabbit")).must_equal(@rabbit)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "marshals on set" do
|
22
|
+
@store.set "rabbit", @white_rabbit
|
23
|
+
_(@store.get("rabbit")).must_equal(@white_rabbit)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "marshals on multi set" do
|
27
|
+
@store.mset("rabbit", @white_rabbit, "rabbit2", @rabbit)
|
28
|
+
_(@store.get("rabbit")).must_equal(@white_rabbit)
|
29
|
+
_(@store.get("rabbit2")).must_equal(@rabbit)
|
30
|
+
end
|
31
|
+
|
32
|
+
if RUBY_VERSION.match(/1\.9/)
|
33
|
+
it "doesn't unmarshal on get if raw option is true" do
|
34
|
+
_(@store.get("rabbit", :raw => true)).must_equal("\x04\bU:\x0FOpenStruct{\x06:\tnameI\"\nbunny\x06:\x06EF")
|
35
|
+
end
|
36
|
+
else
|
37
|
+
it "doesn't unmarshal on get if raw option is true" do
|
38
|
+
_(@store.get("rabbit", :raw => true)).must_include("\x04\bU:\x0FOpenStruct{\x06:\tname")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't marshal set if raw option is true" do
|
43
|
+
@store.set "rabbit", @white_rabbit, :raw => true
|
44
|
+
_(@store.get("rabbit", :raw => true)).must_equal(%(#<OpenStruct color="white">))
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't marshal multi set if raw option is true" do
|
48
|
+
@store.mset("rabbit", @white_rabbit, "rabbit2", @rabbit, :raw => true)
|
49
|
+
_(@store.get("rabbit", :raw => true)).must_equal(%(#<OpenStruct color="white">))
|
50
|
+
_(@store.get("rabbit2", :raw => true)).must_equal(%(#<OpenStruct name="bunny">))
|
51
|
+
end
|
52
|
+
|
53
|
+
it "doesn't unmarshal if get returns an empty string" do
|
54
|
+
@store.set "empty_string", ""
|
55
|
+
_(@store.get("empty_string")).must_equal("")
|
56
|
+
# TODO use a meaningful Exception
|
57
|
+
# lambda { @store.get("empty_string").must_equal("") }.wont_raise Exception
|
58
|
+
end
|
59
|
+
|
60
|
+
it "doesn't set an object if already exist" do
|
61
|
+
@store.setnx "rabbit", @white_rabbit
|
62
|
+
_(@store.get("rabbit")).must_equal(@rabbit)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "marshals on set unless exists" do
|
66
|
+
@store.setnx "rabbit2", @white_rabbit
|
67
|
+
_(@store.get("rabbit2")).must_equal(@white_rabbit)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "doesn't marshal on set unless exists if raw option is true" do
|
71
|
+
@store.setnx "rabbit2", @white_rabbit, :raw => true
|
72
|
+
_(@store.get("rabbit2", :raw => true)).must_equal(%(#<OpenStruct color="white">))
|
73
|
+
end
|
74
|
+
|
75
|
+
it "marshals on set expire" do
|
76
|
+
@store.setex "rabbit2", 1, @white_rabbit
|
77
|
+
_(@store.get("rabbit2")).must_equal(@white_rabbit)
|
78
|
+
sleep 2
|
79
|
+
_(@store.get("rabbit2")).must_be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
unless ENV['CI']
|
83
|
+
it "marshals setex (over a distributed store)" do
|
84
|
+
@store = Redis::DistributedStore.new [
|
85
|
+
{ :host => "localhost", :port => "6380", :db => 0 },
|
86
|
+
{ :host => "localhost", :port => "6381", :db => 0 }
|
87
|
+
]
|
88
|
+
@store.setex "rabbit", 50, @white_rabbit
|
89
|
+
_(@store.get("rabbit")).must_equal(@white_rabbit)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "doesn't marshal setex if raw option is true (over a distributed store)" do
|
93
|
+
@store = Redis::DistributedStore.new [
|
94
|
+
{ :host => "localhost", :port => "6380", :db => 0 },
|
95
|
+
{ :host => "localhost", :port => "6381", :db => 0 }
|
96
|
+
]
|
97
|
+
@store.setex "rabbit", 50, @white_rabbit, :raw => true
|
98
|
+
_(@store.get("rabbit", :raw => true)).must_equal(%(#<OpenStruct color="white">))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "unmarshals on multi get" do
|
103
|
+
@store.set "rabbit2", @white_rabbit
|
104
|
+
@store.mget "rabbit", "rabbit2" do |rabbits|
|
105
|
+
rabbit, rabbit2 = rabbits
|
106
|
+
_(rabbits.length).must_equal(2)
|
107
|
+
_(rabbit).must_equal(@rabbit)
|
108
|
+
_(rabbit2).must_equal(@white_rabbit)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it "unmarshals on mapped_mget" do
|
113
|
+
@store.set "rabbit2", @white_rabbit
|
114
|
+
result = @store.mapped_mget("rabbit", "rabbit2")
|
115
|
+
_(result.keys).must_equal %w[ rabbit rabbit2 ]
|
116
|
+
_(result["rabbit"]).must_equal @rabbit
|
117
|
+
_(result["rabbit2"]).must_equal @white_rabbit
|
118
|
+
end
|
119
|
+
|
120
|
+
if RUBY_VERSION.match(/1\.9/)
|
121
|
+
it "doesn't unmarshal on multi get if raw option is true" do
|
122
|
+
@store.set "rabbit2", @white_rabbit
|
123
|
+
@store.mget "rabbit", "rabbit2", :raw => true do |rabbit, rabbit2|
|
124
|
+
_(rabbit).must_equal("\x04\bU:\x0FOpenStruct{\x06:\tnameI\"\nbunny\x06:\x06EF")
|
125
|
+
_(rabbit2).must_equal("\x04\bU:\x0FOpenStruct{\x06:\ncolorI\"\nwhite\x06:\x06EF")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
else
|
129
|
+
it "doesn't unmarshal on multi get if raw option is true" do
|
130
|
+
@store.set "rabbit2", @white_rabbit
|
131
|
+
@store.mget "rabbit", "rabbit2", :raw => true do |rabbit, rabbit2|
|
132
|
+
_(rabbit).must_include("\x04\bU:\x0FOpenStruct{\x06:\tname")
|
133
|
+
_(rabbit2).must_include("\x04\bU:\x0FOpenStruct{\x06:\ncolor")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "binary safety" do
|
139
|
+
it "marshals objects" do
|
140
|
+
utf8_key = [51339].pack("U*")
|
141
|
+
ascii_rabbit = OpenStruct.new(:name => [128].pack("C*"))
|
142
|
+
|
143
|
+
@store.set(utf8_key, ascii_rabbit)
|
144
|
+
_(@store.get(utf8_key)).must_equal(ascii_rabbit)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "gets and sets raw values" do
|
148
|
+
utf8_key = [51339].pack("U*")
|
149
|
+
ascii_string = [128].pack("C*")
|
150
|
+
|
151
|
+
@store.set(utf8_key, ascii_string, :raw => true)
|
152
|
+
_(@store.get(utf8_key, :raw => true).bytes.to_a).must_equal(ascii_string.bytes.to_a)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "marshals objects on setnx" do
|
156
|
+
utf8_key = [51339].pack("U*")
|
157
|
+
ascii_rabbit = OpenStruct.new(:name => [128].pack("C*"))
|
158
|
+
|
159
|
+
@store.del(utf8_key)
|
160
|
+
@store.setnx(utf8_key, ascii_rabbit)
|
161
|
+
_(@store.get(utf8_key)).must_equal(ascii_rabbit)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "gets and sets raw values on setnx" do
|
165
|
+
utf8_key = [51339].pack("U*")
|
166
|
+
ascii_string = [128].pack("C*")
|
167
|
+
|
168
|
+
@store.del(utf8_key)
|
169
|
+
@store.setnx(utf8_key, ascii_string, :raw => true)
|
170
|
+
_(@store.get(utf8_key, :raw => true).bytes.to_a).must_equal(ascii_string.bytes.to_a)
|
171
|
+
end
|
172
|
+
end if defined?(Encoding)
|
173
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MockRedis
|
4
|
+
def initialize
|
5
|
+
@sets = []
|
6
|
+
@setexes = []
|
7
|
+
@setnxes = []
|
8
|
+
@expires = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(*a)
|
12
|
+
@sets << a
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_set?(*a)
|
16
|
+
@sets.include?(a)
|
17
|
+
end
|
18
|
+
|
19
|
+
def setex(*a)
|
20
|
+
@setexes << a
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_setex?(*a)
|
24
|
+
@setexes.include?(a)
|
25
|
+
end
|
26
|
+
|
27
|
+
def setnx(*a)
|
28
|
+
@setnxes << a
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_setnx?(*a)
|
32
|
+
@setnxes.include?(a)
|
33
|
+
end
|
34
|
+
|
35
|
+
def multi(&block)
|
36
|
+
instance_eval do
|
37
|
+
def setnx(*a)
|
38
|
+
@setnxes << a
|
39
|
+
end
|
40
|
+
|
41
|
+
block.call(self)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
alias_method :pipelined, :multi
|
45
|
+
|
46
|
+
def expire(*a)
|
47
|
+
@expires << a
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_expire?(*a)
|
51
|
+
@expires.include?(a)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class MockTtlStore < MockRedis
|
56
|
+
include Redis::Store::Ttl
|
57
|
+
end
|
58
|
+
|
59
|
+
describe MockTtlStore do
|
60
|
+
let(:key) { 'hello' }
|
61
|
+
let(:mock_value) { 'value' }
|
62
|
+
let(:options) { { :expire_after => 3600 } }
|
63
|
+
let(:redis) { MockTtlStore.new }
|
64
|
+
|
65
|
+
describe '#set' do
|
66
|
+
describe 'without options' do
|
67
|
+
it 'must call super with key and value' do
|
68
|
+
redis.set(key, mock_value)
|
69
|
+
_(redis.has_set?(key, mock_value, nil)).must_equal true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'with options' do
|
74
|
+
it 'must call setex with proper expiry and set raw to true' do
|
75
|
+
redis.set(key, mock_value, options)
|
76
|
+
_(redis.has_setex?(key, options[:expire_after], mock_value, :raw => true)).must_equal true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'with nx and ex option' do
|
81
|
+
it 'must call super with key and value and options' do
|
82
|
+
set_options = { nx: true, ex: 3600 }
|
83
|
+
redis.set(key, mock_value, set_options)
|
84
|
+
_(redis.has_set?(key, mock_value, set_options)).must_equal true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#setnx' do
|
90
|
+
describe 'without expiry' do
|
91
|
+
it 'must call super with key and value' do
|
92
|
+
redis.setnx(key, mock_value)
|
93
|
+
redis.has_setnx?(key, mock_value)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'must not call expire' do
|
97
|
+
redis.expects(:expire).never
|
98
|
+
redis.setnx(key, mock_value)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'with expiry' do
|
103
|
+
it 'uses the mutli command to chain commands' do
|
104
|
+
redis.expects(:multi)
|
105
|
+
redis.setnx(key, mock_value, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'must call expire' do
|
109
|
+
redis.setnx(key, mock_value, options)
|
110
|
+
_(redis.has_expire?(key, options[:expire_after])).must_equal true
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'avoiding multi commands' do
|
114
|
+
let(:options) { { :expire_after => 3600, :avoid_multi_commands => true } }
|
115
|
+
|
116
|
+
it 'uses the redis pipelined feature to chain commands' do
|
117
|
+
redis.expects(:pipelined)
|
118
|
+
redis.setnx(key, mock_value, options)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'must call expire' do
|
122
|
+
redis.setnx(key, mock_value, options)
|
123
|
+
_(redis.has_expire?(key, options[:expire_after])).must_equal true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe 'using a redis cluster' do
|
128
|
+
let(:options) { { :expire_after => 3600, :cluster => %w[redis://127.0.0.1:6379/0] } }
|
129
|
+
|
130
|
+
it 'uses the redis pipelined feature to chain commands' do
|
131
|
+
redis.expects(:pipelined)
|
132
|
+
redis.setnx(key, mock_value, options)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'must call expire' do
|
136
|
+
redis.setnx(key, mock_value, options)
|
137
|
+
_(redis.has_expire?(key, options[:expire_after])).must_equal true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|