Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions spec/std/socket/socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,42 @@ describe Socket do
client.type.should eq(Socket::Type::STREAM)
client.protocol.should eq(Socket::Protocol::TCP)
end

it "sends messages" do
port = unused_local_port
server = Socket.tcp(Socket::Family::INET6)
server.bind("::1", port)
server.listen
address = Socket::IPAddress.new("::1", port)
spawn do
client = server.not_nil!.accept
client.gets.should eq "foo"
client.puts "bar"
ensure
client.try &.close
end
socket = Socket.tcp(Socket::Family::INET6)
socket.connect(address)
socket.puts "foo"
socket.gets.should eq "bar"
ensure
socket.try &.close
server.try &.close
end

describe "#bind" do
each_ip_family do |family, _, any_address|
it "binds to port" do
socket = TCPSocket.new family
socket.bind(any_address, 0)
socket.listen

address = socket.local_address.as(Socket::IPAddress)
address.address.should eq(any_address)
address.port.should be > 0
ensure
socket.try &.close
end
end
end
end
14 changes: 14 additions & 0 deletions spec/std/socket/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,17 @@ def unused_local_port
server.local_address.port
end
end

def each_ip_family(&block : Socket::Family, String, String ->)
describe "using IPv4" do
block.call Socket::Family::INET, "127.0.0.1", "0.0.0.0"
end

describe "using IPv6" do
block.call Socket::Family::INET6, "::1", "::"
end
end

def linux?
{{ flag?(:linux) }}
end
115 changes: 94 additions & 21 deletions spec/std/socket/tcp_server_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,109 @@ require "./spec_helper"
require "socket"

describe TCPServer do
it "creates a raw socket" do
sock = TCPServer.new
sock.family.should eq(Socket::Family::INET)
describe ".new" do
each_ip_family do |family, address|
it "listens on local address" do
port = unused_local_port

sock = TCPServer.new(Socket::Family::INET6)
sock.family.should eq(Socket::Family::INET6)
end
server = TCPServer.new(address, port)

server.reuse_port?.should be_false
server.reuse_address?.should be_true

local_address = Socket::IPAddress.new(address, port)
server.local_address.should eq local_address

server.closed?.should be_false

server.close

server.closed?.should be_true
expect_raises(Errno, "getsockname: Bad file descriptor") do
server.local_address
end
end

it "binds to port 0" do
server = TCPServer.new(address, 0)

server.local_address.address.should eq(address)
server.local_address.port.should be > 0
end

it "raises when port is negative" do
expect_raises(Socket::Error, linux? ? "getaddrinfo: Servname not supported for ai_socktype" : "No address found for #{address}:-12 over TCP") do
TCPServer.new(address, -12)
end
end

it "fails when port is in use" do
port = unused_local_port
describe "reuse_port" do
it "raises when port is in use" do
TCPServer.open(address, 0) do |server|
expect_raises Errno, /(already|Address) in use/ do
TCPServer.open(address, server.local_address.port) { }
end
end
end

expect_raises Errno, /(already|Address) in use/ do
sock = Socket.tcp(Socket::Family::INET6)
sock.bind(Socket::IPAddress.new("::1", port))
it "raises when not binding with reuse_port" do
TCPServer.open(address, 0, reuse_port: true) do |server|
expect_raises Errno, /(already|Address) in use/ do
TCPServer.open(address, server.local_address.port) { }
end
end
end

TCPServer.open("::1", port) { }
it "raises when port is not ready to be reused" do
TCPServer.open(address, 0) do |server|
expect_raises Errno, /(already|Address) in use/ do
TCPServer.open(address, server.local_address.port, reuse_port: true) { }
end
end
end

it "binds to used port with reuse_port = true" do
TCPServer.open(address, 0, reuse_port: true) do |server|
TCPServer.open(address, server.local_address.port, reuse_port: true) { }
end
end
end
end
end

it "doesn't reuse the TCP port by default (SO_REUSEPORT)" do
TCPServer.open("::", 0) do |server|
expect_raises(Errno) do
TCPServer.open("::", server.local_address.port) { }
describe "address resolution" do
it "binds to localhost" do
TCPServer.new("localhost", unused_local_port)
end

it "raises when host doesn't exist" do
expect_raises(Socket::Error, "No address") do
TCPServer.new("doesnotexist.example.org.", 12345)
end
end

it "raises (rather than segfault on darwin) when host doesn't exist and port is 0" do
expect_raises(Socket::Error, "No address") do
TCPServer.new("doesnotexist.example.org.", 0)
end
end
end
end

it "reuses the TCP port (SO_REUSEPORT)" do
TCPServer.open("::", 0, reuse_port: true) do |server|
TCPServer.open("::", server.local_address.port, reuse_port: true) { }
it "binds to all interfaces" do
port = unused_local_port
TCPServer.open(port) do |server|
server.local_address.port.should eq port
end
end
end

{% if flag?(:linux) %}
pending "settings"
{% else %}
it "settings" do
TCPServer.open("::", unused_local_port) do |server|
(server.recv_buffer_size = 42).should eq 42
server.recv_buffer_size.should eq 42
end
end
{% end %}
end
174 changes: 105 additions & 69 deletions spec/std/socket/tcp_socket_spec.cr
Original file line number Diff line number Diff line change
@@ -1,63 +1,109 @@
require "spec"
require "socket"
require "./spec_helper"

describe TCPSocket do
it "creates a raw socket" do
sock = TCPSocket.new
sock.family.should eq(Socket::Family::INET)
describe "#connect" do
each_ip_family do |family, address|
it "connects to server" do
port = unused_local_port

sock = TCPSocket.new(Socket::Family::INET6)
sock.family.should eq(Socket::Family::INET6)
end
TCPServer.open(address, port) do |server|
TCPSocket.open(address, port) do |client|
client.local_address.address.should eq address

it "sends and receives messages" do
port = TCPServer.open("::", 0) do |server|
server.local_address.port
sock = server.accept

sock.closed?.should be_false
client.closed?.should be_false

sock.local_address.port.should eq(port)
sock.local_address.address.should eq(address)

client.remote_address.port.should eq(port)
sock.remote_address.address.should eq address
end
end
end

it "raises when connection is refused" do
port = unused_local_port

expect_raises(Errno, "Error connecting to '#{address}:#{port}': Connection refused") do
TCPSocket.new(address, port)
end
end

it "raises when port is negative" do
expect_raises(Socket::Error, linux? ? "getaddrinfo: Servname not supported for ai_socktype" : "No address found for #{address}:-12 over TCP") do
TCPSocket.new(address, -12)
end
end

it "raises when port is zero" do
expect_raises(Errno, linux? ? "Error connecting to '#{address}:0': Connection refused" : "connect: Can't assign requested address") do
TCPSocket.new(address, 0)
end
end
end
port.should be > 0

TCPServer.open("::", port) do |server|
server.local_address.family.should eq(Socket::Family::INET6)
server.local_address.port.should eq(port)
server.local_address.address.should eq("::")

# test protocol specific socket options
server.reuse_address?.should be_true # defaults to true
(server.reuse_address = false).should be_false
server.reuse_address?.should be_false
(server.reuse_address = true).should be_true
server.reuse_address?.should be_true

{% unless flag?(:openbsd) %}
(server.keepalive = false).should be_false
server.keepalive?.should be_false
(server.keepalive = true).should be_true
server.keepalive?.should be_true
{% end %}

(server.linger = nil).should be_nil
server.linger.should be_nil
(server.linger = 42).should eq 42
server.linger.should eq 42

TCPSocket.open("localhost", server.local_address.port) do |client|
# The commented lines are actually dependent on the system configuration,
# so for now we keep it commented. Once we can force the family
# we can uncomment them.

# client.local_address.family.should eq(Socket::Family::INET)
# client.local_address.address.should eq("127.0.0.1")
describe "address resolution" do
it "connects to localhost" do
port = unused_local_port

TCPServer.open("localhost", port) do |server|
TCPSocket.open("localhost", port) do |client|
sock = server.accept
end
end
end

it "raises when host doesn't exist" do
expect_raises(Socket::Error, "No address") do
TCPSocket.new("doesnotexist.example.org.", 12345)
end
end

it "raises (rather than segfault on darwin) when host doesn't exist and port is 0" do
expect_raises(Socket::Error, "No address") do
TCPSocket.new("doesnotexist.example.org.", 0)
end
end
end

it "fails to connect IPv6 to IPv4 server" do
port = unused_local_port

TCPServer.open("0.0.0.0", port) do |server|
expect_raises(Errno, "Error connecting to '::1:#{port}': Connection refused") do
TCPSocket.new("::1", port)
end
end
end
end

it "sync from server" do
port = unused_local_port

TCPServer.open("::", port) do |server|
TCPSocket.open("localhost", port) do |client|
sock = server.accept
sock.sync?.should eq(server.sync?)
end

# test sync flag propagation after accept
server.sync = !server.sync?

# sock.local_address.family.should eq(Socket::Family::INET6)
# sock.local_address.port.should eq(12345)
# sock.local_address.address.should eq("::ffff:127.0.0.1")
TCPSocket.open("localhost", port) do |client|
sock = server.accept
sock.sync?.should eq(server.sync?)
end
end
end

# sock.remote_address.family.should eq(Socket::Family::INET6)
# sock.remote_address.address.should eq("::ffff:127.0.0.1")
it "settings" do
port = unused_local_port

TCPServer.open("::", port) do |server|
TCPSocket.open("localhost", port) do |client|
# test protocol specific socket options
(client.tcp_nodelay = true).should be_true
client.tcp_nodelay?.should be_true
Expand All @@ -72,19 +118,6 @@ describe TCPSocket do
(client.tcp_keepalive_count = 42).should eq 42
client.tcp_keepalive_count.should eq 42
{% end %}

client << "ping"
sock.gets(4).should eq("ping")
sock << "pong"
client.gets(4).should eq("pong")
end

# test sync flag propagation after accept
server.sync = !server.sync?

TCPSocket.open("localhost", server.local_address.port) do |client|
sock = server.accept
sock.sync?.should eq(server.sync?)
end
end
end
Expand All @@ -99,15 +132,18 @@ describe TCPSocket do
end
end

it "fails when host doesn't exist" do
expect_raises(Socket::Error, /No address/i) do
TCPSocket.new("doesnotexist.example.org.", 12345)
end
end
it "sends and receives messages" do
port = unused_local_port

it "fails (rather than segfault on darwin) when host doesn't exist and port is 0" do
expect_raises(Socket::Error, /No address/i) do
TCPSocket.new("doesnotexist.example.org.", 0)
TCPServer.open("::", port) do |server|
TCPSocket.open("localhost", port) do |client|
sock = server.accept

client << "ping"
sock.gets(4).should eq("ping")
sock << "pong"
client.gets(4).should eq("pong")
end
end
end
end
Loading