redis 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +8 -0
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +190 -0
  4. data/Rakefile +194 -79
  5. data/benchmarking/logging.rb +62 -0
  6. data/benchmarking/pipeline.rb +51 -0
  7. data/benchmarking/speed.rb +21 -0
  8. data/benchmarking/suite.rb +24 -0
  9. data/benchmarking/thread_safety.rb +38 -0
  10. data/benchmarking/worker.rb +71 -0
  11. data/examples/basic.rb +15 -0
  12. data/examples/dist_redis.rb +43 -0
  13. data/examples/incr-decr.rb +17 -0
  14. data/examples/list.rb +26 -0
  15. data/examples/pubsub.rb +31 -0
  16. data/examples/sets.rb +36 -0
  17. data/examples/unicorn/config.ru +3 -0
  18. data/examples/unicorn/unicorn.rb +20 -0
  19. data/lib/redis.rb +612 -156
  20. data/lib/redis/client.rb +98 -57
  21. data/lib/redis/connection.rb +9 -134
  22. data/lib/redis/connection/command_helper.rb +45 -0
  23. data/lib/redis/connection/hiredis.rb +49 -0
  24. data/lib/redis/connection/registry.rb +12 -0
  25. data/lib/redis/connection/ruby.rb +131 -0
  26. data/lib/redis/connection/synchrony.rb +125 -0
  27. data/lib/redis/distributed.rb +161 -5
  28. data/lib/redis/pipeline.rb +6 -0
  29. data/lib/redis/version.rb +3 -0
  30. data/redis.gemspec +24 -0
  31. data/test/commands_on_hashes_test.rb +32 -0
  32. data/test/commands_on_lists_test.rb +60 -0
  33. data/test/commands_on_sets_test.rb +78 -0
  34. data/test/commands_on_sorted_sets_test.rb +109 -0
  35. data/test/commands_on_strings_test.rb +80 -0
  36. data/test/commands_on_value_types_test.rb +88 -0
  37. data/test/connection_handling_test.rb +87 -0
  38. data/test/db/.gitignore +1 -0
  39. data/test/distributed_blocking_commands_test.rb +53 -0
  40. data/test/distributed_commands_on_hashes_test.rb +12 -0
  41. data/test/distributed_commands_on_lists_test.rb +24 -0
  42. data/test/distributed_commands_on_sets_test.rb +85 -0
  43. data/test/distributed_commands_on_strings_test.rb +50 -0
  44. data/test/distributed_commands_on_value_types_test.rb +73 -0
  45. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  46. data/test/distributed_connection_handling_test.rb +25 -0
  47. data/test/distributed_internals_test.rb +18 -0
  48. data/test/distributed_key_tags_test.rb +53 -0
  49. data/test/distributed_persistence_control_commands_test.rb +24 -0
  50. data/test/distributed_publish_subscribe_test.rb +101 -0
  51. data/test/distributed_remote_server_control_commands_test.rb +31 -0
  52. data/test/distributed_sorting_test.rb +21 -0
  53. data/test/distributed_test.rb +60 -0
  54. data/test/distributed_transactions_test.rb +34 -0
  55. data/test/encoding_test.rb +16 -0
  56. data/test/error_replies_test.rb +53 -0
  57. data/test/helper.rb +145 -0
  58. data/test/internals_test.rb +157 -0
  59. data/test/lint/hashes.rb +114 -0
  60. data/test/lint/internals.rb +41 -0
  61. data/test/lint/lists.rb +93 -0
  62. data/test/lint/sets.rb +66 -0
  63. data/test/lint/sorted_sets.rb +167 -0
  64. data/test/lint/strings.rb +137 -0
  65. data/test/lint/value_types.rb +84 -0
  66. data/test/persistence_control_commands_test.rb +22 -0
  67. data/test/pipelining_commands_test.rb +123 -0
  68. data/test/publish_subscribe_test.rb +158 -0
  69. data/test/redis_mock.rb +80 -0
  70. data/test/remote_server_control_commands_test.rb +63 -0
  71. data/test/sorting_test.rb +44 -0
  72. data/test/synchrony_driver.rb +57 -0
  73. data/test/test.conf +8 -0
  74. data/test/thread_safety_test.rb +30 -0
  75. data/test/transactions_test.rb +100 -0
  76. data/test/unknown_commands_test.rb +14 -0
  77. data/test/url_param_test.rb +60 -0
  78. metadata +128 -19
  79. data/README.markdown +0 -129
@@ -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,157 @@
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
+ $TEST_INSPECT = true
16
+
17
+ load File.expand_path("./lint/internals.rb", File.dirname(__FILE__))
18
+
19
+ test "Redis.current" do
20
+ Redis.current.set("foo", "bar")
21
+
22
+ assert "bar" == Redis.current.get("foo")
23
+
24
+ Redis.current = Redis.new(OPTIONS.merge(:db => 14))
25
+
26
+ assert Redis.current.get("foo").nil?
27
+ end
28
+
29
+ test "Timeout" do
30
+ assert_nothing_raised do
31
+ Redis.new(OPTIONS.merge(:timeout => 0))
32
+ end
33
+ end
34
+
35
+ # Don't use assert_raise because Timeour::Error in 1.8 inherits
36
+ # Exception instead of StandardError (on 1.9).
37
+ test "Connection timeout" do
38
+ # EM immediately raises CONNREFUSED
39
+ next if driver == :synchrony
40
+
41
+ result = false
42
+
43
+ begin
44
+ Redis.new(OPTIONS.merge(:host => "10.255.255.254", :timeout => 0.1)).ping
45
+ rescue Timeout::Error
46
+ result = true
47
+ ensure
48
+ assert result
49
+ end
50
+ end
51
+
52
+ test "Retry when first read raises ECONNRESET" do
53
+ $request = 0
54
+
55
+ command = lambda do
56
+ case ($request += 1)
57
+ when 1; nil # Close on first command
58
+ else "+%d" % $request
59
+ end
60
+ end
61
+
62
+ redis_mock(:ping => command) do
63
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
64
+ assert "2" == redis.ping
65
+ end
66
+ end
67
+
68
+ test "Don't retry when wrapped inside #without_reconnect" do
69
+ $request = 0
70
+
71
+ command = lambda do
72
+ case ($request += 1)
73
+ when 1; nil # Close on first command
74
+ else "+%d" % $request
75
+ end
76
+ end
77
+
78
+ redis_mock(:ping => command) do
79
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
80
+ assert_raise Errno::ECONNRESET do
81
+ redis.without_reconnect do
82
+ redis.ping
83
+ end
84
+ end
85
+
86
+ assert !redis.client.connected?
87
+ end
88
+ end
89
+
90
+ test "Retry only once when read raises ECONNRESET" do
91
+ $request = 0
92
+
93
+ command = lambda do
94
+ case ($request += 1)
95
+ when 1; nil # Close on first command
96
+ when 2; nil # Close on second command
97
+ else "+%d" % $request
98
+ end
99
+ end
100
+
101
+ redis_mock(:ping => command) do
102
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
103
+ assert_raise Errno::ECONNRESET do
104
+ redis.ping
105
+ end
106
+
107
+ assert !redis.client.connected?
108
+ end
109
+ end
110
+
111
+ test "Don't retry when second read in pipeline raises ECONNRESET" do
112
+ $request = 0
113
+
114
+ command = lambda do
115
+ case ($request += 1)
116
+ when 2; nil # Close on second command
117
+ else "+%d" % $request
118
+ end
119
+ end
120
+
121
+ redis_mock(:ping => command) do
122
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
123
+ assert_raise Errno::ECONNRESET do
124
+ redis.pipelined do
125
+ redis.ping
126
+ redis.ping # Second #read times out
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ test "Connecting to UNIX domain socket" do
133
+ assert_nothing_raised do
134
+ Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock")).ping
135
+ end
136
+ end
137
+
138
+ # Using a mock server in a thread doesn't work here (possibly because blocking
139
+ # socket ops, raw socket timeouts and Ruby's thread scheduling don't mix).
140
+ test "Bubble EAGAIN without retrying" do
141
+ cmd = %{(sleep 0.3; echo "+PONG\r\n") | nc -l 6380}
142
+ IO.popen(cmd) do |_|
143
+ sleep 0.1 # Give nc a little time to start listening
144
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
145
+
146
+ begin
147
+ assert_raise(Errno::EAGAIN) { redis.ping }
148
+ ensure
149
+ # Explicitly close connection so nc can quit
150
+ redis.client.disconnect
151
+
152
+ # Make the reactor loop do a tick to really close
153
+ EM::Synchrony.sleep(0) if driver == :synchrony
154
+ end
155
+ end
156
+ end
157
+
@@ -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,41 @@
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 "provides a meaningful inspect" do |r, _|
31
+ assert "#<Redis client v#{Redis::VERSION} connected to redis://127.0.0.1:6379/15 (Redis v#{r.info["redis_version"]})>" == r.inspect
32
+ end if $TEST_INSPECT
33
+
34
+ test "raises on protocol errors" do
35
+ redis_mock(:ping => lambda { |*_| "foo" }) do
36
+ assert_raise(Redis::ProtocolError) do
37
+ Redis.connect(:port => 6380).ping
38
+ end
39
+ end
40
+ end
41
+