redis 3.0.0.rc1 → 3.0.0.rc2

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 (77) hide show
  1. data/.travis.yml +50 -0
  2. data/.travis/Gemfile +11 -0
  3. data/CHANGELOG.md +47 -19
  4. data/README.md +160 -149
  5. data/Rakefile +15 -50
  6. data/examples/pubsub.rb +1 -1
  7. data/examples/unicorn/config.ru +1 -1
  8. data/examples/unicorn/unicorn.rb +1 -1
  9. data/lib/redis.rb +790 -390
  10. data/lib/redis/client.rb +137 -49
  11. data/lib/redis/connection/hiredis.rb +26 -15
  12. data/lib/redis/connection/ruby.rb +170 -53
  13. data/lib/redis/connection/synchrony.rb +23 -35
  14. data/lib/redis/distributed.rb +92 -32
  15. data/lib/redis/errors.rb +4 -2
  16. data/lib/redis/pipeline.rb +17 -6
  17. data/lib/redis/version.rb +1 -1
  18. data/redis.gemspec +4 -6
  19. data/test/blocking_commands_test.rb +42 -0
  20. data/test/command_map_test.rb +18 -17
  21. data/test/commands_on_hashes_test.rb +13 -12
  22. data/test/commands_on_lists_test.rb +35 -45
  23. data/test/commands_on_sets_test.rb +55 -54
  24. data/test/commands_on_sorted_sets_test.rb +106 -105
  25. data/test/commands_on_strings_test.rb +64 -55
  26. data/test/commands_on_value_types_test.rb +66 -54
  27. data/test/connection_handling_test.rb +136 -151
  28. data/test/distributed_blocking_commands_test.rb +33 -40
  29. data/test/distributed_commands_on_hashes_test.rb +6 -7
  30. data/test/distributed_commands_on_lists_test.rb +13 -14
  31. data/test/distributed_commands_on_sets_test.rb +57 -58
  32. data/test/distributed_commands_on_sorted_sets_test.rb +11 -12
  33. data/test/distributed_commands_on_strings_test.rb +31 -32
  34. data/test/distributed_commands_on_value_types_test.rb +61 -46
  35. data/test/distributed_commands_requiring_clustering_test.rb +108 -108
  36. data/test/distributed_connection_handling_test.rb +14 -15
  37. data/test/distributed_internals_test.rb +7 -19
  38. data/test/distributed_key_tags_test.rb +36 -36
  39. data/test/distributed_persistence_control_commands_test.rb +17 -14
  40. data/test/distributed_publish_subscribe_test.rb +61 -69
  41. data/test/distributed_remote_server_control_commands_test.rb +39 -28
  42. data/test/distributed_sorting_test.rb +12 -13
  43. data/test/distributed_test.rb +40 -41
  44. data/test/distributed_transactions_test.rb +20 -21
  45. data/test/encoding_test.rb +12 -9
  46. data/test/error_replies_test.rb +42 -36
  47. data/test/helper.rb +118 -85
  48. data/test/helper_test.rb +20 -6
  49. data/test/internals_test.rb +167 -103
  50. data/test/lint/blocking_commands.rb +124 -0
  51. data/test/lint/hashes.rb +115 -93
  52. data/test/lint/lists.rb +86 -80
  53. data/test/lint/sets.rb +68 -62
  54. data/test/lint/sorted_sets.rb +200 -195
  55. data/test/lint/strings.rb +112 -94
  56. data/test/lint/value_types.rb +76 -55
  57. data/test/persistence_control_commands_test.rb +17 -12
  58. data/test/pipelining_commands_test.rb +135 -126
  59. data/test/publish_subscribe_test.rb +105 -110
  60. data/test/remote_server_control_commands_test.rb +74 -58
  61. data/test/sorting_test.rb +31 -29
  62. data/test/support/connection/hiredis.rb +1 -0
  63. data/test/support/connection/ruby.rb +1 -0
  64. data/test/support/connection/synchrony.rb +17 -0
  65. data/test/{redis_mock.rb → support/redis_mock.rb} +24 -21
  66. data/test/support/wire/synchrony.rb +24 -0
  67. data/test/support/wire/thread.rb +5 -0
  68. data/test/synchrony_driver.rb +9 -9
  69. data/test/test.conf +1 -1
  70. data/test/thread_safety_test.rb +21 -19
  71. data/test/transactions_test.rb +189 -118
  72. data/test/unknown_commands_test.rb +9 -8
  73. data/test/url_param_test.rb +46 -41
  74. metadata +28 -43
  75. data/TODO.md +0 -4
  76. data/benchmarking/thread_safety.rb +0 -38
  77. data/test/lint/internals.rb +0 -36
@@ -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
@@ -4,9 +4,12 @@ module RedisMock
4
4
  class Server
5
5
  VERBOSE = false
6
6
 
7
- def initialize(port = 6380, &block)
7
+ def initialize(port, &block)
8
8
  @server = TCPServer.new("127.0.0.1", port)
9
9
  @server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
10
+ end
11
+
12
+ def start(&block)
10
13
  @thread = Thread.new { run(&block) }
11
14
  end
12
15
 
@@ -61,29 +64,29 @@ module RedisMock
61
64
  end
62
65
  end
63
66
 
64
- module Helper
65
- # Starts a mock Redis server in a thread on port 6380.
66
- #
67
- # The server will reply with a `+OK` to all commands, but you can
68
- # customize it by providing a hash. For example:
69
- #
70
- # redis_mock(:ping => lambda { "+PONG" }) do
71
- # assert_equal "PONG", Redis.new(:port => 6380).ping
72
- # end
73
- #
74
- def redis_mock(replies = {})
75
- begin
76
- server = Server.new do |command, *args|
77
- (replies[command.to_sym] || lambda { |*_| "+OK" }).call(*args)
78
- end
79
-
80
- sleep 0.1 # Give time for the socket to start listening.
67
+ MOCK_PORT = 6382
81
68
 
82
- yield
69
+ # Starts a mock Redis 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
+ # RedisMock.start(:ping => lambda { "+PONG" }) do
75
+ # assert_equal "PONG", Redis.new(:port => MOCK_PORT).ping
76
+ # end
77
+ #
78
+ def self.start(commands = {})
79
+ server = Server.new(MOCK_PORT)
83
80
 
84
- ensure
85
- server.shutdown
81
+ begin
82
+ server.start do |command, *args|
83
+ (commands[command.to_sym] || lambda { |*_| "+OK" }).call(*args)
86
84
  end
85
+
86
+ yield(MOCK_PORT)
87
+
88
+ ensure
89
+ server.shutdown
87
90
  end
88
91
  end
89
92
  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
@@ -0,0 +1,5 @@
1
+ class Wire < Thread
2
+ def self.sleep(sec)
3
+ Kernel.sleep(sec)
4
+ end
5
+ end
@@ -20,13 +20,13 @@ EM.synchrony do
20
20
  r.rpush "foo", "s1"
21
21
  r.rpush "foo", "s2"
22
22
 
23
- assert 2 == r.llen("foo")
24
- assert "s2" == r.rpop("foo")
23
+ assert_equal 2, r.llen("foo")
24
+ assert_equal "s2", r.rpop("foo")
25
25
 
26
26
  r.set("foo", "bar")
27
27
 
28
- assert "bar" == r.getset("foo", "baz")
29
- assert "baz" == r.get("foo")
28
+ assert_equal "bar", r.getset("foo", "baz")
29
+ assert_equal "baz", r.get("foo")
30
30
 
31
31
  r.set("foo", "a")
32
32
 
@@ -46,12 +46,12 @@ EM.synchrony do
46
46
  r.lpush "foo", "s2"
47
47
  end
48
48
 
49
- assert 2 == r.llen("foo")
50
- assert "s2" == r.lpop("foo")
51
- assert "s1" == r.lpop("foo")
49
+ assert_equal 2, r.llen("foo")
50
+ assert_equal "s2", r.lpop("foo")
51
+ assert_equal "s1", r.lpop("foo")
52
52
 
53
- assert "OK" == r.client.call(:quit)
54
- assert "PONG" == r.ping
53
+ assert_equal "OK", r.client.call(:quit)
54
+ assert_equal "PONG", r.ping
55
55
 
56
56
  EM.stop
57
57
  end
@@ -1,6 +1,6 @@
1
1
  dir ./test/db
2
2
  pidfile ./redis.pid
3
- port 6379
3
+ port 6381
4
4
  unixsocket /tmp/redis.sock
5
5
  timeout 300
6
6
  loglevel debug
@@ -1,30 +1,32 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require File.expand_path("./helper", File.dirname(__FILE__))
3
+ require "helper"
4
4
 
5
- setup do
6
- init Redis.new(OPTIONS)
7
- end
5
+ class TestThreadSafety < Test::Unit::TestCase
8
6
 
9
- test "thread safety" do
10
- redis = Redis.connect(OPTIONS.merge(:thread_safe => true))
7
+ include Helper::Client
11
8
 
12
- redis.set "foo", 1
13
- redis.set "bar", 2
9
+ driver(:ruby, :hiredis) do
10
+ def test_thread_safety
11
+ redis = Redis.new(OPTIONS)
12
+ redis.set "foo", 1
13
+ redis.set "bar", 2
14
14
 
15
- sample = 100
15
+ sample = 100
16
16
 
17
- t1 = Thread.new do
18
- $foos = Array.new(sample) { redis.get "foo" }
19
- end
17
+ t1 = Thread.new do
18
+ $foos = Array.new(sample) { redis.get "foo" }
19
+ end
20
20
 
21
- t2 = Thread.new do
22
- $bars = Array.new(sample) { redis.get "bar" }
23
- end
21
+ t2 = Thread.new do
22
+ $bars = Array.new(sample) { redis.get "bar" }
23
+ end
24
24
 
25
- t1.join
26
- t2.join
25
+ t1.join
26
+ t2.join
27
27
 
28
- assert_equal ["1"], $foos.uniq
29
- assert_equal ["2"], $bars.uniq
28
+ assert_equal ["1"], $foos.uniq
29
+ assert_equal ["2"], $bars.uniq
30
+ end
31
+ end
30
32
  end
@@ -1,173 +1,244 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require File.expand_path("./helper", File.dirname(__FILE__))
3
+ require "helper"
4
4
 
5
- setup do
6
- init Redis.new(OPTIONS)
7
- end
5
+ class TestTransactions < Test::Unit::TestCase
8
6
 
9
- test "MULTI/DISCARD" do |r|
10
- r.multi
7
+ include Helper::Client
11
8
 
12
- assert "QUEUED" == r.set("foo", "1")
13
- assert "QUEUED" == r.get("foo")
9
+ def test_multi_discard
10
+ r.multi
14
11
 
15
- r.discard
12
+ assert_equal "QUEUED", r.set("foo", "1")
13
+ assert_equal "QUEUED", r.get("foo")
16
14
 
17
- assert nil == r.get("foo")
18
- end
15
+ r.discard
19
16
 
20
- test "MULTI/EXEC with a block" do |r|
21
- r.multi do |multi|
22
- multi.set "foo", "s1"
17
+ assert_equal nil, r.get("foo")
23
18
  end
24
19
 
25
- assert "s1" == r.get("foo")
26
- end
20
+ def test_multi_exec_with_a_block
21
+ r.multi do |multi|
22
+ multi.set "foo", "s1"
23
+ end
27
24
 
28
- test "MULTI/EXEC with a block doesn't return replies for MULTI and EXEC" do |r|
29
- r1, r2, nothing_else = r.multi do |multi|
30
- multi.set "foo", "s1"
31
- multi.get "foo"
25
+ assert_equal "s1", r.get("foo")
32
26
  end
33
27
 
34
- assert_equal "OK", r1
35
- assert_equal "s1", r2
36
- assert_equal nil, nothing_else
37
- end
28
+ def test_multi_exec_with_a_block_doesn_t_return_replies_for_multi_and_exec
29
+ r1, r2, nothing_else = r.multi do |multi|
30
+ multi.set "foo", "s1"
31
+ multi.get "foo"
32
+ end
38
33
 
39
- test "Assignment inside MULTI/EXEC block" do |r|
40
- r.multi do |m|
41
- @first = m.sadd("foo", 1)
42
- @second = m.sadd("foo", 1)
34
+ assert_equal "OK", r1
35
+ assert_equal "s1", r2
36
+ assert_equal nil, nothing_else
43
37
  end
44
38
 
45
- assert_equal true, @first.value
46
- assert_equal false, @second.value
47
- end
48
-
49
- # Although we could support accessing the values in these futures,
50
- # it doesn't make a lot of sense.
51
- test "Assignment inside MULTI/EXEC block with delayed command errors" do |r|
52
- assert_raise do
39
+ def test_assignment_inside_multi_exec_block
53
40
  r.multi do |m|
54
- @first = m.set("foo", "s1")
55
- @second = m.incr("foo") # not an integer
56
- @third = m.lpush("foo", "value") # wrong kind of value
41
+ @first = m.sadd("foo", 1)
42
+ @second = m.sadd("foo", 1)
57
43
  end
44
+
45
+ assert_equal true, @first.value
46
+ assert_equal false, @second.value
58
47
  end
59
48
 
60
- assert_equal "OK", @first.value
61
- assert_raise { @second.value }
62
- assert_raise { @third.value }
63
- end
49
+ # Although we could support accessing the values in these futures,
50
+ # it doesn't make a lot of sense.
51
+ def test_assignment_inside_multi_exec_block_with_delayed_command_errors
52
+ assert_raise(Redis::CommandError) do
53
+ r.multi do |m|
54
+ @first = m.set("foo", "s1")
55
+ @second = m.incr("foo") # not an integer
56
+ @third = m.lpush("foo", "value") # wrong kind of value
57
+ end
58
+ end
64
59
 
65
- test "Assignment inside MULTI/EXEC block with immediate command errors" do |r|
66
- assert_raise do
67
- r.multi do |m|
68
- m.doesnt_exist
69
- @first = m.sadd("foo", 1)
70
- m.doesnt_exist
71
- @second = m.sadd("foo", 1)
72
- m.doesnt_exist
60
+ assert_equal "OK", @first.value
61
+ assert_raise(Redis::CommandError) { @second.value }
62
+ assert_raise(Redis::FutureNotReady) { @third.value }
63
+ end
64
+
65
+ def test_assignment_inside_multi_exec_block_with_immediate_command_errors
66
+ assert_raise(Redis::CommandError) do
67
+ r.multi do |m|
68
+ m.doesnt_exist
69
+ @first = m.sadd("foo", 1)
70
+ m.doesnt_exist
71
+ @second = m.sadd("foo", 1)
72
+ m.doesnt_exist
73
+ end
73
74
  end
75
+
76
+ assert_raise(Redis::FutureNotReady) { @first.value }
77
+ assert_raise(Redis::FutureNotReady) { @second.value }
74
78
  end
75
79
 
76
- assert_raise(Redis::FutureNotReady) { @first.value }
77
- assert_raise(Redis::FutureNotReady) { @second.value }
78
- end
80
+ def test_raise_immediate_errors_in_multi_exec
81
+ assert_raise(RuntimeError) do
82
+ r.multi do |multi|
83
+ multi.set "bar", "s2"
84
+ raise "Some error"
85
+ multi.set "baz", "s3"
86
+ end
87
+ end
79
88
 
80
- test "Raise immediate errors in MULTI/EXEC" do |r|
81
- assert_raise(RuntimeError) do
82
- r.multi do |multi|
83
- multi.set "bar", "s2"
84
- raise "Some error"
85
- multi.set "baz", "s3"
89
+ assert_equal nil, r.get("bar")
90
+ assert_equal nil, r.get("baz")
91
+ end
92
+
93
+ def test_transformed_replies_as_return_values_for_multi_exec_block
94
+ info, _ = r.multi do |m|
95
+ r.info
86
96
  end
97
+
98
+ assert info.kind_of?(Hash)
87
99
  end
88
100
 
89
- assert nil == r.get("bar")
90
- assert nil == r.get("baz")
91
- end
101
+ def test_transformed_replies_inside_multi_exec_block
102
+ r.multi do |m|
103
+ @info = r.info
104
+ end
92
105
 
93
- test "Transformed replies as return values for MULTI/EXEC block" do |r|
94
- info, _ = r.multi do |m|
95
- r.info
106
+ assert @info.value.kind_of?(Hash)
96
107
  end
97
108
 
98
- assert info.kind_of?(Hash)
99
- end
109
+ def test_raise_command_errors_in_multi_exec
110
+ assert_raise(Redis::CommandError) do
111
+ r.multi do |m|
112
+ m.set("foo", "s1")
113
+ m.incr("foo") # not an integer
114
+ m.lpush("foo", "value") # wrong kind of value
115
+ end
116
+ end
100
117
 
101
- test "Transformed replies inside MULTI/EXEC block" do |r|
102
- r.multi do |m|
103
- @info = r.info
118
+ assert_equal "s1", r.get("foo")
104
119
  end
105
120
 
106
- assert @info.value.kind_of?(Hash)
107
- end
121
+ def test_raise_command_errors_when_accessing_futures_after_multi_exec
122
+ begin
123
+ r.multi do |m|
124
+ m.set("foo", "s1")
125
+ @counter = m.incr("foo") # not an integer
126
+ end
127
+ rescue Exception
128
+ # Not gonna deal with it
129
+ end
108
130
 
109
- test "Raise command errors in MULTI/EXEC" do |r|
110
- assert_raise do
111
- r.multi do |m|
112
- m.set("foo", "s1")
113
- m.incr("foo") # not an integer
114
- m.lpush("foo", "value") # wrong kind of value
131
+ # We should test for Redis::Error here, but hiredis doesn't yet do
132
+ # custom error classes.
133
+ err = nil
134
+ begin
135
+ @counter.value
136
+ rescue => err
115
137
  end
138
+
139
+ assert err.kind_of?(RuntimeError)
116
140
  end
117
141
 
118
- assert "s1" == r.get("foo")
119
- end
142
+ def test_multi_with_a_block_yielding_the_client
143
+ r.multi do |multi|
144
+ multi.set "foo", "s1"
145
+ end
120
146
 
121
- test "Raise command errors when accessing futures after MULTI/EXEC" do |r|
122
- begin
123
- r.multi do |m|
124
- m.set("foo", "s1")
125
- @counter = m.incr("foo") # not an integer
147
+ assert_equal "s1", r.get("foo")
148
+ end
149
+
150
+ def test_watch_with_an_unmodified_key
151
+ r.watch "foo"
152
+ r.multi do |multi|
153
+ multi.set "foo", "s1"
126
154
  end
127
- rescue Exception
128
- # Not gonna deal with it
155
+
156
+ assert_equal "s1", r.get("foo")
129
157
  end
130
158
 
131
- # We should test for Redis::Error here, but hiredis doesn't yet do
132
- # custom error classes.
133
- assert_raise(RuntimeError) { @counter.value }
134
- end
159
+ def test_watch_with_an_unmodified_key_passed_as_array
160
+ r.watch ["foo", "bar"]
161
+ r.multi do |multi|
162
+ multi.set "foo", "s1"
163
+ end
135
164
 
136
- test "MULTI with a block yielding the client" do |r|
137
- r.multi do |multi|
138
- multi.set "foo", "s1"
165
+ assert_equal "s1", r.get("foo")
139
166
  end
140
167
 
141
- assert "s1" == r.get("foo")
142
- end
168
+ def test_watch_with_a_modified_key
169
+ r.watch "foo"
170
+ r.set "foo", "s1"
171
+ res = r.multi do |multi|
172
+ multi.set "foo", "s2"
173
+ end
143
174
 
144
- test "WATCH with an unmodified key" do |r|
145
- r.watch "foo"
146
- r.multi do |multi|
147
- multi.set "foo", "s1"
175
+ assert_equal nil, res
176
+ assert_equal "s1", r.get("foo")
148
177
  end
149
178
 
150
- assert "s1" == r.get("foo")
151
- end
179
+ def test_watch_with_a_modified_key_passed_as_array
180
+ r.watch ["foo", "bar"]
181
+ r.set "foo", "s1"
182
+ res = r.multi do |multi|
183
+ multi.set "foo", "s2"
184
+ end
152
185
 
153
- test "WATCH with a modified key" do |r|
154
- r.watch "foo"
155
- r.set "foo", "s1"
156
- res = r.multi do |multi|
157
- multi.set "foo", "s2"
186
+ assert_equal nil, res
187
+ assert_equal "s1", r.get("foo")
158
188
  end
159
189
 
160
- assert nil == res
161
- assert "s1" == r.get("foo")
162
- end
190
+ def test_watch_with_a_block_and_an_unmodified_key
191
+ result = r.watch "foo" do
192
+ r.multi do |multi|
193
+ multi.set "foo", "s1"
194
+ end
195
+ end
163
196
 
164
- test "UNWATCH with a modified key" do |r|
165
- r.watch "foo"
166
- r.set "foo", "s1"
167
- r.unwatch
168
- r.multi do |multi|
169
- multi.set "foo", "s2"
197
+ assert_equal ["OK"], result
198
+ assert_equal "s1", r.get("foo")
170
199
  end
171
200
 
172
- assert "s2" == r.get("foo")
201
+ def test_watch_with_a_block_and_a_modified_key
202
+ result = r.watch "foo" do
203
+ r.set "foo", "s1"
204
+ r.multi do |multi|
205
+ multi.set "foo", "s2"
206
+ end
207
+ end
208
+
209
+ assert_equal nil, result
210
+ assert_equal "s1", r.get("foo")
211
+ end
212
+
213
+ def test_watch_with_a_block_that_raises_an_exception
214
+ r.set("foo", "s1")
215
+
216
+ begin
217
+ r.watch "foo" do
218
+ raise "test"
219
+ end
220
+ rescue RuntimeError
221
+ end
222
+
223
+ r.set("foo", "s2")
224
+
225
+ # If the watch was still set from within the block above, this multi/exec
226
+ # would fail. This proves that raising an exception above unwatches.
227
+ r.multi do |multi|
228
+ multi.set "foo", "s3"
229
+ end
230
+
231
+ assert_equal "s3", r.get("foo")
232
+ end
233
+
234
+ def test_unwatch_with_a_modified_key
235
+ r.watch "foo"
236
+ r.set "foo", "s1"
237
+ r.unwatch
238
+ r.multi do |multi|
239
+ multi.set "foo", "s2"
240
+ end
241
+
242
+ assert_equal "s2", r.get("foo")
243
+ end
173
244
  end