redis 2.0.0 → 2.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.
- data/.gitignore +8 -0
- data/CHANGELOG.md +49 -0
- data/README.md +208 -0
- data/Rakefile +183 -73
- data/TODO.md +4 -0
- 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/client.rb +149 -165
- 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 +135 -0
- data/lib/redis/connection/synchrony.rb +129 -0
- data/lib/redis/connection.rb +9 -0
- data/lib/redis/distributed.rb +182 -7
- data/lib/redis/pipeline.rb +22 -1
- data/lib/redis/subscribe.rb +19 -4
- data/lib/redis/version.rb +3 -0
- data/lib/redis.rb +715 -155
- 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 +88 -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 +27 -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 +43 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +59 -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 +160 -0
- data/test/lint/hashes.rb +114 -0
- data/test/lint/internals.rb +37 -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 +82 -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 +130 -22
- data/README.markdown +0 -120
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
|
4
|
+
require "redis/distributed"
|
|
5
|
+
|
|
6
|
+
setup do
|
|
7
|
+
log = StringIO.new
|
|
8
|
+
init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "handle multiple servers" do
|
|
12
|
+
@r = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
|
|
13
|
+
|
|
14
|
+
100.times do |idx|
|
|
15
|
+
@r.set(idx.to_s, "foo#{idx}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
100.times do |idx|
|
|
19
|
+
assert "foo#{idx}" == @r.get(idx.to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
assert "0" == @r.keys("*").sort.first
|
|
23
|
+
assert "string" == @r.type("1")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test "add nodes" do
|
|
27
|
+
logger = Logger.new("/dev/null")
|
|
28
|
+
|
|
29
|
+
@r = Redis::Distributed.new NODES, :logger => logger, :timeout => 10
|
|
30
|
+
|
|
31
|
+
assert "127.0.0.1" == @r.nodes[0].client.host
|
|
32
|
+
assert 6379 == @r.nodes[0].client.port
|
|
33
|
+
assert 15 == @r.nodes[0].client.db
|
|
34
|
+
assert 10 == @r.nodes[0].client.timeout
|
|
35
|
+
assert logger == @r.nodes[0].client.logger
|
|
36
|
+
|
|
37
|
+
@r.add_node("redis://localhost:6380/14")
|
|
38
|
+
|
|
39
|
+
assert "localhost" == @r.nodes[1].client.host
|
|
40
|
+
assert 6380 == @r.nodes[1].client.port
|
|
41
|
+
assert 14 == @r.nodes[1].client.db
|
|
42
|
+
assert 10 == @r.nodes[1].client.timeout
|
|
43
|
+
assert logger == @r.nodes[1].client.logger
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test "Pipelining commands cannot be distributed" do |r|
|
|
47
|
+
assert_raise Redis::Distributed::CannotDistribute do
|
|
48
|
+
r.pipelined do
|
|
49
|
+
r.lpush "foo", "s1"
|
|
50
|
+
r.lpush "foo", "s2"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "Unknown commands does not work by default" do |r|
|
|
56
|
+
assert_raise NoMethodError do
|
|
57
|
+
r.not_yet_implemented_command
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
|
4
|
+
require "redis/distributed"
|
|
5
|
+
|
|
6
|
+
setup do
|
|
7
|
+
log = StringIO.new
|
|
8
|
+
init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "MULTI/DISCARD" do |r|
|
|
12
|
+
@foo = nil
|
|
13
|
+
|
|
14
|
+
assert_raise Redis::Distributed::CannotDistribute do
|
|
15
|
+
r.multi { @foo = 1 }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
assert nil == @foo
|
|
19
|
+
|
|
20
|
+
assert_raise Redis::Distributed::CannotDistribute do
|
|
21
|
+
r.discard
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "WATCH/UNWATCH" do |r|
|
|
26
|
+
assert_raise Redis::Distributed::CannotDistribute do
|
|
27
|
+
r.watch("foo")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
assert_raise Redis::Distributed::CannotDistribute do
|
|
31
|
+
r.unwatch
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
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 "returns properly encoded strings" do |r|
|
|
10
|
+
with_external_encoding("UTF-8") do
|
|
11
|
+
r.set "foo", "שלום"
|
|
12
|
+
|
|
13
|
+
assert "Shalom שלום" == "Shalom " + r.get("foo")
|
|
14
|
+
end
|
|
15
|
+
end if defined?(Encoding)
|
|
16
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
# Every test shouldn't disconnect from the server. Also, when error replies are
|
|
10
|
+
# in play, the protocol should never get into an invalid state where there are
|
|
11
|
+
# pending replies in the connection. Calling INFO after every test ensures that
|
|
12
|
+
# the protocol is still in a valid state.
|
|
13
|
+
def test_with_reconnection_check(title)
|
|
14
|
+
test(title) do |r|
|
|
15
|
+
before = r.info["total_connections_received"]
|
|
16
|
+
yield(r)
|
|
17
|
+
after = r.info["total_connections_received"]
|
|
18
|
+
assert before == after
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test_with_reconnection_check "Error reply for single command" do |r|
|
|
23
|
+
begin
|
|
24
|
+
r.unknown_command
|
|
25
|
+
rescue => ex
|
|
26
|
+
ensure
|
|
27
|
+
assert ex.message =~ /unknown command/i
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
test_with_reconnection_check "Raise first error reply in pipeline" do |r|
|
|
32
|
+
begin
|
|
33
|
+
r.pipelined do
|
|
34
|
+
r.set("foo", "s1")
|
|
35
|
+
r.incr("foo") # not an integer
|
|
36
|
+
r.lpush("foo", "value") # wrong kind of value
|
|
37
|
+
end
|
|
38
|
+
rescue => ex
|
|
39
|
+
ensure
|
|
40
|
+
assert ex.message =~ /not an integer/i
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test_with_reconnection_check "Recover from raise in #call_loop" do |r|
|
|
45
|
+
begin
|
|
46
|
+
r.client.call_loop([:invalid_monitor]) do
|
|
47
|
+
assert false # Should never be executed
|
|
48
|
+
end
|
|
49
|
+
rescue => ex
|
|
50
|
+
ensure
|
|
51
|
+
assert ex.message =~ /unknown command/i
|
|
52
|
+
end
|
|
53
|
+
end
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
$:.unshift File.expand_path('../lib', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
require "cutest"
|
|
4
|
+
require "logger"
|
|
5
|
+
require "stringio"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "ruby-debug"
|
|
9
|
+
rescue LoadError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
PORT = 6379
|
|
13
|
+
OPTIONS = {:port => PORT, :db => 15, :timeout => 3}
|
|
14
|
+
NODES = ["redis://127.0.0.1:6379/15"]
|
|
15
|
+
|
|
16
|
+
def init(redis)
|
|
17
|
+
begin
|
|
18
|
+
redis.flushdb
|
|
19
|
+
redis.select 14
|
|
20
|
+
redis.flushdb
|
|
21
|
+
redis.select 15
|
|
22
|
+
redis
|
|
23
|
+
rescue Errno::ECONNREFUSED
|
|
24
|
+
puts <<-EOS
|
|
25
|
+
|
|
26
|
+
Cannot connect to Redis.
|
|
27
|
+
|
|
28
|
+
Make sure Redis is running on localhost, port 6379.
|
|
29
|
+
This testing suite connects to the database 15.
|
|
30
|
+
|
|
31
|
+
To install redis:
|
|
32
|
+
visit <http://code.google.com/p/redis/>.
|
|
33
|
+
|
|
34
|
+
To start the server:
|
|
35
|
+
rake start
|
|
36
|
+
|
|
37
|
+
To stop the server:
|
|
38
|
+
rake stop
|
|
39
|
+
|
|
40
|
+
EOS
|
|
41
|
+
exit 1
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
$VERBOSE = true
|
|
46
|
+
|
|
47
|
+
require "redis"
|
|
48
|
+
|
|
49
|
+
def driver
|
|
50
|
+
Redis::Connection.drivers.last.to_s.split("::").last.downcase.to_sym
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if driver == :synchrony
|
|
54
|
+
# Make cutest fiber + eventmachine aware if the synchrony driver is used.
|
|
55
|
+
undef test if defined? test
|
|
56
|
+
def test(name = nil, &block)
|
|
57
|
+
cutest[:test] = name
|
|
58
|
+
|
|
59
|
+
blk = Proc.new do
|
|
60
|
+
prepare.each { |blk| blk.call }
|
|
61
|
+
block.call(setup && setup.call)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
t = Thread.current[:cutest]
|
|
65
|
+
if defined? EventMachine
|
|
66
|
+
EM.synchrony do
|
|
67
|
+
Thread.current[:cutest] = t
|
|
68
|
+
blk.call
|
|
69
|
+
EM.stop
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
blk.call
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Wire < Fiber
|
|
77
|
+
# We cannot run this fiber explicitly because EM schedules it. Resuming the
|
|
78
|
+
# current fiber on the next tick to let the reactor do work.
|
|
79
|
+
def self.pass
|
|
80
|
+
f = Fiber.current
|
|
81
|
+
EM.next_tick { f.resume }
|
|
82
|
+
Fiber.yield
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.sleep(sec)
|
|
86
|
+
EM::Synchrony.sleep(sec)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def initialize(&blk)
|
|
90
|
+
super
|
|
91
|
+
|
|
92
|
+
# Schedule run in next tick
|
|
93
|
+
EM.next_tick { resume }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def join
|
|
97
|
+
self.class.pass while alive?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
class Wire < Thread
|
|
102
|
+
def self.sleep(sec)
|
|
103
|
+
Kernel.sleep(sec)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def capture_stderr
|
|
109
|
+
stderr = $stderr
|
|
110
|
+
$stderr = StringIO.new
|
|
111
|
+
|
|
112
|
+
yield
|
|
113
|
+
|
|
114
|
+
$stderr = stderr
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def silent
|
|
118
|
+
verbose, $VERBOSE = $VERBOSE, false
|
|
119
|
+
|
|
120
|
+
begin
|
|
121
|
+
yield
|
|
122
|
+
ensure
|
|
123
|
+
$VERBOSE = verbose
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def with_external_encoding(encoding)
|
|
128
|
+
original_encoding = Encoding.default_external
|
|
129
|
+
|
|
130
|
+
begin
|
|
131
|
+
silent { Encoding.default_external = Encoding.find(encoding) }
|
|
132
|
+
yield
|
|
133
|
+
ensure
|
|
134
|
+
silent { Encoding.default_external = original_encoding }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def assert_nothing_raised(*exceptions)
|
|
139
|
+
begin
|
|
140
|
+
yield
|
|
141
|
+
rescue *exceptions
|
|
142
|
+
flunk(caller[1])
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
|
4
|
+
require File.expand_path("./redis_mock", File.dirname(__FILE__))
|
|
5
|
+
|
|
6
|
+
include RedisMock::Helper
|
|
7
|
+
|
|
8
|
+
setup do
|
|
9
|
+
log = StringIO.new
|
|
10
|
+
|
|
11
|
+
[Redis.new(OPTIONS.merge(:logger => ::Logger.new(log))), log]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
$TEST_PIPELINING = true
|
|
15
|
+
|
|
16
|
+
load File.expand_path("./lint/internals.rb", File.dirname(__FILE__))
|
|
17
|
+
|
|
18
|
+
test "provides a meaningful inspect" do |r, _|
|
|
19
|
+
assert "#<Redis client v#{Redis::VERSION} connected to redis://127.0.0.1:6379/15 (Redis v#{r.info["redis_version"]})>" == r.inspect
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test "Redis.current" do
|
|
23
|
+
Redis.current.set("foo", "bar")
|
|
24
|
+
|
|
25
|
+
assert "bar" == Redis.current.get("foo")
|
|
26
|
+
|
|
27
|
+
Redis.current = Redis.new(OPTIONS.merge(:db => 14))
|
|
28
|
+
|
|
29
|
+
assert Redis.current.get("foo").nil?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "Timeout" do
|
|
33
|
+
assert_nothing_raised do
|
|
34
|
+
Redis.new(OPTIONS.merge(:timeout => 0))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Don't use assert_raise because Timeour::Error in 1.8 inherits
|
|
39
|
+
# Exception instead of StandardError (on 1.9).
|
|
40
|
+
test "Connection timeout" do
|
|
41
|
+
# EM immediately raises CONNREFUSED
|
|
42
|
+
next if driver == :synchrony
|
|
43
|
+
|
|
44
|
+
result = false
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
Redis.new(OPTIONS.merge(:host => "10.255.255.254", :timeout => 0.1)).ping
|
|
48
|
+
rescue Timeout::Error
|
|
49
|
+
result = true
|
|
50
|
+
ensure
|
|
51
|
+
assert result
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "Retry when first read raises ECONNRESET" do
|
|
56
|
+
$request = 0
|
|
57
|
+
|
|
58
|
+
command = lambda do
|
|
59
|
+
case ($request += 1)
|
|
60
|
+
when 1; nil # Close on first command
|
|
61
|
+
else "+%d" % $request
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
redis_mock(:ping => command) do
|
|
66
|
+
redis = Redis.connect(:port => 6380, :timeout => 0.1)
|
|
67
|
+
assert "2" == redis.ping
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
test "Don't retry when wrapped inside #without_reconnect" do
|
|
72
|
+
$request = 0
|
|
73
|
+
|
|
74
|
+
command = lambda do
|
|
75
|
+
case ($request += 1)
|
|
76
|
+
when 1; nil # Close on first command
|
|
77
|
+
else "+%d" % $request
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
redis_mock(:ping => command) do
|
|
82
|
+
redis = Redis.connect(:port => 6380, :timeout => 0.1)
|
|
83
|
+
assert_raise Errno::ECONNRESET do
|
|
84
|
+
redis.without_reconnect do
|
|
85
|
+
redis.ping
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
assert !redis.client.connected?
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
test "Retry only once when read raises ECONNRESET" do
|
|
94
|
+
$request = 0
|
|
95
|
+
|
|
96
|
+
command = lambda do
|
|
97
|
+
case ($request += 1)
|
|
98
|
+
when 1; nil # Close on first command
|
|
99
|
+
when 2; nil # Close on second command
|
|
100
|
+
else "+%d" % $request
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
redis_mock(:ping => command) do
|
|
105
|
+
redis = Redis.connect(:port => 6380, :timeout => 0.1)
|
|
106
|
+
assert_raise Errno::ECONNRESET do
|
|
107
|
+
redis.ping
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
assert !redis.client.connected?
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
test "Don't retry when second read in pipeline raises ECONNRESET" do
|
|
115
|
+
$request = 0
|
|
116
|
+
|
|
117
|
+
command = lambda do
|
|
118
|
+
case ($request += 1)
|
|
119
|
+
when 2; nil # Close on second command
|
|
120
|
+
else "+%d" % $request
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
redis_mock(:ping => command) do
|
|
125
|
+
redis = Redis.connect(:port => 6380, :timeout => 0.1)
|
|
126
|
+
assert_raise Errno::ECONNRESET do
|
|
127
|
+
redis.pipelined do
|
|
128
|
+
redis.ping
|
|
129
|
+
redis.ping # Second #read times out
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
test "Connecting to UNIX domain socket" do
|
|
136
|
+
assert_nothing_raised do
|
|
137
|
+
Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock")).ping
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Using a mock server in a thread doesn't work here (possibly because blocking
|
|
142
|
+
# socket ops, raw socket timeouts and Ruby's thread scheduling don't mix).
|
|
143
|
+
test "Bubble EAGAIN without retrying" do
|
|
144
|
+
cmd = %{(sleep 0.3; echo "+PONG\r\n") | nc -l 6380}
|
|
145
|
+
IO.popen(cmd) do |_|
|
|
146
|
+
sleep 0.1 # Give nc a little time to start listening
|
|
147
|
+
redis = Redis.connect(:port => 6380, :timeout => 0.1)
|
|
148
|
+
|
|
149
|
+
begin
|
|
150
|
+
assert_raise(Errno::EAGAIN) { redis.ping }
|
|
151
|
+
ensure
|
|
152
|
+
# Explicitly close connection so nc can quit
|
|
153
|
+
redis.client.disconnect
|
|
154
|
+
|
|
155
|
+
# Make the reactor loop do a tick to really close
|
|
156
|
+
EM::Synchrony.sleep(0) if driver == :synchrony
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
data/test/lint/hashes.rb
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
test "HSET and HGET" do |r|
|
|
2
|
+
r.hset("foo", "f1", "s1")
|
|
3
|
+
|
|
4
|
+
assert "s1" == r.hget("foo", "f1")
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
test "HDEL" do |r|
|
|
8
|
+
r.hset("foo", "f1", "s1")
|
|
9
|
+
|
|
10
|
+
assert "s1" == r.hget("foo", "f1")
|
|
11
|
+
|
|
12
|
+
r.hdel("foo", "f1")
|
|
13
|
+
|
|
14
|
+
assert nil == r.hget("foo", "f1")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test "HEXISTS" do |r|
|
|
18
|
+
assert false == r.hexists("foo", "f1")
|
|
19
|
+
|
|
20
|
+
r.hset("foo", "f1", "s1")
|
|
21
|
+
|
|
22
|
+
assert r.hexists("foo", "f1")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "HLEN" do |r|
|
|
26
|
+
assert 0 == r.hlen("foo")
|
|
27
|
+
|
|
28
|
+
r.hset("foo", "f1", "s1")
|
|
29
|
+
|
|
30
|
+
assert 1 == r.hlen("foo")
|
|
31
|
+
|
|
32
|
+
r.hset("foo", "f2", "s2")
|
|
33
|
+
|
|
34
|
+
assert 2 == r.hlen("foo")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
test "HKEYS" do |r|
|
|
38
|
+
assert [] == r.hkeys("foo")
|
|
39
|
+
|
|
40
|
+
r.hset("foo", "f1", "s1")
|
|
41
|
+
r.hset("foo", "f2", "s2")
|
|
42
|
+
|
|
43
|
+
assert ["f1", "f2"] == r.hkeys("foo")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test "HVALS" do |r|
|
|
47
|
+
assert [] == r.hvals("foo")
|
|
48
|
+
|
|
49
|
+
r.hset("foo", "f1", "s1")
|
|
50
|
+
r.hset("foo", "f2", "s2")
|
|
51
|
+
|
|
52
|
+
assert ["s1", "s2"] == r.hvals("foo")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "HGETALL" do |r|
|
|
56
|
+
assert({} == r.hgetall("foo"))
|
|
57
|
+
|
|
58
|
+
r.hset("foo", "f1", "s1")
|
|
59
|
+
r.hset("foo", "f2", "s2")
|
|
60
|
+
|
|
61
|
+
assert({"f1" => "s1", "f2" => "s2"} == r.hgetall("foo"))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
test "HMSET" do |r|
|
|
65
|
+
r.hmset("hash", "foo1", "bar1", "foo2", "bar2")
|
|
66
|
+
|
|
67
|
+
assert "bar1" == r.hget("hash", "foo1")
|
|
68
|
+
assert "bar2" == r.hget("hash", "foo2")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
test "HMSET with invalid arguments" do |r|
|
|
72
|
+
assert_raise(RuntimeError) do
|
|
73
|
+
r.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
test "Mapped HMSET" do |r|
|
|
78
|
+
r.mapped_hmset("foo", :f1 => "s1", :f2 => "s2")
|
|
79
|
+
|
|
80
|
+
assert "s1" == r.hget("foo", "f1")
|
|
81
|
+
assert "s2" == r.hget("foo", "f2")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
test "HMGET" do |r|
|
|
85
|
+
r.hset("foo", "f1", "s1")
|
|
86
|
+
r.hset("foo", "f2", "s2")
|
|
87
|
+
r.hset("foo", "f3", "s3")
|
|
88
|
+
|
|
89
|
+
assert ["s2", "s3"] == r.hmget("foo", "f2", "f3")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
test "HMGET mapped" do |r|
|
|
93
|
+
r.hset("foo", "f1", "s1")
|
|
94
|
+
r.hset("foo", "f2", "s2")
|
|
95
|
+
r.hset("foo", "f3", "s3")
|
|
96
|
+
|
|
97
|
+
assert({"f1" => "s1"} == r.mapped_hmget("foo", "f1"))
|
|
98
|
+
assert({"f1" => "s1", "f2" => "s2"} == r.mapped_hmget("foo", "f1", "f2"))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
test "HINCRBY" do |r|
|
|
102
|
+
r.hincrby("foo", "f1", 1)
|
|
103
|
+
|
|
104
|
+
assert "1" == r.hget("foo", "f1")
|
|
105
|
+
|
|
106
|
+
r.hincrby("foo", "f1", 2)
|
|
107
|
+
|
|
108
|
+
assert "3" == r.hget("foo", "f1")
|
|
109
|
+
|
|
110
|
+
r.hincrby("foo", "f1", -1)
|
|
111
|
+
|
|
112
|
+
assert "2" == r.hget("foo", "f1")
|
|
113
|
+
end
|
|
114
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
test "Logger" do |r, log|
|
|
2
|
+
r.ping
|
|
3
|
+
|
|
4
|
+
assert log.string =~ /Redis >> PING/
|
|
5
|
+
assert log.string =~ /Redis >> \d+\.\d+ms/
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
test "Logger with pipelining" do |r, log|
|
|
9
|
+
r.pipelined do
|
|
10
|
+
r.set "foo", "bar"
|
|
11
|
+
r.get "foo"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
assert log.string["SET foo bar"]
|
|
15
|
+
assert log.string["GET foo"]
|
|
16
|
+
end if $TEST_PIPELINING
|
|
17
|
+
|
|
18
|
+
test "Recovers from failed commands" do |r, _|
|
|
19
|
+
# See http://github.com/ezmobius/redis-rb/issues#issue/28
|
|
20
|
+
|
|
21
|
+
assert_raise(ArgumentError) do
|
|
22
|
+
r.srem "foo"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
assert_nothing_raised do
|
|
26
|
+
r.info
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test "raises on protocol errors" do
|
|
31
|
+
redis_mock(:ping => lambda { |*_| "foo" }) do
|
|
32
|
+
assert_raise(Redis::ProtocolError) do
|
|
33
|
+
Redis.connect(:port => 6380).ping
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
data/test/lint/lists.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
test "RPUSH" do |r|
|
|
2
|
+
r.rpush "foo", "s1"
|
|
3
|
+
r.rpush "foo", "s2"
|
|
4
|
+
|
|
5
|
+
assert 2 == r.llen("foo")
|
|
6
|
+
assert "s2" == r.rpop("foo")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
test "LPUSH" do |r|
|
|
10
|
+
r.lpush "foo", "s1"
|
|
11
|
+
r.lpush "foo", "s2"
|
|
12
|
+
|
|
13
|
+
assert 2 == r.llen("foo")
|
|
14
|
+
assert "s2" == r.lpop("foo")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test "LLEN" do |r|
|
|
18
|
+
r.rpush "foo", "s1"
|
|
19
|
+
r.rpush "foo", "s2"
|
|
20
|
+
|
|
21
|
+
assert 2 == r.llen("foo")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "LRANGE" do |r|
|
|
25
|
+
r.rpush "foo", "s1"
|
|
26
|
+
r.rpush "foo", "s2"
|
|
27
|
+
r.rpush "foo", "s3"
|
|
28
|
+
|
|
29
|
+
assert ["s2", "s3"] == r.lrange("foo", 1, -1)
|
|
30
|
+
assert ["s1", "s2"] == r.lrange("foo", 0, 1)
|
|
31
|
+
|
|
32
|
+
assert [] == r.lrange("bar", 0, -1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "LTRIM" do |r|
|
|
36
|
+
r.rpush "foo", "s1"
|
|
37
|
+
r.rpush "foo", "s2"
|
|
38
|
+
r.rpush "foo", "s3"
|
|
39
|
+
|
|
40
|
+
r.ltrim "foo", 0, 1
|
|
41
|
+
|
|
42
|
+
assert 2 == r.llen("foo")
|
|
43
|
+
assert ["s1", "s2"] == r.lrange("foo", 0, -1)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test "LINDEX" do |r|
|
|
47
|
+
r.rpush "foo", "s1"
|
|
48
|
+
r.rpush "foo", "s2"
|
|
49
|
+
|
|
50
|
+
assert "s1" == r.lindex("foo", 0)
|
|
51
|
+
assert "s2" == r.lindex("foo", 1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
test "LSET" do |r|
|
|
55
|
+
r.rpush "foo", "s1"
|
|
56
|
+
r.rpush "foo", "s2"
|
|
57
|
+
|
|
58
|
+
assert "s2" == r.lindex("foo", 1)
|
|
59
|
+
assert r.lset("foo", 1, "s3")
|
|
60
|
+
assert "s3" == r.lindex("foo", 1)
|
|
61
|
+
|
|
62
|
+
assert_raise RuntimeError do
|
|
63
|
+
r.lset("foo", 4, "s3")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
test "LREM" do |r|
|
|
68
|
+
r.rpush "foo", "s1"
|
|
69
|
+
r.rpush "foo", "s2"
|
|
70
|
+
|
|
71
|
+
assert 1 == r.lrem("foo", 1, "s1")
|
|
72
|
+
assert ["s2"] == r.lrange("foo", 0, -1)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
test "LPOP" do |r|
|
|
76
|
+
r.rpush "foo", "s1"
|
|
77
|
+
r.rpush "foo", "s2"
|
|
78
|
+
|
|
79
|
+
assert 2 == r.llen("foo")
|
|
80
|
+
assert "s1" == r.lpop("foo")
|
|
81
|
+
assert 1 == r.llen("foo")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
test "RPOP" do |r|
|
|
85
|
+
r.rpush "foo", "s1"
|
|
86
|
+
r.rpush "foo", "s2"
|
|
87
|
+
|
|
88
|
+
assert 2 == r.llen("foo")
|
|
89
|
+
assert "s2" == r.rpop("foo")
|
|
90
|
+
assert 1 == r.llen("foo")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|