discourse-redis 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +59 -0
  4. data/.travis/Gemfile +11 -0
  5. data/.yardopts +3 -0
  6. data/CHANGELOG.md +349 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +20 -0
  9. data/README.md +328 -0
  10. data/Rakefile +87 -0
  11. data/benchmarking/logging.rb +71 -0
  12. data/benchmarking/pipeline.rb +51 -0
  13. data/benchmarking/speed.rb +21 -0
  14. data/benchmarking/suite.rb +24 -0
  15. data/benchmarking/worker.rb +71 -0
  16. data/examples/basic.rb +15 -0
  17. data/examples/consistency.rb +114 -0
  18. data/examples/dist_redis.rb +43 -0
  19. data/examples/incr-decr.rb +17 -0
  20. data/examples/list.rb +26 -0
  21. data/examples/pubsub.rb +37 -0
  22. data/examples/sentinel.rb +41 -0
  23. data/examples/sentinel/start +49 -0
  24. data/examples/sets.rb +36 -0
  25. data/examples/unicorn/config.ru +3 -0
  26. data/examples/unicorn/unicorn.rb +20 -0
  27. data/lib/redis.rb +2731 -0
  28. data/lib/redis/client.rb +575 -0
  29. data/lib/redis/connection.rb +9 -0
  30. data/lib/redis/connection/command_helper.rb +44 -0
  31. data/lib/redis/connection/hiredis.rb +64 -0
  32. data/lib/redis/connection/registry.rb +12 -0
  33. data/lib/redis/connection/ruby.rb +322 -0
  34. data/lib/redis/connection/synchrony.rb +124 -0
  35. data/lib/redis/distributed.rb +873 -0
  36. data/lib/redis/errors.rb +40 -0
  37. data/lib/redis/hash_ring.rb +132 -0
  38. data/lib/redis/pipeline.rb +141 -0
  39. data/lib/redis/subscribe.rb +83 -0
  40. data/lib/redis/version.rb +3 -0
  41. data/redis.gemspec +34 -0
  42. data/test/bitpos_test.rb +69 -0
  43. data/test/blocking_commands_test.rb +42 -0
  44. data/test/command_map_test.rb +30 -0
  45. data/test/commands_on_hashes_test.rb +21 -0
  46. data/test/commands_on_hyper_log_log_test.rb +21 -0
  47. data/test/commands_on_lists_test.rb +20 -0
  48. data/test/commands_on_sets_test.rb +77 -0
  49. data/test/commands_on_sorted_sets_test.rb +137 -0
  50. data/test/commands_on_strings_test.rb +101 -0
  51. data/test/commands_on_value_types_test.rb +133 -0
  52. data/test/connection_handling_test.rb +250 -0
  53. data/test/distributed_blocking_commands_test.rb +46 -0
  54. data/test/distributed_commands_on_hashes_test.rb +10 -0
  55. data/test/distributed_commands_on_hyper_log_log_test.rb +33 -0
  56. data/test/distributed_commands_on_lists_test.rb +22 -0
  57. data/test/distributed_commands_on_sets_test.rb +83 -0
  58. data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
  59. data/test/distributed_commands_on_strings_test.rb +59 -0
  60. data/test/distributed_commands_on_value_types_test.rb +95 -0
  61. data/test/distributed_commands_requiring_clustering_test.rb +164 -0
  62. data/test/distributed_connection_handling_test.rb +23 -0
  63. data/test/distributed_internals_test.rb +79 -0
  64. data/test/distributed_key_tags_test.rb +52 -0
  65. data/test/distributed_persistence_control_commands_test.rb +26 -0
  66. data/test/distributed_publish_subscribe_test.rb +92 -0
  67. data/test/distributed_remote_server_control_commands_test.rb +66 -0
  68. data/test/distributed_scripting_test.rb +102 -0
  69. data/test/distributed_sorting_test.rb +20 -0
  70. data/test/distributed_test.rb +58 -0
  71. data/test/distributed_transactions_test.rb +32 -0
  72. data/test/encoding_test.rb +18 -0
  73. data/test/error_replies_test.rb +59 -0
  74. data/test/fork_safety_test.rb +65 -0
  75. data/test/helper.rb +232 -0
  76. data/test/helper_test.rb +24 -0
  77. data/test/internals_test.rb +437 -0
  78. data/test/lint/blocking_commands.rb +150 -0
  79. data/test/lint/hashes.rb +162 -0
  80. data/test/lint/hyper_log_log.rb +60 -0
  81. data/test/lint/lists.rb +143 -0
  82. data/test/lint/sets.rb +125 -0
  83. data/test/lint/sorted_sets.rb +316 -0
  84. data/test/lint/strings.rb +260 -0
  85. data/test/lint/value_types.rb +122 -0
  86. data/test/persistence_control_commands_test.rb +26 -0
  87. data/test/pipelining_commands_test.rb +242 -0
  88. data/test/publish_subscribe_test.rb +254 -0
  89. data/test/remote_server_control_commands_test.rb +118 -0
  90. data/test/scanning_test.rb +413 -0
  91. data/test/scripting_test.rb +78 -0
  92. data/test/sentinel_command_test.rb +80 -0
  93. data/test/sentinel_test.rb +255 -0
  94. data/test/sorting_test.rb +59 -0
  95. data/test/support/connection/hiredis.rb +1 -0
  96. data/test/support/connection/ruby.rb +1 -0
  97. data/test/support/connection/synchrony.rb +17 -0
  98. data/test/support/redis_mock.rb +119 -0
  99. data/test/support/wire/synchrony.rb +24 -0
  100. data/test/support/wire/thread.rb +5 -0
  101. data/test/synchrony_driver.rb +88 -0
  102. data/test/test.conf.erb +9 -0
  103. data/test/thread_safety_test.rb +32 -0
  104. data/test/transactions_test.rb +264 -0
  105. data/test/unknown_commands_test.rb +14 -0
  106. data/test/url_param_test.rb +138 -0
  107. metadata +182 -0
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestDistributedTransactions < Test::Unit::TestCase
6
+
7
+ include Helper::Distributed
8
+
9
+ def test_multi_discard
10
+ @foo = nil
11
+
12
+ assert_raise Redis::Distributed::CannotDistribute do
13
+ r.multi { @foo = 1 }
14
+ end
15
+
16
+ assert_equal nil, @foo
17
+
18
+ assert_raise Redis::Distributed::CannotDistribute do
19
+ r.discard
20
+ end
21
+ end
22
+
23
+ def test_watch_unwatch
24
+ assert_raise Redis::Distributed::CannotDistribute do
25
+ r.watch("foo")
26
+ end
27
+
28
+ assert_raise Redis::Distributed::CannotDistribute do
29
+ r.unwatch
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestEncoding < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ def test_returns_properly_encoded_strings
10
+ if defined?(Encoding)
11
+ with_external_encoding("UTF-8") do
12
+ r.set "foo", "שלום"
13
+
14
+ assert_equal "Shalom שלום", "Shalom " + r.get("foo")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestErrorReplies < Test::Unit::TestCase
6
+
7
+ include Helper::Client
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 with_reconnection_check
14
+ before = r.info["total_connections_received"]
15
+ yield(r)
16
+ after = r.info["total_connections_received"]
17
+ ensure
18
+ assert_equal before, after
19
+ end
20
+
21
+ def test_error_reply_for_single_command
22
+ with_reconnection_check do
23
+ begin
24
+ r.unknown_command
25
+ rescue => ex
26
+ ensure
27
+ assert ex.message =~ /unknown command/i
28
+ end
29
+ end
30
+ end
31
+
32
+ def test_raise_first_error_reply_in_pipeline
33
+ with_reconnection_check do
34
+ begin
35
+ r.pipelined do
36
+ r.set("foo", "s1")
37
+ r.incr("foo") # not an integer
38
+ r.lpush("foo", "value") # wrong kind of value
39
+ end
40
+ rescue => ex
41
+ ensure
42
+ assert ex.message =~ /not an integer/i
43
+ end
44
+ end
45
+ end
46
+
47
+ def test_recover_from_raise_in__call_loop
48
+ with_reconnection_check do
49
+ begin
50
+ r.client.call_loop([:invalid_monitor]) do
51
+ assert false # Should never be executed
52
+ end
53
+ rescue => ex
54
+ ensure
55
+ assert ex.message =~ /unknown command/i
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestForkSafety < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+ include Helper::Skipable
9
+
10
+ driver(:ruby, :hiredis) do
11
+ def test_fork_safety
12
+ redis = Redis.new(OPTIONS)
13
+ redis.set "foo", 1
14
+
15
+ child_pid = fork do
16
+ begin
17
+ # InheritedError triggers a reconnect,
18
+ # so we need to disable reconnects to force
19
+ # the exception bubble up
20
+ redis.without_reconnect do
21
+ redis.set "foo", 2
22
+ end
23
+ rescue Redis::InheritedError
24
+ exit 127
25
+ end
26
+ end
27
+
28
+ _, status = Process.wait2(child_pid)
29
+
30
+ assert_equal 127, status.exitstatus
31
+ assert_equal "1", redis.get("foo")
32
+
33
+ rescue NotImplementedError => error
34
+ raise unless error.message =~ /fork is not available/
35
+ return skip(error.message)
36
+ end
37
+
38
+ def test_fork_safety_with_enabled_inherited_socket
39
+ redis = Redis.new(OPTIONS.merge(:inherit_socket => true))
40
+ redis.set "foo", 1
41
+
42
+ child_pid = fork do
43
+ begin
44
+ # InheritedError triggers a reconnect,
45
+ # so we need to disable reconnects to force
46
+ # the exception bubble up
47
+ redis.without_reconnect do
48
+ redis.set "foo", 2
49
+ end
50
+ rescue Redis::InheritedError
51
+ exit 127
52
+ end
53
+ end
54
+
55
+ _, status = Process.wait2(child_pid)
56
+
57
+ assert_equal 0, status.exitstatus
58
+ assert_equal "2", redis.get("foo")
59
+
60
+ rescue NotImplementedError => error
61
+ raise unless error.message =~ /fork is not available/
62
+ return skip(error.message)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,232 @@
1
+ $:.unshift File.expand_path("../lib", File.dirname(__FILE__))
2
+ $:.unshift File.expand_path(File.dirname(__FILE__))
3
+
4
+ require "test/unit"
5
+ require "logger"
6
+ require "stringio"
7
+
8
+ (class Random; def self.rand(*args) super end; end) unless defined?(Random)
9
+
10
+ begin
11
+ require "ruby-debug"
12
+ rescue LoadError
13
+ end
14
+
15
+ $VERBOSE = true
16
+
17
+ ENV["conn"] ||= "ruby"
18
+
19
+ require "redis"
20
+ require "redis/distributed"
21
+ require "redis/connection/#{ENV["conn"]}"
22
+
23
+ require "support/redis_mock"
24
+ require "support/connection/#{ENV["conn"]}"
25
+
26
+ PORT = 6381
27
+ OPTIONS = {:port => PORT, :db => 15, :timeout => Float(ENV["TIMEOUT"] || 0.1)}
28
+ NODES = ["redis://127.0.0.1:#{PORT}/15"]
29
+
30
+ def init(redis)
31
+ begin
32
+ redis.select 14
33
+ redis.flushdb
34
+ redis.select 15
35
+ redis.flushdb
36
+ redis
37
+ rescue Redis::CannotConnectError
38
+ puts <<-EOS
39
+
40
+ Cannot connect to Redis.
41
+
42
+ Make sure Redis is running on localhost, port #{PORT}.
43
+ This testing suite connects to the database 15.
44
+
45
+ Try this once:
46
+
47
+ $ rake clean
48
+
49
+ Then run the build again:
50
+
51
+ $ rake
52
+
53
+ EOS
54
+ exit 1
55
+ end
56
+ end
57
+
58
+ def driver(*drivers, &blk)
59
+ if drivers.map(&:to_s).include?(ENV["conn"])
60
+ class_eval(&blk)
61
+ end
62
+ end
63
+
64
+ module Helper
65
+
66
+ def run(runner)
67
+ if respond_to?(:around)
68
+ around { super(runner) }
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def silent
75
+ verbose, $VERBOSE = $VERBOSE, false
76
+
77
+ begin
78
+ yield
79
+ ensure
80
+ $VERBOSE = verbose
81
+ end
82
+ end
83
+
84
+ def with_external_encoding(encoding)
85
+ original_encoding = Encoding.default_external
86
+
87
+ begin
88
+ silent { Encoding.default_external = Encoding.find(encoding) }
89
+ yield
90
+ ensure
91
+ silent { Encoding.default_external = original_encoding }
92
+ end
93
+ end
94
+
95
+ def try_encoding(encoding, &block)
96
+ if defined?(Encoding)
97
+ with_external_encoding(encoding, &block)
98
+ else
99
+ yield
100
+ end
101
+ end
102
+
103
+ class Version
104
+
105
+ include Comparable
106
+
107
+ attr :parts
108
+
109
+ def initialize(v)
110
+ case v
111
+ when Version
112
+ @parts = v.parts
113
+ else
114
+ @parts = v.to_s.split(".")
115
+ end
116
+ end
117
+
118
+ def <=>(other)
119
+ other = Version.new(other)
120
+ length = [self.parts.length, other.parts.length].max
121
+ length.times do |i|
122
+ a, b = self.parts[i], other.parts[i]
123
+
124
+ return -1 if a.nil?
125
+ return +1 if b.nil?
126
+ return a.to_i <=> b.to_i if a != b
127
+ end
128
+
129
+ 0
130
+ end
131
+ end
132
+
133
+ module Generic
134
+
135
+ include Helper
136
+
137
+ attr_reader :log
138
+ attr_reader :redis
139
+
140
+ alias :r :redis
141
+
142
+ def setup
143
+ @log = StringIO.new
144
+ @redis = init _new_client
145
+
146
+ # Run GC to make sure orphaned connections are closed.
147
+ GC.start
148
+ end
149
+
150
+ def teardown
151
+ @redis.quit if @redis
152
+ end
153
+
154
+ def redis_mock(commands, options = {}, &blk)
155
+ RedisMock.start(commands, options) do |port|
156
+ yield _new_client(options.merge(:port => port))
157
+ end
158
+ end
159
+
160
+ def redis_mock_with_handler(handler, options = {}, &blk)
161
+ RedisMock.start_with_handler(handler, options) do |port|
162
+ yield _new_client(options.merge(:port => port))
163
+ end
164
+ end
165
+
166
+ def assert_in_range(range, value)
167
+ assert range.include?(value), "expected #{value} to be in #{range.inspect}"
168
+ end
169
+
170
+ def target_version(target)
171
+ if version < target
172
+ skip("Requires Redis > #{target}") if respond_to?(:skip)
173
+ else
174
+ yield
175
+ end
176
+ end
177
+ end
178
+
179
+ module Client
180
+
181
+ include Generic
182
+
183
+ def version
184
+ Version.new(redis.info["redis_version"])
185
+ end
186
+
187
+ private
188
+
189
+ def _format_options(options)
190
+ OPTIONS.merge(:logger => ::Logger.new(@log)).merge(options)
191
+ end
192
+
193
+ def _new_client(options = {})
194
+ Redis.new(_format_options(options).merge(:driver => ENV["conn"]))
195
+ end
196
+ end
197
+
198
+ module Distributed
199
+
200
+ include Generic
201
+
202
+ def version
203
+ Version.new(redis.info.first["redis_version"])
204
+ end
205
+
206
+ private
207
+
208
+ def _format_options(options)
209
+ {
210
+ :timeout => OPTIONS[:timeout],
211
+ :logger => ::Logger.new(@log),
212
+ }.merge(options)
213
+ end
214
+
215
+ def _new_client(options = {})
216
+ Redis::Distributed.new(NODES, _format_options(options).merge(:driver => ENV["conn"]))
217
+ end
218
+ end
219
+
220
+ # Basic support for `skip` in 1.8.x
221
+ # Note: YOU MUST use `return skip(message)` in order to appropriately bail
222
+ # from a running test.
223
+ module Skipable
224
+ Skipped = Class.new(RuntimeError)
225
+
226
+ def skip(message = nil, bt = caller)
227
+ return super if defined?(super)
228
+
229
+ $stderr.puts("SKIPPED: #{self} #{message || 'no reason given'}")
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestHelper < Test::Unit::TestCase
6
+
7
+ include Helper
8
+
9
+ def test_version_comparison
10
+ v = Version.new("2.0.1")
11
+
12
+ assert v > "1"
13
+ assert v > "2"
14
+ assert v < "3"
15
+ assert v < "10"
16
+
17
+ assert v < "2.1"
18
+ assert v < "2.0.2"
19
+ assert v < "2.0.1.1"
20
+ assert v < "2.0.10"
21
+
22
+ assert v == "2.0.1"
23
+ end
24
+ end
@@ -0,0 +1,437 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class TestInternals < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ def test_logger
10
+ r.ping
11
+
12
+ assert log.string["[Redis] command=PING"]
13
+ assert log.string =~ /\[Redis\] call_time=\d+\.\d+ ms/
14
+ end
15
+
16
+ def test_logger_with_pipelining
17
+ r.pipelined do
18
+ r.set "foo", "bar"
19
+ r.get "foo"
20
+ end
21
+
22
+ assert log.string[" command=SET args=\"foo\" \"bar\""]
23
+ assert log.string[" command=GET args=\"foo\""]
24
+ end
25
+
26
+ def test_recovers_from_failed_commands
27
+ # See https://github.com/redis/redis-rb/issues#issue/28
28
+
29
+ assert_raise(Redis::CommandError) do
30
+ r.command_that_doesnt_exist
31
+ end
32
+
33
+ assert_nothing_raised do
34
+ r.info
35
+ end
36
+ end
37
+
38
+ def test_raises_on_protocol_errors
39
+ redis_mock(:ping => lambda { |*_| "foo" }) do |redis|
40
+ assert_raise(Redis::ProtocolError) do
41
+ redis.ping
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_provides_a_meaningful_inspect
47
+ assert_equal "#<Redis client v#{Redis::VERSION} for redis://127.0.0.1:#{PORT}/15>", r.inspect
48
+ end
49
+
50
+ def test_redis_current
51
+ assert_equal "127.0.0.1", Redis.current.client.host
52
+ assert_equal 6379, Redis.current.client.port
53
+ assert_equal 0, Redis.current.client.db
54
+
55
+ Redis.current = Redis.new(OPTIONS.merge(:port => 6380, :db => 1))
56
+
57
+ t = Thread.new do
58
+ assert_equal "127.0.0.1", Redis.current.client.host
59
+ assert_equal 6380, Redis.current.client.port
60
+ assert_equal 1, Redis.current.client.db
61
+ end
62
+
63
+ t.join
64
+
65
+ assert_equal "127.0.0.1", Redis.current.client.host
66
+ assert_equal 6380, Redis.current.client.port
67
+ assert_equal 1, Redis.current.client.db
68
+ end
69
+
70
+ def test_redis_connected?
71
+ fresh_client = _new_client
72
+ assert !fresh_client.connected?
73
+
74
+ fresh_client.ping
75
+ assert fresh_client.connected?
76
+
77
+ fresh_client.quit
78
+ assert !fresh_client.connected?
79
+ end
80
+
81
+ def test_default_id_with_host_and_port
82
+ redis = Redis.new(OPTIONS.merge(:host => "host", :port => "1234", :db => 0))
83
+ assert_equal "redis://host:1234/0", redis.client.id
84
+ end
85
+
86
+ def test_default_id_with_host_and_port_and_explicit_scheme
87
+ redis = Redis.new(OPTIONS.merge(:host => "host", :port => "1234", :db => 0, :scheme => "foo"))
88
+ assert_equal "redis://host:1234/0", redis.client.id
89
+ end
90
+
91
+ def test_default_id_with_path
92
+ redis = Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock", :db => 0))
93
+ assert_equal "redis:///tmp/redis.sock/0", redis.client.id
94
+ end
95
+
96
+ def test_default_id_with_path_and_explicit_scheme
97
+ redis = Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock", :db => 0, :scheme => "foo"))
98
+ assert_equal "redis:///tmp/redis.sock/0", redis.client.id
99
+ end
100
+
101
+ def test_override_id
102
+ redis = Redis.new(OPTIONS.merge(:id => "test"))
103
+ assert_equal redis.client.id, "test"
104
+ end
105
+
106
+ def test_timeout
107
+ assert_nothing_raised do
108
+ Redis.new(OPTIONS.merge(:timeout => 0))
109
+ end
110
+ end
111
+
112
+ def test_id_inside_multi
113
+ redis = Redis.new(OPTIONS)
114
+ id = nil
115
+
116
+ redis.multi do
117
+ id = redis.id
118
+ end
119
+
120
+ assert_equal id, "redis://127.0.0.1:6381/15"
121
+ end
122
+
123
+ driver(:ruby) do
124
+ def test_tcp_keepalive
125
+ keepalive = {:time => 20, :intvl => 10, :probes => 5}
126
+
127
+ redis = Redis.new(OPTIONS.merge(:tcp_keepalive => keepalive))
128
+ redis.ping
129
+
130
+ connection = redis.client.connection
131
+ actual_keepalive = connection.get_tcp_keepalive
132
+
133
+ [:time, :intvl, :probes].each do |key|
134
+ if actual_keepalive.has_key?(key)
135
+ assert_equal actual_keepalive[key], keepalive[key]
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def test_time
142
+ target_version "2.5.4" do
143
+ # Test that the difference between the time that Ruby reports and the time
144
+ # that Redis reports is minimal (prevents the test from being racy).
145
+ rv = r.time
146
+
147
+ redis_usec = rv[0] * 1_000_000 + rv[1]
148
+ ruby_usec = Integer(Time.now.to_f * 1_000_000)
149
+
150
+ assert 500_000 > (ruby_usec - redis_usec).abs
151
+ end
152
+ end
153
+
154
+ def test_connection_timeout
155
+ opts = OPTIONS.merge(:host => "10.255.255.254", :connect_timeout => 0.1, :timeout => 5.0)
156
+ start_time = Time.now
157
+ assert_raise Redis::CannotConnectError do
158
+ Redis.new(opts).ping
159
+ end
160
+ assert (Time.now - start_time) <= opts[:timeout]
161
+ end
162
+
163
+ def close_on_ping(seq, options = {})
164
+ $request = 0
165
+
166
+ command = lambda do
167
+ idx = $request
168
+ $request += 1
169
+
170
+ rv = "+%d" % idx
171
+ rv = nil if seq.include?(idx)
172
+ rv
173
+ end
174
+
175
+ redis_mock({:ping => command}, {:timeout => 0.1}.merge(options)) do |redis|
176
+ yield(redis)
177
+ end
178
+ end
179
+
180
+ def test_retry_by_default
181
+ close_on_ping([0]) do |redis|
182
+ assert_equal "1", redis.ping
183
+ end
184
+ end
185
+
186
+ def test_retry_when_wrapped_in_with_reconnect_true
187
+ close_on_ping([0]) do |redis|
188
+ redis.with_reconnect(true) do
189
+ assert_equal "1", redis.ping
190
+ end
191
+ end
192
+ end
193
+
194
+ def test_dont_retry_when_wrapped_in_with_reconnect_false
195
+ close_on_ping([0]) do |redis|
196
+ assert_raise Redis::ConnectionError do
197
+ redis.with_reconnect(false) do
198
+ redis.ping
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def test_dont_retry_when_wrapped_in_without_reconnect
205
+ close_on_ping([0]) do |redis|
206
+ assert_raise Redis::ConnectionError do
207
+ redis.without_reconnect do
208
+ redis.ping
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ def test_retry_only_once_when_read_raises_econnreset
215
+ close_on_ping([0, 1]) do |redis|
216
+ assert_raise Redis::ConnectionError do
217
+ redis.ping
218
+ end
219
+
220
+ assert !redis.client.connected?
221
+ end
222
+ end
223
+
224
+ def test_retry_with_custom_reconnect_attempts
225
+ close_on_ping([0, 1], :reconnect_attempts => 2) do |redis|
226
+ assert_equal "2", redis.ping
227
+ end
228
+ end
229
+
230
+ def test_retry_with_custom_reconnect_attempts_can_still_fail
231
+ close_on_ping([0, 1, 2], :reconnect_attempts => 2) do |redis|
232
+ assert_raise Redis::ConnectionError do
233
+ redis.ping
234
+ end
235
+
236
+ assert !redis.client.connected?
237
+ end
238
+ end
239
+
240
+ def test_don_t_retry_when_second_read_in_pipeline_raises_econnreset
241
+ close_on_ping([1]) do |redis|
242
+ assert_raise Redis::ConnectionError do
243
+ redis.pipelined do
244
+ redis.ping
245
+ redis.ping # Second #read times out
246
+ end
247
+ end
248
+
249
+ assert !redis.client.connected?
250
+ end
251
+ end
252
+
253
+ def close_on_connection(seq)
254
+ $n = 0
255
+
256
+ read_command = lambda do |session|
257
+ Array.new(session.gets[1..-3].to_i) do
258
+ bytes = session.gets[1..-3].to_i
259
+ arg = session.read(bytes)
260
+ session.read(2) # Discard \r\n
261
+ arg
262
+ end
263
+ end
264
+
265
+ handler = lambda do |session|
266
+ n = $n
267
+ $n += 1
268
+
269
+ select = read_command.call(session)
270
+ if select[0].downcase == "select"
271
+ session.write("+OK\r\n")
272
+ else
273
+ raise "Expected SELECT"
274
+ end
275
+
276
+ if !seq.include?(n)
277
+ while read_command.call(session)
278
+ session.write("+#{n}\r\n")
279
+ end
280
+ end
281
+ end
282
+
283
+ redis_mock_with_handler(handler) do |redis|
284
+ yield(redis)
285
+ end
286
+ end
287
+
288
+ def test_retry_on_write_error_by_default
289
+ close_on_connection([0]) do |redis|
290
+ assert_equal "1", redis.client.call(["x" * 128 * 1024])
291
+ end
292
+ end
293
+
294
+ def test_retry_on_write_error_when_wrapped_in_with_reconnect_true
295
+ close_on_connection([0]) do |redis|
296
+ redis.with_reconnect(true) do
297
+ assert_equal "1", redis.client.call(["x" * 128 * 1024])
298
+ end
299
+ end
300
+ end
301
+
302
+ def test_dont_retry_on_write_error_when_wrapped_in_with_reconnect_false
303
+ close_on_connection([0]) do |redis|
304
+ assert_raise Redis::ConnectionError do
305
+ redis.with_reconnect(false) do
306
+ redis.client.call(["x" * 128 * 1024])
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ def test_dont_retry_on_write_error_when_wrapped_in_without_reconnect
313
+ close_on_connection([0]) do |redis|
314
+ assert_raise Redis::ConnectionError do
315
+ redis.without_reconnect do
316
+ redis.client.call(["x" * 128 * 1024])
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ def test_connecting_to_unix_domain_socket
323
+ assert_nothing_raised do
324
+ Redis.new(OPTIONS.merge(:path => "./test/db/redis.sock")).ping
325
+ end
326
+ end
327
+
328
+ driver(:ruby, :hiredis) do
329
+ def test_bubble_timeout_without_retrying
330
+ serv = TCPServer.new(6380)
331
+
332
+ redis = Redis.new(:port => 6380, :timeout => 0.1)
333
+
334
+ assert_raise(Redis::TimeoutError) do
335
+ redis.ping
336
+ end
337
+
338
+ ensure
339
+ serv.close if serv
340
+ end
341
+ end
342
+
343
+ def test_client_options
344
+ redis = Redis.new(OPTIONS.merge(:host => "host", :port => 1234, :db => 1, :scheme => "foo"))
345
+
346
+ assert_equal "host", redis.client.options[:host]
347
+ assert_equal 1234, redis.client.options[:port]
348
+ assert_equal 1, redis.client.options[:db]
349
+ assert_equal "foo", redis.client.options[:scheme]
350
+ end
351
+
352
+ def test_does_not_change_self_client_options
353
+ redis = Redis.new(OPTIONS.merge(:host => "host", :port => 1234, :db => 1, :scheme => "foo"))
354
+ options = redis.client.options
355
+
356
+ options[:host] << "new_host"
357
+ options[:scheme] << "bar"
358
+ options.merge!(:db => 0)
359
+
360
+ assert_equal "host", redis.client.options[:host]
361
+ assert_equal 1, redis.client.options[:db]
362
+ assert_equal "foo", redis.client.options[:scheme]
363
+ end
364
+
365
+ def test_resolves_localhost
366
+ assert_nothing_raised do
367
+ Redis.new(OPTIONS.merge(:host => 'localhost')).ping
368
+ end
369
+ end
370
+
371
+ class << self
372
+ def af_family_supported(af)
373
+ hosts = {
374
+ Socket::AF_INET => "127.0.0.1",
375
+ Socket::AF_INET6 => "::1",
376
+ }
377
+
378
+ begin
379
+ s = Socket.new(af, Socket::SOCK_STREAM, 0)
380
+ begin
381
+ tries = 5
382
+ begin
383
+ sa = Socket.pack_sockaddr_in(1024 + Random.rand(63076), hosts[af])
384
+ s.bind(sa)
385
+ rescue Errno::EADDRINUSE
386
+ tries -= 1
387
+ retry if tries > 0
388
+
389
+ raise
390
+ end
391
+ yield
392
+ rescue Errno::EADDRNOTAVAIL
393
+ ensure
394
+ s.close
395
+ end
396
+ rescue Errno::ESOCKTNOSUPPORT
397
+ end
398
+ end
399
+ end
400
+
401
+ def af_test(host)
402
+ commands = {
403
+ :ping => lambda { |*_| "+pong" },
404
+ }
405
+
406
+ redis_mock(commands, :host => host) do |redis|
407
+ assert_nothing_raised do
408
+ redis.ping
409
+ end
410
+ end
411
+ end
412
+
413
+ driver(:ruby) do
414
+ af_family_supported(Socket::AF_INET) do
415
+ def test_connect_ipv4
416
+ af_test("127.0.0.1")
417
+ end
418
+ end
419
+ end
420
+
421
+ driver(:ruby) do
422
+ af_family_supported(Socket::AF_INET6) do
423
+ def test_connect_ipv6
424
+ af_test("::1")
425
+ end
426
+ end
427
+ end
428
+
429
+ def test_can_be_duped_to_create_a_new_connection
430
+ clients = r.info["connected_clients"].to_i
431
+
432
+ r2 = r.dup
433
+ r2.ping
434
+
435
+ assert_equal clients + 1, r.info["connected_clients"].to_i
436
+ end
437
+ end