File: connection_test.rb

package info (click to toggle)
ruby-hiredis 0.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 268 kB
  • ctags: 284
  • sloc: ruby: 948; ansic: 503; makefile: 5
file content (371 lines) | stat: -rw-r--r-- 9,244 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
require 'test/unit'

require File.expand_path('../../lib/hiredis/ext/connection', __FILE__) unless RUBY_PLATFORM =~ /java/
require File.expand_path('../../lib/hiredis/ruby/connection', __FILE__)

module ConnectionTests

  attr_reader :hiredis

  DEFAULT_PORT = 6380

  def sockopt(sock, opt, unpack = "i")
    sock.getsockopt(Socket::SOL_SOCKET, opt).unpack("i").first
  end

  def listen(port = DEFAULT_PORT)
    IO.popen("nc -l #{port}", "r+") do |io|
      sleep 0.1 # Give nc a little time to start listening

      begin
        timeout = Thread.new do
          sleep 10 # Tests should complete in 10s
          Process.kill("SIGINT", io.pid) rescue Errno::ESRCH
        end

        yield io
      ensure
        # Abort timeout
        timeout.kill

        # Netcat waits forever if no-one connected, so kill it.
        Process.kill("SIGINT", io.pid) rescue Errno::ESRCH
      end
    end
  end

  def test_connect_wrong_host
    assert_raise RuntimeError do
      hiredis.connect("nonexisting", 6379)
    end
  end

  def test_connect_wrong_port
    assert_raise Errno::ECONNREFUSED do
      hiredis.connect("localhost", 6380)
    end
  end

  def test_connected_tcp
    socket = TCPServer.new("127.0.0.1", 6380)

    assert !hiredis.connected?
    hiredis.connect("127.0.0.1", DEFAULT_PORT)
    assert hiredis.connected?
    hiredis.disconnect
    assert !hiredis.connected?
  ensure
    socket.close if socket
  end

  def test_connected_tcp_has_fileno
    socket = TCPServer.new("127.0.0.1", 6380)

    hiredis.connect("127.0.0.1", DEFAULT_PORT)
    assert hiredis.fileno > $stderr.fileno
  ensure
    socket.close if socket
  end

  def test_connect_unix
    path = "/tmp/hiredis-rb-test.sock"
    File.unlink(path) if File.exist?(path)
    socket = UNIXServer.new(path)

    assert !hiredis.connected?
    hiredis.connect_unix(path)
    assert hiredis.connected?
    hiredis.disconnect
    assert !hiredis.connected?
  ensure
    socket.close if socket
  end

  def test_connect_unix_has_fileno
    path = "/tmp/hiredis-rb-test.sock"
    File.unlink(path) if File.exist?(path)
    socket = UNIXServer.new(path)

    hiredis.connect_unix(path)
    assert hiredis.fileno > $stderr.fileno
  ensure
    socket.close if socket
  end

  def test_fileno_when_disconnected
    assert_raise RuntimeError, "not connected" do
      hiredis.fileno
    end
  end

  def test_wrong_value_for_timeout
    assert_raise ArgumentError do
      hiredis.timeout = -10
    end
  end

  ## The following tests do not work reliably with all possible environments.
  # Connecting to 1.1.1.1 may - among other things - succeed or yield a
  # connection refused or host unreachable error.
  #def test_connect_tcp_with_timeout
  #  hiredis.timeout = 200_000

  #  t = Time.now
  #  assert_raise Errno::ETIMEDOUT do
  #    hiredis.connect("1.1.1.1", 59876)
  #  end

  #  assert 210_000 > (Time.now - t)
  #end

  #def test_connect_tcp_with_timeout_override
  #  hiredis.timeout = 1_000_000

  #  t = Time.now
  #  assert_raise Errno::ETIMEDOUT do
  #    hiredis.connect("1.1.1.1", 59876, 200_000)
  #  end

  #  assert 210_000 > (Time.now - t)
  #end

  def test_connect_tcp_without_timeout
    hiredis.timeout = 0

    finished = false
    thread = Thread.new do
      hiredis.connect("1.1.1.1", 59876)
      finished = true
    end

    sleep(0.5) # double of default timeout
    assert !finished
    thread.kill
  end

  def test_read_when_disconnected
    assert_raise RuntimeError, "not connected" do
      hiredis.read
    end
  end

  def test_read_against_eof
    listen do |server|
      hiredis.connect("localhost", 6380)
      hiredis.write(["QUIT"])

      # Reply to QUIT and disconnect
      server.write "+OK\r\n"
      server.close_write

      # Reply for QUIT can be read
      assert_equal "OK", hiredis.read

      # Next read should raise
      assert_raise Errno::ECONNRESET do
        hiredis.read
      end
    end
  end

  def test_symbol_in_argument_list
    listen do |server|
      hiredis.connect("localhost", 6380)
      hiredis.write([:info])

      server.write "$2\r\nhi\r\n"

      assert_kind_of String, hiredis.read
    end
  end

  def test_read_against_timeout
    listen do |_|
      hiredis.connect("localhost", DEFAULT_PORT)
      hiredis.timeout = 10_000

      assert_raise Errno::EAGAIN do
        hiredis.read
      end
    end
  end

  def test_read_without_timeout
    listen do |_|
      hiredis.connect("localhost", DEFAULT_PORT)
      hiredis.timeout = 0

      finished = false
      thread = Thread.new do
        hiredis.read
        finished = true
      end

      sleep(0.5) # double of default timeout
      assert !finished
      thread.kill
    end
  end

  # Test that the Hiredis thread is scheduled after some time while waiting for
  # the descriptor to be readable.
  def test_read_against_timeout_with_other_thread
    thread = Thread.new do
      sleep 0.1 while true
    end

    listen do |_|
      hiredis.connect("localhost", DEFAULT_PORT)
      hiredis.timeout = 10_000

      assert_raise Errno::EAGAIN do
        hiredis.read
      end
    end
  ensure
    thread.kill
  end

  def test_raise_on_error_reply
    listen do |server|
      hiredis.connect("localhost", 6380)
      hiredis.write(["GET"])

      server.write "-ERR wrong number of arguments\r\n"

      err = hiredis.read
      assert_match /wrong number of arguments/i, err.message
      assert_kind_of RuntimeError, err
    end
  end

  def test_recover_from_partial_write
    listen do |server|
      hiredis.connect("localhost", 6380)

      # Find out send buffer size
      sndbuf = sockopt(hiredis.sock, Socket::SO_SNDBUF)

      # Make request that saturates the send buffer
      hiredis.write(["x" * sndbuf])

      # Flush and disconnect to signal EOF
      hiredis.flush
      hiredis.disconnect

      # Compare to data received on the other end
      formatted = "*1\r\n$#{sndbuf}\r\n#{"x" * sndbuf}\r\n"
      assert formatted == server.read
    end
  end

  #
  # This does not have consistent outcome for different operating systems...
  #
  # def test_eagain_on_write
  #   listen do |server|
  #     hiredis.connect("localhost", 6380)
  #     hiredis.timeout = 100_000

  #     # Find out buffer sizes
  #     sndbuf = sockopt(hiredis.sock, Socket::SO_SNDBUF)
  #     rcvbuf = sockopt(hiredis.sock, Socket::SO_RCVBUF)

  #     # Make request that fills both the remote receive buffer and the local
  #     # send buffer. This assumes that the size of the receive buffer on the
  #     # remote end is equal to our local receive buffer size.
  #     assert_raise Errno::EAGAIN do
  #       hiredis.write(["x" * rcvbuf * 2])
  #       hiredis.write(["x" * sndbuf * 2])
  #       hiredis.flush
  #     end
  #   end
  # end

  def test_eagain_on_write_followed_by_remote_drain
    listen do |server|
      hiredis.connect("localhost", 6380)
      hiredis.timeout = 100_000

      # Find out buffer sizes
      sndbuf = sockopt(hiredis.sock, Socket::SO_SNDBUF)
      rcvbuf = sockopt(hiredis.sock, Socket::SO_RCVBUF)

      # This thread starts reading the server buffer after 50ms. This will
      # cause the local write to first return EAGAIN, wait for the socket to
      # become writable with select(2) and retry.
      begin
        thread = Thread.new do
          sleep(0.050)
          loop do
            server.read(1024)
          end
        end

        # Make request that fills both the remote receive buffer and the local
        # send buffer. This assumes that the size of the receive buffer on the
        # remote end is equal to our local receive buffer size.
        hiredis.write(["x" * rcvbuf])
        hiredis.write(["x" * sndbuf])
        hiredis.flush
        hiredis.disconnect
      ensure
        thread.kill
      end
    end
  end

  # The following test seems to be too sensitive wrt to timing and gives
  # unstable results across operating systems.
  #def test_no_eagain_after_cumulative_wait_exceeds_timeout
  #  listen do |server|
  #    hiredis.connect("localhost", 6380)
  #    hiredis.timeout = 10_000

  #    begin
  #      thread = Thread.new do
  #        loop do
  #          sleep(0.001)
  #          server.write("+ok\r\n")
  #        end
  #      end

  #      # The read timeout for this connection is 10 milliseconds.
  #      # To compensate for the overhead of parsing the reply and the chance
  #      # not having to wait because the reply is already present in the OS
  #      # buffers, continue until we have waited at least 5x the timeout.
  #      waited = 0
  #      while waited < 50_000
  #        t1 = Time.now
  #        hiredis.read
  #        t2 = Time.now
  #        waited += (t2 - t1) * 1_000_000
  #      end
  #    ensure
  #      thread.kill
  #    end
  #  end
  #end
end

if defined?(Hiredis::Ruby::Connection)
  class RubyConnectionTest < Test::Unit::TestCase
    include ConnectionTests

    def setup
      @hiredis = Hiredis::Ruby::Connection.new
      @hiredis.timeout = 250_000
    end
  end
end

if defined?(Hiredis::Ext::Connection)
  class ExtConnectionTest < Test::Unit::TestCase
    include ConnectionTests

    def setup
      @hiredis = Hiredis::Ext::Connection.new
      @hiredis.timeout = 250_000
    end
  end
end