summaryrefslogtreecommitdiff
path: root/test/socket
diff options
context:
space:
mode:
Diffstat (limited to 'test/socket')
-rw-r--r--test/socket/test_addrinfo.rb6
-rw-r--r--test/socket/test_socket.rb329
-rw-r--r--test/socket/test_tcp.rb436
-rw-r--r--test/socket/test_unix.rb21
4 files changed, 620 insertions, 172 deletions
diff --git a/test/socket/test_addrinfo.rb b/test/socket/test_addrinfo.rb
index c61764d76d..0c9529090e 100644
--- a/test/socket/test_addrinfo.rb
+++ b/test/socket/test_addrinfo.rb
@@ -360,6 +360,12 @@ class TestSocketAddrinfo < Test::Unit::TestCase
assert_raise(Socket::ResolutionError) { Addrinfo.tcp("0.0.0.0", 4649).family_addrinfo("::1", 80) }
end
+ def test_ractor_shareable
+ assert_ractor(<<~'RUBY', require: 'socket', timeout: 60)
+ Ractor.make_shareable Addrinfo.new "\x10\x02\x14\xE9\xE0\x00\x00\xFB\x00\x00\x00\x00\x00\x00\x00\x00".b
+ RUBY
+ end
+
def random_port
# IANA suggests dynamic port for 49152 to 65535
# http://www.iana.org/assignments/port-numbers
diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb
index 598a05d123..c42527f370 100644
--- a/test/socket/test_socket.rb
+++ b/test/socket/test_socket.rb
@@ -173,8 +173,11 @@ class TestSocket < Test::Unit::TestCase
def errors_addrinuse
errs = [Errno::EADDRINUSE]
- # MinGW fails with "Errno::EACCES: Permission denied - bind(2) for 0.0.0.0:49721"
- errs << Errno::EACCES if /mingw/ =~ RUBY_PLATFORM
+ # Windows can fail with "Errno::EACCES: Permission denied - bind(2) for 0.0.0.0:49721"
+ # or "Test::Unit::ProxyError: Permission denied - bind(2) for 0.0.0.0:55333"
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ errs += [Errno::EACCES, Test::Unit::ProxyError]
+ end
errs
end
@@ -489,7 +492,7 @@ class TestSocket < Test::Unit::TestCase
end while IO.select([r], nil, nil, 0.1).nil?
n
end
- timeout = (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? ? 120 : 30) # for --jit-wait
+ timeout = 30
assert_equal([[s1],[],[]], IO.select([s1], nil, nil, timeout))
msg, _, _, stamp = s1.recvmsg
assert_equal("a", msg)
@@ -513,7 +516,7 @@ class TestSocket < Test::Unit::TestCase
end
def test_timestamp
- return if /linux|freebsd|netbsd|openbsd|solaris|darwin/ !~ RUBY_PLATFORM
+ return if /linux|freebsd|netbsd|openbsd|darwin/ !~ RUBY_PLATFORM
return if !defined?(Socket::AncillaryData) || !defined?(Socket::SO_TIMESTAMP)
t1 = Time.now.strftime("%Y-%m-%d")
stamp = nil
@@ -778,4 +781,322 @@ class TestSocket < Test::Unit::TestCase
end
end
+ def test_tcp_socket_v6_hostname_resolved_earlier
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ begin
+ # Verify that "localhost" can be resolved to an IPv6 address
+ Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
+ server = TCPServer.new("::1", 0)
+ rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then [Addrinfo.tcp("::1", port)]
+ when Socket::AF_INET then sleep(10); [Addrinfo.tcp("127.0.0.1", port)]
+ end
+ end
+
+ socket = Socket.tcp("localhost", port)
+ assert_true(socket.remote_address.ipv6?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_v4_hostname_resolved_earlier
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ server = TCPServer.new("127.0.0.1", 0)
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then sleep(10); [Addrinfo.tcp("::1", port)]
+ when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)]
+ end
+ end
+
+ socket = Socket.tcp("localhost", port)
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_v6_hostname_resolved_in_resolution_delay
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ begin
+ # Verify that "localhost" can be resolved to an IPv6 address
+ Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
+ server = TCPServer.new("::1", 0)
+ rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+
+ delay_time = 0.025 # Socket::RESOLUTION_DELAY (private) is 0.05
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then sleep(delay_time); [Addrinfo.tcp("::1", port)]
+ when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)]
+ end
+ end
+
+ socket = Socket.tcp("localhost", port)
+ assert_true(socket.remote_address.ipv6?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_v6_hostname_resolved_earlier_and_v6_server_is_not_listening
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ ipv4_address = "127.0.0.1"
+ server = Socket.new(Socket::AF_INET, :STREAM)
+ server.bind(Socket.pack_sockaddr_in(0, ipv4_address))
+ port = server.connect_address.ip_port
+ server_thread = Thread.new { server.listen(1); server.accept }
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then [Addrinfo.tcp("::1", port)]
+ when Socket::AF_INET then sleep(0.001); [Addrinfo.tcp(ipv4_address, port)]
+ end
+ end
+
+ socket = Socket.tcp("localhost", port)
+ assert_equal(ipv4_address, socket.remote_address.ip_address)
+ ensure
+ accepted, _ = server_thread&.value
+ accepted&.close
+ server&.close
+ socket&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_resolv_timeout
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ server = TCPServer.new("localhost", 0)
+ _, port, = server.addr
+
+ Addrinfo.define_singleton_method(:getaddrinfo) { |*_| sleep }
+
+ assert_raise(IO::TimeoutError) do
+ Socket.tcp("localhost", port, resolv_timeout: 0.01)
+ end
+ ensure
+ server&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_resolv_timeout_with_connection_failure
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ server = TCPServer.new("127.0.0.1", 12345)
+ _, port, = server.addr
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ if family == Socket::AF_INET6
+ sleep
+ else
+ [Addrinfo.tcp("127.0.0.1", port)]
+ end
+ end
+
+ server.close
+
+ assert_raise(IO::TimeoutError) do
+ Socket.tcp("localhost", port, resolv_timeout: 0.01)
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_open_timeout
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ if family == Socket::AF_INET6
+ sleep
+ else
+ [Addrinfo.tcp("127.0.0.1", 12345)]
+ end
+ end
+
+ assert_raise(IO::TimeoutError) do
+ Socket.tcp("localhost", 12345, open_timeout: 0.01)
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_open_timeout_with_other_timeouts
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ assert_raise(ArgumentError) do
+ Socket.tcp("localhost", 12345, open_timeout: 0.01, resolv_timout: 0.01)
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_one_hostname_resolution_succeeded_at_least
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ begin
+ # Verify that "localhost" can be resolved to an IPv6 address
+ Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
+ server = TCPServer.new("::1", 0)
+ rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then [Addrinfo.tcp("::1", port)]
+ when Socket::AF_INET then sleep(0.001); raise SocketError
+ end
+ end
+
+ socket = nil
+
+ assert_nothing_raised do
+ socket = Socket.tcp("localhost", port)
+ end
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_all_hostname_resolution_failed
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ server = TCPServer.new("localhost", 0)
+ _, port, = server.addr
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then raise SocketError
+ when Socket::AF_INET then sleep(0.01); raise SocketError, "Last hostname resolution error"
+ end
+ end
+
+ assert_raise_with_message(SocketError, "Last hostname resolution error") do
+ Socket.tcp("localhost", port)
+ end
+ ensure
+ server&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_hostname_resolution_failed_after_connection_failure
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.connect_address.ip_port
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then sleep(0.1); raise Socket::ResolutionError
+ when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)]
+ end
+ end
+
+ server.close
+
+ # SystemCallError is a workaround for Windows environment
+ assert_raise(Errno::ECONNREFUSED, SystemCallError) do
+ Socket.tcp("localhost", port)
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_v6_address_passed
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ begin
+ begin
+ # Verify that "localhost" can be resolved to an IPv6 address
+ Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
+ server = TCPServer.new("::1", 0)
+ rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |*_|
+ [Addrinfo.tcp("::1", port)]
+ end
+
+ socket = Socket.tcp("::1", port)
+ assert_true(socket.remote_address.ipv6?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_fast_fallback_is_false
+ server = TCPServer.new("127.0.0.1", 0)
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+ socket = Socket.tcp("127.0.0.1", port, fast_fallback: false)
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_tcp_fast_fallback
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ assert_true(Socket.tcp_fast_fallback)
+
+ Socket.tcp_fast_fallback = false
+ assert_false(Socket.tcp_fast_fallback)
+
+ Socket.tcp_fast_fallback = true
+ assert_true(Socket.tcp_fast_fallback)
+ RUBY
+ end
end if defined?(Socket)
diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb
index 35d361f060..d689ab2376 100644
--- a/test/socket/test_tcp.rb
+++ b/test/socket/test_tcp.rb
@@ -18,6 +18,12 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
end
def test_initialize_failure
+ assert_raise(Socket::ResolutionError) do
+ t = TCPSocket.open(nil, nil)
+ ensure
+ t&.close
+ end
+
# These addresses are chosen from TEST-NET-1, TEST-NET-2, and TEST-NET-3.
# [RFC 5737]
# They are chosen because probably they are not used as a host address.
@@ -43,16 +49,14 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
server_addr = '127.0.0.1'
server_port = 80
- begin
+ e = assert_raise_kind_of(SystemCallError) do
# Since client_addr is not an IP address of this host,
# bind() in TCPSocket.new should fail as EADDRNOTAVAIL.
t = TCPSocket.new(server_addr, server_port, client_addr, client_port)
- flunk "expected SystemCallError"
- rescue SystemCallError => e
- assert_match "for \"#{client_addr}\" port #{client_port}", e.message
+ ensure
+ t&.close
end
- ensure
- t.close if t && !t.closed?
+ assert_include e.message, "for \"#{client_addr}\" port #{client_port}"
end
def test_initialize_resolv_timeout
@@ -69,8 +73,32 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
end
end
+ def test_tcp_initialize_open_timeout
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.connect_address.ip_port
+ server.close
+
+ assert_raise(IO::TimeoutError) do
+ TCPSocket.new(
+ "localhost",
+ port,
+ open_timeout: 0.01,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 1000 } }
+ )
+ end
+ end
+
+ def test_initialize_open_timeout_with_other_timeouts
+ assert_raise(ArgumentError) do
+ TCPSocket.new("localhost", 12345, open_timeout: 0.01, resolv_timeout: 0.01)
+ end
+ end
+
def test_initialize_connect_timeout
- assert_raise(IO::TimeoutError, Errno::ENETUNREACH) do
+ assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do
TCPSocket.new("192.0.2.1", 80, connect_timeout: 0)
end
end
@@ -141,167 +169,255 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
end
end
- def test_ai_addrconfig
- # This test verifies that we pass AI_ADDRCONFIG to the DNS resolver when making
- # an outgoing connection.
- # The verification of this is unfortunately incredibly convoluted. We perform the
- # test by setting up a fake DNS server to receive queries. Then, we construct
- # an environment which has only IPv4 addresses and uses that fake DNS server. We
- # then attempt to make an outgoing TCP connection. Finally, we verify that we
- # only received A and not AAAA queries on our fake resolver.
- # This test can only possibly work on Linux, and only when run as root. If either
- # of these conditions aren't met, the test will be skipped.
-
- # The construction of our IPv6-free environment must happen in a child process,
- # which we can put in its own network & mount namespaces.
-
- omit "This test is disabled. It is retained to show the original intent of [ruby-core:110870]"
-
- IO.popen("-") do |test_io|
- if test_io.nil?
- begin
- # Child program
- require 'fiddle'
- require 'resolv'
- require 'open3'
-
- libc = Fiddle.dlopen(nil)
- begin
- unshare = Fiddle::Function.new(libc['unshare'], [Fiddle::TYPE_INT], Fiddle::TYPE_INT)
- rescue Fiddle::DLError
- # Test can't run because we don't have unshare(2) in libc
- # This will be the case on not-linux, and also on very old glibc versions (or
- # possibly other libc's that don't expose this syscall wrapper)
- $stdout.write(Marshal.dump({result: :skip, reason: "unshare(2) or mount(2) not in libc"}))
- exit
- end
+ def test_initialize_v6_hostname_resolved_earlier
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
- # Move our test process into a new network & mount namespace.
- # This environment will be configured to be IPv6 free and point DNS resolution
- # at a fake DNS server.
- # (n.b. these flags are CLONE_NEWNS | CLONE_NEWNET)
- ret = unshare.call(0x00020000 | 0x40000000)
- errno = Fiddle.last_error
- if ret == -1 && errno == Errno::EPERM::Errno
- # Test can't run because we're not root.
- $stdout.write(Marshal.dump({result: :skip, reason: "insufficient permissions to unshare namespaces"}))
- exit
- elsif ret == -1 && (errno == Errno::ENOSYS::Errno || errno == Errno::EINVAL::Errno)
- # No unshare(2) in the kernel (or kernel too old to know about this namespace type)
- $stdout.write(Marshal.dump({result: :skip, reason: "errno #{errno} calling unshare(2)"}))
- exit
- elsif ret == -1
- # Unexpected failure
- raise "errno #{errno} calling unshare(2)"
- end
+ begin
+ # Verify that "localhost" can be resolved to an IPv6 address
+ Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
+ server = TCPServer.new("::1", 0)
+ rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
- # Set up our fake DNS environment. Clean out /etc/hosts...
- fake_hosts_file = Tempfile.new('ruby_test_hosts')
- fake_hosts_file.write <<~HOSTS
- 127.0.0.1 localhost
- ::1 localhost
- HOSTS
- fake_hosts_file.flush
-
- # Have /etc/resolv.conf point to 127.0.0.1...
- fake_resolv_conf = Tempfile.new('ruby_test_resolv')
- fake_resolv_conf.write <<~RESOLV
- nameserver 127.0.0.1
- RESOLV
- fake_resolv_conf.flush
-
- # Also stub out /etc/nsswitch.conf; glibc can have other resolver modules
- # (like systemd-resolved) configured in there other than just using dns,
- # so rewrite it to remove any `hosts:` lines and add one which just uses
- # dns.
- real_nsswitch_conf = File.read('/etc/nsswitch.conf') rescue ""
- fake_nsswitch_conf = Tempfile.new('ruby_test_nsswitch')
- real_nsswitch_conf.lines.reject { _1 =~ /^\s*hosts:/ }.each do |ln|
- fake_nsswitch_conf.puts ln
- end
- fake_nsswitch_conf.puts "hosts: files myhostname dns"
- fake_nsswitch_conf.flush
-
- # This is needed to make sure our bind-mounds aren't visible outside this process.
- system 'mount', '--make-rprivate', '/', exception: true
- # Bind-mount the fake files over the top of the real files.
- system 'mount', '--bind', '--make-private', fake_hosts_file.path, '/etc/hosts', exception: true
- system 'mount', '--bind', '--make-private', fake_resolv_conf.path, '/etc/resolv.conf', exception: true
- system 'mount', '--bind', '--make-private', fake_nsswitch_conf.path, '/etc/nsswitch.conf', exception: true
-
- # Create a dummy interface with only an IPv4 address
- system 'ip', 'link', 'add', 'dummy0', 'type', 'dummy', exception: true
- system 'ip', 'addr', 'add', '192.168.1.2/24', 'dev', 'dummy0', exception: true
- system 'ip', 'link', 'set', 'dummy0', 'up', exception: true
- system 'ip', 'link', 'set', 'lo', 'up', exception: true
-
- # Disable IPv6 on this interface (this is needed to disable the link-local
- # IPv6 address)
- File.open('/proc/sys/net/ipv6/conf/dummy0/disable_ipv6', 'w') do |f|
- f.puts "1"
- end
+ server_thread = Thread.new { server.accept }
+ port = server.addr[1]
- # Create a fake DNS server which will receive the DNS queries triggered by TCPSocket.new
- fake_dns_server_socket = UDPSocket.new
- fake_dns_server_socket.bind('127.0.0.1', 53)
- received_dns_queries = []
- fake_dns_server_thread = Thread.new do
- Socket.udp_server_loop_on([fake_dns_server_socket]) do |msg, msg_src|
- request = Resolv::DNS::Message.decode(msg)
- received_dns_queries << request
- response = request.dup.tap do |r|
- r.qr = 0
- r.rcode = 3 # NXDOMAIN
- end
- msg_src.reply response.encode
- end
- end
+ socket = TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 1000 } }
+ )
+ assert_true(socket.remote_address.ipv6?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
- # Make a request which will hit our fake DNS swerver - this needs to be in _another_
- # process because glibc will cache resolver info across the fork otherwise.
- load_path_args = $LOAD_PATH.flat_map { ['-I', _1] }
- Open3.capture3('/proc/self/exe', *load_path_args, '-rsocket', '-e', <<~RUBY)
- TCPSocket.open('www.example.com', 4444)
- RUBY
+ def test_initialize_v4_hostname_resolved_earlier
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
- fake_dns_server_thread.kill
- fake_dns_server_thread.join
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.addr[1]
- have_aaaa_qs = received_dns_queries.any? do |query|
- query.question.any? do |question|
- question[1] == Resolv::DNS::Resource::IN::AAAA
- end
- end
+ server_thread = Thread.new { server.accept }
+ socket = TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv6: 1000 } }
+ )
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
- have_a_q = received_dns_queries.any? do |query|
- query.question.any? do |question|
- question[0].to_s == "www.example.com"
- end
- end
+ def test_initialize_v6_hostname_resolved_in_resolution_delay
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
- if have_aaaa_qs
- $stdout.write(Marshal.dump({result: :fail, reason: "got AAAA queries, expected none"}))
- elsif !have_a_q
- $stdout.write(Marshal.dump({result: :fail, reason: "got no A query for example.com"}))
- else
- $stdout.write(Marshal.dump({result: :success}))
- end
- rescue => ex
- $stdout.write(Marshal.dump({result: :fail, reason: ex.full_message}))
- ensure
- # Make sure the child process does not transfer control back into the test runner.
- exit!
- end
- else
- test_result = Marshal.load(test_io.read)
-
- case test_result[:result]
- when :skip
- omit test_result[:reason]
- when :fail
- fail test_result[:reason]
- end
- end
+ begin
+ # Verify that "localhost" can be resolved to an IPv6 address
+ Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
+ server = TCPServer.new("::1", 0)
+ rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
end
+
+ port = server.addr[1]
+ delay_time = 25 # Socket::RESOLUTION_DELAY (private) is 50ms
+
+ server_thread = Thread.new { server.accept }
+ socket = TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv6: delay_time } }
+ )
+ assert_true(socket.remote_address.ipv6?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_initialize_v6_hostname_resolved_earlier_and_v6_server_is_not_listening
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ ipv4_address = "127.0.0.1"
+ server = Socket.new(Socket::AF_INET, :STREAM)
+ server.bind(Socket.pack_sockaddr_in(0, ipv4_address))
+ port = server.connect_address.ip_port
+
+ server_thread = Thread.new { server.listen(1); server.accept }
+ socket = TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 10 } }
+ )
+ assert_equal(ipv4_address, socket.remote_address.ip_address)
+ ensure
+ accepted, _ = server_thread&.value
+ accepted&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_initialize_v6_hostname_resolved_later_and_v6_server_is_not_listening
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = Socket.new(Socket::AF_INET, :STREAM)
+ server.bind(Socket.pack_sockaddr_in(0, "127.0.0.1"))
+ port = server.connect_address.ip_port
+
+ server_thread = Thread.new { server.listen(1); server.accept }
+ socket = TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv6: 25 } }
+ )
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ accepted, _ = server_thread&.value
+ accepted&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_initialize_v6_hostname_resolution_failed_and_v4_hostname_resolution_is_success
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.addr[1]
+
+ server_thread = Thread.new { server.accept }
+ socket = TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 10 }, error: { ipv6: Socket::EAI_FAIL } }
+ )
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_initialize_resolv_timeout_with_connection_failure
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ begin
+ server = TCPServer.new("::1", 0)
+ rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ port = server.connect_address.ip_port
+ server.close
+
+ assert_raise(IO::TimeoutError) do
+ TCPSocket.new(
+ "localhost",
+ port,
+ resolv_timeout: 0.01,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 1000 } }
+ )
+ end
+ end
+
+ def test_initialize_with_hostname_resolution_failure_after_connection_failure
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ begin
+ server = TCPServer.new("::1", 0)
+ rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ port = server.connect_address.ip_port
+ server.close
+
+ assert_raise(Errno::ECONNREFUSED) do
+ TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 100 }, error: { ipv4: Socket::EAI_FAIL } }
+ )
+ end
+ end
+
+ def test_initialize_with_connection_failure_after_hostname_resolution_failure
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.connect_address.ip_port
+ server.close
+
+ assert_raise(Errno::ECONNREFUSED) do
+ TCPSocket.new(
+ "localhost",
+ port,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 100 }, error: { ipv6: Socket::EAI_FAIL } }
+ )
+ end
+ end
+
+ def test_initialize_v6_connected_socket_with_v6_address
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ begin
+ server = TCPServer.new("::1", 0)
+ rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
+ return
+ end
+
+ server_thread = Thread.new { server.accept }
+ port = server.addr[1]
+
+ socket = TCPSocket.new("::1", port)
+ assert_true(socket.remote_address.ipv6?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_initialize_v4_connected_socket_with_v4_address
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = TCPServer.new("127.0.0.1", 0)
+ server_thread = Thread.new { server.accept }
+ port = server.addr[1]
+
+ socket = TCPSocket.new("127.0.0.1", port)
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
+ end
+
+ def test_initialize_fast_fallback_is_false
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = TCPServer.new("127.0.0.1", 0)
+ _, port, = server.addr
+ server_thread = Thread.new { server.accept }
+
+ socket = TCPSocket.new("127.0.0.1", port, fast_fallback: false)
+ assert_true(socket.remote_address.ipv4?)
+ ensure
+ server_thread&.value&.close
+ server&.close
+ socket&.close
end
end if defined?(TCPSocket)
diff --git a/test/socket/test_unix.rb b/test/socket/test_unix.rb
index 3e7d85befc..e239e3935b 100644
--- a/test/socket/test_unix.rb
+++ b/test/socket/test_unix.rb
@@ -146,6 +146,7 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
end
def test_fd_passing_race_condition
+ omit 'randomly crashes on macOS' if RUBY_PLATFORM =~ /darwin/
r1, w = IO.pipe
s1, s2 = UNIXSocket.pair
s1.nonblock = s2.nonblock = true
@@ -292,14 +293,18 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
File.unlink path if path && File.socket?(path)
end
- def test_open_nul_byte
- tmpfile = Tempfile.new("s")
- path = tmpfile.path
- tmpfile.close(true)
- assert_raise(ArgumentError) {UNIXServer.open(path+"\0")}
- assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")}
- ensure
- File.unlink path if path && File.socket?(path)
+ def test_open_argument
+ assert_raise(TypeError) {UNIXServer.new(nil)}
+ assert_raise(TypeError) {UNIXServer.new(1)}
+ Tempfile.create("s") do |s|
+ path = s.path
+ s.close
+ File.unlink(path)
+ assert_raise(ArgumentError) {UNIXServer.open(path+"\0")}
+ assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")}
+ arg = Struct.new(:to_path).new(path)
+ assert_equal(path, UNIXServer.open(arg) { |server| server.path })
+ end
end
def test_addr