redis 2.1.1 → 2.2.0
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/.gitignore +8 -0
- data/CHANGELOG.md +34 -0
- data/README.md +190 -0
- data/Rakefile +194 -79
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis.rb +612 -156
- data/lib/redis/client.rb +98 -57
- data/lib/redis/connection.rb +9 -134
- data/lib/redis/connection/command_helper.rb +45 -0
- data/lib/redis/connection/hiredis.rb +49 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +131 -0
- data/lib/redis/connection/synchrony.rb +125 -0
- data/lib/redis/distributed.rb +161 -5
- data/lib/redis/pipeline.rb +6 -0
- data/lib/redis/version.rb +3 -0
- data/redis.gemspec +24 -0
- data/test/commands_on_hashes_test.rb +32 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +78 -0
- data/test/commands_on_sorted_sets_test.rb +109 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +88 -0
- data/test/connection_handling_test.rb +87 -0
- data/test/db/.gitignore +1 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +12 -0
- data/test/distributed_commands_on_lists_test.rb +24 -0
- data/test/distributed_commands_on_sets_test.rb +85 -0
- data/test/distributed_commands_on_strings_test.rb +50 -0
- data/test/distributed_commands_on_value_types_test.rb +73 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +25 -0
- data/test/distributed_internals_test.rb +18 -0
- data/test/distributed_key_tags_test.rb +53 -0
- data/test/distributed_persistence_control_commands_test.rb +24 -0
- data/test/distributed_publish_subscribe_test.rb +101 -0
- data/test/distributed_remote_server_control_commands_test.rb +31 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +60 -0
- data/test/distributed_transactions_test.rb +34 -0
- data/test/encoding_test.rb +16 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +145 -0
- data/test/internals_test.rb +157 -0
- data/test/lint/hashes.rb +114 -0
- data/test/lint/internals.rb +41 -0
- data/test/lint/lists.rb +93 -0
- data/test/lint/sets.rb +66 -0
- data/test/lint/sorted_sets.rb +167 -0
- data/test/lint/strings.rb +137 -0
- data/test/lint/value_types.rb +84 -0
- data/test/persistence_control_commands_test.rb +22 -0
- data/test/pipelining_commands_test.rb +123 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +80 -0
- data/test/remote_server_control_commands_test.rb +63 -0
- data/test/sorting_test.rb +44 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +8 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +100 -0
- data/test/unknown_commands_test.rb +14 -0
- data/test/url_param_test.rb +60 -0
- metadata +128 -19
- data/README.markdown +0 -129
@@ -0,0 +1,84 @@
|
|
1
|
+
test "EXISTS" do |r|
|
2
|
+
assert false == r.exists("foo")
|
3
|
+
|
4
|
+
r.set("foo", "s1")
|
5
|
+
|
6
|
+
assert true == r.exists("foo")
|
7
|
+
end
|
8
|
+
|
9
|
+
test "TYPE" do |r|
|
10
|
+
assert "none" == r.type("foo")
|
11
|
+
|
12
|
+
r.set("foo", "s1")
|
13
|
+
|
14
|
+
assert "string" == r.type("foo")
|
15
|
+
end
|
16
|
+
|
17
|
+
test "KEYS" do |r|
|
18
|
+
r.set("f", "s1")
|
19
|
+
r.set("fo", "s2")
|
20
|
+
r.set("foo", "s3")
|
21
|
+
|
22
|
+
assert ["f","fo", "foo"] == r.keys("f*").sort
|
23
|
+
end
|
24
|
+
|
25
|
+
test "EXPIRE" do |r|
|
26
|
+
r.set("foo", "s1")
|
27
|
+
r.expire("foo", 1)
|
28
|
+
|
29
|
+
assert "s1" == r.get("foo")
|
30
|
+
|
31
|
+
sleep 2
|
32
|
+
|
33
|
+
assert nil == r.get("foo")
|
34
|
+
end
|
35
|
+
|
36
|
+
test "EXPIREAT" do |r|
|
37
|
+
r.set("foo", "s1")
|
38
|
+
r.expireat("foo", Time.now.to_i + 1)
|
39
|
+
|
40
|
+
assert "s1" == r.get("foo")
|
41
|
+
|
42
|
+
sleep 2
|
43
|
+
|
44
|
+
assert nil == r.get("foo")
|
45
|
+
end
|
46
|
+
|
47
|
+
test "PERSIST" do |r|
|
48
|
+
r.set("foo", "s1")
|
49
|
+
r.expire("foo", 1)
|
50
|
+
r.persist("foo")
|
51
|
+
|
52
|
+
assert(-1 == r.ttl("foo"))
|
53
|
+
end
|
54
|
+
|
55
|
+
test "TTL" do |r|
|
56
|
+
r.set("foo", "s1")
|
57
|
+
r.expire("foo", 1)
|
58
|
+
|
59
|
+
assert 1 == r.ttl("foo")
|
60
|
+
end
|
61
|
+
|
62
|
+
test "MOVE" do |r|
|
63
|
+
r.select 14
|
64
|
+
r.flushdb
|
65
|
+
|
66
|
+
r.set "bar", "s3"
|
67
|
+
|
68
|
+
r.select 15
|
69
|
+
|
70
|
+
r.set "foo", "s1"
|
71
|
+
r.set "bar", "s2"
|
72
|
+
|
73
|
+
assert r.move("foo", 14)
|
74
|
+
assert nil == r.get("foo")
|
75
|
+
|
76
|
+
assert !r.move("bar", 14)
|
77
|
+
assert "s2" == r.get("bar")
|
78
|
+
|
79
|
+
r.select 14
|
80
|
+
|
81
|
+
assert "s1" == r.get("foo")
|
82
|
+
assert "s3" == r.get("bar")
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
setup do
|
6
|
+
init Redis.new(OPTIONS)
|
7
|
+
end
|
8
|
+
|
9
|
+
test "SAVE and BGSAVE" do |r|
|
10
|
+
assert_nothing_raised do
|
11
|
+
r.save
|
12
|
+
end
|
13
|
+
|
14
|
+
assert_nothing_raised do
|
15
|
+
r.bgsave
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
test "LASTSAVE" do |r|
|
20
|
+
assert Time.at(r.lastsave) <= Time.now
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
setup do
|
6
|
+
init Redis.new(OPTIONS)
|
7
|
+
end
|
8
|
+
|
9
|
+
test "BULK commands" do |r|
|
10
|
+
r.pipelined do
|
11
|
+
r.lpush "foo", "s1"
|
12
|
+
r.lpush "foo", "s2"
|
13
|
+
end
|
14
|
+
|
15
|
+
assert 2 == r.llen("foo")
|
16
|
+
assert "s2" == r.lpop("foo")
|
17
|
+
assert "s1" == r.lpop("foo")
|
18
|
+
end
|
19
|
+
|
20
|
+
test "MULTI_BULK commands" do |r|
|
21
|
+
r.pipelined do
|
22
|
+
r.mset("foo", "s1", "bar", "s2")
|
23
|
+
r.mset("baz", "s3", "qux", "s4")
|
24
|
+
end
|
25
|
+
|
26
|
+
assert "s1" == r.get("foo")
|
27
|
+
assert "s2" == r.get("bar")
|
28
|
+
assert "s3" == r.get("baz")
|
29
|
+
assert "s4" == r.get("qux")
|
30
|
+
end
|
31
|
+
|
32
|
+
test "BULK and MULTI_BULK commands mixed" do |r|
|
33
|
+
r.pipelined do
|
34
|
+
r.lpush "foo", "s1"
|
35
|
+
r.lpush "foo", "s2"
|
36
|
+
r.mset("baz", "s3", "qux", "s4")
|
37
|
+
end
|
38
|
+
|
39
|
+
assert 2 == r.llen("foo")
|
40
|
+
assert "s2" == r.lpop("foo")
|
41
|
+
assert "s1" == r.lpop("foo")
|
42
|
+
assert "s3" == r.get("baz")
|
43
|
+
assert "s4" == r.get("qux")
|
44
|
+
end
|
45
|
+
|
46
|
+
test "MULTI_BULK and BULK commands mixed" do |r|
|
47
|
+
r.pipelined do
|
48
|
+
r.mset("baz", "s3", "qux", "s4")
|
49
|
+
r.lpush "foo", "s1"
|
50
|
+
r.lpush "foo", "s2"
|
51
|
+
end
|
52
|
+
|
53
|
+
assert 2 == r.llen("foo")
|
54
|
+
assert "s2" == r.lpop("foo")
|
55
|
+
assert "s1" == r.lpop("foo")
|
56
|
+
assert "s3" == r.get("baz")
|
57
|
+
assert "s4" == r.get("qux")
|
58
|
+
end
|
59
|
+
|
60
|
+
test "Pipelined with an empty block" do |r|
|
61
|
+
assert_nothing_raised do
|
62
|
+
r.pipelined do
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
assert 0 == r.dbsize
|
67
|
+
end
|
68
|
+
|
69
|
+
test "Returning the result of a pipeline" do |r|
|
70
|
+
result = r.pipelined do
|
71
|
+
r.set "foo", "bar"
|
72
|
+
r.get "foo"
|
73
|
+
r.get "bar"
|
74
|
+
end
|
75
|
+
|
76
|
+
assert ["OK", "bar", nil] == result
|
77
|
+
end
|
78
|
+
|
79
|
+
test "Nesting pipeline blocks" do |r|
|
80
|
+
r.pipelined do
|
81
|
+
r.set("foo", "s1")
|
82
|
+
r.pipelined do
|
83
|
+
r.set("bar", "s2")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
assert "s1" == r.get("foo")
|
88
|
+
assert "s2" == r.get("bar")
|
89
|
+
end
|
90
|
+
|
91
|
+
test "INFO in a pipeline" do |r|
|
92
|
+
result = r.pipelined do
|
93
|
+
r.info
|
94
|
+
end
|
95
|
+
|
96
|
+
assert result.first.kind_of?(String)
|
97
|
+
end
|
98
|
+
|
99
|
+
test "CONFIG GET in a pipeline" do |r|
|
100
|
+
result = r.pipelined do
|
101
|
+
r.config(:get, "*")
|
102
|
+
end
|
103
|
+
|
104
|
+
assert result.first.kind_of?(Array)
|
105
|
+
end
|
106
|
+
|
107
|
+
test "HGETALL in a pipeline should not return hash" do |r|
|
108
|
+
r.hmset("hash", "field", "value")
|
109
|
+
result = r.pipelined do
|
110
|
+
r.hgetall("hash")
|
111
|
+
end
|
112
|
+
|
113
|
+
assert ["field", "value"] == result.first
|
114
|
+
end
|
115
|
+
|
116
|
+
test "KEYS in a pipeline" do |r|
|
117
|
+
r.set("key", "value")
|
118
|
+
result = r.pipelined do
|
119
|
+
r.keys("*")
|
120
|
+
end
|
121
|
+
|
122
|
+
assert ["key"] == result.first
|
123
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
setup do
|
6
|
+
init Redis.new(OPTIONS)
|
7
|
+
end
|
8
|
+
|
9
|
+
test "SUBSCRIBE and UNSUBSCRIBE" do |r|
|
10
|
+
listening = false
|
11
|
+
|
12
|
+
wire = Wire.new do
|
13
|
+
r.subscribe("foo") do |on|
|
14
|
+
on.subscribe do |channel, total|
|
15
|
+
@subscribed = true
|
16
|
+
@t1 = total
|
17
|
+
end
|
18
|
+
|
19
|
+
on.message do |channel, message|
|
20
|
+
if message == "s1"
|
21
|
+
r.unsubscribe
|
22
|
+
@message = message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
on.unsubscribe do |channel, total|
|
27
|
+
@unsubscribed = true
|
28
|
+
@t2 = total
|
29
|
+
end
|
30
|
+
|
31
|
+
listening = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Wire.pass while !listening
|
36
|
+
|
37
|
+
Redis.new(OPTIONS).publish("foo", "s1")
|
38
|
+
|
39
|
+
wire.join
|
40
|
+
|
41
|
+
assert @subscribed
|
42
|
+
assert 1 == @t1
|
43
|
+
assert @unsubscribed
|
44
|
+
assert 0 == @t2
|
45
|
+
assert "s1" == @message
|
46
|
+
end
|
47
|
+
|
48
|
+
test "PSUBSCRIBE and PUNSUBSCRIBE" do |r|
|
49
|
+
listening = false
|
50
|
+
|
51
|
+
wire = Wire.new do
|
52
|
+
r.psubscribe("f*") do |on|
|
53
|
+
on.psubscribe do |pattern, total|
|
54
|
+
@subscribed = true
|
55
|
+
@t1 = total
|
56
|
+
end
|
57
|
+
|
58
|
+
on.pmessage do |pattern, channel, message|
|
59
|
+
if message == "s1"
|
60
|
+
r.punsubscribe
|
61
|
+
@message = message
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
on.punsubscribe do |pattern, total|
|
66
|
+
@unsubscribed = true
|
67
|
+
@t2 = total
|
68
|
+
end
|
69
|
+
|
70
|
+
listening = true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Wire.pass while !listening
|
75
|
+
|
76
|
+
Redis.new(OPTIONS).publish("foo", "s1")
|
77
|
+
|
78
|
+
wire.join
|
79
|
+
|
80
|
+
assert @subscribed
|
81
|
+
assert 1 == @t1
|
82
|
+
assert @unsubscribed
|
83
|
+
assert 0 == @t2
|
84
|
+
assert "s1" == @message
|
85
|
+
end
|
86
|
+
|
87
|
+
test "SUBSCRIBE within SUBSCRIBE" do |r|
|
88
|
+
listening = false
|
89
|
+
|
90
|
+
@channels = []
|
91
|
+
|
92
|
+
wire = Wire.new do
|
93
|
+
r.subscribe("foo") do |on|
|
94
|
+
on.subscribe do |channel, total|
|
95
|
+
@channels << channel
|
96
|
+
|
97
|
+
r.subscribe("bar") if channel == "foo"
|
98
|
+
r.unsubscribe if channel == "bar"
|
99
|
+
end
|
100
|
+
|
101
|
+
listening = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
Wire.pass while !listening
|
106
|
+
|
107
|
+
Redis.new(OPTIONS).publish("foo", "s1")
|
108
|
+
|
109
|
+
wire.join
|
110
|
+
|
111
|
+
assert ["foo", "bar"] == @channels
|
112
|
+
end
|
113
|
+
|
114
|
+
test "other commands within a SUBSCRIBE" do |r|
|
115
|
+
assert_raise RuntimeError do
|
116
|
+
r.subscribe("foo") do |on|
|
117
|
+
on.subscribe do |channel, total|
|
118
|
+
r.set("bar", "s2")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
test "SUBSCRIBE without a block" do |r|
|
125
|
+
assert_raise LocalJumpError do
|
126
|
+
r.subscribe("foo")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
test "UNSUBSCRIBE without a SUBSCRIBE" do |r|
|
131
|
+
assert_raise RuntimeError do
|
132
|
+
r.unsubscribe
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_raise RuntimeError do
|
136
|
+
r.punsubscribe
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
test "SUBSCRIBE past a timeout" do |r|
|
141
|
+
# For some reason, a thread here doesn't reproduce the issue.
|
142
|
+
sleep = %{sleep #{OPTIONS[:timeout] + 1}}
|
143
|
+
publish = %{echo "publish foo bar\r\n" | nc localhost #{OPTIONS[:port]}}
|
144
|
+
cmd = [sleep, publish].join("; ")
|
145
|
+
|
146
|
+
IO.popen(cmd, "r+") do |pipe|
|
147
|
+
received = false
|
148
|
+
|
149
|
+
r.subscribe "foo" do |on|
|
150
|
+
on.message do |channel, message|
|
151
|
+
received = true
|
152
|
+
r.unsubscribe
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
assert received
|
157
|
+
end
|
158
|
+
end
|
data/test/redis_mock.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module RedisMock
|
4
|
+
class Server
|
5
|
+
VERBOSE = false
|
6
|
+
|
7
|
+
def initialize(port = 6380, &block)
|
8
|
+
@server = TCPServer.new("127.0.0.1", port)
|
9
|
+
@server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
10
|
+
@thread = Thread.new { run(&block) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# This raises an exception in the thread calling @server.accept which
|
14
|
+
# in turn will cause the thread to terminate.
|
15
|
+
def shutdown
|
16
|
+
@server.close if @server
|
17
|
+
rescue => ex
|
18
|
+
$stderr.puts "Error closing mock server: #{ex.message}" if VERBOSE
|
19
|
+
$stderr.puts ex.backtrace if VERBOSE
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
loop do
|
24
|
+
session = @server.accept
|
25
|
+
|
26
|
+
begin
|
27
|
+
while line = session.gets
|
28
|
+
parts = Array.new(line[1..-3].to_i) do
|
29
|
+
bytes = session.gets[1..-3].to_i
|
30
|
+
argument = session.read(bytes)
|
31
|
+
session.read(2) # Discard \r\n
|
32
|
+
argument
|
33
|
+
end
|
34
|
+
|
35
|
+
response = yield(*parts)
|
36
|
+
|
37
|
+
if response.nil?
|
38
|
+
session.shutdown(Socket::SHUT_RDWR)
|
39
|
+
break
|
40
|
+
else
|
41
|
+
session.write(response)
|
42
|
+
session.write("\r\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue Errno::ECONNRESET
|
46
|
+
# Ignore client closing the connection
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue => ex
|
50
|
+
$stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
|
51
|
+
$stderr.puts ex.backtrace if VERBOSE
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module Helper
|
56
|
+
# Starts a mock Redis server in a thread on port 6380.
|
57
|
+
#
|
58
|
+
# The server will reply with a `+OK` to all commands, but you can
|
59
|
+
# customize it by providing a hash. For example:
|
60
|
+
#
|
61
|
+
# redis_mock(:ping => lambda { "+PONG" }) do
|
62
|
+
# assert_equal "PONG", Redis.new(:port => 6380).ping
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
def redis_mock(replies = {})
|
66
|
+
begin
|
67
|
+
server = Server.new do |command, *args|
|
68
|
+
(replies[command.to_sym] || lambda { |*_| "+OK" }).call(*args)
|
69
|
+
end
|
70
|
+
|
71
|
+
sleep 0.1 # Give time for the socket to start listening.
|
72
|
+
|
73
|
+
yield
|
74
|
+
|
75
|
+
ensure
|
76
|
+
server.shutdown
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|