redis2-namespaced 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.order +170 -0
  4. data/.travis/Gemfile +11 -0
  5. data/.travis.yml +55 -0
  6. data/.yardopts +3 -0
  7. data/CHANGELOG.md +285 -0
  8. data/LICENSE +20 -0
  9. data/README.md +251 -0
  10. data/Rakefile +403 -0
  11. data/benchmarking/logging.rb +71 -0
  12. data/benchmarking/pipeline.rb +51 -0
  13. data/benchmarking/speed.rb +21 -0
  14. data/benchmarking/suite.rb +24 -0
  15. data/benchmarking/worker.rb +71 -0
  16. data/examples/basic.rb +15 -0
  17. data/examples/dist_redis.rb +43 -0
  18. data/examples/incr-decr.rb +17 -0
  19. data/examples/list.rb +26 -0
  20. data/examples/pubsub.rb +37 -0
  21. data/examples/sets.rb +36 -0
  22. data/examples/unicorn/config.ru +3 -0
  23. data/examples/unicorn/unicorn.rb +20 -0
  24. data/lib/redis2/client.rb +419 -0
  25. data/lib/redis2/connection/command_helper.rb +44 -0
  26. data/lib/redis2/connection/hiredis.rb +63 -0
  27. data/lib/redis2/connection/registry.rb +12 -0
  28. data/lib/redis2/connection/ruby.rb +322 -0
  29. data/lib/redis2/connection/synchrony.rb +124 -0
  30. data/lib/redis2/connection.rb +9 -0
  31. data/lib/redis2/distributed.rb +853 -0
  32. data/lib/redis2/errors.rb +40 -0
  33. data/lib/redis2/hash_ring.rb +131 -0
  34. data/lib/redis2/pipeline.rb +141 -0
  35. data/lib/redis2/subscribe.rb +83 -0
  36. data/lib/redis2/version.rb +3 -0
  37. data/lib/redis2.rb +2533 -0
  38. data/redis.gemspec +43 -0
  39. data/test/bitpos_test.rb +69 -0
  40. data/test/blocking_commands_test.rb +42 -0
  41. data/test/command_map_test.rb +30 -0
  42. data/test/commands_on_hashes_test.rb +21 -0
  43. data/test/commands_on_lists_test.rb +20 -0
  44. data/test/commands_on_sets_test.rb +77 -0
  45. data/test/commands_on_sorted_sets_test.rb +109 -0
  46. data/test/commands_on_strings_test.rb +101 -0
  47. data/test/commands_on_value_types_test.rb +131 -0
  48. data/test/connection_handling_test.rb +189 -0
  49. data/test/db/.gitkeep +0 -0
  50. data/test/distributed_blocking_commands_test.rb +46 -0
  51. data/test/distributed_commands_on_hashes_test.rb +10 -0
  52. data/test/distributed_commands_on_lists_test.rb +22 -0
  53. data/test/distributed_commands_on_sets_test.rb +83 -0
  54. data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
  55. data/test/distributed_commands_on_strings_test.rb +59 -0
  56. data/test/distributed_commands_on_value_types_test.rb +95 -0
  57. data/test/distributed_commands_requiring_clustering_test.rb +164 -0
  58. data/test/distributed_connection_handling_test.rb +23 -0
  59. data/test/distributed_internals_test.rb +70 -0
  60. data/test/distributed_key_tags_test.rb +52 -0
  61. data/test/distributed_persistence_control_commands_test.rb +26 -0
  62. data/test/distributed_publish_subscribe_test.rb +92 -0
  63. data/test/distributed_remote_server_control_commands_test.rb +66 -0
  64. data/test/distributed_scripting_test.rb +102 -0
  65. data/test/distributed_sorting_test.rb +20 -0
  66. data/test/distributed_test.rb +58 -0
  67. data/test/distributed_transactions_test.rb +32 -0
  68. data/test/encoding_test.rb +18 -0
  69. data/test/error_replies_test.rb +59 -0
  70. data/test/helper.rb +218 -0
  71. data/test/helper_test.rb +24 -0
  72. data/test/internals_test.rb +410 -0
  73. data/test/lint/blocking_commands.rb +150 -0
  74. data/test/lint/hashes.rb +162 -0
  75. data/test/lint/lists.rb +143 -0
  76. data/test/lint/sets.rb +125 -0
  77. data/test/lint/sorted_sets.rb +238 -0
  78. data/test/lint/strings.rb +260 -0
  79. data/test/lint/value_types.rb +122 -0
  80. data/test/persistence_control_commands_test.rb +26 -0
  81. data/test/pipelining_commands_test.rb +242 -0
  82. data/test/publish_subscribe_test.rb +210 -0
  83. data/test/remote_server_control_commands_test.rb +117 -0
  84. data/test/scanning_test.rb +413 -0
  85. data/test/scripting_test.rb +78 -0
  86. data/test/sorting_test.rb +59 -0
  87. data/test/support/connection/hiredis.rb +1 -0
  88. data/test/support/connection/ruby.rb +1 -0
  89. data/test/support/connection/synchrony.rb +17 -0
  90. data/test/support/redis_mock.rb +115 -0
  91. data/test/support/wire/synchrony.rb +24 -0
  92. data/test/support/wire/thread.rb +5 -0
  93. data/test/synchrony_driver.rb +88 -0
  94. data/test/test.conf +9 -0
  95. data/test/thread_safety_test.rb +32 -0
  96. data/test/transactions_test.rb +264 -0
  97. data/test/unknown_commands_test.rb +14 -0
  98. data/test/url_param_test.rb +132 -0
  99. metadata +226 -0
@@ -0,0 +1,413 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ unless defined?(Enumerator)
6
+ Enumerator = Enumerable::Enumerator
7
+ end
8
+
9
+ class TestScanning < Test::Unit::TestCase
10
+
11
+ include Helper::Client
12
+
13
+ def test_scan_basic
14
+ target_version "2.7.105" do
15
+ r.debug :populate, 1000
16
+
17
+ cursor = 0
18
+ all_keys = []
19
+ loop {
20
+ cursor, keys = r.scan cursor
21
+ all_keys += keys
22
+ break if cursor == "0"
23
+ }
24
+
25
+ assert_equal 1000, all_keys.uniq.size
26
+ end
27
+ end
28
+
29
+ def test_scan_count
30
+ target_version "2.7.105" do
31
+ r.debug :populate, 1000
32
+
33
+ cursor = 0
34
+ all_keys = []
35
+ loop {
36
+ cursor, keys = r.scan cursor, :count => 5
37
+ all_keys += keys
38
+ break if cursor == "0"
39
+ }
40
+
41
+ assert_equal 1000, all_keys.uniq.size
42
+ end
43
+ end
44
+
45
+ def test_scan_match
46
+ target_version "2.7.105" do
47
+ r.debug :populate, 1000
48
+
49
+ cursor = 0
50
+ all_keys = []
51
+ loop {
52
+ cursor, keys = r.scan cursor, :match => "key:1??"
53
+ all_keys += keys
54
+ break if cursor == "0"
55
+ }
56
+
57
+ assert_equal 100, all_keys.uniq.size
58
+ end
59
+ end
60
+
61
+ def test_scan_each_enumerator
62
+ target_version "2.7.105" do
63
+
64
+ r.debug :populate, 1000
65
+
66
+ scan_enumerator = r.scan_each
67
+ assert_equal true, scan_enumerator.is_a?(::Enumerator)
68
+
69
+ keys_from_scan = scan_enumerator.to_a.uniq
70
+ all_keys = r.keys "*"
71
+
72
+ assert all_keys.sort == keys_from_scan.sort
73
+ end
74
+ end
75
+
76
+ def test_scan_each_enumerator_match
77
+ target_version "2.7.105" do
78
+
79
+ r.debug :populate, 1000
80
+
81
+ keys_from_scan = r.scan_each(:match => "key:1??").to_a.uniq
82
+ all_keys = r.keys "key:1??"
83
+
84
+ assert all_keys.sort == keys_from_scan.sort
85
+ end
86
+ end
87
+
88
+ def test_scan_each_block
89
+ target_version "2.7.105" do
90
+
91
+ r.debug :populate, 100
92
+
93
+ keys_from_scan = []
94
+ r.scan_each {|key|
95
+ keys_from_scan << key
96
+ }
97
+
98
+ all_keys = r.keys "*"
99
+
100
+ assert all_keys.sort == keys_from_scan.uniq.sort
101
+ end
102
+ end
103
+
104
+ def test_scan_each_block_match
105
+ target_version "2.7.105" do
106
+
107
+ r.debug :populate, 100
108
+
109
+ keys_from_scan = []
110
+ r.scan_each(:match => "key:1?") {|key|
111
+ keys_from_scan << key
112
+ }
113
+
114
+ all_keys = r.keys "key:1?"
115
+
116
+ assert all_keys.sort == keys_from_scan.uniq.sort
117
+ end
118
+ end
119
+
120
+ def test_sscan_with_encoding
121
+ target_version "2.7.105" do
122
+ [:intset, :hashtable].each do |enc|
123
+ r.del "set"
124
+
125
+ prefix = ""
126
+ prefix = "ele:" if enc == :hashtable
127
+
128
+ elements = []
129
+ 100.times { |j| elements << "#{prefix}#{j}" }
130
+
131
+ r.sadd "set", elements
132
+
133
+ assert_equal enc.to_s, r.object("encoding", "set")
134
+
135
+ cursor = 0
136
+ all_keys = []
137
+ loop {
138
+ cursor, keys = r.sscan "set", cursor
139
+ all_keys += keys
140
+ break if cursor == "0"
141
+ }
142
+
143
+ assert_equal 100, all_keys.uniq.size
144
+ end
145
+ end
146
+ end
147
+
148
+ def test_sscan_each_enumerator
149
+ target_version "2.7.105" do
150
+ elements = []
151
+ 100.times { |j| elements << "ele:#{j}" }
152
+ r.sadd "set", elements
153
+
154
+ scan_enumerator = r.sscan_each("set")
155
+ assert_equal true, scan_enumerator.is_a?(::Enumerator)
156
+
157
+ keys_from_scan = scan_enumerator.to_a.uniq
158
+ all_keys = r.smembers("set")
159
+
160
+ assert all_keys.sort == keys_from_scan.sort
161
+ end
162
+ end
163
+
164
+ def test_sscan_each_enumerator_match
165
+ target_version "2.7.105" do
166
+ elements = []
167
+ 100.times { |j| elements << "ele:#{j}" }
168
+ r.sadd "set", elements
169
+
170
+ keys_from_scan = r.sscan_each("set", :match => "ele:1?").to_a.uniq
171
+
172
+ all_keys = r.smembers("set").grep(/^ele:1.$/)
173
+
174
+ assert all_keys.sort == keys_from_scan.sort
175
+ end
176
+ end
177
+
178
+ def test_sscan_each_enumerator_block
179
+ target_version "2.7.105" do
180
+ elements = []
181
+ 100.times { |j| elements << "ele:#{j}" }
182
+ r.sadd "set", elements
183
+
184
+ keys_from_scan = []
185
+ r.sscan_each("set") do |key|
186
+ keys_from_scan << key
187
+ end
188
+
189
+ all_keys = r.smembers("set")
190
+
191
+ assert all_keys.sort == keys_from_scan.uniq.sort
192
+ end
193
+ end
194
+
195
+ def test_sscan_each_enumerator_block_match
196
+ target_version "2.7.105" do
197
+ elements = []
198
+ 100.times { |j| elements << "ele:#{j}" }
199
+ r.sadd "set", elements
200
+
201
+ keys_from_scan = []
202
+ r.sscan_each("set", :match => "ele:1?") do |key|
203
+ keys_from_scan << key
204
+ end
205
+
206
+ all_keys = r.smembers("set").grep(/^ele:1.$/)
207
+
208
+ assert all_keys.sort == keys_from_scan.uniq.sort
209
+ end
210
+ end
211
+
212
+ def test_hscan_with_encoding
213
+ target_version "2.7.105" do
214
+ [:ziplist, :hashtable].each do |enc|
215
+ r.del "set"
216
+
217
+ count = 1000
218
+ count = 30 if enc == :ziplist
219
+
220
+ elements = []
221
+ count.times { |j| elements << "key:#{j}" << j.to_s }
222
+
223
+ r.hmset "hash", *elements
224
+
225
+ assert_equal enc.to_s, r.object("encoding", "hash")
226
+
227
+ cursor = 0
228
+ all_key_values = []
229
+ loop {
230
+ cursor, key_values = r.hscan "hash", cursor
231
+ all_key_values.concat key_values
232
+ break if cursor == "0"
233
+ }
234
+
235
+ keys2 = []
236
+ all_key_values.each do |k, v|
237
+ assert_equal "key:#{v}", k
238
+ keys2 << k
239
+ end
240
+
241
+ assert_equal count, keys2.uniq.size
242
+ end
243
+ end
244
+ end
245
+
246
+ def test_hscan_each_enumerator
247
+ target_version "2.7.105" do
248
+ count = 1000
249
+ elements = []
250
+ count.times { |j| elements << "key:#{j}" << j.to_s }
251
+ r.hmset "hash", *elements
252
+
253
+ scan_enumerator = r.hscan_each("hash")
254
+ assert_equal true, scan_enumerator.is_a?(::Enumerator)
255
+
256
+ keys_from_scan = scan_enumerator.to_a.uniq
257
+ all_keys = r.hgetall("hash").to_a
258
+
259
+ assert all_keys.sort == keys_from_scan.sort
260
+ end
261
+ end
262
+
263
+ def test_hscan_each_enumerator_match
264
+ target_version "2.7.105" do
265
+ count = 100
266
+ elements = []
267
+ count.times { |j| elements << "key:#{j}" << j.to_s }
268
+ r.hmset "hash", *elements
269
+
270
+ keys_from_scan = r.hscan_each("hash", :match => "key:1?").to_a.uniq
271
+ all_keys = r.hgetall("hash").to_a.select{|k,v| k =~ /^key:1.$/}
272
+
273
+ assert all_keys.sort == keys_from_scan.sort
274
+ end
275
+ end
276
+
277
+ def test_hscan_each_block
278
+ target_version "2.7.105" do
279
+ count = 1000
280
+ elements = []
281
+ count.times { |j| elements << "key:#{j}" << j.to_s }
282
+ r.hmset "hash", *elements
283
+
284
+ keys_from_scan = []
285
+ r.hscan_each("hash") do |field, value|
286
+ keys_from_scan << [field, value]
287
+ end
288
+ all_keys = r.hgetall("hash").to_a
289
+
290
+ assert all_keys.sort == keys_from_scan.uniq.sort
291
+ end
292
+ end
293
+
294
+ def test_hscan_each_block_match
295
+ target_version "2.7.105" do
296
+ count = 1000
297
+ elements = []
298
+ count.times { |j| elements << "key:#{j}" << j.to_s }
299
+ r.hmset "hash", *elements
300
+
301
+ keys_from_scan = []
302
+ r.hscan_each("hash", :match => "key:1?") do |field, value|
303
+ keys_from_scan << [field, value]
304
+ end
305
+ all_keys = r.hgetall("hash").to_a.select{|k,v| k =~ /^key:1.$/}
306
+
307
+ assert all_keys.sort == keys_from_scan.uniq.sort
308
+ end
309
+ end
310
+
311
+ def test_zscan_with_encoding
312
+ target_version "2.7.105" do
313
+ [:ziplist, :skiplist].each do |enc|
314
+ r.del "zset"
315
+
316
+ count = 1000
317
+ count = 30 if enc == :ziplist
318
+
319
+ elements = []
320
+ count.times { |j| elements << j << "key:#{j}" }
321
+
322
+ r.zadd "zset", elements
323
+
324
+ assert_equal enc.to_s, r.object("encoding", "zset")
325
+
326
+ cursor = 0
327
+ all_key_scores = []
328
+ loop {
329
+ cursor, key_scores = r.zscan "zset", cursor
330
+ all_key_scores.concat key_scores
331
+ break if cursor == "0"
332
+ }
333
+
334
+ keys2 = []
335
+ all_key_scores.each do |k, v|
336
+ assert_equal true, v.is_a?(Float)
337
+ assert_equal "key:#{Integer(v)}", k
338
+ keys2 << k
339
+ end
340
+
341
+ assert_equal count, keys2.uniq.size
342
+ end
343
+ end
344
+ end
345
+
346
+ def test_zscan_each_enumerator
347
+ target_version "2.7.105" do
348
+ count = 1000
349
+ elements = []
350
+ count.times { |j| elements << j << "key:#{j}" }
351
+ r.zadd "zset", elements
352
+
353
+ scan_enumerator = r.zscan_each "zset"
354
+ assert_equal true, scan_enumerator.is_a?(::Enumerator)
355
+
356
+ scores_from_scan = scan_enumerator.to_a.uniq
357
+ member_scores = r.zrange("zset", 0, -1, :with_scores => true)
358
+
359
+ assert member_scores.sort == scores_from_scan.sort
360
+ end
361
+ end
362
+
363
+ def test_zscan_each_enumerator_match
364
+ target_version "2.7.105" do
365
+ count = 1000
366
+ elements = []
367
+ count.times { |j| elements << j << "key:#{j}" }
368
+ r.zadd "zset", elements
369
+
370
+ scores_from_scan = r.zscan_each("zset", :match => "key:1??").to_a.uniq
371
+ member_scores = r.zrange("zset", 0, -1, :with_scores => true)
372
+ filtered_members = member_scores.select{|k,s| k =~ /^key:1..$/}
373
+
374
+ assert filtered_members.sort == scores_from_scan.sort
375
+ end
376
+ end
377
+
378
+ def test_zscan_each_block
379
+ target_version "2.7.105" do
380
+ count = 1000
381
+ elements = []
382
+ count.times { |j| elements << j << "key:#{j}" }
383
+ r.zadd "zset", elements
384
+
385
+ scores_from_scan = []
386
+ r.zscan_each("zset") do |member, score|
387
+ scores_from_scan << [member, score]
388
+ end
389
+ member_scores = r.zrange("zset", 0, -1, :with_scores => true)
390
+
391
+ assert member_scores.sort == scores_from_scan.sort
392
+ end
393
+ end
394
+
395
+ def test_zscan_each_block_match
396
+ target_version "2.7.105" do
397
+ count = 1000
398
+ elements = []
399
+ count.times { |j| elements << j << "key:#{j}" }
400
+ r.zadd "zset", elements
401
+
402
+ scores_from_scan = []
403
+ r.zscan_each("zset", :match => "key:1??") do |member, score|
404
+ scores_from_scan << [member, score]
405
+ end
406
+ member_scores = r.zrange("zset", 0, -1, :with_scores => true)
407
+ filtered_members = member_scores.select{|k,s| k =~ /^key:1..$/}
408
+
409
+ assert filtered_members.sort == scores_from_scan.sort
410
+ end
411
+ end
412
+
413
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestScripting < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ def to_sha(script)
10
+ r.script(:load, script)
11
+ end
12
+
13
+ def test_script_exists
14
+ target_version "2.5.9" do # 2.6-rc1
15
+ a = to_sha("return 1")
16
+ b = a.succ
17
+
18
+ assert_equal true, r.script(:exists, a)
19
+ assert_equal false, r.script(:exists, b)
20
+ assert_equal [true], r.script(:exists, [a])
21
+ assert_equal [false], r.script(:exists, [b])
22
+ assert_equal [true, false], r.script(:exists, [a, b])
23
+ end
24
+ end
25
+
26
+ def test_script_flush
27
+ target_version "2.5.9" do # 2.6-rc1
28
+ sha = to_sha("return 1")
29
+ assert r.script(:exists, sha)
30
+ assert_equal "OK", r.script(:flush)
31
+ assert !r.script(:exists, sha)
32
+ end
33
+ end
34
+
35
+ def test_script_kill
36
+ target_version "2.5.9" do # 2.6-rc1
37
+ redis_mock(:script => lambda { |arg| "+#{arg.upcase}" }) do |redis|
38
+ assert_equal "KILL", redis.script(:kill)
39
+ end
40
+ end
41
+ end
42
+
43
+ def test_eval
44
+ target_version "2.5.9" do # 2.6-rc1
45
+ assert_equal 0, r.eval("return #KEYS")
46
+ assert_equal 0, r.eval("return #ARGV")
47
+ assert_equal ["k1", "k2"], r.eval("return KEYS", ["k1", "k2"])
48
+ assert_equal ["a1", "a2"], r.eval("return ARGV", [], ["a1", "a2"])
49
+ end
50
+ end
51
+
52
+ def test_eval_with_options_hash
53
+ target_version "2.5.9" do # 2.6-rc1
54
+ assert_equal 0, r.eval("return #KEYS", {})
55
+ assert_equal 0, r.eval("return #ARGV", {})
56
+ assert_equal ["k1", "k2"], r.eval("return KEYS", { :keys => ["k1", "k2"] })
57
+ assert_equal ["a1", "a2"], r.eval("return ARGV", { :argv => ["a1", "a2"] })
58
+ end
59
+ end
60
+
61
+ def test_evalsha
62
+ target_version "2.5.9" do # 2.6-rc1
63
+ assert_equal 0, r.evalsha(to_sha("return #KEYS"))
64
+ assert_equal 0, r.evalsha(to_sha("return #ARGV"))
65
+ assert_equal ["k1", "k2"], r.evalsha(to_sha("return KEYS"), ["k1", "k2"])
66
+ assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), [], ["a1", "a2"])
67
+ end
68
+ end
69
+
70
+ def test_evalsha_with_options_hash
71
+ target_version "2.5.9" do # 2.6-rc1
72
+ assert_equal 0, r.evalsha(to_sha("return #KEYS"), {})
73
+ assert_equal 0, r.evalsha(to_sha("return #ARGV"), {})
74
+ assert_equal ["k1", "k2"], r.evalsha(to_sha("return KEYS"), { :keys => ["k1", "k2"] })
75
+ assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), { :argv => ["a1", "a2"] })
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestSorting < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ def test_sort
10
+ r.set("foo:1", "s1")
11
+ r.set("foo:2", "s2")
12
+
13
+ r.rpush("bar", "1")
14
+ r.rpush("bar", "2")
15
+
16
+ assert_equal ["s1"], r.sort("bar", :get => "foo:*", :limit => [0, 1])
17
+ assert_equal ["s2"], r.sort("bar", :get => "foo:*", :limit => [0, 1], :order => "desc alpha")
18
+ end
19
+
20
+ def test_sort_with_an_array_of_gets
21
+ r.set("foo:1:a", "s1a")
22
+ r.set("foo:1:b", "s1b")
23
+
24
+ r.set("foo:2:a", "s2a")
25
+ r.set("foo:2:b", "s2b")
26
+
27
+ r.rpush("bar", "1")
28
+ r.rpush("bar", "2")
29
+
30
+ assert_equal [["s1a", "s1b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1])
31
+ assert_equal [["s2a", "s2b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1], :order => "desc alpha")
32
+ assert_equal [["s1a", "s1b"], ["s2a", "s2b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"])
33
+ end
34
+
35
+ def test_sort_with_store
36
+ r.set("foo:1", "s1")
37
+ r.set("foo:2", "s2")
38
+
39
+ r.rpush("bar", "1")
40
+ r.rpush("bar", "2")
41
+
42
+ r.sort("bar", :get => "foo:*", :store => "baz")
43
+ assert_equal ["s1", "s2"], r.lrange("baz", 0, -1)
44
+ end
45
+
46
+ def test_sort_with_an_array_of_gets_and_with_store
47
+ r.set("foo:1:a", "s1a")
48
+ r.set("foo:1:b", "s1b")
49
+
50
+ r.set("foo:2:a", "s2a")
51
+ r.set("foo:2:b", "s2b")
52
+
53
+ r.rpush("bar", "1")
54
+ r.rpush("bar", "2")
55
+
56
+ r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :store => 'baz')
57
+ assert_equal ["s1a", "s1b", "s2a", "s2b"], r.lrange("baz", 0, -1)
58
+ end
59
+ end
@@ -0,0 +1 @@
1
+ require "support/wire/thread"
@@ -0,0 +1 @@
1
+ require "support/wire/thread"
@@ -0,0 +1,17 @@
1
+ require "support/wire/synchrony"
2
+
3
+ module Helper
4
+ def around
5
+ rv = nil
6
+
7
+ EM.synchrony do
8
+ begin
9
+ rv = yield
10
+ ensure
11
+ EM.stop
12
+ end
13
+ end
14
+
15
+ rv
16
+ end
17
+ end
@@ -0,0 +1,115 @@
1
+ require "socket"
2
+
3
+ module Redis2Mock
4
+ class Server
5
+ VERBOSE = false
6
+
7
+ def initialize(port, options = {}, &block)
8
+ @server = TCPServer.new(options[:host] || "127.0.0.1", port)
9
+ @server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
10
+ end
11
+
12
+ def start(&block)
13
+ @thread = Thread.new { run(&block) }
14
+ end
15
+
16
+ # Bail out of @server.accept before closing the socket. This is required
17
+ # to avoid EADDRINUSE after a couple of iterations.
18
+ def shutdown
19
+ @thread.terminate if @thread
20
+ @server.close if @server
21
+ rescue => ex
22
+ $stderr.puts "Error closing mock server: #{ex.message}" if VERBOSE
23
+ $stderr.puts ex.backtrace if VERBOSE
24
+ end
25
+
26
+ def run
27
+ loop do
28
+ session = @server.accept
29
+
30
+ begin
31
+ return if yield(session) == :exit
32
+ ensure
33
+ session.close
34
+ end
35
+ end
36
+ rescue => ex
37
+ $stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
38
+ $stderr.puts ex.backtrace if VERBOSE
39
+ ensure
40
+ @server.close
41
+ end
42
+ end
43
+
44
+ MOCK_PORT = 6382
45
+
46
+ # Starts a mock Redis2 server in a thread.
47
+ #
48
+ # The server will use the lambda handler passed as argument to handle
49
+ # connections. For example:
50
+ #
51
+ # handler = lambda { |session| session.close }
52
+ # Redis2Mock.start_with_handler(handler) do
53
+ # # Every connection will be closed immediately
54
+ # end
55
+ #
56
+ def self.start_with_handler(blk, options = {})
57
+ server = Server.new(MOCK_PORT, options)
58
+
59
+ begin
60
+ server.start(&blk)
61
+
62
+ yield(MOCK_PORT)
63
+
64
+ ensure
65
+ server.shutdown
66
+ end
67
+ end
68
+
69
+ # Starts a mock Redis2 server in a thread.
70
+ #
71
+ # The server will reply with a `+OK` to all commands, but you can
72
+ # customize it by providing a hash. For example:
73
+ #
74
+ # Redis2Mock.start(:ping => lambda { "+PONG" }) do
75
+ # assert_equal "PONG", Redis2.new(:port => MOCK_PORT).ping
76
+ # end
77
+ #
78
+ def self.start(commands, options = {}, &blk)
79
+ handler = lambda do |session|
80
+ while line = session.gets
81
+ argv = Array.new(line[1..-3].to_i) do
82
+ bytes = session.gets[1..-3].to_i
83
+ arg = session.read(bytes)
84
+ session.read(2) # Discard \r\n
85
+ arg
86
+ end
87
+
88
+ command = argv.shift
89
+ blk = commands[command.to_sym]
90
+ blk ||= lambda { |*_| "+OK" }
91
+
92
+ response = blk.call(*argv)
93
+
94
+ # Convert a nil response to :close
95
+ response ||= :close
96
+
97
+ if response == :exit
98
+ break :exit
99
+ elsif response == :close
100
+ break :close
101
+ elsif response.is_a?(Array)
102
+ session.write("*%d\r\n" % response.size)
103
+ response.each do |e|
104
+ session.write("$%d\r\n%s\r\n" % [e.length, e])
105
+ end
106
+ else
107
+ session.write(response)
108
+ session.write("\r\n") unless response.end_with?("\r\n")
109
+ end
110
+ end
111
+ end
112
+
113
+ start_with_handler(handler, options, &blk)
114
+ end
115
+ end
@@ -0,0 +1,24 @@
1
+ class Wire < Fiber
2
+ # We cannot run this fiber explicitly because EM schedules it. Resuming the
3
+ # current fiber on the next tick to let the reactor do work.
4
+ def self.pass
5
+ f = Fiber.current
6
+ EM.next_tick { f.resume }
7
+ Fiber.yield
8
+ end
9
+
10
+ def self.sleep(sec)
11
+ EM::Synchrony.sleep(sec)
12
+ end
13
+
14
+ def initialize(&blk)
15
+ super
16
+
17
+ # Schedule run in next tick
18
+ EM.next_tick { resume }
19
+ end
20
+
21
+ def join
22
+ self.class.pass while alive?
23
+ end
24
+ end