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.
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
+