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.
- data/.travis.yml +50 -0
- data/.travis/Gemfile +11 -0
- data/CHANGELOG.md +47 -19
- data/README.md +160 -149
- data/Rakefile +15 -50
- data/examples/pubsub.rb +1 -1
- data/examples/unicorn/config.ru +1 -1
- data/examples/unicorn/unicorn.rb +1 -1
- data/lib/redis.rb +790 -390
- data/lib/redis/client.rb +137 -49
- data/lib/redis/connection/hiredis.rb +26 -15
- data/lib/redis/connection/ruby.rb +170 -53
- data/lib/redis/connection/synchrony.rb +23 -35
- data/lib/redis/distributed.rb +92 -32
- data/lib/redis/errors.rb +4 -2
- data/lib/redis/pipeline.rb +17 -6
- data/lib/redis/version.rb +1 -1
- data/redis.gemspec +4 -6
- data/test/blocking_commands_test.rb +42 -0
- data/test/command_map_test.rb +18 -17
- data/test/commands_on_hashes_test.rb +13 -12
- data/test/commands_on_lists_test.rb +35 -45
- data/test/commands_on_sets_test.rb +55 -54
- data/test/commands_on_sorted_sets_test.rb +106 -105
- data/test/commands_on_strings_test.rb +64 -55
- data/test/commands_on_value_types_test.rb +66 -54
- data/test/connection_handling_test.rb +136 -151
- data/test/distributed_blocking_commands_test.rb +33 -40
- data/test/distributed_commands_on_hashes_test.rb +6 -7
- data/test/distributed_commands_on_lists_test.rb +13 -14
- data/test/distributed_commands_on_sets_test.rb +57 -58
- data/test/distributed_commands_on_sorted_sets_test.rb +11 -12
- data/test/distributed_commands_on_strings_test.rb +31 -32
- data/test/distributed_commands_on_value_types_test.rb +61 -46
- data/test/distributed_commands_requiring_clustering_test.rb +108 -108
- data/test/distributed_connection_handling_test.rb +14 -15
- data/test/distributed_internals_test.rb +7 -19
- data/test/distributed_key_tags_test.rb +36 -36
- data/test/distributed_persistence_control_commands_test.rb +17 -14
- data/test/distributed_publish_subscribe_test.rb +61 -69
- data/test/distributed_remote_server_control_commands_test.rb +39 -28
- data/test/distributed_sorting_test.rb +12 -13
- data/test/distributed_test.rb +40 -41
- data/test/distributed_transactions_test.rb +20 -21
- data/test/encoding_test.rb +12 -9
- data/test/error_replies_test.rb +42 -36
- data/test/helper.rb +118 -85
- data/test/helper_test.rb +20 -6
- data/test/internals_test.rb +167 -103
- data/test/lint/blocking_commands.rb +124 -0
- data/test/lint/hashes.rb +115 -93
- data/test/lint/lists.rb +86 -80
- data/test/lint/sets.rb +68 -62
- data/test/lint/sorted_sets.rb +200 -195
- data/test/lint/strings.rb +112 -94
- data/test/lint/value_types.rb +76 -55
- data/test/persistence_control_commands_test.rb +17 -12
- data/test/pipelining_commands_test.rb +135 -126
- data/test/publish_subscribe_test.rb +105 -110
- data/test/remote_server_control_commands_test.rb +74 -58
- data/test/sorting_test.rb +31 -29
- data/test/support/connection/hiredis.rb +1 -0
- data/test/support/connection/ruby.rb +1 -0
- data/test/support/connection/synchrony.rb +17 -0
- data/test/{redis_mock.rb → support/redis_mock.rb} +24 -21
- data/test/support/wire/synchrony.rb +24 -0
- data/test/support/wire/thread.rb +5 -0
- data/test/synchrony_driver.rb +9 -9
- data/test/test.conf +1 -1
- data/test/thread_safety_test.rb +21 -19
- data/test/transactions_test.rb +189 -118
- data/test/unknown_commands_test.rb +9 -8
- data/test/url_param_test.rb +46 -41
- metadata +28 -43
- data/TODO.md +0 -4
- data/benchmarking/thread_safety.rb +0 -38
- data/test/lint/internals.rb +0 -36
@@ -0,0 +1 @@
|
|
1
|
+
require "support/wire/thread"
|
@@ -0,0 +1 @@
|
|
1
|
+
require "support/wire/thread"
|
@@ -4,9 +4,12 @@ module RedisMock
|
|
4
4
|
class Server
|
5
5
|
VERBOSE = false
|
6
6
|
|
7
|
-
def initialize(port
|
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
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
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
|
data/test/synchrony_driver.rb
CHANGED
@@ -20,13 +20,13 @@ EM.synchrony do
|
|
20
20
|
r.rpush "foo", "s1"
|
21
21
|
r.rpush "foo", "s2"
|
22
22
|
|
23
|
-
|
24
|
-
|
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
|
-
|
29
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
49
|
+
assert_equal 2, r.llen("foo")
|
50
|
+
assert_equal "s2", r.lpop("foo")
|
51
|
+
assert_equal "s1", r.lpop("foo")
|
52
52
|
|
53
|
-
|
54
|
-
|
53
|
+
assert_equal "OK", r.client.call(:quit)
|
54
|
+
assert_equal "PONG", r.ping
|
55
55
|
|
56
56
|
EM.stop
|
57
57
|
end
|
data/test/test.conf
CHANGED
data/test/thread_safety_test.rb
CHANGED
@@ -1,30 +1,32 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require
|
3
|
+
require "helper"
|
4
4
|
|
5
|
-
|
6
|
-
init Redis.new(OPTIONS)
|
7
|
-
end
|
5
|
+
class TestThreadSafety < Test::Unit::TestCase
|
8
6
|
|
9
|
-
|
10
|
-
redis = Redis.connect(OPTIONS.merge(:thread_safe => true))
|
7
|
+
include Helper::Client
|
11
8
|
|
12
|
-
|
13
|
-
|
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
|
-
|
15
|
+
sample = 100
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
t1 = Thread.new do
|
18
|
+
$foos = Array.new(sample) { redis.get "foo" }
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
t2 = Thread.new do
|
22
|
+
$bars = Array.new(sample) { redis.get "bar" }
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
t1.join
|
26
|
+
t2.join
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
assert_equal ["1"], $foos.uniq
|
29
|
+
assert_equal ["2"], $bars.uniq
|
30
|
+
end
|
31
|
+
end
|
30
32
|
end
|
data/test/transactions_test.rb
CHANGED
@@ -1,173 +1,244 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require
|
3
|
+
require "helper"
|
4
4
|
|
5
|
-
|
6
|
-
init Redis.new(OPTIONS)
|
7
|
-
end
|
5
|
+
class TestTransactions < Test::Unit::TestCase
|
8
6
|
|
9
|
-
|
10
|
-
r.multi
|
7
|
+
include Helper::Client
|
11
8
|
|
12
|
-
|
13
|
-
|
9
|
+
def test_multi_discard
|
10
|
+
r.multi
|
14
11
|
|
15
|
-
|
12
|
+
assert_equal "QUEUED", r.set("foo", "1")
|
13
|
+
assert_equal "QUEUED", r.get("foo")
|
16
14
|
|
17
|
-
|
18
|
-
end
|
15
|
+
r.discard
|
19
16
|
|
20
|
-
|
21
|
-
r.multi do |multi|
|
22
|
-
multi.set "foo", "s1"
|
17
|
+
assert_equal nil, r.get("foo")
|
23
18
|
end
|
24
19
|
|
25
|
-
|
26
|
-
|
20
|
+
def test_multi_exec_with_a_block
|
21
|
+
r.multi do |multi|
|
22
|
+
multi.set "foo", "s1"
|
23
|
+
end
|
27
24
|
|
28
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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.
|
55
|
-
@second = m.
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
m
|
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
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
101
|
+
def test_transformed_replies_inside_multi_exec_block
|
102
|
+
r.multi do |m|
|
103
|
+
@info = r.info
|
104
|
+
end
|
92
105
|
|
93
|
-
|
94
|
-
info, _ = r.multi do |m|
|
95
|
-
r.info
|
106
|
+
assert @info.value.kind_of?(Hash)
|
96
107
|
end
|
97
108
|
|
98
|
-
|
99
|
-
|
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
|
-
|
102
|
-
r.multi do |m|
|
103
|
-
@info = r.info
|
118
|
+
assert_equal "s1", r.get("foo")
|
104
119
|
end
|
105
120
|
|
106
|
-
|
107
|
-
|
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
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
155
|
+
|
156
|
+
assert_equal "s1", r.get("foo")
|
129
157
|
end
|
130
158
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
137
|
-
r.multi do |multi|
|
138
|
-
multi.set "foo", "s1"
|
165
|
+
assert_equal "s1", r.get("foo")
|
139
166
|
end
|
140
167
|
|
141
|
-
|
142
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
151
|
-
|
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
|
-
|
154
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
165
|
-
|
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
|
-
|
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
|