discourse-redis 3.2.2

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 (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