diff options
Diffstat (limited to 'spec/ruby/library/socket')
200 files changed, 13705 insertions, 0 deletions
diff --git a/spec/ruby/library/socket/addrinfo/afamily_spec.rb b/spec/ruby/library/socket/addrinfo/afamily_spec.rb new file mode 100644 index 0000000000..5d075be057 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/afamily_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +describe "Addrinfo#afamily" do + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns Socket::AF_INET" do + @addrinfo.afamily.should == Socket::AF_INET + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns Socket::AF_INET6" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::AF_UNIX" do + @addrinfo.afamily.should == Socket::AF_UNIX + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/bind_spec.rb b/spec/ruby/library/socket/addrinfo/bind_spec.rb new file mode 100644 index 0000000000..6f78890a4d --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/bind_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Addrinfo#bind" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 0) + end + + after :each do + @socket.close unless @socket.closed? + end + + it "returns a bound socket when no block is given" do + @socket = @addrinfo.bind + @socket.should be_kind_of(Socket) + @socket.closed?.should be_false + end + + it "yields the socket if a block is given" do + @addrinfo.bind do |sock| + @socket = sock + sock.should be_kind_of(Socket) + end + @socket.closed?.should be_true + end + +end diff --git a/spec/ruby/library/socket/addrinfo/canonname_spec.rb b/spec/ruby/library/socket/addrinfo/canonname_spec.rb new file mode 100644 index 0000000000..a1cc8b3980 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/canonname_spec.rb @@ -0,0 +1,27 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Addrinfo#canonname" do + + before :each do + @addrinfos = Addrinfo.getaddrinfo("localhost", 80, :INET, :STREAM, nil, Socket::AI_CANONNAME) + end + + it "returns the canonical name for a host" do + canonname = @addrinfos.map { |a| a.canonname }.find { |name| name and name.include?("localhost") } + if canonname + canonname.should include("localhost") + else + canonname.should == nil + end + end + + describe 'when the canonical name is not available' do + it 'returns nil' do + addr = Addrinfo.new(Socket.sockaddr_in(0, '127.0.0.1')) + + addr.canonname.should be_nil + end + end + +end diff --git a/spec/ruby/library/socket/addrinfo/connect_from_spec.rb b/spec/ruby/library/socket/addrinfo/connect_from_spec.rb new file mode 100644 index 0000000000..55fce2e159 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/connect_from_spec.rb @@ -0,0 +1,75 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#connect_from' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + @addr = Addrinfo.tcp(ip_address, @port) + end + + after do + @socket.close if @socket + @server.close + end + + describe 'using separate arguments' do + it 'returns a Socket when no block is given' do + @socket = @addr.connect_from(ip_address, 0) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_from(ip_address, 0) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_from(ip_address, 0, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the socket to the local address' do + @socket = @addr.connect_from(ip_address, 0) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + + describe 'using an Addrinfo as the 1st argument' do + before do + @from_addr = Addrinfo.tcp(ip_address, 0) + end + + it 'returns a Socket when no block is given' do + @socket = @addr.connect_from(@from_addr) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_from(@from_addr) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_from(@from_addr, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the socket to the local address' do + @socket = @addr.connect_from(@from_addr) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/connect_spec.rb b/spec/ruby/library/socket/addrinfo/connect_spec.rb new file mode 100644 index 0000000000..1c2dc609ca --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/connect_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#connect' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + end + + after do + @socket.close if @socket + @server.close + end + + it 'returns a Socket when no block is given' do + addr = Addrinfo.tcp(ip_address, @port) + @socket = addr.connect + @socket.should be_an_instance_of(Socket) + end + + it 'yields a Socket when a block is given' do + addr = Addrinfo.tcp(ip_address, @port) + addr.connect do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'accepts a Hash of options' do + addr = Addrinfo.tcp(ip_address, @port) + @socket = addr.connect(timeout: 2) + @socket.should be_an_instance_of(Socket) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/connect_to_spec.rb b/spec/ruby/library/socket/addrinfo/connect_to_spec.rb new file mode 100644 index 0000000000..69666da19b --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/connect_to_spec.rb @@ -0,0 +1,75 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#connect_to' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + @addr = Addrinfo.tcp(ip_address, 0) + end + + after do + @socket.close if @socket + @server.close + end + + describe 'using separate arguments' do + it 'returns a Socket when no block is given' do + @socket = @addr.connect_to(ip_address, @port) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_to(ip_address, @port) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_to(ip_address, @port, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the Addrinfo to the local address' do + @socket = @addr.connect_to(ip_address, @port) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + + describe 'using an Addrinfo as the 1st argument' do + before do + @to_addr = Addrinfo.tcp(ip_address, @port) + end + + it 'returns a Socket when no block is given' do + @socket = @addr.connect_to(@to_addr) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_to(@to_addr) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_to(@to_addr, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the socket to the local address' do + @socket = @addr.connect_to(@to_addr) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb new file mode 100644 index 0000000000..3c2f9f73d8 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb @@ -0,0 +1,113 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#family_addrinfo' do + it 'raises ArgumentError if no arguments are given' do + addr = Addrinfo.tcp('127.0.0.1', 0) + + -> { addr.family_addrinfo }.should raise_error(ArgumentError) + end + + describe 'using multiple arguments' do + describe 'with an IP Addrinfo' do + before do + @source = Addrinfo.tcp('127.0.0.1', 0) + end + + it 'raises ArgumentError if only 1 argument is given' do + -> { @source.family_addrinfo('127.0.0.1') }.should raise_error(ArgumentError) + end + + it 'raises ArgumentError if more than 2 arguments are given' do + -> { @source.family_addrinfo('127.0.0.1', 0, 666) }.should raise_error(ArgumentError) + end + + it 'returns an Addrinfo when a host and port are given' do + addr = @source.family_addrinfo('127.0.0.1', 0) + + addr.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + @addr = @source.family_addrinfo('127.0.0.1', 0) + end + + it 'uses the same address family as the source Addrinfo' do + @addr.afamily.should == @source.afamily + end + + it 'uses the same protocol family as the source Addrinfo' do + @addr.pfamily.should == @source.pfamily + end + + it 'uses the same socket type as the source Addrinfo' do + @addr.socktype.should == @source.socktype + end + + it 'uses the same protocol as the source Addrinfo' do + @addr.protocol.should == @source.protocol + end + end + end + + describe 'with a UNIX Addrinfo' do + before do + @source = Addrinfo.unix('cats') + end + + it 'raises ArgumentError if more than 1 argument is given' do + -> { @source.family_addrinfo('foo', 'bar') }.should raise_error(ArgumentError) + end + + it 'returns an Addrinfo when a UNIX socket path is given' do + addr = @source.family_addrinfo('dogs') + + addr.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + @addr = @source.family_addrinfo('dogs') + end + + it 'uses AF_UNIX as the address family' do + @addr.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @addr.pfamily.should == Socket::PF_UNIX + end + + it 'uses the given socket path' do + @addr.unix_path.should == 'dogs' + end + end + end + end + + describe 'using an Addrinfo as the 1st argument' do + before do + @source = Addrinfo.tcp('127.0.0.1', 0) + end + + it 'returns the input Addrinfo' do + input = Addrinfo.tcp('127.0.0.2', 0) + @source.family_addrinfo(input).should == input + end + + it 'raises ArgumentError if more than 1 argument is given' do + input = Addrinfo.tcp('127.0.0.2', 0) + -> { @source.family_addrinfo(input, 666) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if the protocol families don't match" do + input = Addrinfo.tcp('::1', 0) + -> { @source.family_addrinfo(input) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if the socket types don't match" do + input = Addrinfo.udp('127.0.0.1', 0) + -> { @source.family_addrinfo(input) }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/foreach_spec.rb b/spec/ruby/library/socket/addrinfo/foreach_spec.rb new file mode 100644 index 0000000000..6ec8fab905 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/foreach_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +describe 'Addrinfo.foreach' do + it 'yields Addrinfo instances to the supplied block' do + Addrinfo.foreach('127.0.0.1', 80) do |addr| + addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb new file mode 100644 index 0000000000..e05fe9967a --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb @@ -0,0 +1,87 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo.getaddrinfo' do + it 'returns an Array of Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80) + + array.should be_an_instance_of(Array) + array[0].should be_an_instance_of(Addrinfo) + end + + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'sets the IP address of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].ip_address.should == ip_address + end + + it 'sets the port of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].ip_port.should == 80 + end + + it 'sets the address family of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].afamily.should == family + end + + it 'sets the protocol family of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].pfamily.should == family + end + end + + guard -> { SocketSpecs.ipv6_available? } do + it 'sets a custom protocol family of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('::1', 80, Socket::PF_INET6) + + array[0].pfamily.should == Socket::PF_INET6 + end + + it 'sets a corresponding address family based on a custom protocol family' do + array = Addrinfo.getaddrinfo('::1', 80, Socket::PF_INET6) + + array[0].afamily.should == Socket::AF_INET6 + end + end + + platform_is_not :windows do + it 'sets the default socket type of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80) + possible = [Socket::SOCK_STREAM, Socket::SOCK_DGRAM] + + possible.should include(array[0].socktype) + end + end + + it 'sets a custom socket type of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80, nil, Socket::SOCK_DGRAM) + + array[0].socktype.should == Socket::SOCK_DGRAM + end + + platform_is_not :windows do + it 'sets the default socket protocol of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80) + possible = [Socket::IPPROTO_TCP, Socket::IPPROTO_UDP] + + possible.should include(array[0].protocol) + end + end + + it 'sets a custom socket protocol of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80, nil, nil, Socket::IPPROTO_UDP) + + array[0].protocol.should == Socket::IPPROTO_UDP + end + + it 'sets the canonical name when AI_CANONNAME is given as a flag' do + array = Addrinfo.getaddrinfo('localhost', 80, nil, nil, nil, Socket::AI_CANONNAME) + + array[0].canonname.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb new file mode 100644 index 0000000000..43b5a2000a --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb @@ -0,0 +1,40 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#getnameinfo' do + describe 'using an IP Addrinfo' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @addr = Addrinfo.tcp(ip_address, 21) + end + + it 'returns the node and service names' do + host, service = @addr.getnameinfo + service.should == 'ftp' + end + + it 'accepts flags as an Integer as the first argument' do + host, service = @addr.getnameinfo(Socket::NI_NUMERICSERV) + service.should == '21' + end + end + end + + platform_is :linux do + platform_is_not :android do + describe 'using a UNIX Addrinfo' do + before do + @addr = Addrinfo.unix('cats') + @host = Socket.gethostname + end + + it 'returns the hostname and UNIX socket path' do + host, path = @addr.getnameinfo + + host.should == @host + path.should == 'cats' + end + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/initialize_spec.rb b/spec/ruby/library/socket/addrinfo/initialize_spec.rb new file mode 100644 index 0000000000..1f16531aaa --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/initialize_spec.rb @@ -0,0 +1,589 @@ +require_relative '../spec_helper' + +describe "Addrinfo#initialize" do + + describe "with a sockaddr string" do + + describe "without a family" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1")) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the UNSPEC pfamily" do + @addrinfo.pfamily.should == Socket::PF_UNSPEC + end + + it 'returns AF_INET as the default address family' do + addr = Addrinfo.new(Socket.sockaddr_in(80, '127.0.0.1')) + + addr.afamily.should == Socket::AF_INET + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family given" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the INET6 pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family and socket type" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6, Socket::SOCK_STREAM) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the INET6 pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the specified socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family, socket type and protocol" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the INET6 pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the specified socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the specified protocol" do + @addrinfo.protocol.should == Socket::IPPROTO_TCP + end + end + + end + + describe "with a sockaddr array" do + + describe "without a family" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"]) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe 'with a valid IP address' do + # Uses AF_INET6 since AF_INET is the default, making it a better test + # that Addrinfo actually sets the family correctly. + before do + @sockaddr = ['AF_INET6', 80, 'hostname', '::1'] + end + + it 'returns an Addrinfo with the correct IP' do + addr = Addrinfo.new(@sockaddr) + + addr.ip_address.should == '::1' + end + + it 'returns an Addrinfo with the correct address family' do + addr = Addrinfo.new(@sockaddr) + + addr.afamily.should == Socket::AF_INET6 + end + + it 'returns an Addrinfo with the correct protocol family' do + addr = Addrinfo.new(@sockaddr) + + addr.pfamily.should == Socket::PF_INET6 + end + + it 'returns an Addrinfo with the correct port' do + addr = Addrinfo.new(@sockaddr) + + addr.ip_port.should == 80 + end + end + + describe 'with an invalid IP address' do + it 'raises SocketError' do + block = -> { Addrinfo.new(['AF_INET6', 80, 'hostname', '127.0.0.1']) } + + block.should raise_error(SocketError) + end + end + + describe "with a family given" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family and socket type" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET, Socket::SOCK_STREAM) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + + [:SOCK_STREAM, :SOCK_DGRAM, :SOCK_RAW].each do |type| + it "overwrites the socket type #{type}" do + sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + + value = Socket.const_get(type) + addr = Addrinfo.new(sockaddr, nil, value) + + addr.socktype.should == value + end + end + + platform_is_not :android do + with_feature :sock_packet do + [:SOCK_SEQPACKET].each do |type| + it "overwrites the socket type #{type}" do + sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + + value = Socket.const_get(type) + addr = Addrinfo.new(sockaddr, nil, value) + + addr.socktype.should == value + end + end + end + end + + it "raises SocketError when using SOCK_RDM" do + sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + value = Socket::SOCK_RDM + block = -> { Addrinfo.new(sockaddr, nil, value) } + + block.should raise_error(SocketError) + end + end + + describe "with a family, socket type and protocol" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the specified protocol" do + @addrinfo.protocol.should == Socket::IPPROTO_TCP + end + end + end + + describe 'using an Array with extra arguments' do + describe 'with the AF_INET6 address family and an explicit protocol family' do + before do + @sockaddr = ['AF_INET6', 80, 'hostname', '127.0.0.1'] + end + + it "raises SocketError when using any Socket constant except AF_INET(6)/PF_INET(6)" do + Socket.constants.grep(/(^AF_|^PF_)(?!INET)/).each do |constant| + value = Socket.const_get(constant) + -> { + Addrinfo.new(@sockaddr, value) + }.should raise_error(SocketError) + end + end + end + + describe 'with the AF_INET address family and an explicit socket protocol' do + before do + @sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + end + + describe 'and no socket type is given' do + valid = [:IPPROTO_IP, :IPPROTO_UDP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, nil, value) + + addr.protocol.should == value + end + end + + platform_is_not :windows, :aix do + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, nil, value) } + + block.should raise_error(SocketError) + end + end + end + end + + describe 'and the socket type is set to SOCK_DGRAM' do + before do + @socktype = Socket::SOCK_DGRAM + end + + valid = [:IPPROTO_IP, :IPPROTO_UDP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + + platform_is_not :windows, :aix do + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + + with_feature :sock_packet do + describe 'and the socket type is set to SOCK_PACKET' do + before do + @socktype = Socket::SOCK_PACKET + end + + Socket.constants.grep(/^IPPROTO/).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + + describe 'and the socket type is set to SOCK_RAW' do + before do + @socktype = Socket::SOCK_RAW + end + + Socket.constants.grep(/^IPPROTO/).each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + end + + describe 'and the socket type is set to SOCK_RDM' do + before do + @socktype = Socket::SOCK_RDM + end + + Socket.constants.grep(/^IPPROTO/).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + + platform_is :linux do + platform_is_not :android do + describe 'and the socket type is set to SOCK_SEQPACKET' do + before do + @socktype = Socket::SOCK_SEQPACKET + end + + valid = [:IPPROTO_IP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + end + + describe 'and the socket type is set to SOCK_STREAM' do + before do + @socktype = Socket::SOCK_STREAM + end + + valid = [:IPPROTO_IP, :IPPROTO_TCP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + + platform_is_not :windows, :aix do + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + end + end + + describe 'with Symbols' do + before do + @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') + end + + it 'returns an Addrinfo with :PF_INET family' do + addr = Addrinfo.new(@sockaddr, :PF_INET) + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with :INET family' do + addr = Addrinfo.new(@sockaddr, :INET) + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with :SOCK_STREAM as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, :SOCK_STREAM) + + addr.socktype.should == Socket::SOCK_STREAM + end + + it 'returns an Addrinfo with :STREAM as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, :STREAM) + + addr.socktype.should == Socket::SOCK_STREAM + end + end + + describe 'with Strings' do + before do + @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') + end + + it 'returns an Addrinfo with "PF_INET" family' do + addr = Addrinfo.new(@sockaddr, 'PF_INET') + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with "INET" family' do + addr = Addrinfo.new(@sockaddr, 'INET') + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with "SOCK_STREAM" as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, 'SOCK_STREAM') + + addr.socktype.should == Socket::SOCK_STREAM + end + + it 'returns an Addrinfo with "STREAM" as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, 'STREAM') + + addr.socktype.should == Socket::SOCK_STREAM + end + end + + describe 'using separate arguments for a Unix socket' do + before do + @sockaddr = Socket.pack_sockaddr_un('socket') + end + + it 'returns an Addrinfo with the correct unix path' do + Addrinfo.new(@sockaddr).unix_path.should == 'socket' + end + + it 'returns an Addrinfo with the correct protocol family' do + Addrinfo.new(@sockaddr).pfamily.should == Socket::PF_UNSPEC + end + + it 'returns an Addrinfo with the correct address family' do + Addrinfo.new(@sockaddr).afamily.should == Socket::AF_UNIX + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb new file mode 100644 index 0000000000..6b18c79469 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb @@ -0,0 +1,48 @@ +require_relative '../spec_helper' + + +describe 'Addrinfo#inspect_sockaddr' do + describe 'using an IPv4 address' do + it 'returns a String containing the IP address and port number' do + addr = Addrinfo.tcp('127.0.0.1', 80) + + addr.inspect_sockaddr.should == '127.0.0.1:80' + end + + it 'returns a String containing just the IP address when no port is given' do + addr = Addrinfo.tcp('127.0.0.1', 0) + + addr.inspect_sockaddr.should == '127.0.0.1' + end + end + + describe 'using an IPv6 address' do + before :each do + @ip = '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + end + + it 'returns a String containing the IP address and port number' do + Addrinfo.tcp('::1', 80).inspect_sockaddr.should == '[::1]:80' + Addrinfo.tcp(@ip, 80).inspect_sockaddr.should == '[2001:db8:85a3::8a2e:370:7334]:80' + end + + it 'returns a String containing just the IP address when no port is given' do + Addrinfo.tcp('::1', 0).inspect_sockaddr.should == '::1' + Addrinfo.tcp(@ip, 0).inspect_sockaddr.should == '2001:db8:85a3::8a2e:370:7334' + end + end + + describe 'using a UNIX path' do + it 'returns a String containing the UNIX path' do + addr = Addrinfo.unix('/foo/bar') + + addr.inspect_sockaddr.should == '/foo/bar' + end + + it 'returns a String containing the UNIX path when using a relative path' do + addr = Addrinfo.unix('foo') + + addr.inspect_sockaddr.should == 'UNIX foo' + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/inspect_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_spec.rb new file mode 100644 index 0000000000..1442af6162 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/inspect_spec.rb @@ -0,0 +1,63 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#inspect' do + describe 'using an IPv4 Addrinfo' do + it 'returns a String when using a TCP Addrinfo' do + addr = Addrinfo.tcp('127.0.0.1', 80) + + addr.inspect.should == '#<Addrinfo: 127.0.0.1:80 TCP>' + end + + it 'returns a String when using an UDP Addrinfo' do + addr = Addrinfo.udp('127.0.0.1', 80) + + addr.inspect.should == '#<Addrinfo: 127.0.0.1:80 UDP>' + end + + it 'returns a String when using an Addrinfo without a port' do + addr = Addrinfo.ip('127.0.0.1') + + addr.inspect.should == '#<Addrinfo: 127.0.0.1>' + end + end + + describe 'using an IPv6 Addrinfo' do + it 'returns a String when using a TCP Addrinfo' do + addr = Addrinfo.tcp('::1', 80) + + addr.inspect.should == '#<Addrinfo: [::1]:80 TCP>' + end + + it 'returns a String when using an UDP Addrinfo' do + addr = Addrinfo.udp('::1', 80) + + addr.inspect.should == '#<Addrinfo: [::1]:80 UDP>' + end + + it 'returns a String when using an Addrinfo without a port' do + addr = Addrinfo.ip('::1') + + addr.inspect.should == '#<Addrinfo: ::1>' + end + end + + describe 'using a UNIX Addrinfo' do + it 'returns a String' do + addr = Addrinfo.unix('/foo') + + addr.inspect.should == '#<Addrinfo: /foo SOCK_STREAM>' + end + + it 'returns a String when using a relative UNIX path' do + addr = Addrinfo.unix('foo') + + addr.inspect.should == '#<Addrinfo: UNIX foo SOCK_STREAM>' + end + + it 'returns a String when using a DGRAM socket' do + addr = Addrinfo.unix('/foo', Socket::SOCK_DGRAM) + + addr.inspect.should == '#<Addrinfo: /foo SOCK_DGRAM>' + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_address_spec.rb b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb new file mode 100644 index 0000000000..193432e861 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb @@ -0,0 +1,64 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ip_address" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns the ip address" do + @addrinfo.ip_address.should == "127.0.0.1" + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns the ip address" do + @addrinfo.ip_address.should == "::1" + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "raises an exception" do + -> { @addrinfo.ip_address }.should raise_error(SocketError) + end + end + + describe 'with an Array as the socket address' do + it 'returns the IP as a String' do + sockaddr = ['AF_INET', 80, 'localhost', '127.0.0.1'] + addr = Addrinfo.new(sockaddr) + + addr.ip_address.should == '127.0.0.1' + end + end + + describe 'without an IP address' do + before do + @ips = ['127.0.0.1', '0.0.0.0', '::1'] + end + + # Both these cases seem to return different values at times on MRI. Since + # this is network dependent we can't rely on an exact IP being returned. + it 'returns the local IP address when using an empty String as the IP' do + sockaddr = Socket.sockaddr_in(80, '') + addr = Addrinfo.new(sockaddr) + + @ips.include?(addr.ip_address).should == true + end + + it 'returns the local IP address when using nil as the IP' do + sockaddr = Socket.sockaddr_in(80, nil) + addr = Addrinfo.new(sockaddr) + + @ips.include?(addr.ip_address).should == true + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_port_spec.rb b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb new file mode 100644 index 0000000000..f10ce35143 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ip_port" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns the port" do + @addrinfo.ip_port.should == 80 + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns the port" do + @addrinfo.ip_port.should == 80 + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "raises an exception" do + -> { @addrinfo.ip_port }.should raise_error(SocketError) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_spec.rb b/spec/ruby/library/socket/addrinfo/ip_spec.rb new file mode 100644 index 0000000000..09b9341605 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_spec.rb @@ -0,0 +1,62 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Addrinfo#ip?" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns true" do + @addrinfo.ip?.should be_true + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns true" do + @addrinfo.ip?.should be_true + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ip?.should be_false + end + end +end + +describe 'Addrinfo.ip' do + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'returns an Addrinfo instance' do + Addrinfo.ip(ip_address).should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.ip(ip_address).ip_address.should == ip_address + end + + it 'sets the port to 0' do + Addrinfo.ip(ip_address).ip_port.should == 0 + end + + it 'sets the address family' do + Addrinfo.ip(ip_address).afamily.should == family + end + + it 'sets the protocol family' do + Addrinfo.ip(ip_address).pfamily.should == family + end + + it 'sets the socket type to 0' do + Addrinfo.ip(ip_address).socktype.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb new file mode 100644 index 0000000000..58260c4557 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ip_unpack" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns the ip address and port pair" do + @addrinfo.ip_unpack.should == ["127.0.0.1", 80] + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns the ip address and port pair" do + @addrinfo.ip_unpack.should == ["::1", 80] + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "raises an exception" do + -> { @addrinfo.ip_unpack }.should raise_error(SocketError) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb new file mode 100644 index 0000000000..3a584d4f52 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb @@ -0,0 +1,41 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4_loopback?" do + describe "for an ipv4 socket" do + it "returns true for the loopback address" do + Addrinfo.ip('127.0.0.1').should.ipv4_loopback? + Addrinfo.ip('127.0.0.2').should.ipv4_loopback? + Addrinfo.ip('127.255.0.1').should.ipv4_loopback? + Addrinfo.ip('127.255.255.255').should.ipv4_loopback? + end + + it "returns false for another address" do + Addrinfo.ip('255.255.255.0').ipv4_loopback?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @loopback = Addrinfo.tcp("::1", 80) + @other = Addrinfo.tcp("::", 80) + end + + it "returns false for the loopback address" do + @loopback.ipv4_loopback?.should be_false + end + + it "returns false for another address" do + @other.ipv4_loopback?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_loopback?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb new file mode 100644 index 0000000000..e4b4cfcc84 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb @@ -0,0 +1,27 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4_multicast?" do + it 'returns true for a multicast address' do + Addrinfo.ip('224.0.0.0').should.ipv4_multicast? + Addrinfo.ip('224.0.0.9').should.ipv4_multicast? + Addrinfo.ip('239.255.255.250').should.ipv4_multicast? + end + + it 'returns false for a regular address' do + Addrinfo.ip('8.8.8.8').should_not.ipv4_multicast? + end + + it 'returns false for an IPv6 address' do + Addrinfo.ip('::1').should_not.ipv4_multicast? + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_multicast?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb new file mode 100644 index 0000000000..97218b5ba3 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb @@ -0,0 +1,45 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4_private?" do + describe "for an ipv4 socket" do + before :each do + @private = Addrinfo.tcp("10.0.0.1", 80) + @other = Addrinfo.tcp("0.0.0.0", 80) + end + + it "returns true for a private address" do + Addrinfo.ip('10.0.0.0').should.ipv4_private? + Addrinfo.ip('10.0.0.5').should.ipv4_private? + + Addrinfo.ip('172.16.0.0').should.ipv4_private? + Addrinfo.ip('172.16.0.5').should.ipv4_private? + + Addrinfo.ip('192.168.0.0').should.ipv4_private? + Addrinfo.ip('192.168.0.5').should.ipv4_private? + end + + it "returns false for a public address" do + @other.ipv4_private?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @other = Addrinfo.tcp("::", 80) + end + + it "returns false" do + @other.ipv4_private?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_private?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb new file mode 100644 index 0000000000..61f7759b10 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4?" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("10.0.0.1", 80) + end + + it "returns true" do + @addrinfo.ipv4?.should be_true + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns false" do + @addrinfo.ipv4?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb new file mode 100644 index 0000000000..bfef396381 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Addrinfo#ipv6_linklocal?' do + platform_is_not :aix do + it 'returns true for a link-local address' do + Addrinfo.ip('fe80::').should.ipv6_linklocal? + Addrinfo.ip('fe81::').should.ipv6_linklocal? + Addrinfo.ip('fe8f::').should.ipv6_linklocal? + Addrinfo.ip('fe80::1').should.ipv6_linklocal? + end + end + + it 'returns false for a regular address' do + Addrinfo.ip('::1').should_not.ipv6_linklocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_linklocal? + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb new file mode 100644 index 0000000000..ffc75185ea --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv6_loopback?" do + describe "for an ipv4 socket" do + before :each do + @loopback = Addrinfo.tcp("127.0.0.1", 80) + @other = Addrinfo.tcp("0.0.0.0", 80) + end + + it "returns false for the loopback address" do + @loopback.ipv6_loopback?.should be_false + end + + it "returns false for another address" do + @other.ipv6_loopback?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @loopback = Addrinfo.tcp("::1", 80) + @other = Addrinfo.tcp("::", 80) + end + + it "returns true for the loopback address" do + @loopback.ipv6_loopback?.should be_true + end + + it "returns false for another address" do + @other.ipv6_loopback?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6_loopback?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb new file mode 100644 index 0000000000..01fa0992ba --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_global?' do + it 'returns true for a multi-cast address in the global scope' do + Addrinfo.ip('ff1e::').should.ipv6_mc_global? + Addrinfo.ip('fffe::').should.ipv6_mc_global? + Addrinfo.ip('ff0e::').should.ipv6_mc_global? + Addrinfo.ip('ff1e::1').should.ipv6_mc_global? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_global? + Addrinfo.ip('ff1a::').should_not.ipv6_mc_global? + Addrinfo.ip('ff1f::1').should_not.ipv6_mc_global? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_global? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb new file mode 100644 index 0000000000..a1298919eb --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb @@ -0,0 +1,19 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_linklocal?' do + it 'returns true for a multi-cast link-local address' do + Addrinfo.ip('ff12::').should.ipv6_mc_linklocal? + Addrinfo.ip('ff02::').should.ipv6_mc_linklocal? + Addrinfo.ip('fff2::').should.ipv6_mc_linklocal? + Addrinfo.ip('ff12::1').should.ipv6_mc_linklocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_linklocal? + Addrinfo.ip('fff1::').should_not.ipv6_mc_linklocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_linklocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb new file mode 100644 index 0000000000..0aee952d88 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_nodelocal?' do + it 'returns true for a multi-cast node-local address' do + Addrinfo.ip('ff11::').should.ipv6_mc_nodelocal? + Addrinfo.ip('ff01::').should.ipv6_mc_nodelocal? + Addrinfo.ip('fff1::').should.ipv6_mc_nodelocal? + Addrinfo.ip('ff11::1').should.ipv6_mc_nodelocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_nodelocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_nodelocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb new file mode 100644 index 0000000000..2977a98d30 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_orglocal?' do + it 'returns true for a multi-cast org-local address' do + Addrinfo.ip('ff18::').should.ipv6_mc_orglocal? + Addrinfo.ip('ff08::').should.ipv6_mc_orglocal? + Addrinfo.ip('fff8::').should.ipv6_mc_orglocal? + Addrinfo.ip('ff18::1').should.ipv6_mc_orglocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_orglocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_orglocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb new file mode 100644 index 0000000000..58e5976a40 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_sitelocal?' do + it 'returns true for a multi-cast site-local address' do + Addrinfo.ip('ff15::').should.ipv6_mc_sitelocal? + Addrinfo.ip('ff05::').should.ipv6_mc_sitelocal? + Addrinfo.ip('fff5::').should.ipv6_mc_sitelocal? + Addrinfo.ip('ff15::1').should.ipv6_mc_sitelocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_sitelocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_sitelocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb new file mode 100644 index 0000000000..99d4e8cf4d --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb @@ -0,0 +1,46 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv6_multicast?" do + describe "for an ipv4 socket" do + before :each do + @multicast = Addrinfo.tcp("224.0.0.1", 80) + @other = Addrinfo.tcp("0.0.0.0", 80) + end + + it "returns true for a multicast address" do + @multicast.ipv6_multicast?.should be_false + end + + it "returns false for another address" do + @other.ipv6_multicast?.should be_false + end + end + + describe "for an ipv6 socket" do + it "returns true for a multicast address" do + Addrinfo.ip('ff00::').should.ipv6_multicast? + Addrinfo.ip('ff00::1').should.ipv6_multicast? + Addrinfo.ip('ff08::1').should.ipv6_multicast? + Addrinfo.ip('fff8::1').should.ipv6_multicast? + + Addrinfo.ip('ff02::').should.ipv6_multicast? + Addrinfo.ip('ff02::1').should.ipv6_multicast? + Addrinfo.ip('ff0f::').should.ipv6_multicast? + end + + it "returns false for another address" do + Addrinfo.ip('::1').should_not.ipv6_multicast? + Addrinfo.ip('fe80::').should_not.ipv6_multicast? + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6_multicast?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb new file mode 100644 index 0000000000..9158eb5809 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Addrinfo#ipv6_sitelocal?' do + platform_is_not :aix do + it 'returns true for a site-local address' do + Addrinfo.ip('feef::').should.ipv6_sitelocal? + Addrinfo.ip('fee0::').should.ipv6_sitelocal? + Addrinfo.ip('fee2::').should.ipv6_sitelocal? + Addrinfo.ip('feef::1').should.ipv6_sitelocal? + end + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_sitelocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_sitelocal? + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb new file mode 100644 index 0000000000..436d5e930b --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv6?" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("10.0.0.1", 80) + end + + it "returns true" do + @addrinfo.ipv6?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns false" do + @addrinfo.ipv6?.should be_true + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb new file mode 100644 index 0000000000..29050bec20 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb @@ -0,0 +1,71 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Addrinfo#ipv6_to_ipv4' do + it 'returns an Addrinfo for ::192.168.1.1' do + addr = Addrinfo.ip('::192.168.1.1').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '192.168.1.1' + end + + platform_is_not :aix do + it 'returns an Addrinfo for ::0.0.1.1' do + addr = Addrinfo.ip('::0.0.1.1').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '0.0.1.1' + end + + it 'returns an Addrinfo for ::0.0.1.0' do + addr = Addrinfo.ip('::0.0.1.0').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '0.0.1.0' + end + + it 'returns an Addrinfo for ::0.1.0.0' do + addr = Addrinfo.ip('::0.1.0.0').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '0.1.0.0' + end + end + + it 'returns an Addrinfo for ::ffff:192.168.1.1' do + addr = Addrinfo.ip('::ffff:192.168.1.1').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '192.168.1.1' + end + + it 'returns nil for ::0.0.0.1' do + Addrinfo.ip('::0.0.0.1').ipv6_to_ipv4.should be_nil + end + + it 'returns nil for a pure IPv6 Addrinfo' do + Addrinfo.ip('::1').ipv6_to_ipv4.should be_nil + end + + it 'returns nil for an IPv4 Addrinfo' do + Addrinfo.ip('192.168.1.1').ipv6_to_ipv4.should be_nil + end + + describe 'for a unix socket' do + it 'returns nil for a UNIX Addrinfo' do + Addrinfo.unix('foo').ipv6_to_ipv4.should be_nil + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb new file mode 100644 index 0000000000..22f0fa3b75 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_unique_local?' do + it 'returns true for an unique local IPv6 address' do + Addrinfo.ip('fc00::').should.ipv6_unique_local? + Addrinfo.ip('fd00::').should.ipv6_unique_local? + Addrinfo.ip('fcff::').should.ipv6_unique_local? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_unique_local? + Addrinfo.ip('fe00::').should_not.ipv6_unique_local? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_unique_local? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb new file mode 100644 index 0000000000..d63979ceda --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb @@ -0,0 +1,15 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_unspecified?' do + it 'returns true for an unspecified IPv6 address' do + Addrinfo.ip('::').should.ipv6_unspecified? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_unspecified? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_unspecified? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb new file mode 100644 index 0000000000..21ca85af99 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_v4compat?' do + it 'returns true for an IPv4 compatible address' do + Addrinfo.ip('::127.0.0.1').should.ipv6_v4compat? + Addrinfo.ip('::192.168.1.1').should.ipv6_v4compat? + end + + it 'returns false for an IPv4 mapped address' do + Addrinfo.ip('::ffff:192.168.1.1').should_not.ipv6_v4compat? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_v4compat? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_v4compat? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb new file mode 100644 index 0000000000..7dac0e75db --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_v4mapped?' do + it 'returns true for an IPv4 compatible address' do + Addrinfo.ip('::ffff:192.168.1.1').should.ipv6_v4mapped? + end + + it 'returns false for an IPv4 compatible address' do + Addrinfo.ip('::192.168.1.1').should_not.ipv6_v4mapped? + Addrinfo.ip('::127.0.0.1').should_not.ipv6_v4mapped? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_v4mapped? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_v4mapped? + end +end diff --git a/spec/ruby/library/socket/addrinfo/listen_spec.rb b/spec/ruby/library/socket/addrinfo/listen_spec.rb new file mode 100644 index 0000000000..931093f732 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/listen_spec.rb @@ -0,0 +1,34 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#listen' do + before do + @addr = Addrinfo.tcp('127.0.0.1', 0) + @socket = nil + end + + after do + @socket.close if @socket + end + + it 'returns a Socket when no block is given' do + @socket = @addr.listen + + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket if a block is given' do + @addr.listen do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'closes the socket if a block is given' do + socket = nil + + @addr.listen do |sock| + socket = sock + end + + socket.should.closed? + end +end diff --git a/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb new file mode 100644 index 0000000000..e2c3497f7f --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb @@ -0,0 +1,80 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#marshal_dump' do + describe 'using an IP Addrinfo' do + before do + @addr = Addrinfo.getaddrinfo('localhost', 80, :INET, :STREAM, + Socket::IPPROTO_TCP, Socket::AI_CANONNAME)[0] + end + + it 'returns an Array' do + @addr.marshal_dump.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @addr.marshal_dump + end + + it 'includes the address family as the 1st value' do + @array[0].should == 'AF_INET' + end + + it 'includes the IP address as the 2nd value' do + @array[1].should == [@addr.ip_address, @addr.ip_port.to_s] + end + + it 'includes the protocol family as the 3rd value' do + @array[2].should == 'PF_INET' + end + + it 'includes the socket type as the 4th value' do + @array[3].should == 'SOCK_STREAM' + end + + it 'includes the protocol as the 5th value' do + @array[4].should == 'IPPROTO_TCP' + end + + it 'includes the canonical name as the 6th value' do + @array[5].should == @addr.canonname + end + end + end + + describe 'using a UNIX Addrinfo' do + before do + @addr = Addrinfo.unix('foo') + end + + it 'returns an Array' do + @addr.marshal_dump.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @addr.marshal_dump + end + + it 'includes the address family as the 1st value' do + @array[0].should == 'AF_UNIX' + end + + it 'includes the UNIX path as the 2nd value' do + @array[1].should == @addr.unix_path + end + + it 'includes the protocol family as the 3rd value' do + @array[2].should == 'PF_UNIX' + end + + it 'includes the socket type as the 4th value' do + @array[3].should == 'SOCK_STREAM' + end + + it 'includes the protocol as the 5th value' do + @array[4].should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb new file mode 100644 index 0000000000..02cef90115 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#marshal_load' do + describe 'using an IP address' do + it 'returns a new Addrinfo' do + source = Addrinfo.getaddrinfo('localhost', 80, :INET, :STREAM, + Socket::IPPROTO_TCP, Socket::AI_CANONNAME)[0] + + addr = Marshal.load(Marshal.dump(source)) + + addr.afamily.should == source.afamily + addr.pfamily.should == source.pfamily + addr.socktype.should == source.socktype + addr.protocol.should == source.protocol + addr.ip_address.should == source.ip_address + addr.ip_port.should == source.ip_port + addr.canonname.should == source.canonname + end + end + + describe 'using a UNIX socket' do + it 'returns a new Addrinfo' do + source = Addrinfo.unix('foo') + addr = Marshal.load(Marshal.dump(source)) + + addr.afamily.should == source.afamily + addr.pfamily.should == source.pfamily + addr.socktype.should == source.socktype + addr.protocol.should == source.protocol + addr.unix_path.should == source.unix_path + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/pfamily_spec.rb b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb new file mode 100644 index 0000000000..da530b7fdc --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb @@ -0,0 +1,41 @@ +require_relative '../spec_helper' + +describe "Addrinfo#pfamily" do + it 'returns PF_UNSPEC as the default socket family' do + sockaddr = Socket.pack_sockaddr_in(80, 'localhost') + + Addrinfo.new(sockaddr).pfamily.should == Socket::PF_UNSPEC + end + + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns Socket::PF_INET" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns Socket::PF_INET6" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::PF_UNIX" do + @addrinfo.pfamily.should == Socket::PF_UNIX + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/protocol_spec.rb b/spec/ruby/library/socket/addrinfo/protocol_spec.rb new file mode 100644 index 0000000000..f6ffc9acf9 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/protocol_spec.rb @@ -0,0 +1,22 @@ +require_relative '../spec_helper' + +describe "Addrinfo#protocol" do + it 'returns 0 by default' do + Addrinfo.ip('127.0.0.1').protocol.should == 0 + end + + it 'returns a custom protocol when given' do + Addrinfo.tcp('127.0.0.1', 80).protocol.should == Socket::IPPROTO_TCP + Addrinfo.tcp('::1', 80).protocol.should == Socket::IPPROTO_TCP + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns 0" do + @addrinfo.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb new file mode 100644 index 0000000000..70d6bfbbfe --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb @@ -0,0 +1,47 @@ +describe :socket_addrinfo_to_sockaddr, shared: true do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_in(80, '127.0.0.1') + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_in(80, '::1') + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_un('/tmp/sock') + end + end + + describe 'using a Addrinfo with just an IP address' do + it 'returns a String' do + addr = Addrinfo.ip('127.0.0.1') + + addr.send(@method).should == Socket.sockaddr_in(0, '127.0.0.1') + end + end + + describe 'using a Addrinfo without an IP and port' do + it 'returns a String' do + addr = Addrinfo.new(['AF_INET', 0, '', '']) + + addr.send(@method).should == Socket.sockaddr_in(0, '') + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/socktype_spec.rb b/spec/ruby/library/socket/addrinfo/socktype_spec.rb new file mode 100644 index 0000000000..e5f02cd759 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/socktype_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' + +describe "Addrinfo#socktype" do + it 'returns 0 by default' do + Addrinfo.ip('127.0.0.1').socktype.should == 0 + end + + it 'returns the socket type when given' do + Addrinfo.tcp('127.0.0.1', 80).socktype.should == Socket::SOCK_STREAM + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::SOCK_STREAM" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/tcp_spec.rb b/spec/ruby/library/socket/addrinfo/tcp_spec.rb new file mode 100644 index 0000000000..c74c9c21c2 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/tcp_spec.rb @@ -0,0 +1,34 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo.tcp' do + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'returns an Addrinfo instance' do + Addrinfo.tcp(ip_address, 80).should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.tcp(ip_address, 80).ip_address.should == ip_address + end + + it 'sets the port' do + Addrinfo.tcp(ip_address, 80).ip_port.should == 80 + end + + it 'sets the address family' do + Addrinfo.tcp(ip_address, 80).afamily.should == family + end + + it 'sets the protocol family' do + Addrinfo.tcp(ip_address, 80).pfamily.should == family + end + + it 'sets the socket type' do + Addrinfo.tcp(ip_address, 80).socktype.should == Socket::SOCK_STREAM + end + + it 'sets the socket protocol' do + Addrinfo.tcp(ip_address, 80).protocol.should == Socket::IPPROTO_TCP + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/to_s_spec.rb b/spec/ruby/library/socket/addrinfo/to_s_spec.rb new file mode 100644 index 0000000000..ddf994e051 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../spec_helper' +require_relative 'shared/to_sockaddr' + +describe "Addrinfo#to_s" do + it_behaves_like :socket_addrinfo_to_sockaddr, :to_s +end diff --git a/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb new file mode 100644 index 0000000000..b9f75454bd --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb @@ -0,0 +1,6 @@ +require_relative '../spec_helper' +require_relative 'shared/to_sockaddr' + +describe "Addrinfo#to_sockaddr" do + it_behaves_like :socket_addrinfo_to_sockaddr, :to_sockaddr +end diff --git a/spec/ruby/library/socket/addrinfo/udp_spec.rb b/spec/ruby/library/socket/addrinfo/udp_spec.rb new file mode 100644 index 0000000000..ac02e76ef5 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/udp_spec.rb @@ -0,0 +1,34 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo.udp' do + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'returns an Addrinfo instance' do + Addrinfo.udp(ip_address, 80).should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.udp(ip_address, 80).ip_address.should == ip_address + end + + it 'sets the port' do + Addrinfo.udp(ip_address, 80).ip_port.should == 80 + end + + it 'sets the address family' do + Addrinfo.udp(ip_address, 80).afamily.should == family + end + + it 'sets the protocol family' do + Addrinfo.udp(ip_address, 80).pfamily.should == family + end + + it 'sets the socket type' do + Addrinfo.udp(ip_address, 80).socktype.should == Socket::SOCK_DGRAM + end + + it 'sets the socket protocol' do + Addrinfo.udp(ip_address, 80).protocol.should == Socket::IPPROTO_UDP + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/unix_path_spec.rb b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb new file mode 100644 index 0000000000..2a9076a354 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +describe "Addrinfo#unix_path" do + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "raises an exception" do + -> { @addrinfo.unix_path }.should raise_error(SocketError) + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "raises an exception" do + -> { @addrinfo.unix_path }.should raise_error(SocketError) + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns the socket path" do + @addrinfo.unix_path.should == "/tmp/sock" + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/unix_spec.rb b/spec/ruby/library/socket/addrinfo/unix_spec.rb new file mode 100644 index 0000000000..7597533a76 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/unix_spec.rb @@ -0,0 +1,69 @@ +require_relative '../spec_helper' + +describe 'Addrinfo.unix' do + it 'returns an Addrinfo instance' do + Addrinfo.unix('socket').should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.unix('socket').unix_path.should == 'socket' + end + + it 'sets the address family' do + Addrinfo.unix('socket').afamily.should == Socket::AF_UNIX + end + + it 'sets the protocol family' do + Addrinfo.unix('socket').pfamily.should == Socket::PF_UNIX + end + + it 'sets the socket type' do + Addrinfo.unix('socket').socktype.should == Socket::SOCK_STREAM + end + + it 'sets a custom socket type' do + addr = Addrinfo.unix('socket', Socket::SOCK_DGRAM) + + addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'sets the socket protocol to 0' do + Addrinfo.unix('socket').protocol.should == 0 + end +end + +describe "Addrinfo#unix?" do + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns false" do + @addrinfo.unix?.should be_false + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns false" do + @addrinfo.unix?.should be_false + end + end + + platform_is_not :windows do + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns true" do + @addrinfo.unix?.should be_true + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb b/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb new file mode 100644 index 0000000000..c54ee29825 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#cmsg_is?' do + describe 'using :INET, :IP, :TTL as the family, level, and type' do + before do + @data = Socket::AncillaryData.new(:INET, :IP, :TTL, '') + end + + it 'returns true when comparing with IPPROTO_IP and IP_TTL' do + @data.cmsg_is?(Socket::IPPROTO_IP, Socket::IP_TTL).should == true + end + + it 'returns true when comparing with :IP and :TTL' do + @data.cmsg_is?(:IP, :TTL).should == true + end + + with_feature :pktinfo do + it 'returns false when comparing with :IP and :PKTINFO' do + @data.cmsg_is?(:IP, :PKTINFO).should == false + end + end + + it 'returns false when comparing with :SOCKET and :RIGHTS' do + @data.cmsg_is?(:SOCKET, :RIGHTS).should == false + end + + it 'raises SocketError when comparing with :IPV6 and :RIGHTS' do + -> { @data.cmsg_is?(:IPV6, :RIGHTS) }.should raise_error(SocketError) + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/data_spec.rb b/spec/ruby/library/socket/ancillarydata/data_spec.rb new file mode 100644 index 0000000000..5a1a446dd5 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/data_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#data' do + it 'returns the data as a String' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, 'ugh').data.should == 'ugh' + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/family_spec.rb b/spec/ruby/library/socket/ancillarydata/family_spec.rb new file mode 100644 index 0000000000..975f0d2538 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/family_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#family' do + it 'returns the family as an Integer' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').family.should == Socket::AF_INET + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/initialize_spec.rb b/spec/ruby/library/socket/ancillarydata/initialize_spec.rb new file mode 100644 index 0000000000..eca45599d7 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/initialize_spec.rb @@ -0,0 +1,284 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#initialize' do + describe 'using Integers for the family, level, and type' do + before do + @data = Socket::AncillaryData + .new(Socket::AF_INET, Socket::IPPROTO_IP, Socket::IP_RECVTTL, 'ugh') + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using Symbols for the family, level, and type' do + before do + @data = Socket::AncillaryData.new(:INET, :IPPROTO_IP, :RECVTTL, 'ugh') + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using Strings for the family, level, and type' do + before do + @data = Socket::AncillaryData.new('INET', 'IPPROTO_IP', 'RECVTTL', 'ugh') + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using custom objects with a to_str method for the family, level, and type' do + before do + fmock = mock(:family) + lmock = mock(:level) + tmock = mock(:type) + dmock = mock(:data) + + fmock.stub!(:to_str).and_return('INET') + lmock.stub!(:to_str).and_return('IP') + tmock.stub!(:to_str).and_return('RECVTTL') + dmock.stub!(:to_str).and_return('ugh') + + @data = Socket::AncillaryData.new(fmock, lmock, tmock, dmock) + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using :AF_INET as the family and :SOCKET as the level' do + it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + + platform_is_not :aix do + it 'sets the type to SCM_TIMESTAMP when using :TIMESTAMP as the type argument' do + Socket::AncillaryData.new(:INET, :SOCKET, :TIMESTAMP, '').type.should == Socket::SCM_TIMESTAMP + end + end + + it 'raises TypeError when using a numeric string as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IGMP, Socket::SCM_RIGHTS.to_s, '') + }.should raise_error(TypeError) + end + + it 'raises SocketError when using :RECVTTL as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :SOCKET, :RECVTTL, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :SOCKET, :MOO, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :IP_RECVTTL as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :SOCKET, :IP_RECVTTL, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :SOCKET as the level' do + it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + end + + describe 'using :AF_INET as the family and :IP as the level' do + it 'sets the type to IP_RECVTTL when using :RECVTTL as the type argument' do + Socket::AncillaryData.new(:INET, :IP, :RECVTTL, '').type.should == Socket::IP_RECVTTL + end + + with_feature :ip_mtu do + it 'sets the type to IP_MTU when using :MTU as the type argument' do + Socket::AncillaryData.new(:INET, :IP, :MTU, '').type.should == Socket::IP_MTU + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IP, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IP, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :IPV6 as the level' do + it 'sets the type to IPV6_CHECKSUM when using :CHECKSUM as the type argument' do + Socket::AncillaryData.new(:INET, :IPV6, :CHECKSUM, '').type.should == Socket::IPV6_CHECKSUM + end + + with_feature :ipv6_nexthop do + it 'sets the type to IPV6_NEXTHOP when using :NEXTHOP as the type argument' do + Socket::AncillaryData.new(:INET, :IPV6, :NEXTHOP, '').type.should == Socket::IPV6_NEXTHOP + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IPV6, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IPV6, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :TCP as the level' do + with_feature :tcp_cork do + it 'sets the type to TCP_CORK when using :CORK as the type argument' do + Socket::AncillaryData.new(:INET, :TCP, :CORK, '').type.should == Socket::TCP_CORK + end + end + + with_feature :tcp_info do + it 'sets the type to TCP_INFO when using :INFO as the type argument' do + Socket::AncillaryData.new(:INET, :TCP, :INFO, '').type.should == Socket::TCP_INFO + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :TCP, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :TCP, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :UDP as the level' do + with_feature :udp_cork do + it 'sets the type to UDP_CORK when using :CORK as the type argument' do + Socket::AncillaryData.new(:INET, :UDP, :CORK, '').type.should == Socket::UDP_CORK + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :UDP, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :UDP, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :SOCKET as the level' do + it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do + Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + + it 'raises SocketError when using :CORK sa the type argument' do + -> { + Socket::AncillaryData.new(:UNIX, :SOCKET, :CORK, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :IP as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :IP, :RECVTTL, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :IPV6 as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :IPV6, :NEXTHOP, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :TCP as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :TCP, :CORK, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :UDP as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :UDP, :CORK, '') + }.should raise_error(SocketError) + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/int_spec.rb b/spec/ruby/library/socket/ancillarydata/int_spec.rb new file mode 100644 index 0000000000..fe41a30a1a --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/int_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData.int' do + before do + @data = Socket::AncillaryData.int(:INET, :SOCKET, :RIGHTS, 4) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET + end + + it 'sets the level SOL_SOCKET' do + @data.level.should == Socket::SOL_SOCKET + end + + it 'sets the type SCM_RIGHTS' do + @data.type.should == Socket::SCM_RIGHTS + end + + it 'sets the data to a packed String' do + @data.data.should == [4].pack('I') + end + end + + describe 'Socket::AncillaryData#int' do + it 'returns the data as an Integer' do + data = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, 4) + + data.int.should == 4 + end + + it 'raises when the data is not an Integer' do + data = Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, 'ugh') + + -> { data.int }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb b/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb new file mode 100644 index 0000000000..84910a038a --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb @@ -0,0 +1,145 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :pktinfo do + describe 'Socket::AncillaryData.ip_pktinfo' do + describe 'with a source address and index' do + before do + @data = Socket::AncillaryData.ip_pktinfo(Addrinfo.ip('127.0.0.1'), 4) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET + end + + it 'sets the level to IPPROTO_IP' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the type to IP_PKTINFO' do + @data.type.should == Socket::IP_PKTINFO + end + end + + describe 'with a source address, index, and destination address' do + before do + source = Addrinfo.ip('127.0.0.1') + dest = Addrinfo.ip('127.0.0.5') + @data = Socket::AncillaryData.ip_pktinfo(source, 4, dest) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET + end + + it 'sets the level to IPPROTO_IP' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the type to IP_PKTINFO' do + @data.type.should == Socket::IP_PKTINFO + end + end + end + + describe 'Socket::AncillaryData#ip_pktinfo' do + describe 'using an Addrinfo without a port number' do + before do + @source = Addrinfo.ip('127.0.0.1') + @dest = Addrinfo.ip('127.0.0.5') + @data = Socket::AncillaryData.ip_pktinfo(@source, 4, @dest) + end + + it 'returns an Array' do + @data.ip_pktinfo.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @info = @data.ip_pktinfo + end + + it 'stores an Addrinfo at index 0' do + @info[0].should be_an_instance_of(Addrinfo) + end + + it 'stores the ifindex at index 1' do + @info[1].should be_kind_of(Integer) + end + + it 'stores an Addrinfo at index 2' do + @info[2].should be_an_instance_of(Addrinfo) + end + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ip_pktinfo[0] + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.1' + end + + it 'is not the same object as the input Addrinfo' do + @addr.should_not equal @source + end + end + + describe 'the ifindex' do + it 'is an Integer' do + @data.ip_pktinfo[1].should == 4 + end + end + + describe 'the destination Addrinfo' do + before do + @addr = @data.ip_pktinfo[2] + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.5' + end + + it 'is not the same object as the input Addrinfo' do + @addr.should_not equal @dest + end + end + end + + describe 'using an Addrinfo with a port number' do + before do + @source = Addrinfo.tcp('127.0.0.1', 80) + @dest = Addrinfo.tcp('127.0.0.5', 85) + @data = Socket::AncillaryData.ip_pktinfo(@source, 4, @dest) + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ip_pktinfo[0] + end + + it 'does not contain a port number' do + @addr.ip_port.should == 0 + end + end + + describe 'the destination Addrinfo' do + before do + @addr = @data.ip_pktinfo[2] + end + + it 'does not contain a port number' do + @addr.ip_port.should == 0 + end + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb new file mode 100644 index 0000000000..f70fe27d6a --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb @@ -0,0 +1,11 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :ipv6_pktinfo do + describe 'Socket::AncillaryData#ipv6_pktinfo_addr' do + it 'returns an Addrinfo' do + data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4) + + data.ipv6_pktinfo_addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb new file mode 100644 index 0000000000..bda37eec98 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb @@ -0,0 +1,11 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :ipv6_pktinfo do + describe 'Socket::AncillaryData#ipv6_pktinfo_ifindex' do + it 'returns an Addrinfo' do + data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4) + + data.ipv6_pktinfo_ifindex.should == 4 + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb new file mode 100644 index 0000000000..0fffc720dc --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb @@ -0,0 +1,89 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :ipv6_pktinfo do + describe 'Socket::AncillaryData.ipv6_pktinfo' do + before do + @data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET6 + end + + it 'sets the level to IPPROTO_IP' do + @data.level.should == Socket::IPPROTO_IPV6 + end + + it 'sets the type to IP_PKTINFO' do + @data.type.should == Socket::IPV6_PKTINFO + end + end + + describe 'Socket::AncillaryData#ipv6_pktinfo' do + describe 'using an Addrinfo without a port number' do + before do + @source = Addrinfo.ip('::1') + @data = Socket::AncillaryData.ipv6_pktinfo(@source, 4) + end + + it 'returns an Array' do + @data.ipv6_pktinfo.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @info = @data.ipv6_pktinfo + end + + it 'stores an Addrinfo at index 0' do + @info[0].should be_an_instance_of(Addrinfo) + end + + it 'stores the ifindex at index 1' do + @info[1].should be_kind_of(Integer) + end + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ipv6_pktinfo[0] + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '::1' + end + + it 'is not the same object as the input Addrinfo' do + @addr.should_not equal @source + end + end + + describe 'the ifindex' do + it 'is an Integer' do + @data.ipv6_pktinfo[1].should == 4 + end + end + end + + describe 'using an Addrinfo with a port number' do + before do + @source = Addrinfo.tcp('::1', 80) + @data = Socket::AncillaryData.ipv6_pktinfo(@source, 4) + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ipv6_pktinfo[0] + end + + it 'does not contain a port number' do + @addr.ip_port.should == 0 + end + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/level_spec.rb b/spec/ruby/library/socket/ancillarydata/level_spec.rb new file mode 100644 index 0000000000..a2ff216f9d --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/level_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#level' do + it 'returns the level as an Integer' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').level.should == Socket::SOL_SOCKET + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/type_spec.rb b/spec/ruby/library/socket/ancillarydata/type_spec.rb new file mode 100644 index 0000000000..972beeeca0 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/type_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#type' do + it 'returns the type as an Integer' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb b/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb new file mode 100644 index 0000000000..95052fd91c --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb @@ -0,0 +1,61 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData.unix_rights' do + describe 'using a list of IO objects' do + before do + @data = Socket::AncillaryData.unix_rights(STDOUT, STDERR) + end + + it 'sets the family to AF_UNIX' do + @data.family.should == Socket::AF_UNIX + end + + it 'sets the level to SOL_SOCKET' do + @data.level.should == Socket::SOL_SOCKET + end + + it 'sets the type to SCM_RIGHTS' do + @data.type.should == Socket::SCM_RIGHTS + end + + it 'sets the data to a String containing the file descriptors' do + @data.data.unpack('I*').should == [STDOUT.fileno, STDERR.fileno] + end + end + + describe 'using non IO objects' do + it 'raises TypeError' do + -> { Socket::AncillaryData.unix_rights(10) }.should raise_error(TypeError) + end + end + end + + describe 'Socket::AncillaryData#unix_rights' do + it 'returns the data as an Array of IO objects' do + data = Socket::AncillaryData.unix_rights(STDOUT, STDERR) + + data.unix_rights.should == [STDOUT, STDERR] + end + + it 'returns nil when the data is not a list of file descriptors' do + data = Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, '') + + data.unix_rights.should be_nil + end + + it 'raises TypeError when the level is not SOL_SOCKET' do + data = Socket::AncillaryData.new(:INET, :IP, :RECVTTL, '') + + -> { data.unix_rights }.should raise_error(TypeError) + end + + platform_is_not :aix do + it 'raises TypeError when the type is not SCM_RIGHTS' do + data = Socket::AncillaryData.new(:INET, :SOCKET, :TIMESTAMP, '') + + -> { data.unix_rights }.should raise_error(TypeError) + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/close_read_spec.rb b/spec/ruby/library/socket/basicsocket/close_read_spec.rb new file mode 100644 index 0000000000..f317b34955 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/close_read_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#close_read" do + before :each do + @server = TCPServer.new(0) + end + + after :each do + @server.close unless @server.closed? + end + + it "closes the reading end of the socket" do + @server.close_read + -> { @server.read }.should raise_error(IOError) + end + + it 'does not raise when called on a socket already closed for reading' do + @server.close_read + @server.close_read + -> { @server.read }.should raise_error(IOError) + end + + it 'does not fully close the socket' do + @server.close_read + @server.closed?.should be_false + end + + it "fully closes the socket if it was already closed for writing" do + @server.close_write + @server.close_read + @server.closed?.should be_true + end + + it 'raises IOError when called on a fully closed socket' do + @server.close + -> { @server.close_read }.should raise_error(IOError) + end + + it "returns nil" do + @server.close_read.should be_nil + end +end diff --git a/spec/ruby/library/socket/basicsocket/close_write_spec.rb b/spec/ruby/library/socket/basicsocket/close_write_spec.rb new file mode 100644 index 0000000000..232cfbb7c6 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/close_write_spec.rb @@ -0,0 +1,48 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#close_write" do + before :each do + @server = TCPServer.new(0) + end + + after :each do + @server.close unless @server.closed? + end + + it "closes the writing end of the socket" do + @server.close_write + -> { @server.write("foo") }.should raise_error(IOError) + end + + it 'does not raise when called on a socket already closed for writing' do + @server.close_write + @server.close_write + -> { @server.write("foo") }.should raise_error(IOError) + end + + it 'does not fully close the socket' do + @server.close_write + @server.closed?.should be_false + end + + it "does not prevent reading" do + @server.close_write + @server.read(0).should == "" + end + + it "fully closes the socket if it was already closed for reading" do + @server.close_read + @server.close_write + @server.closed?.should be_true + end + + it 'raises IOError when called on a fully closed socket' do + @server.close + -> { @server.close_write }.should raise_error(IOError) + end + + it "returns nil" do + @server.close_write.should be_nil + end +end diff --git a/spec/ruby/library/socket/basicsocket/connect_address_spec.rb b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb new file mode 100644 index 0000000000..2e318fcb85 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb @@ -0,0 +1,152 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#connect_address' do + describe 'using an unbound socket' do + after do + @sock.close + end + + it 'raises SocketError' do + @sock = Socket.new(:INET, :STREAM) + + -> { @sock.connect_address }.should raise_error(SocketError) + end + end + + describe 'using a socket bound to 0.0.0.0' do + before do + @sock = Socket.new(:INET, :STREAM) + @sock.bind(Socket.sockaddr_in(0, '0.0.0.0')) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end + + it 'uses 127.0.0.1 as the IP address' do + @sock.connect_address.ip_address.should == '127.0.0.1' + end + + it 'uses the correct port number' do + @sock.connect_address.ip_port.should > 0 + end + + it 'uses AF_INET as the address family' do + @sock.connect_address.afamily.should == Socket::AF_INET + end + + it 'uses PF_INET as the address family' do + @sock.connect_address.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 + end + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using a socket bound to ::' do + before do + @sock = Socket.new(:INET6, :STREAM) + @sock.bind(Socket.sockaddr_in(0, '::')) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end + + it 'uses ::1 as the IP address' do + @sock.connect_address.ip_address.should == '::1' + end + + it 'uses the correct port number' do + @sock.connect_address.ip_port.should > 0 + end + + it 'uses AF_INET6 as the address family' do + @sock.connect_address.afamily.should == Socket::AF_INET6 + end + + it 'uses PF_INET6 as the address family' do + @sock.connect_address.pfamily.should == Socket::PF_INET6 + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 + end + end + end + + platform_is_not :aix do + describe 'using an unbound UNIX socket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + rm_r(@path) + end + + it 'raises SocketError' do + -> { @client.connect_address }.should raise_error(SocketError) + end + end + end + + describe 'using a bound UNIX socket' do + before do + @path = SocketSpecs.socket_path + @sock = UNIXServer.new(@path) + end + + after do + @sock.close + rm_r(@path) + end + + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end + + it 'uses the correct socket path' do + @sock.connect_address.unix_path.should == @path + end + + it 'uses AF_UNIX as the address family' do + @sock.connect_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @sock.connect_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb new file mode 100644 index 0000000000..a8800a8493 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb @@ -0,0 +1,103 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket.do_not_reverse_lookup" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + @socket = TCPSocket.new('127.0.0.1', @port) + end + + after :each do + @server.close unless @server.closed? + @socket.close unless @socket.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it "defaults to true" do + BasicSocket.do_not_reverse_lookup.should be_true + end + + it "causes 'peeraddr' to avoid name lookups" do + @socket.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + @socket.peeraddr.should == ["AF_INET", @port, "127.0.0.1", "127.0.0.1"] + end + + it "looks for hostnames when set to false" do + @socket.do_not_reverse_lookup = false + BasicSocket.do_not_reverse_lookup = false + @socket.peeraddr[2].should == SocketSpecs.hostname + end + + it "looks for numeric addresses when set to true" do + @socket.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + @socket.peeraddr[2].should == "127.0.0.1" + end +end + +describe :socket_do_not_reverse_lookup, shared: true do + it "inherits from BasicSocket.do_not_reverse_lookup when the socket is created" do + @socket = @method.call + reverse = BasicSocket.do_not_reverse_lookup + @socket.do_not_reverse_lookup.should == reverse + + BasicSocket.do_not_reverse_lookup = !reverse + @socket.do_not_reverse_lookup.should == reverse + end + + it "is true when BasicSocket.do_not_reverse_lookup is true" do + BasicSocket.do_not_reverse_lookup = true + @socket = @method.call + @socket.do_not_reverse_lookup.should == true + end + + it "is false when BasicSocket.do_not_reverse_lookup is false" do + BasicSocket.do_not_reverse_lookup = false + @socket = @method.call + @socket.do_not_reverse_lookup.should == false + end + + it "can be changed with #do_not_reverse_lookup=" do + @socket = @method.call + reverse = @socket.do_not_reverse_lookup + @socket.do_not_reverse_lookup = !reverse + @socket.do_not_reverse_lookup.should == !reverse + end +end + +describe "BasicSocket#do_not_reverse_lookup" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + @socket.close if @socket && !@socket.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + describe "for an TCPSocket.new socket" do + it_behaves_like :socket_do_not_reverse_lookup, -> { + TCPSocket.new('127.0.0.1', @port) + } + end + + describe "for an TCPServer#accept socket" do + before :each do + @client = TCPSocket.new('127.0.0.1', @port) + end + + after :each do + @client.close if @client && !@client.closed? + end + + it_behaves_like :socket_do_not_reverse_lookup, -> { + @server.accept + } + end +end diff --git a/spec/ruby/library/socket/basicsocket/for_fd_spec.rb b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb new file mode 100644 index 0000000000..9c9e6a8b55 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb @@ -0,0 +1,38 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket.for_fd" do + before :each do + @server = TCPServer.new(0) + @s2 = nil + end + + after :each do + @socket1.close if @socket1 + @server.close if @server + end + + it "return a Socket instance wrapped around the descriptor" do + @s2 = TCPServer.for_fd(@server.fileno) + @s2.autoclose = false + @s2.should be_kind_of(TCPServer) + @s2.fileno.should == @server.fileno + end + + it 'returns a new socket for a file descriptor' do + @socket1 = Socket.new(:INET, :DGRAM) + socket2 = Socket.for_fd(@socket1.fileno) + socket2.autoclose = false + + socket2.should be_an_instance_of(Socket) + socket2.fileno.should == @socket1.fileno + end + + it 'sets the socket into binary mode' do + @socket1 = Socket.new(:INET, :DGRAM) + socket2 = Socket.for_fd(@socket1.fileno) + socket2.autoclose = false + + socket2.binmode?.should be_true + end +end diff --git a/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb new file mode 100644 index 0000000000..2e03cd3684 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb @@ -0,0 +1,36 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#getpeereid' do + platform_is_not :windows do + describe 'using a UNIXSocket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + + rm_r(@path) + end + + it 'returns an Array with the user and group ID' do + @client.getpeereid.should == [Process.euid, Process.egid] + end + end + end + + describe 'using an IPSocket' do + after do + @sock.close + end + + it 'raises NoMethodError' do + @sock = TCPServer.new('127.0.0.1', 0) + -> { @sock.getpeereid }.should raise_error(NoMethodError) + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/getpeername_spec.rb b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb new file mode 100644 index 0000000000..0b93f02eef --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb @@ -0,0 +1,25 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#getpeername" do + + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", @port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns the sockaddr of the other end of the connection" do + server_sockaddr = Socket.pack_sockaddr_in(@port, "127.0.0.1") + @client.getpeername.should == server_sockaddr + end + + it 'raises Errno::ENOTCONN for a disconnected socket' do + -> { @server.getpeername }.should raise_error(Errno::ENOTCONN) + end +end diff --git a/spec/ruby/library/socket/basicsocket/getsockname_spec.rb b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb new file mode 100644 index 0000000000..b33db088b6 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#getsockname" do + after :each do + @socket.closed?.should be_false + @socket.close + end + + it "returns the sockaddr associated with the socket" do + @socket = TCPServer.new("127.0.0.1", 0) + sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname) + sockaddr.should == [@socket.addr[1], "127.0.0.1"] + end + + it "works on sockets listening in ipaddr_any" do + @socket = TCPServer.new(0) + sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname) + ["::", "0.0.0.0", "::ffff:0.0.0.0"].include?(sockaddr[1]).should be_true + sockaddr[0].should == @socket.addr[1] + end + + it 'returns a default socket address for a disconnected socket' do + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname) + sockaddr.should == [0, "0.0.0.0"] + end +end diff --git a/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb new file mode 100644 index 0000000000..ce65d6c92b --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb @@ -0,0 +1,188 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#getsockopt" do + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + end + + after :each do + @sock.closed?.should be_false + @sock.close + end + + platform_is_not :aix do + # A known bug in AIX. getsockopt(2) does not properly set + # the fifth argument for SO_TYPE, SO_OOBINLINE, SO_BROADCAST, etc. + + it "gets a socket option Socket::SO_TYPE" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE).to_s + n.should == [Socket::SOCK_STREAM].pack("i") + end + + it "gets a socket option Socket::SO_OOBINLINE" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + end + end + + it "gets a socket option Socket::SO_LINGER" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + if (n.size == 8) # linger struct on some platforms, not just a value + n.should == [0, 0].pack("ii") + else + n.should == [0].pack("i") + end + end + + it "gets a socket option Socket::SO_SNDBUF" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should > 0 + end + + it "raises a SystemCallError with an invalid socket option" do + -> { @sock.getsockopt Socket::SOL_SOCKET, -1 }.should raise_error(Errno::ENOPROTOOPT) + end + + it 'returns a Socket::Option using a constant' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE) + + opt.should be_an_instance_of(Socket::Option) + end + + it 'returns a Socket::Option for a boolean option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR) + + opt.bool.should == false + end + + it 'returns a Socket::Option for a numeric option' do + opt = @sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL) + + opt.int.should be_kind_of(Integer) + end + + it 'returns a Socket::Option for a struct option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER) + + opt.linger.should == [false, 0] + end + + it 'raises Errno::ENOPROTOOPT when requesting an invalid option' do + -> { @sock.getsockopt(Socket::SOL_SOCKET, -1) }.should raise_error(Errno::ENOPROTOOPT) + end + + describe 'using Symbols as arguments' do + it 'returns a Socket::Option for arguments :SOCKET and :TYPE' do + opt = @sock.getsockopt(:SOCKET, :TYPE) + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_TYPE + end + + it 'returns a Socket::Option for arguments :IP and :TTL' do + opt = @sock.getsockopt(:IP, :TTL) + + opt.level.should == Socket::IPPROTO_IP + opt.optname.should == Socket::IP_TTL + end + + it 'returns a Socket::Option for arguments :SOCKET and :REUSEADDR' do + opt = @sock.getsockopt(:SOCKET, :REUSEADDR) + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_REUSEADDR + end + + it 'returns a Socket::Option for arguments :SOCKET and :LINGER' do + opt = @sock.getsockopt(:SOCKET, :LINGER) + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_LINGER + end + + with_feature :udp_cork do + it 'returns a Socket::Option for arguments :UDP and :CORK' do + sock = Socket.new(:INET, :DGRAM) + begin + opt = sock.getsockopt(:UDP, :CORK) + + opt.level.should == Socket::IPPROTO_UDP + opt.optname.should == Socket::UDP_CORK + ensure + sock.close + end + end + end + end + + describe 'using Strings as arguments' do + it 'returns a Socket::Option for arguments "SOCKET" and "TYPE"' do + opt = @sock.getsockopt("SOCKET", "TYPE") + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_TYPE + end + + it 'returns a Socket::Option for arguments "IP" and "TTL"' do + opt = @sock.getsockopt("IP", "TTL") + + opt.level.should == Socket::IPPROTO_IP + opt.optname.should == Socket::IP_TTL + end + + it 'returns a Socket::Option for arguments "SOCKET" and "REUSEADDR"' do + opt = @sock.getsockopt("SOCKET", "REUSEADDR") + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_REUSEADDR + end + + it 'returns a Socket::Option for arguments "SOCKET" and "LINGER"' do + opt = @sock.getsockopt("SOCKET", "LINGER") + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_LINGER + end + + with_feature :udp_cork do + it 'returns a Socket::Option for arguments "UDP" and "CORK"' do + sock = Socket.new("INET", "DGRAM") + begin + opt = sock.getsockopt("UDP", "CORK") + + opt.level.should == Socket::IPPROTO_UDP + opt.optname.should == Socket::UDP_CORK + ensure + sock.close + end + end + end + end + + describe 'using a String based option' do + it 'allows unpacking of a boolean option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR).to_s + + opt.unpack('i').should == [0] + end + + it 'allows unpacking of a numeric option' do + opt = @sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL).to_s + array = opt.unpack('i') + + array[0].should be_kind_of(Integer) + array[0].should > 0 + end + + it 'allows unpacking of a struct option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + + if opt.bytesize == 8 + opt.unpack('ii').should == [0, 0] + else + opt.unpack('i').should == [0] + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/ioctl_spec.rb b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb new file mode 100644 index 0000000000..615d92bea8 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb @@ -0,0 +1,42 @@ +require_relative '../spec_helper' + +describe "Socket::BasicSocket#ioctl" do + platform_is :linux do + it "passes data from and to a String correctly" do + s = Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, 0 + # /usr/include/net/if.h, structure ifreq + # The structure is 32 bytes on x86, 40 bytes on x86_64 + if_name = ['lo'].pack('a16') + buffer = if_name + 'z' * 24 + # SIOCGIFADDR in /usr/include/bits/ioctls.h + s.ioctl 0x8915, buffer + s.close + + # Interface name should remain unchanged. + buffer[0, 16].should == if_name + # lo should have an IPv4 address of 127.0.0.1 + buffer[16, 2].unpack('S!').first.should == Socket::AF_INET + buffer[20, 4].should == "\x7f\0\0\x01" + end + end + + platform_is :freebsd do + it "passes data from and to a String correctly" do + s = Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, 0 + # /usr/include/net/if.h, structure ifreq + # The structure is 32 bytes on x86, 40 bytes on x86_64 + if_name = ['lo0'].pack('a16') + buffer = if_name + 'z' * 24 + # SIOCGIFADDR in /usr/include/bits/ioctls.h + s.ioctl 0xc0206921, buffer + s.close + + # Interface name should remain unchanged. + buffer[0, 16].should == if_name + # lo should have an IPv4 address of 127.0.0.1 + buffer[16, 1].unpack('C').first.should == 16 + buffer[17, 1].unpack('C').first.should == Socket::AF_INET + buffer[20, 4].should == "\x7f\0\0\x01" + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/local_address_spec.rb b/spec/ruby/library/socket/basicsocket/local_address_spec.rb new file mode 100644 index 0000000000..0bd60a44cd --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/local_address_spec.rb @@ -0,0 +1,10 @@ +require_relative '../spec_helper' +require_relative '../shared/address' + +describe 'BasicSocket#local_address' do + it_behaves_like :socket_local_remote_address, :local_address, -> socket { + a2 = BasicSocket.for_fd(socket.fileno) + a2.autoclose = false + a2.local_address + } +end diff --git a/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb new file mode 100644 index 0000000000..ea5e65da5c --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb @@ -0,0 +1,74 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#read_nonblock" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.send("aaa", 0, @r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "receives data after it's ready" do + IO.select([@r], nil, nil, 2) + @r.read_nonblock(5).should == "aaa" + end + + platform_is_not :windows do + it 'returned data is binary encoded regardless of the external encoding' do + IO.select([@r], nil, nil, 2) + @r.read_nonblock(1).encoding.should == Encoding::BINARY + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + IO.select([@r], nil, nil, 2) + buffer = @r.read_nonblock(3) + buffer.should == "bbb" + buffer.encoding.should == Encoding::BINARY + end + end + + it 'replaces the content of the provided buffer without changing its encoding' do + buffer = "initial data".dup.force_encoding(Encoding::UTF_8) + + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3, buffer) + buffer.should == "aaa" + buffer.encoding.should == Encoding::UTF_8 + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3, buffer) + buffer.should == "bbb" + buffer.encoding.should == Encoding::UTF_8 + end + + platform_is :linux do + it 'does not set the IO in nonblock mode' do + require 'io/nonblock' + @r.nonblock = false + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3).should == "aaa" + @r.should_not.nonblock? + end + end + + platform_is_not :linux, :windows do + it 'sets the IO in nonblock mode' do + require 'io/nonblock' + @r.nonblock = false + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3).should == "aaa" + @r.should.nonblock? + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/read_spec.rb b/spec/ruby/library/socket/basicsocket/read_spec.rb new file mode 100644 index 0000000000..ba9de7d5cf --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/read_spec.rb @@ -0,0 +1,47 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#read" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.send("aaa", 0, @r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "receives data after it's ready" do + @r.read(3).should == "aaa" + end + + it 'returned data is binary encoded regardless of the external encoding' do + @r.read(3).encoding.should == Encoding::BINARY + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::UTF_8) + buffer = @r.read(3) + buffer.should == "bbb" + buffer.encoding.should == Encoding::BINARY + end + + it 'replaces the content of the provided buffer without changing its encoding' do + buffer = "initial data".dup.force_encoding(Encoding::UTF_8) + + @r.read(3, buffer) + buffer.should == "aaa" + buffer.encoding.should == Encoding::UTF_8 + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + @r.read(3, buffer) + buffer.should == "bbb" + buffer.encoding.should == Encoding::UTF_8 + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb new file mode 100644 index 0000000000..f2a6682f12 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb @@ -0,0 +1,172 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#recv_nonblock" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @s1 = Socket.new(family, :DGRAM) + @s2 = Socket.new(family, :DGRAM) + end + + after :each do + @s1.close unless @s1.closed? + @s2.close unless @s2.closed? + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises an exception extending IO::WaitReadable' do + -> { @s1.recv_nonblock(1) }.should raise_error(IO::WaitReadable) + end + end + end + + it "raises an exception extending IO::WaitReadable if there's no data available" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + -> { + @s1.recv_nonblock(5) + }.should raise_error(IO::WaitReadable) { |e| + platform_is_not :windows do + e.should be_kind_of(Errno::EAGAIN) + end + platform_is :windows do + e.should be_kind_of(Errno::EWOULDBLOCK) + end + } + end + + it "returns :wait_readable with exception: false" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s1.recv_nonblock(5, exception: false).should == :wait_readable + end + + it "receives data after it's ready" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("aaa", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + @s1.recv_nonblock(5).should == "aaa" + end + + it "allows an output buffer as third argument" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("data", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + + buffer = +"foo" + @s1.recv_nonblock(5, 0, buffer).should.equal?(buffer) + buffer.should == "data" + end + + it "preserves the encoding of the given buffer" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("data", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + + buffer = ''.encode(Encoding::ISO_8859_1) + @s1.recv_nonblock(5, 0, buffer) + buffer.encoding.should == Encoding::ISO_8859_1 + end + + it "does not block if there's no data available" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("a", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + @s1.recv_nonblock(1).should == "a" + -> { + @s1.recv_nonblock(5) + }.should raise_error(IO::WaitReadable) + end + end + + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a connected but not bound socket' do + before do + @server = Socket.new(family, :STREAM) + end + + after do + @server.close + end + + it "raises Errno::ENOTCONN" do + -> { @server.recv_nonblock(1) }.should raise_error { |e| + [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class) + } + -> { @server.recv_nonblock(1, exception: false) }.should raise_error { |e| + [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class) + } + end + end + end +end + +describe "Socket::BasicSocket#recv_nonblock" do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recv_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recv_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should be_nil + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb new file mode 100644 index 0000000000..a51920f52a --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb @@ -0,0 +1,250 @@ +# encoding: binary +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#recv" do + + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + ScratchPad.clear + end + + it "receives a specified number of bytes of a message from another socket" do + t = Thread.new do + client = @server.accept + ScratchPad.record client.recv(10) + client.recv(1) # this recv is important + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.send('hello', 0) + socket.close + + t.join + ScratchPad.recorded.should == 'hello' + end + + it "accepts flags to specify unusual receiving behaviour" do + t = Thread.new do + client = @server.accept + + # in-band data (TCP), doesn't receive the flag. + ScratchPad.record client.recv(10) + + # this recv is important (TODO: explain) + client.recv(10) + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.send('helloU', Socket::MSG_OOB) + socket.shutdown(1) + t.join + socket.close + ScratchPad.recorded.should == 'hello' + end + + it "gets lines delimited with a custom separator" do + t = Thread.new do + client = @server.accept + ScratchPad.record client.gets("\377") + + # this call is important (TODO: explain) + client.gets(nil) + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("firstline\377secondline\377") + socket.close + + t.join + ScratchPad.recorded.should == "firstline\377" + end + + it "allows an output buffer as third argument" do + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("data") + + client = @server.accept + buffer = +"foo" + begin + client.recv(4, 0, buffer).should.equal?(buffer) + ensure + client.close + end + buffer.should == "data" + + socket.close + end + + it "preserves the encoding of the given buffer" do + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("data") + + client = @server.accept + buffer = ''.encode(Encoding::ISO_8859_1) + begin + client.recv(4, 0, buffer) + ensure + client.close + end + buffer.encoding.should == Encoding::ISO_8859_1 + + socket.close + end +end + +describe 'BasicSocket#recv' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + describe 'using an unbound socket' do + it 'blocks the caller' do + -> { @server.recv(4) }.should block_caller + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + describe 'without any data available' do + it 'blocks the caller' do + -> { @server.recv(4) }.should block_caller + end + end + + describe 'with data available' do + before do + @client.connect(@server.getsockname) + end + + it 'reads the given amount of bytes' do + @client.write('hello') + + @server.recv(2).should == 'he' + end + + it 'reads the given amount of bytes when it exceeds the data size' do + @client.write('he') + + @server.recv(6).should == 'he' + end + + it 'blocks the caller when called twice without new data being available' do + @client.write('hello') + + @server.recv(2).should == 'he' + + -> { @server.recv(4) }.should block_caller + end + + it 'takes a peek at the data when using the MSG_PEEK flag' do + @client.write('hello') + + @server.recv(2, Socket::MSG_PEEK).should == 'he' + @server.recv(2).should == 'he' + end + end + end + end +end + +describe "BasicSocket#recv" do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recv(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recv(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should be_nil + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns empty String" do + @server.bind(ip_address, 0) + addr = @server.connect_address + @client.connect(addr.ip_address, addr.ip_port) + + @client.send('', 0) + + @server.recv(1).should == "" + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb new file mode 100644 index 0000000000..b5fdd7c93b --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb @@ -0,0 +1,300 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#recvmsg_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises an exception extending IO::WaitReadable' do + -> { @server.recvmsg_nonblock }.should raise_error(IO::WaitReadable) + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + describe 'without any data available' do + it 'raises an exception extending IO::WaitReadable' do + -> { @server.recvmsg_nonblock }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable with exception: false' do + @server.recvmsg_nonblock(exception: false).should == :wait_readable + end + end + + describe 'with data available' do + before do + @client.connect(@server.getsockname) + + @client.write('hello') + + IO.select([@server], nil, nil, 5) + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @server.recvmsg_nonblock.should be_an_instance_of(Array) + end + + describe 'without a maximum message length' do + it 'reads all the available data' do + @server.recvmsg_nonblock[0].should == 'hello' + end + end + + describe 'with a maximum message length' do + platform_is_not :windows do + it 'reads up to the maximum amount of bytes' do + @server.recvmsg_nonblock(2)[0].should == 'he' + end + end + end + + describe 'the returned Array' do + before do + @array = @server.recvmsg_nonblock + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + platform_is_not :windows do + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the correct address family' do + @addr.afamily.should == family + end + + it 'uses the correct protocol family' do + @addr.pfamily.should == family + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the port number of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end + + platform_is_not :windows do + describe 'using a connected but not bound socket' do + before do + @server = Socket.new(family, :STREAM) + end + + after do + @server.close + end + + it "raises Errno::ENOTCONN" do + -> { @server.recvmsg_nonblock }.should raise_error(Errno::ENOTCONN) + -> { @server.recvmsg_nonblock(exception: false) }.should raise_error(Errno::ENOTCONN) + end + end + + describe 'using a connected socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'without any data available' do + it 'raises IO::WaitReadable' do + -> { + socket, _ = @server.accept + begin + socket.recvmsg_nonblock + ensure + socket.close + end + }.should raise_error(IO::WaitReadable) + end + end + + describe 'with data available' do + before do + @client.write('hello') + + @socket, _ = @server.accept + IO.select([@socket]) + end + + after do + @socket.close + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @socket.recvmsg_nonblock.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @socket.recvmsg_nonblock + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'raises when receiving the ip_address message' do + -> { @addr.ip_address }.should raise_error(SocketError) + end + + it 'uses the correct address family' do + @addr.afamily.should == Socket::AF_UNSPEC + end + + it 'uses 0 for the protocol family' do + @addr.pfamily.should == 0 + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'raises when receiving the ip_port message' do + -> { @addr.ip_port }.should raise_error(SocketError) + end + end + end + end + end + end + end +end + +describe 'BasicSocket#recvmsg_nonblock' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + platform_is_not :windows do # #recvmsg_nonblock() raises 'Errno::EINVAL: Invalid argument - recvmsg(2)' + it "returns an empty String as received data on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recvmsg_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + end + + ruby_version_is "3.3" do + platform_is_not :windows do + it "returns nil on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recvmsg_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should be_nil + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb new file mode 100644 index 0000000000..04ba1d74c7 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb @@ -0,0 +1,281 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#recvmsg' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'blocks the caller' do + -> { @server.recvmsg }.should block_caller + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + describe 'without any data available' do + it 'blocks the caller' do + -> { @server.recvmsg }.should block_caller + end + end + + describe 'with data available' do + before do + @client.connect(@server.getsockname) + + @client.write('hello') + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @server.recvmsg.should be_an_instance_of(Array) + end + + describe 'without a maximum message length' do + it 'reads all the available data' do + @server.recvmsg[0].should == 'hello' + end + end + + describe 'with a maximum message length' do + it 'reads up to the maximum amount of bytes' do + @server.recvmsg(2)[0].should == 'he' + end + end + + describe 'the returned Array' do + before do + @array = @server.recvmsg + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + platform_is_not :windows do + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the correct address family' do + @addr.afamily.should == family + end + + it 'uses the correct protocol family' do + @addr.pfamily.should == family + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the port number of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end + + platform_is_not :windows do + describe 'using a connected socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'without any data available' do + it 'blocks the caller' do + socket, _ = @server.accept + begin + -> { socket.recvmsg }.should block_caller + ensure + socket.close + end + end + end + + describe 'with data available' do + before do + @client.write('hello') + @socket, _ = @server.accept + end + + after do + @socket.close + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @socket.recvmsg.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @socket.recvmsg + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'raises when receiving the ip_address message' do + -> { @addr.ip_address }.should raise_error(SocketError) + end + + it 'uses the correct address family' do + @addr.afamily.should == Socket::AF_UNSPEC + end + + it 'returns 0 for the protocol family' do + @addr.pfamily.should == 0 + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'raises when receiving the ip_port message' do + -> { @addr.ip_port }.should raise_error(SocketError) + end + end + end + end + end + end + end +end + +describe 'BasicSocket#recvmsg' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + platform_is_not :windows do + it "returns an empty String as received data on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recvmsg(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + end + + ruby_version_is "3.3" do + platform_is_not :windows do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recvmsg(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should be_nil + end + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns an empty String as received data" do + @server.bind(ip_address, 0) + addr = @server.connect_address + @client.connect(addr.ip_address, addr.ip_port) + + @client.send('', 0) + message = @server.recvmsg(1) + + message.should.is_a? Array + message[0].should == "" + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/remote_address_spec.rb b/spec/ruby/library/socket/basicsocket/remote_address_spec.rb new file mode 100644 index 0000000000..439bf31592 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/remote_address_spec.rb @@ -0,0 +1,10 @@ +require_relative '../spec_helper' +require_relative '../shared/address' + +describe 'BasicSocket#remote_address' do + it_behaves_like :socket_local_remote_address, :remote_address, -> socket { + a2 = BasicSocket.for_fd(socket.fileno) + a2.autoclose = false + a2.remote_address + } +end diff --git a/spec/ruby/library/socket/basicsocket/send_spec.rb b/spec/ruby/library/socket/basicsocket/send_spec.rb new file mode 100644 index 0000000000..25ba3f5655 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/send_spec.rb @@ -0,0 +1,220 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#send" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + @socket = TCPSocket.new('127.0.0.1', @port) + end + + after :each do + @server.closed?.should be_false + @socket.closed?.should be_false + + @server.close + @socket.close + end + + it "sends a message to another socket and returns the number of bytes sent" do + data = +"" + t = Thread.new do + client = @server.accept + loop do + got = client.recv(5) + break if got.nil? || got.empty? + data << got + end + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @socket.send('hello', 0).should == 5 + @socket.shutdown(1) # indicate, that we are done sending + @socket.recv(10) + + t.join + data.should == 'hello' + end + + platform_is_not :windows do + it "accepts flags to specify unusual sending behaviour" do + data = nil + peek_data = nil + t = Thread.new do + client = @server.accept + peek_data = client.recv(6, Socket::MSG_PEEK) + data = client.recv(6) + client.recv(10) # this recv is important + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @socket.send('helloU', Socket::MSG_PEEK | Socket::MSG_OOB).should == 6 + @socket.shutdown # indicate, that we are done sending + + t.join + peek_data.should == "hello" + data.should == 'hello' + end + end + + it "accepts a sockaddr as recipient address" do + data = +"" + t = Thread.new do + client = @server.accept + loop do + got = client.recv(5) + break if got.nil? || got.empty? + data << got + end + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + sockaddr = Socket.pack_sockaddr_in(@port, "127.0.0.1") + @socket.send('hello', 0, sockaddr).should == 5 + @socket.shutdown # indicate, that we are done sending + + t.join + data.should == 'hello' + end +end + +describe 'BasicSocket#send' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'with an object implementing #to_str' do + it 'returns the amount of sent bytes' do + data = mock('message') + data.should_receive(:to_str).and_return('hello') + @client.send(data, 0, @server.getsockname).should == 5 + end + end + + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as a String' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @server.getsockname).should == 5 + end + + it 'does not persist the connection after writing to the socket' do + @client.send('hello', 0, @server.getsockname) + + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as an Addrinfo' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @server.connect_address).should == 5 + end + end + end + + describe 'using a connected UDP socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address argument' do + before do + @client.connect(@server.getsockname) + end + + it 'returns the amount of bytes written' do + @client.send('hello', 0).should == 5 + end + end + + describe 'with a destination address argument' do + before do + @alt_server = Socket.new(family, :DGRAM) + + @alt_server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @alt_server.close + end + + it 'sends the message to the given address instead' do + @client.send('hello', 0, @alt_server.getsockname).should == 5 + + -> { @server.recv(5) }.should block_caller + + @alt_server.recv(5).should == 'hello' + end + + it 'does not persist the alternative connection after writing to the socket' do + @client.send('hello', 0, @alt_server.getsockname) + + @client.connect(@server.getsockname) + @client.send('world', 0) + + @server.recv(5).should == 'world' + end + end + end + + platform_is_not :darwin, :windows do + describe 'using a connected TCP socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'using the MSG_OOB flag' do + it 'sends an out-of-band message' do + socket, _ = @server.accept + socket.setsockopt(:SOCKET, :OOBINLINE, true) + @client.send('a', Socket::MSG_OOB).should == 1 + begin + socket.recv(10).should == 'a' + ensure + socket.close + end + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb new file mode 100644 index 0000000000..7acfc659bd --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb @@ -0,0 +1,118 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#sendmsg_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { + @client.sendmsg_nonblock('hello') + }.should raise_error(SocketSpecs.dest_addr_req_error) + -> { + @client.sendmsg_nonblock('hello', exception: false) + }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as a String' do + it 'returns the amount of sent bytes' do + @client.sendmsg_nonblock('hello', 0, @server.getsockname).should == 5 + end + end + + describe 'with a destination address as an Addrinfo' do + it 'returns the amount of sent bytes' do + @client.sendmsg_nonblock('hello', 0, @server.connect_address).should == 5 + end + end + end + + describe 'using a connected UDP socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address argument' do + before do + @client.connect(@server.getsockname) + end + + it 'returns the amount of bytes written' do + @client.sendmsg_nonblock('hello').should == 5 + end + end + + describe 'with a destination address argument' do + before do + @alt_server = Socket.new(family, :DGRAM) + @alt_server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @alt_server.close + end + + it 'sends the message to the given address instead' do + @client.sendmsg_nonblock('hello', 0, @alt_server.getsockname).should == 5 + -> { @server.recv(5) }.should block_caller + @alt_server.recv(5).should == 'hello' + end + end + end + + platform_is_not :windows do + describe 'using a connected TCP socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + it 'raises IO::WaitWritable when the underlying buffer is full' do + -> { + 10.times { @client.sendmsg_nonblock('hello' * 1_000_000) } + }.should raise_error(IO::WaitWritable) + end + + it 'returns :wait_writable when the underlying buffer is full with exception: false' do + ret = nil + 10.times { + ret = @client.sendmsg_nonblock('hello' * 1_000_000, exception: false) + break unless ret.is_a?(Integer) + } + ret.should == :wait_writable + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb b/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb new file mode 100644 index 0000000000..7ff336c0b7 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb @@ -0,0 +1,111 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#sendmsg' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { @client.sendmsg('hello') }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + end + + describe 'with a destination address as a String' do + it 'returns the amount of sent bytes' do + @client.sendmsg('hello', 0, @server.getsockname).should == 5 + end + end + + describe 'with a destination address as an Addrinfo' do + it 'returns the amount of sent bytes' do + @client.sendmsg('hello', 0, @server.connect_address).should == 5 + end + end + end + + describe 'using a connected UDP socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address argument' do + before do + @client.connect(@server.getsockname) + end + + it 'returns the amount of bytes written' do + @client.sendmsg('hello').should == 5 + end + end + + describe 'with a destination address argument' do + before do + @alt_server = Socket.new(family, :DGRAM) + + @alt_server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @alt_server.close + end + + it 'sends the message to the given address instead' do + @client.sendmsg('hello', 0, @alt_server.getsockname).should == 5 + + -> { @server.recv(5) }.should block_caller + + @alt_server.recv(5).should == 'hello' + end + end + end + + platform_is_not :windows do # spurious + describe 'using a connected TCP socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + it 'blocks when the underlying buffer is full' do + # Buffer sizes may differ per platform, so sadly this is the only + # reliable way of testing blocking behaviour. + -> do + 10.times { @client.sendmsg('hello' * 1_000_000) } + end.should block_caller + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb new file mode 100644 index 0000000000..f686e67326 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb @@ -0,0 +1,334 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#setsockopt" do + + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + end + + after :each do + @sock.close unless @sock.closed? + end + + it "sets the socket linger to 0" do + linger = [0, 0].pack("ii") + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + + if (n.size == 8) # linger struct on some platforms, not just a value + n.should == [0, 0].pack("ii") + else + n.should == [0].pack("i") + end + end + + it "sets the socket linger to some positive value" do + linger = [64, 64].pack("ii") + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + if (n.size == 8) # linger struct on some platforms, not just a value + a = n.unpack('ii') + a[0].should_not == 0 + a[1].should == 64 + else + n.should == [64].pack("i") + end + end + + platform_is_not :windows do + it "raises EINVAL if passed wrong linger value" do + -> do + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, 0) + end.should raise_error(Errno::EINVAL) + end + end + + platform_is_not :aix do + # A known bug in AIX. getsockopt(2) does not properly set + # the fifth argument for SO_TYPE, SO_OOBINLINE, SO_BROADCAST, etc. + + it "sets the socket option Socket::SO_OOBINLINE" do + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, true).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, false).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 0).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 2).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "") + }.should raise_error(SystemCallError) + end + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "blah").should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "0") + }.should raise_error(SystemCallError) + end + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00\x00").should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "1") + }.should raise_error(SystemCallError) + end + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00") + }.should raise_error(SystemCallError) + end + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [0].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1000].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + end + end + + it "sets the socket option Socket::SO_SNDBUF" do + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 4000).should == 0 + sndbuf = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + # might not always be possible to set to exact size + sndbuf.unpack('i')[0].should >= 4000 + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, true).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 1 + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, nil).should == 0 + }.should raise_error(TypeError) + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 1).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 1 + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 2).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 2 + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "bla") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "0") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "1") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x00") + }.should raise_error(SystemCallError) + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x01\x00").should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= "\x00\x00\x01\x00".unpack('i')[0] + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, [4000].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 4000 + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, [1000].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 1000 + end + + platform_is_not :aix do + describe 'accepts Socket::Option as argument' do + it 'boolean' do + option = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + @sock.setsockopt(option).should == 0 + @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool.should == true + end + + it 'int' do + option = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + @sock.setsockopt(option).should == 0 + @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool.should == true + end + end + end + + platform_is :aix do + describe 'accepts Socket::Option as argument' do + it 'boolean' do + option = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + @sock.setsockopt(option).should == 0 + end + + it 'int' do + option = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + @sock.setsockopt(option).should == 0 + end + end + end + + describe 'accepts Socket::Option as argument' do + it 'linger' do + option = Socket::Option.linger(true, 10) + @sock.setsockopt(option).should == 0 + onoff, seconds = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).linger + seconds.should == 10 + # Both results can be produced depending on the OS and value of Socket::SO_LINGER + [true, Socket::SO_LINGER].should include(onoff) + end + end +end + +describe 'BasicSocket#setsockopt' do + describe 'using a STREAM socket' do + before do + @socket = Socket.new(:INET, :STREAM) + end + + after do + @socket.close + end + + describe 'using separate arguments with Symbols' do + it 'raises TypeError when the first argument is nil' do + -> { @socket.setsockopt(nil, :REUSEADDR, true) }.should raise_error(TypeError) + end + + it 'sets a boolean option' do + @socket.setsockopt(:SOCKET, :REUSEADDR, true).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt(:IP, :TTL, 255).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + + guard -> { SocketSpecs.ipv6_available? } do + it 'sets an IPv6 boolean option' do + socket = Socket.new(:INET6, :STREAM) + begin + socket.setsockopt(:IPV6, :V6ONLY, true).should == 0 + socket.getsockopt(:IPV6, :V6ONLY).bool.should == true + ensure + socket.close + end + end + end + + platform_is_not :windows do + it 'raises Errno::EINVAL when setting an invalid option value' do + -> { @socket.setsockopt(:SOCKET, :OOBINLINE, 'bla') }.should raise_error(Errno::EINVAL) + end + end + end + + describe 'using separate arguments with Symbols' do + it 'sets a boolean option' do + @socket.setsockopt('SOCKET', 'REUSEADDR', true).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt('IP', 'TTL', 255).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + end + + describe 'using separate arguments with constants' do + it 'sets a boolean option' do + @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, 255).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + end + + describe 'using separate arguments with custom objects' do + it 'sets a boolean option' do + level = mock(:level) + name = mock(:name) + + level.stub!(:to_str).and_return('SOCKET') + name.stub!(:to_str).and_return('REUSEADDR') + + @socket.setsockopt(level, name, true).should == 0 + end + end + + describe 'using a Socket::Option as the first argument' do + it 'sets a boolean option' do + @socket.setsockopt(Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true)).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt(Socket::Option.int(:INET, :IP, :TTL, 255)).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + + it 'raises ArgumentError when passing 2 arguments' do + option = Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true) + -> { @socket.setsockopt(option, :REUSEADDR) }.should raise_error(ArgumentError) + end + + it 'raises TypeError when passing 3 arguments' do + option = Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true) + -> { @socket.setsockopt(option, :REUSEADDR, true) }.should raise_error(TypeError) + end + end + end + + describe 'using a UNIX socket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r @path + end + + it 'sets a boolean option' do + @server.setsockopt(:SOCKET, :REUSEADDR, true) + @server.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/shutdown_spec.rb b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb new file mode 100644 index 0000000000..c78b32de38 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb @@ -0,0 +1,155 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +platform_is_not :windows do # hangs + describe "Socket::BasicSocket#shutdown" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'using an Integer' do + it 'shuts down a socket for reading' do + @client.shutdown(Socket::SHUT_RD) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for writing' do + @client.shutdown(Socket::SHUT_WR) + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for reading and writing' do + @client.shutdown(Socket::SHUT_RDWR) + + @client.recv(1).to_s.should be_empty + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown(666) }.should raise_error(ArgumentError) + end + end + + describe 'using a Symbol' do + it 'shuts down a socket for reading using :RD' do + @client.shutdown(:RD) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading using :SHUT_RD' do + @client.shutdown(:SHUT_RD) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for writing using :WR' do + @client.shutdown(:WR) + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for writing using :SHUT_WR' do + @client.shutdown(:SHUT_WR) + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for reading and writing' do + @client.shutdown(:RDWR) + + @client.recv(1).to_s.should be_empty + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown(:Nope) }.should raise_error(SocketError) + end + end + + describe 'using a String' do + it 'shuts down a socket for reading using "RD"' do + @client.shutdown('RD') + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading using "SHUT_RD"' do + @client.shutdown('SHUT_RD') + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for writing using "WR"' do + @client.shutdown('WR') + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for writing using "SHUT_WR"' do + @client.shutdown('SHUT_WR') + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown('Nope') }.should raise_error(SocketError) + end + end + + describe 'using an object that responds to #to_str' do + before do + @dummy = mock(:dummy) + end + + it 'shuts down a socket for reading using "RD"' do + @dummy.stub!(:to_str).and_return('RD') + + @client.shutdown(@dummy) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading using "SHUT_RD"' do + @dummy.stub!(:to_str).and_return('SHUT_RD') + + @client.shutdown(@dummy) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading and writing' do + @dummy.stub!(:to_str).and_return('RDWR') + + @client.shutdown(@dummy) + + @client.recv(1).to_s.should be_empty + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + end + + describe 'using an object that does not respond to #to_str' do + it 'raises TypeError' do + -> { @server.shutdown(mock(:dummy)) }.should raise_error(TypeError) + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb new file mode 100644 index 0000000000..523e732959 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#write_nonblock" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.connect(@r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "sends data" do + @w.write_nonblock("aaa").should == 3 + IO.select([@r], nil, nil, 2) + @r.recv_nonblock(5).should == "aaa" + end + + platform_is :linux do + it 'does not set the IO in nonblock mode' do + require 'io/nonblock' + @w.nonblock = false + @w.write_nonblock("aaa").should == 3 + @w.should_not.nonblock? + end + end + + platform_is_not :linux, :windows do + it 'sets the IO in nonblock mode' do + require 'io/nonblock' + @w.nonblock = false + @w.write_nonblock("aaa").should == 3 + @w.should.nonblock? + end + end + end +end diff --git a/spec/ruby/library/socket/constants/constants_spec.rb b/spec/ruby/library/socket/constants/constants_spec.rb new file mode 100644 index 0000000000..b9a9d42725 --- /dev/null +++ b/spec/ruby/library/socket/constants/constants_spec.rb @@ -0,0 +1,108 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Constants" do + it "defines socket types" do + consts = ["SOCK_DGRAM", "SOCK_RAW", "SOCK_RDM", "SOCK_SEQPACKET", "SOCK_STREAM"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines protocol families" do + consts = ["PF_INET6", "PF_INET", "PF_UNIX", "PF_UNSPEC"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :aix do + it "defines PF_IPX protocol" do + Socket::Constants.should have_constant("PF_IPX") + end + end + + it "defines address families" do + consts = ["AF_INET6", "AF_INET", "AF_UNIX", "AF_UNSPEC"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :aix do + it "defines AF_IPX address" do + Socket::Constants.should have_constant("AF_IPX") + end + end + + it "defines send/receive options" do + consts = ["MSG_DONTROUTE", "MSG_OOB", "MSG_PEEK"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines socket level options" do + consts = ["SOL_SOCKET"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines socket options" do + consts = ["SO_BROADCAST", "SO_DEBUG", "SO_DONTROUTE", "SO_ERROR", "SO_KEEPALIVE", "SO_LINGER", + "SO_OOBINLINE", "SO_RCVBUF", "SO_REUSEADDR", "SO_SNDBUF", "SO_TYPE"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines multicast options" do + consts = ["IP_ADD_MEMBERSHIP", + "IP_MULTICAST_LOOP", "IP_MULTICAST_TTL"] + platform_is_not :windows do + consts += ["IP_DEFAULT_MULTICAST_LOOP", "IP_DEFAULT_MULTICAST_TTL"] + end + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :windows, :aix, :android do + it "defines multicast options" do + consts = ["IP_MAX_MEMBERSHIPS"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + end + + it "defines TCP options" do + consts = ["TCP_NODELAY"] + platform_is_not :windows do + consts << "TCP_MAXSEG" + end + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :windows do + it 'defines SCM options' do + Socket::Constants.should have_constant('SCM_RIGHTS') + end + + it 'defines error options' do + consts = ["EAI_ADDRFAMILY", "EAI_NODATA"] + + # FreeBSD (11.1, at least) obsoletes EAI_ADDRFAMILY and EAI_NODATA + platform_is :freebsd do + consts = %w(EAI_MEMORY) + end + + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + end +end diff --git a/spec/ruby/library/socket/fixtures/classes.rb b/spec/ruby/library/socket/fixtures/classes.rb new file mode 100644 index 0000000000..786629d2ef --- /dev/null +++ b/spec/ruby/library/socket/fixtures/classes.rb @@ -0,0 +1,166 @@ +require 'socket' + +module SocketSpecs + # helper to get the hostname associated to 127.0.0.1 or the given ip + def self.hostname(ip = "127.0.0.1") + # Calculate each time, without caching, since the result might + # depend on things like do_not_reverse_lookup mode, which is + # changing from test to test + Socket.getaddrinfo(ip, nil)[0][2] + end + + def self.hostname_reverse_lookup(ip = "127.0.0.1") + Socket.getaddrinfo(ip, nil, 0, 0, 0, 0, true)[0][2] + end + + def self.addr(which=:ipv4) + case which + when :ipv4 + host = "127.0.0.1" + when :ipv6 + host = "::1" + end + Socket.getaddrinfo(host, nil)[0][3] + end + + def self.reserved_unused_port + # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers + 0 + end + + def self.sockaddr_in(port, host) + Socket::SockAddr_In.new(Socket.sockaddr_in(port, host)) + end + + def self.socket_path + path = tmp("unix.sock", false) + # Check for too long unix socket path (max 104 bytes on macOS) + # Note that Linux accepts not null-terminated paths but the man page advises against it. + if path.bytesize > 104 + # rm_r in spec/mspec/lib/mspec/helpers/fs.rb fails against + # "/tmp/unix_server_spec.socket" + skip "too long unix socket path: #{path}" + end + rm_socket(path) + path + end + + def self.rm_socket(path) + File.delete(path) if File.exist?(path) + end + + def self.ipv6_available? + @ipv6_available ||= begin + server = TCPServer.new('::1', 0) + rescue Errno::EAFNOSUPPORT, Errno::EADDRNOTAVAIL, SocketError + :no + else + server.close + :yes + end + @ipv6_available == :yes + end + + def self.each_ip_protocol + describe 'using IPv4' do + yield Socket::AF_INET, '127.0.0.1', 'AF_INET' + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using IPv6' do + yield Socket::AF_INET6, '::1', 'AF_INET6' + end + end + end + + def self.loop_with_timeout(timeout = TIME_TOLERANCE) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + while yield == :retry + if Process.clock_gettime(Process::CLOCK_MONOTONIC) - start >= timeout + raise RuntimeError, "Did not succeed within #{timeout} seconds" + end + end + end + + def self.dest_addr_req_error + error = Errno::EDESTADDRREQ + platform_is :windows do + error = Errno::ENOTCONN + end + error + end + + # TCPServer echo server accepting one connection + class SpecTCPServer + attr_reader :hostname, :port + + def initialize + @hostname = SocketSpecs.hostname + @server = TCPServer.new @hostname, 0 + @port = @server.addr[1] + + log "SpecTCPServer starting on #{@hostname}:#{@port}" + + @thread = Thread.new do + socket = @server.accept + log "SpecTCPServer accepted connection: #{socket}" + service socket + end + end + + def service(socket) + begin + data = socket.recv(1024) + + return if data.nil? || data.empty? + log "SpecTCPServer received: #{data.inspect}" + + return if data == "QUIT" + + socket.send data, 0 + ensure + socket.close + end + end + + def shutdown + log "SpecTCPServer shutting down" + @thread.join + @server.close + end + + def log(message) + @logger.puts message if @logger + end + end + + # We need to find a free port for Socket.tcp_server_loop and Socket.udp_server_loop, + # and the only reliable way to do that is to pass 0 as the port, but then we need to + # find out which one was chosen and the API doesn't let us find what it is. So we + # intercept one of the public API methods called by these methods. + class ServerLoopPortFinder < Socket + def self.tcp_server_sockets(*args) + super(*args) { |sockets| + @port = sockets.first.local_address.ip_port + yield(sockets) + } + end + + def self.udp_server_sockets(*args, &block) + super(*args) { |sockets| + @port = sockets.first.local_address.ip_port + yield(sockets) + } + end + + def self.cleanup + @port = nil + end + + def self.port + sleep 0.001 until @port + @port + end + end +end diff --git a/spec/ruby/library/socket/fixtures/send_io.txt b/spec/ruby/library/socket/fixtures/send_io.txt new file mode 100644 index 0000000000..eaaa1eb3ec --- /dev/null +++ b/spec/ruby/library/socket/fixtures/send_io.txt @@ -0,0 +1 @@ +This data is magic. diff --git a/spec/ruby/library/socket/ipsocket/addr_spec.rb b/spec/ruby/library/socket/ipsocket/addr_spec.rb new file mode 100644 index 0000000000..199eb85ab7 --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/addr_spec.rb @@ -0,0 +1,105 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#addr" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @socket = TCPServer.new("127.0.0.1", 0) + end + + after :each do + @socket.close unless @socket.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it "returns an array with the socket's information" do + @socket.do_not_reverse_lookup = false + BasicSocket.do_not_reverse_lookup = false + addrinfo = @socket.addr + addrinfo[0].should == "AF_INET" + addrinfo[1].should be_kind_of(Integer) + addrinfo[2].should == SocketSpecs.hostname + addrinfo[3].should == "127.0.0.1" + end + + it "returns an address in the array if do_not_reverse_lookup is true" do + @socket.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + addrinfo = @socket.addr + addrinfo[0].should == "AF_INET" + addrinfo[1].should be_kind_of(Integer) + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end + + it "returns an address in the array if passed false" do + addrinfo = @socket.addr(false) + addrinfo[0].should == "AF_INET" + addrinfo[1].should be_kind_of(Integer) + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end +end + +describe 'Socket::IPSocket#addr' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'without reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil)[0][2] + end + + it 'returns an Array containing address information' do + @server.addr.should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'with reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + end + + describe 'using true as the argument' do + it 'returns an Array containing address information' do + @server.addr(true).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :hostname as the argument' do + it 'returns an Array containing address information' do + @server.addr(:hostname).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :cats as the argument' do + it 'raises ArgumentError' do + -> { @server.addr(:cats) }.should raise_error(ArgumentError) + end + end + end + + describe 'with do_not_reverse_lookup disabled on socket level' do + before do + @server.do_not_reverse_lookup = false + + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + end + + after do + @server.do_not_reverse_lookup = true + end + + it 'returns an Array containing address information' do + @server.addr.should == [family_name, @port, @hostname, ip_address] + end + end + end +end diff --git a/spec/ruby/library/socket/ipsocket/getaddress_spec.rb b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb new file mode 100644 index 0000000000..329f8267d3 --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#getaddress" do + it "returns the IP address of hostname" do + addr_local = IPSocket.getaddress(SocketSpecs.hostname) + ["127.0.0.1", "::1"].include?(addr_local).should == true + end + + it "returns the IP address when passed an IP" do + IPSocket.getaddress("127.0.0.1").should == "127.0.0.1" + IPSocket.getaddress("0.0.0.0").should == "0.0.0.0" + IPSocket.getaddress('::1').should == '::1' + end + + it 'returns IPv4 compatible IPv6 addresses' do + IPSocket.getaddress('::ffff:192.168.1.1').should == '::ffff:192.168.1.1' + end + + # There is no way to make this fail-proof on all machines, because + # DNS servers like opendns return A records for ANY host, including + # traditionally invalidly named ones. + it "raises an error on unknown hostnames" do + -> { + IPSocket.getaddress("rubyspecdoesntexist.ruby-lang.org") + }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb b/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb new file mode 100644 index 0000000000..702650940b --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb @@ -0,0 +1,117 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#peeraddr" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", @port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it "raises error if socket is not connected" do + -> { + @server.peeraddr + }.should raise_error(Errno::ENOTCONN) + end + + it "returns an array of information on the peer" do + @client.do_not_reverse_lookup = false + BasicSocket.do_not_reverse_lookup = false + addrinfo = @client.peeraddr + addrinfo[0].should == "AF_INET" + addrinfo[1].should == @port + addrinfo[2].should == SocketSpecs.hostname + addrinfo[3].should == "127.0.0.1" + end + + it "returns an IP instead of hostname if do_not_reverse_lookup is true" do + @client.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + addrinfo = @client.peeraddr + addrinfo[0].should == "AF_INET" + addrinfo[1].should == @port + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end + + it "returns an IP instead of hostname if passed false" do + addrinfo = @client.peeraddr(false) + addrinfo[0].should == "AF_INET" + addrinfo[1].should == @port + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end +end + +describe 'Socket::IPSocket#peeraddr' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + @client = TCPSocket.new(ip_address, @port) + end + + after do + @client.close + @server.close + end + + describe 'without reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil)[0][2] + end + + it 'returns an Array containing address information' do + @client.peeraddr.should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'with reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + end + + describe 'using true as the argument' do + it 'returns an Array containing address information' do + @client.peeraddr(true).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :hostname as the argument' do + it 'returns an Array containing address information' do + @client.peeraddr(:hostname).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :cats as the argument' do + it 'raises ArgumentError' do + -> { @client.peeraddr(:cats) }.should raise_error(ArgumentError) + end + end + end + + describe 'with do_not_reverse_lookup disabled on socket level' do + before do + @client.do_not_reverse_lookup = false + + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + @port = @client.local_address.ip_port + end + + after do + @client.do_not_reverse_lookup = true + end + + it 'returns an Array containing address information' do + @client.addr.should == [family_name, @port, @hostname, ip_address] + end + end + end +end diff --git a/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb new file mode 100644 index 0000000000..b58903df23 --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb @@ -0,0 +1,205 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#recvfrom" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", @port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "reads data from the connection" do + data = nil + t = Thread.new do + client = @server.accept + begin + data = client.recvfrom(6) + ensure + client.close + end + end + + @client.send('hello', 0) + @client.shutdown rescue nil + # shutdown may raise Errno::ENOTCONN when sent data is pending. + t.join + + data.first.should == 'hello' + end + + it "reads up to len bytes" do + data = nil + t = Thread.new do + client = @server.accept + begin + data = client.recvfrom(3) + ensure + client.close + end + end + + @client.send('hello', 0) + @client.shutdown rescue nil + t.join + + data.first.should == 'hel' + end + + it "returns an array with the data and connection info" do + data = nil + t = Thread.new do + client = @server.accept + data = client.recvfrom(3) + client.close + end + + @client.send('hello', 0) + @client.shutdown rescue nil + t.join + + data.size.should == 2 + data.first.should == "hel" + # This does not apply to every platform, dependent on recvfrom(2) + # data.last.should == nil + end +end + +describe "Socket::IPSocket#recvfrom" do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String as received data on a closed stream socket" do + t = Thread.new do + client = @server.accept + message = client.recvfrom(10) + message + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.close + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + message = client.recvfrom(10) + message + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.close + + t.value.should be_nil + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns an empty String as received data" do + @server.bind(ip_address, 0) + addr = @server.connect_address + @client.connect(addr.ip_address, addr.ip_port) + + @client.send('', 0) + message = @server.recvfrom(1) + + message.should.is_a? Array + message[0].should == "" + end + end + end + end +end + +describe 'Socket::IPSocket#recvfrom' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + + @server.bind(ip_address, 0) + @client.connect(ip_address, @server.connect_address.ip_port) + + @hostname = Socket.getaddrinfo(ip_address, nil)[0][2] + end + + after do + @client.close + @server.close + end + + it 'returns an Array containing up to N bytes and address information' do + @client.write('hello') + + port = @client.local_address.ip_port + ret = @server.recvfrom(2) + + ret.should == ['he', [family_name, port, @hostname, ip_address]] + end + + it 'allows specifying of flags when receiving data' do + @client.write('hello') + + @server.recvfrom(2, Socket::MSG_PEEK)[0].should == 'he' + + @server.recvfrom(2)[0].should == 'he' + end + + describe 'using reverse lookups' do + before do + @server.do_not_reverse_lookup = false + + @hostname = Socket.getaddrinfo(ip_address, nil, 0, 0, 0, 0, true)[0][2] + end + + it 'includes the hostname in the address Array' do + @client.write('hello') + + port = @client.local_address.ip_port + ret = @server.recvfrom(2) + + ret.should == ['he', [family_name, port, @hostname, ip_address]] + end + end + end +end diff --git a/spec/ruby/library/socket/option/bool_spec.rb b/spec/ruby/library/socket/option/bool_spec.rb new file mode 100644 index 0000000000..144a78043d --- /dev/null +++ b/spec/ruby/library/socket/option/bool_spec.rb @@ -0,0 +1,27 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Option.bool" do + it "creates a new Socket::Option" do + so = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + so.should be_an_instance_of(Socket::Option) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + so.data.should == [1].pack('i') + end +end + +describe "Socket::Option#bool" do + it "returns boolean value" do + Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true).bool.should == true + Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, false).bool.should == false + end + + platform_is_not :windows do + it 'raises TypeError when called on a non boolean option' do + opt = Socket::Option.linger(1, 4) + -> { opt.bool }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/option/initialize_spec.rb b/spec/ruby/library/socket/option/initialize_spec.rb new file mode 100644 index 0000000000..8071ad7ef0 --- /dev/null +++ b/spec/ruby/library/socket/option/initialize_spec.rb @@ -0,0 +1,83 @@ +require_relative '../spec_helper' + +describe 'Socket::Option#initialize' do + before do + @bool = [0].pack('i') + end + + describe 'using Integers' do + it 'returns a Socket::Option' do + opt = Socket::Option + .new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, @bool) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_KEEPALIVE + opt.data.should == @bool + end + end + + describe 'using Symbols' do + it 'returns a Socket::Option' do + opt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, @bool) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_KEEPALIVE + opt.data.should == @bool + end + + it 'raises when using an invalid address family' do + -> { + Socket::Option.new(:INET2, :SOCKET, :KEEPALIVE, @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid level' do + -> { + Socket::Option.new(:INET, :CATS, :KEEPALIVE, @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid option name' do + -> { + Socket::Option.new(:INET, :SOCKET, :CATS, @bool) + }.should raise_error(SocketError) + end + end + + describe 'using Strings' do + it 'returns a Socket::Option' do + opt = Socket::Option.new('INET', 'SOCKET', 'KEEPALIVE', @bool) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_KEEPALIVE + opt.data.should == @bool + end + + it 'raises when using an invalid address family' do + -> { + Socket::Option.new('INET2', 'SOCKET', 'KEEPALIVE', @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid level' do + -> { + Socket::Option.new('INET', 'CATS', 'KEEPALIVE', @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid option name' do + -> { + Socket::Option.new('INET', 'SOCKET', 'CATS', @bool) + }.should raise_error(SocketError) + end + end +end diff --git a/spec/ruby/library/socket/option/inspect_spec.rb b/spec/ruby/library/socket/option/inspect_spec.rb new file mode 100644 index 0000000000..ebea940d2f --- /dev/null +++ b/spec/ruby/library/socket/option/inspect_spec.rb @@ -0,0 +1,19 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + + +describe 'Socket::Option#inspect' do + it 'correctly returns SO_LINGER value' do + value = Socket::Option.linger(nil, 0).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER off 0sec>' + + value = Socket::Option.linger(false, 30).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER off 30sec>' + + value = Socket::Option.linger(true, 0).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER on 0sec>' + + value = Socket::Option.linger(true, 30).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER on 30sec>' + end +end diff --git a/spec/ruby/library/socket/option/int_spec.rb b/spec/ruby/library/socket/option/int_spec.rb new file mode 100644 index 0000000000..8c69ef6cbd --- /dev/null +++ b/spec/ruby/library/socket/option/int_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Option.int" do + it "creates a new Socket::Option" do + so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 5) + so.should be_an_instance_of(Socket::Option) + so.family.should == Socket::Constants::AF_INET + so.level.should == Socket::Constants::SOL_SOCKET + so.optname.should == Socket::Constants::SO_KEEPALIVE + so.data.should == [5].pack('i') + end + + it 'returns a Socket::Option' do + opt = Socket::Option.int(:INET, :IP, :TTL, 4) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::IPPROTO_IP + opt.optname.should == Socket::IP_TTL + opt.data.should == [4].pack('i') + end +end + +describe "Socket::Option#int" do + it "returns int value" do + so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 17) + so.int.should == 17 + + so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 32765) + so.int.should == 32765 + + Socket::Option.int(:INET, :IP, :TTL, 4).int.should == 4 + end + + platform_is_not :windows do + it 'raises TypeError when called on a non integer option' do + opt = Socket::Option.linger(1, 4) + -> { opt.int }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/option/linger_spec.rb b/spec/ruby/library/socket/option/linger_spec.rb new file mode 100644 index 0000000000..ee987db85b --- /dev/null +++ b/spec/ruby/library/socket/option/linger_spec.rb @@ -0,0 +1,76 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +option_pack = 'i*' +platform_is :windows do + option_pack = 's*' +end + +describe "Socket::Option.linger" do + it "creates a new Socket::Option for SO_LINGER" do + so = Socket::Option.linger(1, 10) + so.should be_an_instance_of(Socket::Option) + + so.family.should == Socket::Constants::AF_UNSPEC + so.level.should == Socket::Constants::SOL_SOCKET + so.optname.should == Socket::Constants::SO_LINGER + + so.data.should == [1, 10].pack(option_pack) + end + + it "accepts boolean as onoff argument" do + so = Socket::Option.linger(false, 0) + so.data.should == [0, 0].pack(option_pack) + + so = Socket::Option.linger(true, 1) + so.data.should == [1, 1].pack(option_pack) + end +end + +describe "Socket::Option#linger" do + it "returns linger option" do + so = Socket::Option.linger(0, 5) + ary = so.linger + ary[0].should be_false + ary[1].should == 5 + + so = Socket::Option.linger(false, 4) + ary = so.linger + ary[0].should be_false + ary[1].should == 4 + + so = Socket::Option.linger(1, 10) + ary = so.linger + ary[0].should be_true + ary[1].should == 10 + + so = Socket::Option.linger(true, 9) + ary = so.linger + ary[0].should be_true + ary[1].should == 9 + end + + it "raises TypeError if not a SO_LINGER" do + so = Socket::Option.int(:AF_UNSPEC, :SOL_SOCKET, :KEEPALIVE, 1) + -> { so.linger }.should raise_error(TypeError) + end + + it 'raises TypeError when called on a non SOL_SOCKET/SO_LINGER option' do + opt = Socket::Option.int(:INET, :IP, :TTL, 4) + + -> { opt.linger }.should raise_error(TypeError) + end + + platform_is_not :windows do + it "raises TypeError if option has not good size" do + so = Socket::Option.int(:AF_UNSPEC, :SOL_SOCKET, :LINGER, 1) + -> { so.linger }.should raise_error(TypeError) + end + end + + it 'raises TypeError when called on a non linger option' do + opt = Socket::Option.new(:INET, :SOCKET, :LINGER, '') + + -> { opt.linger }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/socket/option/new_spec.rb b/spec/ruby/library/socket/option/new_spec.rb new file mode 100644 index 0000000000..a9e6f09097 --- /dev/null +++ b/spec/ruby/library/socket/option/new_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Option.new" do + it "should accept integers" do + so = Socket::Option.new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [0].pack('i')) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + end + + it "should accept symbols" do + so = Socket::Option.new(:AF_INET, :SOL_SOCKET, :SO_KEEPALIVE, [0].pack('i')) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + + so = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [0].pack('i')) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + end + + it "should raise error on unknown family" do + -> { Socket::Option.new(:INET4, :SOCKET, :KEEPALIVE, [0].pack('i')) }.should raise_error(SocketError) + end + + it "should raise error on unknown level" do + -> { Socket::Option.new(:INET, :ROCKET, :KEEPALIVE, [0].pack('i')) }.should raise_error(SocketError) + end + + it "should raise error on unknown option name" do + -> { Socket::Option.new(:INET, :SOCKET, :ALIVE, [0].pack('i')) }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/shared/address.rb b/spec/ruby/library/socket/shared/address.rb new file mode 100644 index 0000000000..49ba17c400 --- /dev/null +++ b/spec/ruby/library/socket/shared/address.rb @@ -0,0 +1,259 @@ +require_relative '../fixtures/classes' + +describe :socket_local_remote_address, shared: true do + describe 'using TCPSocket' do + before :each do + @s = TCPServer.new('127.0.0.1', 0) + @a = TCPSocket.new('127.0.0.1', @s.addr[1]) + @b = @s.accept + @addr = @object.call(@a) + end + + after :each do + [@b, @a, @s].each(&:close) + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == Socket::AF_INET + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.1' + end + + it 'uses the correct port' do + if @method == :local_address + @addr.ip_port.should != @s.addr[1] + else + @addr.ip_port.should == @s.addr[1] + end + end + + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using IPv6' do + before :each do + @s = TCPServer.new('::1', 0) + @a = TCPSocket.new('::1', @s.addr[1]) + @b = @s.accept + @addr = @object.call(@a) + end + + after :each do + [@b, @a, @s].each(&:close) + end + + it 'uses AF_INET6 as the address family' do + @addr.afamily.should == Socket::AF_INET6 + end + + it 'uses PF_INET6 as the protocol family' do + @addr.pfamily.should == Socket::PF_INET6 + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '::1' + end + + it 'uses the correct port' do + if @method == :local_address + @addr.ip_port.should != @s.addr[1] + else + @addr.ip_port.should == @s.addr[1] + end + end + + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end + end + + describe 'using UNIXSocket' do + before :each do + @path = SocketSpecs.socket_path + @s = UNIXServer.new(@path) + @a = UNIXSocket.new(@path) + @b = @s.accept + @addr = @object.call(@a) + end + + after :each do + [@b, @a, @s].each(&:close) + rm_r(@path) + end + + it 'uses AF_UNIX as the address family' do + @addr.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @addr.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct socket path' do + if @method == :local_address + @addr.unix_path.should == "" + else + @addr.unix_path.should == @path + end + end + + platform_is_not :windows do + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end + + describe 'using UDPSocket' do + before :each do + @s = UDPSocket.new + @s.bind("127.0.0.1", 0) + @a = UDPSocket.new + @a.connect("127.0.0.1", @s.addr[1]) + @addr = @object.call(@a) + end + + after :each do + [@a, @s].each(&:close) + end + + it 'uses the correct address family' do + @addr.afamily.should == Socket::AF_INET + end + + it 'uses the correct protocol family' do + @addr.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_DGRAM as the socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.1' + end + + it 'uses the correct port' do + if @method == :local_address + @addr.ip_port.should != @s.addr[1] + else + @addr.ip_port.should == @s.addr[1] + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the peer' do + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end +end diff --git a/spec/ruby/library/socket/shared/pack_sockaddr.rb b/spec/ruby/library/socket/shared/pack_sockaddr.rb new file mode 100644 index 0000000000..4bfcf4edb9 --- /dev/null +++ b/spec/ruby/library/socket/shared/pack_sockaddr.rb @@ -0,0 +1,92 @@ +# coding: utf-8 +describe :socket_pack_sockaddr_in, shared: true do + it "packs and unpacks" do + sockaddr_in = Socket.public_send(@method, 0, nil) + port, addr = Socket.unpack_sockaddr_in(sockaddr_in) + ["127.0.0.1", "::1"].include?(addr).should == true + port.should == 0 + + sockaddr_in = Socket.public_send(@method, 0, '') + Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '0.0.0.0'] + + sockaddr_in = Socket.public_send(@method, 80, '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, '80', '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, nil, '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, 80, Socket::INADDR_ANY) + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '0.0.0.0'] + end + + it 'resolves the service name to a port' do + sockaddr_in = Socket.public_send(@method, 'http', '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1'] + end + + describe 'using an IPv4 address' do + it 'returns a String of 16 bytes' do + str = Socket.public_send(@method, 80, '127.0.0.1') + + str.should be_an_instance_of(String) + str.bytesize.should == 16 + end + end + + describe 'using an IPv6 address' do + it 'returns a String of 28 bytes' do + str = Socket.public_send(@method, 80, '::1') + + str.should be_an_instance_of(String) + str.bytesize.should == 28 + end + end +end + +describe :socket_pack_sockaddr_un, shared: true do + it 'should be idempotent' do + bytes = Socket.public_send(@method, '/tmp/foo').bytes + bytes[2..9].should == [47, 116, 109, 112, 47, 102, 111, 111] + bytes[10..-1].all?(&:zero?).should == true + end + + it "packs and unpacks" do + sockaddr_un = Socket.public_send(@method, '/tmp/s') + Socket.unpack_sockaddr_un(sockaddr_un).should == '/tmp/s' + end + + it "handles correctly paths with multibyte chars" do + sockaddr_un = Socket.public_send(@method, '/home/вася/sock') + path = Socket.unpack_sockaddr_un(sockaddr_un).encode('UTF-8', 'UTF-8') + path.should == '/home/вася/sock' + end + + platform_is :linux do + it 'returns a String of 110 bytes' do + str = Socket.public_send(@method, '/tmp/test.sock') + + str.should be_an_instance_of(String) + str.bytesize.should == 110 + end + end + + platform_is :bsd do + it 'returns a String of 106 bytes' do + str = Socket.public_send(@method, '/tmp/test.sock') + + str.should be_an_instance_of(String) + str.bytesize.should == 106 + end + end + + platform_is_not :aix do + it "raises ArgumentError for paths that are too long" do + # AIX doesn't raise error + long_path = 'a' * 110 + -> { Socket.public_send(@method, long_path) }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/socket/shared/partially_closable_sockets.rb b/spec/ruby/library/socket/shared/partially_closable_sockets.rb new file mode 100644 index 0000000000..b1c2ebabe1 --- /dev/null +++ b/spec/ruby/library/socket/shared/partially_closable_sockets.rb @@ -0,0 +1,13 @@ +describe :partially_closable_sockets, shared: true do + it "if the write end is closed then the other side can read past EOF without blocking" do + @s1.write("foo") + @s1.close_write + @s2.read("foo".size + 1).should == "foo" + end + + it "closing the write end ensures that the other side can read until EOF" do + @s1.write("hello world") + @s1.close_write + @s2.read.should == "hello world" + end +end diff --git a/spec/ruby/library/socket/shared/socketpair.rb b/spec/ruby/library/socket/shared/socketpair.rb new file mode 100644 index 0000000000..25146cfff6 --- /dev/null +++ b/spec/ruby/library/socket/shared/socketpair.rb @@ -0,0 +1,138 @@ +describe :socket_socketpair, shared: true do + platform_is_not :windows do + it "ensures the returned sockets are connected" do + s1, s2 = Socket.public_send(@method, Socket::AF_UNIX, 1, 0) + s1.puts("test") + s2.gets.should == "test\n" + s1.close + s2.close + end + + it "responses with array of two sockets" do + begin + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + ensure + s1.close + s2.close + end + end + + describe 'using an Integer as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.public_send(@method, Socket::AF_UNIX, Socket::SOCK_STREAM) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + end + + describe 'using a Symbol as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'raises SocketError for an unknown address family' do + -> { Socket.public_send(@method, :CATS, :STREAM) }.should raise_error(SocketError) + end + + it 'raises SocketError for an unknown socket type' do + -> { Socket.public_send(@method, :UNIX, :CATS) }.should raise_error(SocketError) + end + end + + describe 'using a String as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.public_send(@method, 'UNIX', 'STREAM') + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'raises SocketError for an unknown address family' do + -> { Socket.public_send(@method, 'CATS', 'STREAM') }.should raise_error(SocketError) + end + + it 'raises SocketError for an unknown socket type' do + -> { Socket.public_send(@method, 'UNIX', 'CATS') }.should raise_error(SocketError) + end + end + + describe 'using an object that responds to #to_str as the 1st and 2nd argument' do + it 'returns two Socket objects' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('UNIX') + type.stub!(:to_str).and_return('STREAM') + + s1, s2 = Socket.public_send(@method, family, type) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'raises TypeError when #to_str does not return a String' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return(Socket::AF_UNIX) + type.stub!(:to_str).and_return(Socket::SOCK_STREAM) + + -> { Socket.public_send(@method, family, type) }.should raise_error(TypeError) + end + + it 'raises SocketError for an unknown address family' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('CATS') + type.stub!(:to_str).and_return('STREAM') + + -> { Socket.public_send(@method, family, type) }.should raise_error(SocketError) + end + + it 'raises SocketError for an unknown socket type' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('UNIX') + type.stub!(:to_str).and_return('CATS') + + -> { Socket.public_send(@method, family, type) }.should raise_error(SocketError) + end + end + + it 'accepts a custom protocol as an Integer as the 3rd argument' do + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM, Socket::IPPROTO_IP) + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'connects the returned Socket objects' do + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM) + begin + s1.write('hello') + s2.recv(5).should == 'hello' + ensure + s1.close + s2.close + end + end + end +end diff --git a/spec/ruby/library/socket/socket/accept_loop_spec.rb b/spec/ruby/library/socket/socket/accept_loop_spec.rb new file mode 100644 index 0000000000..78e8c3fa4a --- /dev/null +++ b/spec/ruby/library/socket/socket/accept_loop_spec.rb @@ -0,0 +1,84 @@ +require_relative '../spec_helper' + +describe 'Socket.accept_loop' do + before do + @server = Socket.new(:INET, :STREAM) + @client = Socket.new(:INET, :STREAM) + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + @server.listen(1) + end + + after do + @client.close + @server.close + end + + describe 'using an Array of Sockets' do + describe 'without any available connections' do + # FIXME windows randomly hangs here forever + # https://ci.appveyor.com/project/ruby/ruby/builds/20817932/job/dor2ipny7ru4erpa + platform_is_not :windows do + it 'blocks the caller' do + -> { Socket.accept_loop([@server]) }.should block_caller + end + end + end + + describe 'with available connections' do + before do + @client.connect(@server.getsockname) + end + + it 'yields a Socket and an Addrinfo' do + conn = nil + addr = nil + + Socket.accept_loop([@server]) do |connection, address| + conn = connection + addr = address + break + end + + begin + conn.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + ensure + conn.close + end + end + end + end + + describe 'using separate Socket arguments' do + describe 'without any available connections' do + it 'blocks the caller' do + -> { Socket.accept_loop(@server) }.should block_caller + end + end + + describe 'with available connections' do + before do + @client.connect(@server.getsockname) + end + + it 'yields a Socket and an Addrinfo' do + conn = nil + addr = nil + + Socket.accept_loop(@server) do |connection, address| + conn = connection + addr = address + break + end + + begin + conn.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + ensure + conn.close + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/accept_nonblock_spec.rb b/spec/ruby/library/socket/socket/accept_nonblock_spec.rb new file mode 100644 index 0000000000..011622988c --- /dev/null +++ b/spec/ruby/library/socket/socket/accept_nonblock_spec.rb @@ -0,0 +1,141 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#accept_nonblock" do + before :each do + @hostname = "127.0.0.1" + @addr = Socket.sockaddr_in(0, @hostname) + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + @socket.bind(@addr) + @socket.listen(1) + end + + after :each do + @socket.close + end + + it "raises IO::WaitReadable if the connection is not accepted yet" do + -> { + @socket.accept_nonblock + }.should raise_error(IO::WaitReadable) { |e| + platform_is_not :windows do + e.should be_kind_of(Errno::EAGAIN) + end + platform_is :windows do + e.should be_kind_of(Errno::EWOULDBLOCK) + end + } + end + + it 'returns :wait_readable in exceptionless mode' do + @socket.accept_nonblock(exception: false).should == :wait_readable + end +end + +describe 'Socket#accept_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM, 0) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @server.close unless @server.closed? + end + + describe 'using an unbound socket' do + it 'raises Errno::EINVAL' do + -> { @server.accept_nonblock }.should raise_error(Errno::EINVAL) + -> { @server.accept_nonblock(exception: false) }.should raise_error(Errno::EINVAL) + end + end + + describe "using a bound socket that's not listening" do + before do + @server.bind(@sockaddr) + end + + it 'raises Errno::EINVAL' do + -> { @server.accept_nonblock }.should raise_error(Errno::EINVAL) + -> { @server.accept_nonblock(exception: false) }.should raise_error(Errno::EINVAL) + end + end + + describe 'using a closed socket' do + it 'raises IOError' do + @server.close + + -> { @server.accept_nonblock }.should raise_error(IOError) + -> { @server.accept_nonblock(exception: false) }.should raise_error(IOError) + end + end + + describe "using a bound socket that's listening" do + before do + @server.bind(@sockaddr) + @server.listen(1) + end + + describe 'without a connected client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + end + + platform_is_not :windows do + describe 'with a connected client' do + before do + addr = Socket.sockaddr_in(@server.local_address.ip_port, ip_address) + @client = Socket.new(family, :STREAM, 0) + + @client.connect(addr) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns an Array containing a Socket and an Addrinfo' do + IO.select([@server]) + @socket, addrinfo = @server.accept_nonblock + + @socket.should be_an_instance_of(Socket) + addrinfo.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + IO.select([@server]) + @socket, @addr = @server.accept_nonblock + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the same IP address as the client Socket' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the same port as the client Socket' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/accept_spec.rb b/spec/ruby/library/socket/socket/accept_spec.rb new file mode 100644 index 0000000000..417f996c55 --- /dev/null +++ b/spec/ruby/library/socket/socket/accept_spec.rb @@ -0,0 +1,121 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#accept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM, 0) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @server.close unless @server.closed? + end + + platform_is :linux do # hangs on other platforms + describe 'using an unbound socket' do + it 'raises Errno::EINVAL' do + -> { @server.accept }.should raise_error(Errno::EINVAL) + end + end + + describe "using a bound socket that's not listening" do + before do + @server.bind(@sockaddr) + end + + it 'raises Errno::EINVAL' do + -> { @server.accept }.should raise_error(Errno::EINVAL) + end + end + end + + describe 'using a closed socket' do + it 'raises IOError' do + @server.close + + -> { @server.accept }.should raise_error(IOError) + end + end + + describe "using a bound socket that's listening" do + before do + @server.bind(@sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, ip_address) + end + + describe 'without a connected client' do + it 'blocks the caller until a connection is available' do + client = Socket.new(family, :STREAM, 0) + thread = Thread.new do + @server.accept + end + + client.connect(@server_addr) + + value = thread.value + begin + value.should be_an_instance_of(Array) + ensure + client.close + value[0].close + end + end + end + + describe 'with a connected client' do + before do + addr = Socket.sockaddr_in(@server.local_address.ip_port, ip_address) + @client = Socket.new(family, :STREAM, 0) + + @client.connect(addr) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns an Array containing a Socket and an Addrinfo' do + @socket, addrinfo = @server.accept + + @socket.should be_an_instance_of(Socket) + addrinfo.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + @socket, @addr = @server.accept + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the same IP address as the client Socket' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the same port as the client Socket' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/bind_spec.rb b/spec/ruby/library/socket/socket/bind_spec.rb new file mode 100644 index 0000000000..e76336eafa --- /dev/null +++ b/spec/ruby/library/socket/socket/bind_spec.rb @@ -0,0 +1,150 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#bind on SOCK_DGRAM socket" do + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) + @sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1") + end + + after :each do + @sock.closed?.should be_false + @sock.close + end + + it "binds to a port" do + -> { @sock.bind(@sockaddr) }.should_not raise_error + end + + it "returns 0 if successful" do + @sock.bind(@sockaddr).should == 0 + end + + it "raises Errno::EINVAL when already bound" do + @sock.bind(@sockaddr) + + -> { @sock.bind(@sockaddr) }.should raise_error(Errno::EINVAL) + end + + it "raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available from the local machine" do + sockaddr1 = Socket.pack_sockaddr_in(0, "4.3.2.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + platform_is_not :windows, :cygwin do + as_user do + break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil + it "raises Errno::EACCES when the current user does not have permission to bind" do + sockaddr1 = Socket.pack_sockaddr_in(1, "127.0.0.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EACCES) + end + end + end +end + +describe "Socket#bind on SOCK_STREAM socket" do + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + @sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1") + end + + after :each do + @sock.closed?.should be_false + @sock.close + end + + it "binds to a port" do + -> { @sock.bind(@sockaddr) }.should_not raise_error + end + + it "returns 0 if successful" do + @sock.bind(@sockaddr).should == 0 + end + + it "raises Errno::EINVAL when already bound" do + @sock.bind(@sockaddr) + + -> { @sock.bind(@sockaddr) }.should raise_error(Errno::EINVAL) + end + + it "raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available from the local machine" do + sockaddr1 = Socket.pack_sockaddr_in(0, "4.3.2.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + platform_is_not :windows, :cygwin do + as_user do + break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil + it "raises Errno::EACCES when the current user does not have permission to bind" do + sockaddr1 = Socket.pack_sockaddr_in(1, "127.0.0.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EACCES) + end + end + end +end + +describe 'Socket#bind' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a packed socket address' do + before do + @socket = Socket.new(family, :DGRAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @socket.close + end + + it 'returns 0 when successfully bound' do + @socket.bind(@sockaddr).should == 0 + end + + it 'raises Errno::EINVAL when binding to an already bound port' do + @socket.bind(@sockaddr) + + -> { @socket.bind(@sockaddr) }.should raise_error(Errno::EINVAL) + end + + it 'raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available' do + ip = family == Socket::AF_INET ? '4.3.2.1' : '::2' + sockaddr1 = Socket.sockaddr_in(0, ip) + + -> { @socket.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + platform_is_not :windows do + as_user do + break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil + + it 'raises Errno::EACCES when the user is not allowed to bind to the port' do + sockaddr1 = Socket.pack_sockaddr_in(1, ip_address) + + -> { @socket.bind(sockaddr1) }.should raise_error(Errno::EACCES) + end + end + end + end + + describe 'using an Addrinfo' do + before do + @addr = Addrinfo.udp(ip_address, 0) + @socket = Socket.new(@addr.afamily, @addr.socktype) + end + + after do + @socket.close + end + + it 'binds to an Addrinfo' do + @socket.bind(@addr).should == 0 + @socket.local_address.should be_an_instance_of(Addrinfo) + end + + it 'uses a new Addrinfo for the local address' do + @socket.bind(@addr) + @socket.local_address.should_not == @addr + end + end + end +end diff --git a/spec/ruby/library/socket/socket/connect_nonblock_spec.rb b/spec/ruby/library/socket/socket/connect_nonblock_spec.rb new file mode 100644 index 0000000000..359b8719fb --- /dev/null +++ b/spec/ruby/library/socket/socket/connect_nonblock_spec.rb @@ -0,0 +1,147 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#connect_nonblock" do + before :each do + @hostname = "127.0.0.1" + @server = TCPServer.new(@hostname, 0) # started, but no accept + @addr = Socket.sockaddr_in(@server.addr[1], @hostname) + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + @thread = nil + end + + after :each do + @socket.close + @server.close + @thread.join if @thread + end + + it "connects the socket to the remote side" do + port = nil + accept = false + @thread = Thread.new do + server = TCPServer.new(@hostname, 0) + port = server.addr[1] + Thread.pass until accept + conn = server.accept + conn << "hello!" + conn.close + server.close + end + + Thread.pass until port + + addr = Socket.sockaddr_in(port, @hostname) + begin + @socket.connect_nonblock(addr) + rescue Errno::EINPROGRESS + end + + accept = true + IO.select nil, [@socket] + + begin + @socket.connect_nonblock(addr) + rescue Errno::EISCONN + # Not all OS's use this errno, so we trap and ignore it + end + + @socket.read(6).should == "hello!" + end + + platform_is_not :freebsd, :aix do + it "raises Errno::EINPROGRESS when the connect would block" do + -> do + @socket.connect_nonblock(@addr) + end.should raise_error(Errno::EINPROGRESS) + end + + it "raises Errno::EINPROGRESS with IO::WaitWritable mixed in when the connect would block" do + -> do + @socket.connect_nonblock(@addr) + end.should raise_error(IO::WaitWritable) + end + + it "returns :wait_writable in exceptionless mode when the connect would block" do + @socket.connect_nonblock(@addr, exception: false).should == :wait_writable + end + end +end + +describe 'Socket#connect_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a DGRAM socket' do + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + + @server.bind(@sockaddr) + end + + after do + @client.close + @server.close + end + + it 'returns 0 when successfully connected using a String' do + @client.connect_nonblock(@server.getsockname).should == 0 + end + + it 'returns 0 when successfully connected using an Addrinfo' do + @client.connect_nonblock(@server.connect_address).should == 0 + end + + it 'raises TypeError when passed an Integer' do + -> { @client.connect_nonblock(666) }.should raise_error(TypeError) + end + end + + describe 'using a STREAM socket' do + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + it 'raises Errno::EISCONN when already connected' do + @server.listen(1) + @client.connect(@server.connect_address).should == 0 + + -> { + @client.connect_nonblock(@server.connect_address) + + # A second call needed if non-blocking sockets become default + # XXX honestly I don't expect any real code to care about this spec + # as it's too implementation-dependent and checking for connect() + # errors is futile anyways because of TOCTOU + @client.connect_nonblock(@server.connect_address) + }.should raise_error(Errno::EISCONN) + end + + it 'returns 0 when already connected in exceptionless mode' do + @server.listen(1) + @client.connect(@server.connect_address).should == 0 + + @client.connect_nonblock(@server.connect_address, exception: false).should == 0 + end + end + + platform_is_not :freebsd do + it 'raises IO:EINPROGRESSWaitWritable when the connection would block' do + @server.bind(@sockaddr) + + -> { + @client.connect_nonblock(@server.connect_address) + }.should raise_error(IO::EINPROGRESSWaitWritable) + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/connect_spec.rb b/spec/ruby/library/socket/socket/connect_spec.rb new file mode 100644 index 0000000000..130379ce2b --- /dev/null +++ b/spec/ruby/library/socket/socket/connect_spec.rb @@ -0,0 +1,78 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#connect' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + it 'returns 0 when connected successfully using a String' do + @server.listen(1) + + @client.connect(@server.getsockname).should == 0 + end + + it 'returns 0 when connected successfully using an Addrinfo' do + @server.listen(1) + + @client.connect(@server.connect_address).should == 0 + end + + it 'raises Errno::EISCONN when already connected' do + @server.listen(1) + + @client.connect(@server.getsockname).should == 0 + + -> { + @client.connect(@server.getsockname) + + # A second call needed if non-blocking sockets become default + # XXX honestly I don't expect any real code to care about this spec + # as it's too implementation-dependent and checking for connect() + # errors is futile anyways because of TOCTOU + @client.connect(@server.getsockname) + }.should raise_error(Errno::EISCONN) + end + + platform_is_not :darwin do + it 'raises Errno::ECONNREFUSED or Errno::ETIMEDOUT when the connection failed' do + begin + @client.connect(@server.getsockname) + rescue => e + [Errno::ECONNREFUSED, Errno::ETIMEDOUT].include?(e.class).should == true + end + end + end + end + + ruby_version_is "3.4" do + it "fails with timeout" do + # TEST-NET-1 IP address are reserved for documentation and example purposes. + address = Socket.pack_sockaddr_in(1, "192.0.2.1") + + client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) + client.timeout = 0 + + -> { + begin + client.connect(address) + rescue Errno::ECONNREFUSED + skip "Outgoing packets may be filtered" + rescue Errno::ENETUNREACH + skip "Off line" + end + }.should raise_error(IO::TimeoutError) + ensure + client.close + end + end +end diff --git a/spec/ruby/library/socket/socket/for_fd_spec.rb b/spec/ruby/library/socket/socket/for_fd_spec.rb new file mode 100644 index 0000000000..e89228d436 --- /dev/null +++ b/spec/ruby/library/socket/socket/for_fd_spec.rb @@ -0,0 +1,30 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.for_fd" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.open("127.0.0.1", @port) + end + + after :each do + @socket.close + @client.close + @host.close + @server.close + end + + it "creates a new Socket that aliases the existing Socket's file descriptor" do + @socket = Socket.for_fd(@client.fileno) + @socket.autoclose = false + @socket.fileno.should == @client.fileno + + @socket.send("foo", 0) + @client.send("bar", 0) + + @host = @server.accept + @host.read(3).should == "foo" + @host.read(3).should == "bar" + end +end diff --git a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb new file mode 100644 index 0000000000..6576af52ee --- /dev/null +++ b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb @@ -0,0 +1,391 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.getaddrinfo" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = true + end + + after :each do + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + platform_is_not :windows do + it "gets the address information" do + expected = [] + # The check for AP_INET6's class is needed because ipaddr.rb adds + # fake AP_INET6 even in case when IPv6 is not really supported. + # Without such check, this test might fail when ipaddr was required + # by some other specs. + if (Socket.constants.include? 'AF_INET6') && + (Socket::AF_INET6.class != Object) then + expected.concat [ + ['AF_INET6', 9, SocketSpecs.hostname, '::1', Socket::AF_INET6, + Socket::SOCK_DGRAM, Socket::IPPROTO_UDP], + ['AF_INET6', 9, SocketSpecs.hostname, '::1', Socket::AF_INET6, + Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ['AF_INET6', 9, SocketSpecs.hostname, 'fe80::1%lo0', Socket::AF_INET6, + Socket::SOCK_DGRAM, Socket::IPPROTO_UDP], + ['AF_INET6', 9, SocketSpecs.hostname, 'fe80::1%lo0', Socket::AF_INET6, + Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ] + end + + expected.concat [ + ['AF_INET', 9, SocketSpecs.hostname, '127.0.0.1', Socket::AF_INET, + Socket::SOCK_DGRAM, Socket::IPPROTO_UDP], + ['AF_INET', 9, SocketSpecs.hostname, '127.0.0.1', Socket::AF_INET, + Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ] + + addrinfo = Socket.getaddrinfo SocketSpecs.hostname, 'discard' + addrinfo.each do |a| + case a.last + when Socket::IPPROTO_UDP, Socket::IPPROTO_TCP + expected.should include(a) + else + # don't check this. It's some weird protocol we don't know about + # so we can't spec it. + end + end + end + + # #getaddrinfo will return a INADDR_ANY address (0.0.0.0 or "::") + # if it's a passive socket. In the case of non-passive + # sockets (AI_PASSIVE not set) it should return the loopback + # address (127.0.0.1 or "::1"). + + it "accepts empty addresses for IPv4 passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + Socket::AI_PASSIVE) + + expected = [["AF_INET", 9, "0.0.0.0", "0.0.0.0", Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]] + res.should == expected + end + + it "accepts empty addresses for IPv4 non-passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + 0) + + expected = [["AF_INET", 9, "127.0.0.1", "127.0.0.1", Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]] + res.should == expected + end + + + it "accepts empty addresses for IPv6 passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET6, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + Socket::AI_PASSIVE) + + expected = [ + ["AF_INET6", 9, "::", "::", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ["AF_INET6", 9, "0:0:0:0:0:0:0:0", "0:0:0:0:0:0:0:0", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP] + ] + res.each { |a| expected.should include(a) } + end + + it "accepts empty addresses for IPv6 non-passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET6, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + 0) + + expected = [ + ["AF_INET6", 9, "::1", "::1", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ["AF_INET6", 9, "0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:1", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP] + ] + res.each { |a| expected.should include(a) } + end + + ruby_version_is ""..."3.3" do + it "raises SocketError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(SocketError) + end + end + + ruby_version_is "3.3" do + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(Socket::ResolutionError) { |e| + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) + } + end + end + end +end + +describe 'Socket.getaddrinfo' do + describe 'without global reverse lookups' do + it 'returns an Array' do + Socket.getaddrinfo(nil, 'ftp').should be_an_instance_of(Array) + end + + it 'accepts an Integer as the address family' do + array = Socket.getaddrinfo(nil, 'ftp', Socket::AF_INET)[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an Integer as the address family using IPv6' do + array = Socket.getaddrinfo(nil, 'ftp', Socket::AF_INET6)[0] + + array[0].should == 'AF_INET6' + array[1].should == 21 + array[2].should == '::1' + array[3].should == '::1' + array[4].should == Socket::AF_INET6 + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a Symbol as the address family' do + array = Socket.getaddrinfo(nil, 'ftp', :INET)[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a Symbol as the address family using IPv6' do + array = Socket.getaddrinfo(nil, 'ftp', :INET6)[0] + + array[0].should == 'AF_INET6' + array[1].should == 21 + array[2].should == '::1' + array[3].should == '::1' + array[4].should == Socket::AF_INET6 + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a String as the address family' do + array = Socket.getaddrinfo(nil, 'ftp', 'INET')[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a String as the address family using IPv6' do + array = Socket.getaddrinfo(nil, 'ftp', 'INET6')[0] + + array[0].should == 'AF_INET6' + array[1].should == 21 + array[2].should == '::1' + array[3].should == '::1' + array[4].should == Socket::AF_INET6 + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an object responding to #to_str as the host' do + dummy = mock(:dummy) + + dummy.stub!(:to_str).and_return('127.0.0.1') + + array = Socket.getaddrinfo(dummy, 'ftp')[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an object responding to #to_str as the address family' do + dummy = mock(:dummy) + + dummy.stub!(:to_str).and_return('INET') + + array = Socket.getaddrinfo(nil, 'ftp', dummy)[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an Integer as the socket type' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, Socket::SOCK_STREAM)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'accepts a Symbol as the socket type' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'accepts a String as the socket type' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, 'STREAM')[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'accepts an object responding to #to_str as the socket type' do + dummy = mock(:dummy) + + dummy.stub!(:to_str).and_return('STREAM') + + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, dummy)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + platform_is_not :windows do + it 'accepts an Integer as the protocol family' do + *array, proto = Socket.getaddrinfo(nil, 'discard', :INET, :DGRAM, Socket::IPPROTO_UDP)[0] + array.should == [ + 'AF_INET', + 9, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_DGRAM, + ] + [0, Socket::IPPROTO_UDP].should include(proto) + end + end + + it 'accepts an Integer as the flags' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, Socket::AI_PASSIVE)[0] + array.should == [ + 'AF_INET', + 21, + '0.0.0.0', + '0.0.0.0', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'performs a reverse lookup when the reverse_lookup argument is true' do + addr = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, 0, true)[0] + + addr[0].should == 'AF_INET' + addr[1].should == 21 + + addr[2].should be_an_instance_of(String) + addr[2].should_not == addr[3] + + addr[3].should == '127.0.0.1' + end + + it 'performs a reverse lookup when the reverse_lookup argument is :hostname' do + addr = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, 0, :hostname)[0] + + addr[0].should == 'AF_INET' + addr[1].should == 21 + + addr[2].should be_an_instance_of(String) + addr[2].should_not == addr[3] + + addr[3].should == '127.0.0.1' + end + + it 'performs a reverse lookup when the reverse_lookup argument is :numeric' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, 0, :numeric)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + end + + describe 'with global reverse lookups' do + before do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = false + end + + after do + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it 'returns an address honoring the global lookup option' do + addr = Socket.getaddrinfo(nil, 'ftp', :INET)[0] + + addr[0].should == 'AF_INET' + addr[1].should == 21 + + # We don't have control over this value and there's no way to test this + # without relying on Socket.getaddrinfo()'s own behaviour (meaning this + # test would faily any way of the method was not implemented correctly). + addr[2].should be_an_instance_of(String) + addr[2].should_not == addr[3] + + addr[3].should == '127.0.0.1' + end + end +end diff --git a/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb b/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb new file mode 100644 index 0000000000..5d936046f5 --- /dev/null +++ b/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb @@ -0,0 +1,121 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require 'ipaddr' + +describe 'Socket.gethostbyaddr' do + describe 'using an IPv4 address' do + before do + @addr = IPAddr.new('127.0.0.1').hton + end + + describe 'without an explicit address family' do + it 'returns an Array' do + suppress_warning { Socket.gethostbyaddr(@addr) }.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = suppress_warning { Socket.gethostbyaddr(@addr) } + end + + it 'includes the hostname as the first value' do + @array[0].should == SocketSpecs.hostname_reverse_lookup + end + + it 'includes the aliases as the 2nd value' do + @array[1].should be_an_instance_of(Array) + + @array[1].each do |val| + val.should be_an_instance_of(String) + end + end + + it 'includes the address type as the 3rd value' do + @array[2].should == Socket::AF_INET + end + + it 'includes all address strings as the remaining values' do + @array[3].should == @addr + + @array[4..-1].each do |val| + val.should be_an_instance_of(String) + end + end + end + end + + describe 'with an explicit address family' do + it 'returns an Array when using an Integer as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, Socket::AF_INET) }.should be_an_instance_of(Array) + end + + it 'returns an Array when using a Symbol as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, :INET) }.should be_an_instance_of(Array) + end + + it 'raises SocketError when the address is not supported by the family' do + -> { suppress_warning { Socket.gethostbyaddr(@addr, :INET6) } }.should raise_error(SocketError) + end + end + end + + guard -> { SocketSpecs.ipv6_available? && platform_is_not(:aix) } do + describe 'using an IPv6 address' do + before do + @addr = IPAddr.new('::1').hton + end + + describe 'without an explicit address family' do + it 'returns an Array' do + suppress_warning { Socket.gethostbyaddr(@addr) }.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = suppress_warning { Socket.gethostbyaddr(@addr) } + end + + it 'includes the hostname as the first value' do + @array[0].should == SocketSpecs.hostname_reverse_lookup("::1") + end + + it 'includes the aliases as the 2nd value' do + @array[1].should be_an_instance_of(Array) + + @array[1].each do |val| + val.should be_an_instance_of(String) + end + end + + it 'includes the address type as the 3rd value' do + @array[2].should == Socket::AF_INET6 + end + + it 'includes all address strings as the remaining values' do + @array[3].should be_an_instance_of(String) + + @array[4..-1].each do |val| + val.should be_an_instance_of(String) + end + end + end + end + + describe 'with an explicit address family' do + it 'returns an Array when using an Integer as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, Socket::AF_INET6) }.should be_an_instance_of(Array) + end + + it 'returns an Array when using a Symbol as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, :INET6) }.should be_an_instance_of(Array) + end + + platform_is_not :windows, :wsl do + it 'raises SocketError when the address is not supported by the family' do + -> { suppress_warning { Socket.gethostbyaddr(@addr, :INET) } }.should raise_error(SocketError) + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/gethostbyname_spec.rb b/spec/ruby/library/socket/socket/gethostbyname_spec.rb new file mode 100644 index 0000000000..618ef85387 --- /dev/null +++ b/spec/ruby/library/socket/socket/gethostbyname_spec.rb @@ -0,0 +1,135 @@ +# encoding: binary +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.gethostbyname" do + it "returns broadcast address info for '<broadcast>'" do + addr = suppress_warning { Socket.gethostbyname('<broadcast>') } + addr.should == ["255.255.255.255", [], 2, "\xFF\xFF\xFF\xFF"] + end + + it "returns broadcast address info for '<any>'" do + addr = suppress_warning { Socket.gethostbyname('<any>') } + addr.should == ["0.0.0.0", [], 2, "\x00\x00\x00\x00"] + end +end + +describe 'Socket.gethostbyname' do + it 'returns an Array' do + suppress_warning { Socket.gethostbyname('127.0.0.1') }.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = suppress_warning { Socket.gethostbyname('127.0.0.1') } + end + + it 'includes the hostname as the first value' do + @array[0].should == '127.0.0.1' + end + + it 'includes the aliases as the 2nd value' do + @array[1].should be_an_instance_of(Array) + + @array[1].each do |val| + val.should be_an_instance_of(String) + end + end + + it 'includes the address type as the 3rd value' do + possible = [Socket::AF_INET, Socket::AF_INET6] + + possible.include?(@array[2]).should == true + end + + it 'includes the address strings as the remaining values' do + @array[3].should be_an_instance_of(String) + + @array[4..-1].each do |val| + val.should be_an_instance_of(String) + end + end + end + + describe 'using <broadcast> as the input address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('<broadcast>') } + end + + it 'includes the broadcast address as the first value' do + @addr[0].should == '255.255.255.255' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [255, 255, 255, 255].pack('C4') + end + end + end + + describe 'using <any> as the input address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('<any>') } + end + + it 'includes the wildcard address as the first value' do + @addr[0].should == '0.0.0.0' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [0, 0, 0, 0].pack('C4') + end + end + end + + describe 'using an IPv4 address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('127.0.0.1') } + end + + it 'includes the IP address as the first value' do + @addr[0].should == '127.0.0.1' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [127, 0, 0, 1].pack('C4') + end + end + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using an IPv6 address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('::1') } + end + + it 'includes the IP address as the first value' do + @addr[0].should == '::1' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET6 + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [0, 0, 0, 0, 0, 0, 0, 1].pack('n8') + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/gethostname_spec.rb b/spec/ruby/library/socket/socket/gethostname_spec.rb new file mode 100644 index 0000000000..dfca7cf5cd --- /dev/null +++ b/spec/ruby/library/socket/socket/gethostname_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.gethostname" do + def system_hostname + if platform_is_not :windows + # `uname -n` is the most portable way to get the hostname, as it is a POSIX standard: + `uname -n`.strip + else + # Windows does not have uname, so we use hostname instead: + `hostname`.strip + end + end + + it "returns the host name" do + Socket.gethostname.should == system_hostname + end +end diff --git a/spec/ruby/library/socket/socket/getifaddrs_spec.rb b/spec/ruby/library/socket/socket/getifaddrs_spec.rb new file mode 100644 index 0000000000..839854ea27 --- /dev/null +++ b/spec/ruby/library/socket/socket/getifaddrs_spec.rb @@ -0,0 +1,117 @@ +require_relative '../spec_helper' + +platform_is_not :aix do +describe 'Socket.getifaddrs' do + before do + @ifaddrs = Socket.getifaddrs + end + + it 'returns an Array' do + @ifaddrs.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + it 'should not be empty' do + @ifaddrs.should_not be_empty + end + + it 'contains instances of Socket::Ifaddr' do + @ifaddrs.each do |ifaddr| + ifaddr.should be_an_instance_of(Socket::Ifaddr) + end + end + end + + describe 'each returned Socket::Ifaddr' do + it 'has an interface index' do + @ifaddrs.each do |ifaddr| + ifaddr.ifindex.should be_kind_of(Integer) + end + end + + it 'has an interface name' do + @ifaddrs.each do |ifaddr| + ifaddr.name.should be_an_instance_of(String) + end + end + + it 'has a set of flags' do + @ifaddrs.each do |ifaddr| + ifaddr.flags.should be_kind_of(Integer) + end + end + end + + describe 'the Socket::Ifaddr address' do + before do + @addrs = @ifaddrs.map(&:addr).compact + end + + it 'is an Addrinfo' do + @addrs.all? do |addr| + addr.should be_an_instance_of(Addrinfo) + true + end.should be_true + end + + it 'has an address family' do + @addrs.all? do |addr| + addr.afamily.should be_kind_of(Integer) + addr.afamily.should_not == Socket::AF_UNSPEC + true + end.should be_true + end + end + + platform_is_not :windows do + describe 'the Socket::Ifaddr broadcast address' do + before do + @addrs = @ifaddrs.map(&:broadaddr).compact + end + + it 'is an Addrinfo' do + @addrs.all? do |addr| + addr.should be_an_instance_of(Addrinfo) + true + end.should be_true + end + + it 'has an address family' do + @addrs.all? do |addr| + addr.afamily.should be_kind_of(Integer) + addr.afamily.should_not == Socket::AF_UNSPEC + true + end.should be_true + end + end + + describe 'the Socket::Ifaddr netmask address' do + before do + @addrs = @ifaddrs.map(&:netmask).compact.select(&:ip?) + end + + it 'is an Addrinfo' do + @addrs.all? do |addr| + addr.should be_an_instance_of(Addrinfo) + true + end.should be_true + end + + it 'has an address family' do + @addrs.all? do |addr| + addr.afamily.should be_kind_of(Integer) + addr.afamily.should_not == Socket::AF_UNSPEC + true + end.should be_true + end + + it 'has an IP address' do + @addrs.all? do |addr| + addr.ip_address.should be_an_instance_of(String) + true + end.should be_true + end + end + end +end +end diff --git a/spec/ruby/library/socket/socket/getnameinfo_spec.rb b/spec/ruby/library/socket/socket/getnameinfo_spec.rb new file mode 100644 index 0000000000..af4a10c9c2 --- /dev/null +++ b/spec/ruby/library/socket/socket/getnameinfo_spec.rb @@ -0,0 +1,165 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.getnameinfo" do + before :each do + @reverse_lookup = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = true + end + + after :each do + BasicSocket.do_not_reverse_lookup = @reverse_lookup + end + + it "gets the name information and don't resolve it" do + sockaddr = Socket.sockaddr_in 3333, '127.0.0.1' + name_info = Socket.getnameinfo(sockaddr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV) + name_info.should == ['127.0.0.1', "3333"] + end + + def should_be_valid_dns_name(name) + # http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address + # ftp://ftp.rfc-editor.org/in-notes/rfc3696.txt + # http://domainkeys.sourceforge.net/underscore.html + valid_dns = /^(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\-_]*[a-zA-Z0-9_])\.)*([A-Za-z_]|[A-Za-z_][A-Za-z0-9\-_]*[A-Za-z0-9_])\.?$/ + name.should =~ valid_dns + end + + it "gets the name information and resolve the host" do + sockaddr = Socket.sockaddr_in 3333, '127.0.0.1' + name_info = Socket.getnameinfo(sockaddr, Socket::NI_NUMERICSERV) + should_be_valid_dns_name(name_info[0]) + name_info[1].should == 3333.to_s + end + + it "gets the name information and resolves the service" do + sockaddr = Socket.sockaddr_in 9, '127.0.0.1' + name_info = Socket.getnameinfo(sockaddr) + name_info.size.should == 2 + should_be_valid_dns_name(name_info[0]) + # see http://www.iana.org/assignments/port-numbers + name_info[1].should == 'discard' + end + + it "gets a 3-element array and doesn't resolve hostname" do + name_info = Socket.getnameinfo(["AF_INET", 3333, '127.0.0.1'], Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV) + name_info.should == ['127.0.0.1', "3333"] + end + + it "gets a 3-element array and resolves the service" do + name_info = Socket.getnameinfo ["AF_INET", 9, '127.0.0.1'] + name_info[1].should == 'discard' + end + + it "gets a 4-element array and doesn't resolve hostname" do + name_info = Socket.getnameinfo(["AF_INET", 3333, 'foo', '127.0.0.1'], Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV) + name_info.should == ['127.0.0.1', "3333"] + end + + it "gets a 4-element array and resolves the service" do + name_info = Socket.getnameinfo ["AF_INET", 9, 'foo', '127.0.0.1'] + name_info[1].should == 'discard' + end + + ruby_version_is ""..."3.3" do + it "raises SocketError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(SocketError) + end + end + + ruby_version_is "3.3" do + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(Socket::ResolutionError) { |e| + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) + } + end + end +end + +describe 'Socket.getnameinfo' do + describe 'using a String as the first argument' do + before do + @addr = Socket.sockaddr_in(21, '127.0.0.1') + end + + it 'raises SocketError or TypeError when using an invalid String' do + -> { Socket.getnameinfo('cats') }.should raise_error(Exception) { |e| + (e.is_a?(SocketError) || e.is_a?(TypeError)).should == true + } + end + + describe 'without custom flags' do + it 'returns an Array containing the hostname and service name' do + Socket.getnameinfo(@addr).should == [SocketSpecs.hostname_reverse_lookup, 'ftp'] + end + end + + describe 'using NI_NUMERICHOST as the flag' do + it 'returns an Array containing the numeric hostname and service name' do + array = Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST) + + %w{127.0.0.1 ::1}.include?(array[0]).should == true + + array[1].should == 'ftp' + end + end + end + + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @hostname = SocketSpecs.hostname_reverse_lookup(ip_address) + end + + describe 'using a 3 element Array as the first argument' do + before do + @addr = [family_name, 21, @hostname] + end + + it 'raises ArgumentError when using an invalid Array' do + -> { Socket.getnameinfo([family_name]) }.should raise_error(ArgumentError) + end + + platform_is_not :windows do + describe 'using NI_NUMERICHOST as the flag' do + it 'returns an Array containing the numeric hostname and service name' do + Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST).should == [ip_address, 'ftp'] + end + end + end + end + + describe 'using a 4 element Array as the first argument' do + before do + @addr = [family_name, 21, ip_address, ip_address] + end + + describe 'without custom flags' do + it 'returns an Array containing the hostname and service name' do + array = Socket.getnameinfo(@addr) + array.should be_an_instance_of(Array) + array[0].should == @hostname + array[1].should == 'ftp' + end + + it 'uses the 3rd value as the hostname if the 4th is not present' do + addr = [family_name, 21, ip_address, nil] + + array = Socket.getnameinfo(addr) + array.should be_an_instance_of(Array) + array[0].should == @hostname + array[1].should == 'ftp' + end + end + + describe 'using NI_NUMERICHOST as the flag' do + it 'returns an Array containing the numeric hostname and service name' do + Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST).should == [ip_address, 'ftp'] + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/getservbyname_spec.rb b/spec/ruby/library/socket/socket/getservbyname_spec.rb new file mode 100644 index 0000000000..d361e619f2 --- /dev/null +++ b/spec/ruby/library/socket/socket/getservbyname_spec.rb @@ -0,0 +1,32 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#getservbyname" do + it "returns the port for service 'discard'" do + Socket.getservbyname('discard').should == 9 + end + + it "returns the port for service 'discard' with protocol 'tcp'" do + Socket.getservbyname('discard', 'tcp').should == 9 + end + + it 'returns the port for service "ftp"' do + Socket.getservbyname('ftp').should == 21 + end + + it 'returns the port for service "ftp" with protocol "tcp"' do + Socket.getservbyname('ftp', 'tcp').should == 21 + end + + it "returns the port for service 'domain' with protocol 'udp'" do + Socket.getservbyname('domain', 'udp').should == 53 + end + + it "returns the port for service 'daytime'" do + Socket.getservbyname('daytime').should == 13 + end + + it "raises a SocketError when the service or port is invalid" do + -> { Socket.getservbyname('invalid') }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/socket/getservbyport_spec.rb b/spec/ruby/library/socket/socket/getservbyport_spec.rb new file mode 100644 index 0000000000..563c592b54 --- /dev/null +++ b/spec/ruby/library/socket/socket/getservbyport_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' + +describe 'Socket.getservbyport' do + platform_is_not :windows do + it 'returns the service name as a String' do + Socket.getservbyport(514).should == 'shell' + end + end + + platform_is :windows do + it 'returns the service name as a String' do + Socket.getservbyport(514).should == 'cmd' + end + end + + it 'returns the service name when using a custom protocol name' do + Socket.getservbyport(514, 'udp').should == 'syslog' + end + + it 'raises SocketError for an unknown port number' do + -> { Socket.getservbyport(0) }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/socket/initialize_spec.rb b/spec/ruby/library/socket/socket/initialize_spec.rb new file mode 100644 index 0000000000..f8337bcaa5 --- /dev/null +++ b/spec/ruby/library/socket/socket/initialize_spec.rb @@ -0,0 +1,87 @@ +require_relative '../spec_helper' + +describe 'Socket#initialize' do + before do + @socket = nil + end + + after do + @socket.close if @socket + end + + describe 'using an Integer as the 1st and 2nd arguments' do + it 'returns a Socket' do + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'using Symbols as the 1st and 2nd arguments' do + it 'returns a Socket' do + @socket = Socket.new(:INET, :STREAM) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'using Strings as the 1st and 2nd arguments' do + it 'returns a Socket' do + @socket = Socket.new('INET', 'STREAM') + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'using objects that respond to #to_str' do + it 'returns a Socket' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('AF_INET') + type.stub!(:to_str).and_return('STREAM') + + @socket = Socket.new(family, type) + + @socket.should be_an_instance_of(Socket) + end + + it 'raises TypeError when the #to_str method does not return a String' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return(Socket::AF_INET) + type.stub!(:to_str).and_return(Socket::SOCK_STREAM) + + -> { Socket.new(family, type) }.should raise_error(TypeError) + end + end + + describe 'using a custom protocol' do + it 'returns a Socket when using an Integer' do + @socket = Socket.new(:INET, :STREAM, Socket::IPPROTO_TCP) + + @socket.should be_an_instance_of(Socket) + end + + it 'raises TypeError when using a Symbol' do + -> { Socket.new(:INET, :STREAM, :TCP) }.should raise_error(TypeError) + end + end + + it 'sets the do_not_reverse_lookup option' do + @socket = Socket.new(:INET, :STREAM) + + @socket.do_not_reverse_lookup.should == Socket.do_not_reverse_lookup + end + + it "sets basic IO accessors" do + @socket = Socket.new(:INET, :STREAM) + @socket.lineno.should == 0 + end + + it "sets the socket to binary mode" do + @socket = Socket.new(:INET, :STREAM) + @socket.binmode?.should be_true + end +end diff --git a/spec/ruby/library/socket/socket/ip_address_list_spec.rb b/spec/ruby/library/socket/socket/ip_address_list_spec.rb new file mode 100644 index 0000000000..f97c2d7f85 --- /dev/null +++ b/spec/ruby/library/socket/socket/ip_address_list_spec.rb @@ -0,0 +1,50 @@ +require_relative '../spec_helper' + +describe 'Socket.ip_address_list' do + it 'returns an Array' do + Socket.ip_address_list.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = Socket.ip_address_list + end + + it 'is not empty' do + @array.should_not be_empty + end + + it 'contains Addrinfo objects' do + @array.each do |klass| + klass.should be_an_instance_of(Addrinfo) + end + end + end + + describe 'each returned Addrinfo' do + before do + @array = Socket.ip_address_list + end + + it 'has a non-empty IP address' do + @array.each do |addr| + addr.ip_address.should be_an_instance_of(String) + addr.ip_address.should_not be_empty + end + end + + it 'has an address family' do + families = [Socket::AF_INET, Socket::AF_INET6] + + @array.each do |addr| + families.include?(addr.afamily).should == true + end + end + + it 'uses 0 as the port number' do + @array.each do |addr| + addr.ip_port.should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb b/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb new file mode 100644 index 0000000000..4f429c089e --- /dev/null +++ b/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Socket#ipv6only!' do + before do + @socket = Socket.new(:INET6, :DGRAM) + end + + after do + @socket.close + end + + it 'enables IPv6 only mode' do + @socket.ipv6only! + + @socket.getsockopt(:IPV6, :V6ONLY).bool.should == true + end + end +end diff --git a/spec/ruby/library/socket/socket/listen_spec.rb b/spec/ruby/library/socket/socket/listen_spec.rb new file mode 100644 index 0000000000..4d2aedab19 --- /dev/null +++ b/spec/ruby/library/socket/socket/listen_spec.rb @@ -0,0 +1,66 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#listen" do + before :each do + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + end + + after :each do + @socket.closed?.should be_false + @socket.close + end + + it "verifies we can listen for incoming connections" do + sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1") + @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + @socket.bind(sockaddr) + @socket.listen(1).should == 0 + end +end + +describe 'Socket#listen' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a DGRAM socket' do + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + it 'raises Errno::EOPNOTSUPP or Errno::EACCES' do + -> { @server.listen(1) }.should raise_error { |e| + [Errno::EOPNOTSUPP, Errno::EACCES].should.include?(e.class) + } + end + end + + describe 'using a STREAM socket' do + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + it 'returns 0' do + @server.listen(1).should == 0 + end + + it "raises when the given argument can't be coerced to an Integer" do + -> { @server.listen('cats') }.should raise_error(TypeError) + end + end + end +end diff --git a/spec/ruby/library/socket/socket/local_address_spec.rb b/spec/ruby/library/socket/socket/local_address_spec.rb new file mode 100644 index 0000000000..3687f93a0c --- /dev/null +++ b/spec/ruby/library/socket/socket/local_address_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' + +describe 'Socket#local_address' do + before do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @sock.local_address.afamily.should == Socket::AF_INET + end + + it 'uses PF_INET as the protocol family' do + @sock.local_address.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0.0.0.0 as the IP address' do + @sock.local_address.ip_address.should == '0.0.0.0' + end + + platform_is_not :windows do + it 'uses 0 as the port' do + @sock.local_address.ip_port.should == 0 + end + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb new file mode 100644 index 0000000000..ef2a2d4ba9 --- /dev/null +++ b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket.pack_sockaddr_in" do + it_behaves_like :socket_pack_sockaddr_in, :pack_sockaddr_in +end diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb new file mode 100644 index 0000000000..1ee0bc6157 --- /dev/null +++ b/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket#pack_sockaddr_un" do + it_behaves_like :socket_pack_sockaddr_un, :pack_sockaddr_un +end diff --git a/spec/ruby/library/socket/socket/pair_spec.rb b/spec/ruby/library/socket/socket/pair_spec.rb new file mode 100644 index 0000000000..8dd470a95e --- /dev/null +++ b/spec/ruby/library/socket/socket/pair_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/socketpair' + +describe "Socket.pair" do + it_behaves_like :socket_socketpair, :pair +end diff --git a/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb new file mode 100644 index 0000000000..01b42bcc52 --- /dev/null +++ b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb @@ -0,0 +1,219 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#recvfrom_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + @client.connect(@server.getsockname) + end + + describe 'without any data available' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable with exception: false' do + @server.recvfrom_nonblock(1, exception: false).should == :wait_readable + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + platform_is_not :windows do + it 'returns an Array containing the data and an Addrinfo' do + IO.select([@server]) + ret = @server.recvfrom_nonblock(1) + + ret.should be_an_instance_of(Array) + ret.length.should == 2 + end + end + + it "allows an output buffer as third argument" do + @client.write('hello') + + IO.select([@server]) + buffer = +'' + message, = @server.recvfrom_nonblock(5, 0, buffer) + + message.should.equal?(buffer) + buffer.should == 'hello' + end + + it "preserves the encoding of the given buffer" do + @client.write('hello') + + IO.select([@server]) + buffer = ''.encode(Encoding::ISO_8859_1) + @server.recvfrom_nonblock(5, 0, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + + describe 'the returned data' do + it 'is the same as the sent data' do + 5.times do + @client.write('hello') + + IO.select([@server]) + msg, _ = @server.recvfrom_nonblock(5) + + msg.should == 'hello' + end + end + end + + platform_is_not :windows do + describe 'the returned Array' do + before do + IO.select([@server]) + @array = @server.recvfrom_nonblock(1) + end + + it 'contains the data at index 0' do + @array[0].should == 'h' + end + + it 'contains an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + end + + describe 'the returned Addrinfo' do + before do + IO.select([@server]) + @addr = @server.recvfrom_nonblock(1)[1] + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == ip_address + end + + it 'uses the port of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end +end + +describe 'Socket#recvfrom_nonblock' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = Socket.new Socket::AF_INET, :STREAM, 0 + @sockaddr = Socket.sockaddr_in(0, "127.0.0.1") + @server.bind(@sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, "127.0.0.1") + + @client = Socket.new(Socket::AF_INET, :STREAM, 0) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String as received data on a closed stream socket" do + ready = false + + t = Thread.new do + client, _ = @server.accept + + Thread.pass while !ready + begin + client.recvfrom_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + ready = true + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + ready = false + + t = Thread.new do + client, _ = @server.accept + + Thread.pass while !ready + begin + client.recvfrom_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + ready = true + + t.value.should be_nil + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/recvfrom_spec.rb b/spec/ruby/library/socket/socket/recvfrom_spec.rb new file mode 100644 index 0000000000..6ba39ffcaf --- /dev/null +++ b/spec/ruby/library/socket/socket/recvfrom_spec.rb @@ -0,0 +1,179 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#recvfrom' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + describe 'using an unbound socket' do + it 'blocks the caller' do + -> { @server.recvfrom(1) }.should block_caller + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + @client.connect(@server.getsockname) + end + + describe 'without any data available' do + it 'blocks the caller' do + -> { @server.recvfrom(1) }.should block_caller + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns an Array containing the data and an Addrinfo' do + ret = @server.recvfrom(1) + + ret.should be_an_instance_of(Array) + ret.length.should == 2 + end + + describe 'the returned Array' do + before do + @array = @server.recvfrom(1) + end + + it 'contains the data at index 0' do + @array[0].should == 'h' + end + + it 'contains an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + end + + describe 'the returned Addrinfo' do + before do + @addr = @server.recvfrom(1)[1] + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == ip_address + end + + it 'uses the port of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end +end + +describe 'Socket#recvfrom' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = Socket.new Socket::AF_INET, :STREAM, 0 + sockaddr = Socket.sockaddr_in(0, "127.0.0.1") + @server.bind(sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, "127.0.0.1") + + @client = Socket.new(Socket::AF_INET, :STREAM, 0) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String as received data on a closed stream socket" do + t = Thread.new do + client, _ = @server.accept + client.recvfrom(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + t = Thread.new do + client, _ = @server.accept + client.recvfrom(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + + t.value.should be_nil + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns an empty String as received data" do + @server.bind(Socket.sockaddr_in(0, ip_address)) + @client.connect(@server.getsockname) + + @client.send('', 0) + message = @server.recvfrom(1) + + message.should.is_a? Array + message[0].should == "" + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/remote_address_spec.rb b/spec/ruby/library/socket/socket/remote_address_spec.rb new file mode 100644 index 0000000000..24d60d7f58 --- /dev/null +++ b/spec/ruby/library/socket/socket/remote_address_spec.rb @@ -0,0 +1,54 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#remote_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @host = @server.local_address.ip_address + @port = @server.local_address.ip_port + @client = Socket.new(family, :STREAM, Socket::IPPROTO_TCP) + + @client.connect(Socket.sockaddr_in(@port, @host)) + end + + after do + @client.close + @server.close + end + + it 'returns an Addrinfo' do + @client.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @client.remote_address.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @client.remote_address.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @client.remote_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @client.remote_address.ip_address.should == @host + end + + it 'uses the correct port' do + @client.remote_address.ip_port.should == @port + end + + it 'uses 0 as the protocol' do + @client.remote_address.protocol.should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/socket/sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/sockaddr_in_spec.rb new file mode 100644 index 0000000000..8ee956ac26 --- /dev/null +++ b/spec/ruby/library/socket/socket/sockaddr_in_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket#sockaddr_in" do + it_behaves_like :socket_pack_sockaddr_in, :sockaddr_in +end diff --git a/spec/ruby/library/socket/socket/sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/sockaddr_un_spec.rb new file mode 100644 index 0000000000..8922ff4d6d --- /dev/null +++ b/spec/ruby/library/socket/socket/sockaddr_un_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket#sockaddr_un" do + it_behaves_like :socket_pack_sockaddr_un, :sockaddr_un +end diff --git a/spec/ruby/library/socket/socket/socket_spec.rb b/spec/ruby/library/socket/socket/socket_spec.rb new file mode 100644 index 0000000000..5a3d6733e0 --- /dev/null +++ b/spec/ruby/library/socket/socket/socket_spec.rb @@ -0,0 +1,38 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket" do + it "inherits from BasicSocket and IO" do + Socket.superclass.should == BasicSocket + BasicSocket.superclass.should == IO + end +end + +describe "The socket class hierarchy" do + it "has an IPSocket in parallel to Socket" do + Socket.ancestors.include?(IPSocket).should == false + IPSocket.ancestors.include?(Socket).should == false + IPSocket.superclass.should == BasicSocket + end + + it "has TCPSocket and UDPSocket subclasses of IPSocket" do + TCPSocket.superclass.should == IPSocket + UDPSocket.superclass.should == IPSocket + end + + platform_is_not :windows do + it "has a UNIXSocket in parallel to Socket" do + Socket.ancestors.include?(UNIXSocket).should == false + UNIXSocket.ancestors.include?(Socket).should == false + UNIXSocket.superclass.should == BasicSocket + end + end +end + +platform_is_not :windows do + describe "Server class hierarchy" do + it "contains UNIXServer" do + UNIXServer.superclass.should == UNIXSocket + end + end +end diff --git a/spec/ruby/library/socket/socket/socketpair_spec.rb b/spec/ruby/library/socket/socket/socketpair_spec.rb new file mode 100644 index 0000000000..551c376d49 --- /dev/null +++ b/spec/ruby/library/socket/socket/socketpair_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/socketpair' + +describe "Socket.socketpair" do + it_behaves_like :socket_socketpair, :socketpair +end diff --git a/spec/ruby/library/socket/socket/sysaccept_spec.rb b/spec/ruby/library/socket/socket/sysaccept_spec.rb new file mode 100644 index 0000000000..92ac21124e --- /dev/null +++ b/spec/ruby/library/socket/socket/sysaccept_spec.rb @@ -0,0 +1,91 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#sysaccept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @server.close + end + + platform_is :linux do # hangs on other platforms + describe 'using an unbound socket' do + it 'raises Errno::EINVAL' do + -> { @server.sysaccept }.should raise_error(Errno::EINVAL) + end + end + + describe "using a bound socket that's not listening" do + before do + @server.bind(@sockaddr) + end + + it 'raises Errno::EINVAL' do + -> { @server.sysaccept }.should raise_error(Errno::EINVAL) + end + end + end + + describe "using a bound socket that's listening" do + before do + @server.bind(@sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, ip_address) + end + + after do + Socket.for_fd(@fd).close if @fd + end + + describe 'without a connected client' do + before do + @client = Socket.new(family, :STREAM) + end + + after do + @client.close + end + + it 'blocks the caller until a connection is available' do + thread = Thread.new do + @fd, _ = @server.sysaccept + end + + @client.connect(@server_addr) + + thread.value.should be_an_instance_of(Array) + end + end + + describe 'with a connected client' do + before do + @client = Socket.new(family, :STREAM) + @client.connect(@server.getsockname) + end + + after do + @client.close + end + + it 'returns an Array containing an Integer and an Addrinfo' do + @fd, addrinfo = @server.sysaccept + + @fd.should be_kind_of(Integer) + addrinfo.should be_an_instance_of(Addrinfo) + end + + it 'returns a new file descriptor' do + @fd, _ = @server.sysaccept + + @fd.should_not == @client.fileno + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb b/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb new file mode 100644 index 0000000000..a46c6df5c6 --- /dev/null +++ b/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb @@ -0,0 +1,54 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.tcp_server_loop' do + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.tcp_server_loop('127.0.0.1', 0) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = Socket.new(:INET, :STREAM) + SocketSpecs::ServerLoopPortFinder.cleanup + end + + after do + @sock.close if @sock + @client.close + end + + it 'yields a Socket and an Addrinfo' do + @sock, addr = nil + + thread = Thread.new do + SocketSpecs::ServerLoopPortFinder.tcp_server_loop('127.0.0.1', 0) do |socket, addrinfo| + @sock = socket + addr = addrinfo + + break + end + end + + port = SocketSpecs::ServerLoopPortFinder.port + + SocketSpecs.loop_with_timeout do + begin + @client.connect(Socket.sockaddr_in(port, '127.0.0.1')) + rescue SystemCallError + sleep 0.01 + :retry + end + end + + # At this point the connection has been set up but the thread may not yet + # have returned, thus we'll need to wait a little longer for it to + # complete. + thread.join + + @sock.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb b/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb new file mode 100644 index 0000000000..bd496d3015 --- /dev/null +++ b/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb @@ -0,0 +1,39 @@ +require_relative '../spec_helper' + +describe 'Socket.tcp_server_sockets' do + describe 'without a block' do + before do + @sockets = nil + end + + after do + @sockets.each(&:close) + end + + it 'returns an Array of Socket objects' do + @sockets = Socket.tcp_server_sockets(0) + + @sockets.should be_an_instance_of(Array) + @sockets[0].should be_an_instance_of(Socket) + end + end + + describe 'with a block' do + it 'yields the sockets to the supplied block' do + Socket.tcp_server_sockets(0) do |sockets| + sockets.should be_an_instance_of(Array) + sockets[0].should be_an_instance_of(Socket) + end + end + + it 'closes all sockets after the block returns' do + sockets = nil + + Socket.tcp_server_sockets(0) { |socks| sockets = socks } + + sockets.each do |socket| + socket.should.closed? + end + end + end +end diff --git a/spec/ruby/library/socket/socket/tcp_spec.rb b/spec/ruby/library/socket/socket/tcp_spec.rb new file mode 100644 index 0000000000..faf020b1ea --- /dev/null +++ b/spec/ruby/library/socket/socket/tcp_spec.rb @@ -0,0 +1,70 @@ +require_relative '../spec_helper' + +describe 'Socket.tcp' do + before do + @server = Socket.new(:INET, :STREAM) + @client = nil + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + @server.listen(1) + + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @client.close if @client && !@client.closed? + @client = nil + + @server.close + end + + it 'returns a Socket when no block is given' do + @client = Socket.tcp(@host, @port) + + @client.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + Socket.tcp(@host, @port) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'closes the Socket automatically when a block is given' do + Socket.tcp(@host, @port) do |socket| + @socket = socket + end + + @socket.should.closed? + end + + it 'binds to a local address and port when specified' do + @client = Socket.tcp(@host, @port, @host, 0) + + @client.local_address.ip_address.should == @host + + @client.local_address.ip_port.should > 0 + @client.local_address.ip_port.should_not == @port + end + + it 'raises ArgumentError when 6 arguments are provided' do + -> { + Socket.tcp(@host, @port, @host, 0, {:connect_timeout => 1}, 10) + }.should raise_error(ArgumentError) + end + + it 'connects to the server' do + @client = Socket.tcp(@host, @port) + + @client.write('hello') + + connection, _ = @server.accept + + begin + connection.recv(5).should == 'hello' + ensure + connection.close + end + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb new file mode 100644 index 0000000000..cb8c5c5587 --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb @@ -0,0 +1,47 @@ +require_relative '../spec_helper' + +describe 'Socket.udp_server_loop_on' do + before do + @server = Socket.new(:INET, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + end + + after do + @server.close + end + + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.udp_server_loop_on([@server]) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = Socket.new(:INET, :DGRAM) + end + + after do + @client.close + end + + it 'yields the message and a Socket::UDPSource' do + msg = nil + src = nil + + @client.connect(@server.getsockname) + @client.write('hello') + + Socket.udp_server_loop_on([@server]) do |message, source| + msg = message + src = source + + break + end + + msg.should == 'hello' + src.should be_an_instance_of(Socket::UDPSource) + end + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_loop_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb new file mode 100644 index 0000000000..cd22ea56cf --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb @@ -0,0 +1,59 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.udp_server_loop' do + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.udp_server_loop('127.0.0.1', 0) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = Socket.new(:INET, :DGRAM) + SocketSpecs::ServerLoopPortFinder.cleanup + end + + after do + @client.close + end + + it 'yields the message and a Socket::UDPSource' do + msg, src = nil + + thread = Thread.new do + SocketSpecs::ServerLoopPortFinder.udp_server_loop('127.0.0.1', 0) do |message, source| + msg = message + src = source + + break + end + end + + port = SocketSpecs::ServerLoopPortFinder.port + + # Because this will return even if the server is up and running (it's UDP + # after all) we'll have to write and wait until "msg" is set. + @client.connect(Socket.sockaddr_in(port, '127.0.0.1')) + + SocketSpecs.loop_with_timeout do + begin + @client.write('hello') + rescue SystemCallError + sleep 0.01 + :retry + else + unless msg + sleep 0.001 + :retry + end + end + end + + thread.join + + msg.should == 'hello' + src.should be_an_instance_of(Socket::UDPSource) + end + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_recv_spec.rb b/spec/ruby/library/socket/socket/udp_server_recv_spec.rb new file mode 100644 index 0000000000..47ed74bc03 --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_recv_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +describe 'Socket.udp_server_recv' do + before do + @server = Socket.new(:INET, :DGRAM) + @client = Socket.new(:INET, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + it 'yields the message and a Socket::UDPSource' do + msg = :unset + src = :unset + + @client.write('hello') + + readable, _, _ = IO.select([@server]) + readable.size.should == 1 + + Socket.udp_server_recv(readable) do |message, source| + msg = message + src = source + break + end + + msg.should == 'hello' + src.should be_an_instance_of(Socket::UDPSource) + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb b/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb new file mode 100644 index 0000000000..f8be672612 --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb @@ -0,0 +1,39 @@ +require_relative '../spec_helper' + +describe 'Socket.udp_server_sockets' do + describe 'without a block' do + before do + @sockets = nil + end + + after do + @sockets.each(&:close) + end + + it 'returns an Array of Socket objects' do + @sockets = Socket.udp_server_sockets(0) + + @sockets.should be_an_instance_of(Array) + @sockets[0].should be_an_instance_of(Socket) + end + end + + describe 'with a block' do + it 'yields the sockets to the supplied block' do + Socket.udp_server_sockets(0) do |sockets| + sockets.should be_an_instance_of(Array) + sockets[0].should be_an_instance_of(Socket) + end + end + + it 'closes all sockets after the block returns' do + sockets = nil + + Socket.udp_server_sockets(0) { |socks| sockets = socks } + + sockets.each do |socket| + socket.should.closed? + end + end + end +end diff --git a/spec/ruby/library/socket/socket/unix_server_loop_spec.rb b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb new file mode 100644 index 0000000000..6192bc8bf6 --- /dev/null +++ b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb @@ -0,0 +1,56 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unix_server_loop' do + before do + @path = SocketSpecs.socket_path + end + + after do + rm_r(@path) if File.file?(@path) + end + + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.unix_server_loop(@path) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = nil + end + + after do + @sock.close if @sock + @client.close if @client + end + + it 'yields a Socket and an Addrinfo' do + @sock, addr = nil + + thread = Thread.new do + Socket.unix_server_loop(@path) do |socket, addrinfo| + @sock = socket + addr = addrinfo + + break + end + end + + SocketSpecs.loop_with_timeout do + begin + @client = Socket.unix(@path) + rescue SystemCallError + sleep 0.01 + :retry + end + end + + thread.join + + @sock.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/socket/unix_server_socket_spec.rb b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb new file mode 100644 index 0000000000..34c3b96d07 --- /dev/null +++ b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb @@ -0,0 +1,46 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unix_server_socket' do + before do + @path = SocketSpecs.socket_path + end + + after do + rm_r(@path) + end + + describe 'when no block is given' do + before do + @socket = nil + end + + after do + @socket.close + end + + it 'returns a Socket' do + @socket = Socket.unix_server_socket(@path) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'when a block is given' do + it 'yields a Socket' do + Socket.unix_server_socket(@path) do |sock| + sock.should be_an_instance_of(Socket) + end + end + + it 'closes the Socket when the block returns' do + socket = nil + + Socket.unix_server_socket(@path) do |sock| + socket = sock + end + + socket.should be_an_instance_of(Socket) + end + end +end diff --git a/spec/ruby/library/socket/socket/unix_spec.rb b/spec/ruby/library/socket/socket/unix_spec.rb new file mode 100644 index 0000000000..2a5d77f96f --- /dev/null +++ b/spec/ruby/library/socket/socket/unix_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unix' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @socket = nil + end + + after do + @server.close + @socket.close if @socket + + rm_r(@path) + end + + describe 'when no block is given' do + it 'returns a Socket' do + @socket = Socket.unix(@path) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'when a block is given' do + it 'yields a Socket' do + Socket.unix(@path) do |sock| + sock.should be_an_instance_of(Socket) + end + end + + it 'closes the Socket when the block returns' do + socket = nil + + Socket.unix(@path) do |sock| + socket = sock + end + + socket.should.closed? + end + end +end diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb new file mode 100644 index 0000000000..935b5cb543 --- /dev/null +++ b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb @@ -0,0 +1,44 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.unpack_sockaddr_in" do + it "decodes the host name and port number of a packed sockaddr_in" do + sockaddr = Socket.sockaddr_in 3333, '127.0.0.1' + Socket.unpack_sockaddr_in(sockaddr).should == [3333, '127.0.0.1'] + end + + it "gets the hostname and port number from a passed Addrinfo" do + addrinfo = Addrinfo.tcp('127.0.0.1', 3333) + Socket.unpack_sockaddr_in(addrinfo).should == [3333, '127.0.0.1'] + end + + describe 'using an IPv4 address' do + it 'returns an Array containing the port and IP address' do + port = 80 + ip = '127.0.0.1' + addr = Socket.pack_sockaddr_in(port, ip) + + Socket.unpack_sockaddr_in(addr).should == [port, ip] + end + end + + describe 'using an IPv6 address' do + it 'returns an Array containing the port and IP address' do + port = 80 + ip = '::1' + addr = Socket.pack_sockaddr_in(port, ip) + + Socket.unpack_sockaddr_in(addr).should == [port, ip] + end + end + + it "raises an ArgumentError when the sin_family is not AF_INET" do + sockaddr = Socket.sockaddr_un '/tmp/x' + -> { Socket.unpack_sockaddr_in sockaddr }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed addrinfo is not AF_INET/AF_INET6" do + addrinfo = Addrinfo.unix('/tmp/sock') + -> { Socket.unpack_sockaddr_in(addrinfo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb new file mode 100644 index 0000000000..6e0f11de3d --- /dev/null +++ b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb @@ -0,0 +1,24 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unpack_sockaddr_un' do + it 'decodes sockaddr to unix path' do + sockaddr = Socket.sockaddr_un('/tmp/sock') + Socket.unpack_sockaddr_un(sockaddr).should == '/tmp/sock' + end + + it 'returns unix path from a passed Addrinfo' do + addrinfo = Addrinfo.unix('/tmp/sock') + Socket.unpack_sockaddr_un(addrinfo).should == '/tmp/sock' + end + + it 'raises an ArgumentError when the sa_family is not AF_UNIX' do + sockaddr = Socket.sockaddr_in(0, '127.0.0.1') + -> { Socket.unpack_sockaddr_un(sockaddr) }.should raise_error(ArgumentError) + end + + it 'raises an ArgumentError when passed addrinfo is not AF_UNIX' do + addrinfo = Addrinfo.tcp('127.0.0.1', 0) + -> { Socket.unpack_sockaddr_un(addrinfo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/socket/spec_helper.rb b/spec/ruby/library/socket/spec_helper.rb new file mode 100644 index 0000000000..b33663e02d --- /dev/null +++ b/spec/ruby/library/socket/spec_helper.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'socket' + +MSpec.enable_feature :sock_packet if Socket.const_defined?(:SOCK_PACKET) +MSpec.enable_feature :udp_cork if Socket.const_defined?(:UDP_CORK) +MSpec.enable_feature :tcp_cork if Socket.const_defined?(:TCP_CORK) +MSpec.enable_feature :pktinfo if Socket.const_defined?(:IP_PKTINFO) +MSpec.enable_feature :ipv6_pktinfo if Socket.const_defined?(:IPV6_PKTINFO) +MSpec.enable_feature :ip_mtu if Socket.const_defined?(:IP_MTU) +MSpec.enable_feature :ipv6_nexthop if Socket.const_defined?(:IPV6_NEXTHOP) +MSpec.enable_feature :tcp_info if Socket.const_defined?(:TCP_INFO) +MSpec.enable_feature :ancillary_data if Socket.const_defined?(:AncillaryData) diff --git a/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb new file mode 100644 index 0000000000..91f6a327f0 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb @@ -0,0 +1,85 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::TCPServer.accept_nonblock" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + it "accepts non blocking connections" do + @server.listen(5) + -> { + @server.accept_nonblock + }.should raise_error(IO::WaitReadable) + + c = TCPSocket.new("127.0.0.1", @port) + IO.select([@server]) + s = @server.accept_nonblock + + port, address = Socket.unpack_sockaddr_in(s.getsockname) + + port.should == @port + address.should == "127.0.0.1" + s.should be_kind_of(TCPSocket) + + c.close + s.close + end + + it "raises an IOError if the socket is closed" do + @server.close + -> { @server.accept }.should raise_error(IOError) + end + + describe 'without a connected client' do + it 'raises error' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable in exceptionless mode' do + @server.accept_nonblock(exception: false).should == :wait_readable + end + end +end + +describe 'TCPServer#accept_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + describe 'without a connected client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + end + + platform_is_not :windows do # spurious + describe 'with a connected client' do + before do + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns a TCPSocket' do + IO.select([@server]) + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(TCPSocket) + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/accept_spec.rb b/spec/ruby/library/socket/tcpserver/accept_spec.rb new file mode 100644 index 0000000000..d8892cd5f0 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/accept_spec.rb @@ -0,0 +1,132 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer#accept" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + it "accepts a connection and returns a TCPSocket" do + data = nil + t = Thread.new do + client = @server.accept + client.should be_kind_of(TCPSocket) + data = client.read(5) + client << "goodbye" + client.close + end + Thread.pass while t.status and t.status != "sleep" + + socket = TCPSocket.new('127.0.0.1', @port) + socket.write('hello') + socket.shutdown(1) # we are done with sending + socket.read.should == 'goodbye' + t.join + data.should == 'hello' + socket.close + end + + it "can be interrupted by Thread#kill" do + t = Thread.new { @server.accept } + + Thread.pass while t.status and t.status != "sleep" + + # kill thread, ensure it dies in a reasonable amount of time + t.kill + a = 0 + while t.alive? and a < 5000 + sleep 0.001 + a += 1 + end + a.should < 5000 + end + + it "can be interrupted by Thread#raise" do + t = Thread.new { + -> { + @server.accept + }.should raise_error(Exception, "interrupted") + } + + Thread.pass while t.status and t.status != "sleep" + t.raise Exception, "interrupted" + t.join + end + + it "is automatically retried when interrupted by SIGVTALRM" do + t = Thread.new do + client = @server.accept + value = client.read(2) + client.close + value + end + + Thread.pass while t.status and t.status != "sleep" + # Thread#backtrace uses SIGVTALRM on TruffleRuby and potentially other implementations. + # Sending a signal to a thread is not possible with Ruby APIs. + t.backtrace.join("\n").should =~ /in [`'](?:TCPServer#)?accept'/ + + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("OK") + socket.close + + t.value.should == "OK" + end + + it "raises an IOError if the socket is closed" do + @server.close + -> { @server.accept }.should raise_error(IOError) + end +end + +describe 'TCPServer#accept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + describe 'without a connected client' do + it 'blocks the caller' do + -> { @server.accept }.should block_caller + end + end + + describe 'with a connected client' do + before do + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns a TCPSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(TCPSocket) + end + + platform_is_not :windows do + it "returns a TCPSocket which is set to nonblocking" do + require 'io/nonblock' + @socket = @server.accept + @socket.should.nonblock? + end + end + + it "returns a TCPSocket which is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? + end + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/gets_spec.rb b/spec/ruby/library/socket/tcpserver/gets_spec.rb new file mode 100644 index 0000000000..417976d737 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/gets_spec.rb @@ -0,0 +1,16 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer#gets" do + before :each do + @server = TCPServer.new(SocketSpecs.hostname, 0) + end + + after :each do + @server.close + end + + it "raises Errno::ENOTCONN on gets" do + -> { @server.gets }.should raise_error(Errno::ENOTCONN) + end +end diff --git a/spec/ruby/library/socket/tcpserver/initialize_spec.rb b/spec/ruby/library/socket/tcpserver/initialize_spec.rb new file mode 100644 index 0000000000..4ddd1f465f --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/initialize_spec.rb @@ -0,0 +1,101 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPServer#initialize' do + describe 'with a single Integer argument' do + before do + @server = TCPServer.new(0) + end + + after do + @server.close + end + + it 'sets the port to the given argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + platform_is_not :windows do + it 'sets the hostname to 0.0.0.0 or ::' do + a = @server.local_address + a.ip_address.should == (a.ipv6? ? '::' : '0.0.0.0') + end + end + + it "sets the socket to binmode" do + @server.binmode?.should be_true + end + end + + describe 'with a single String argument containing a numeric value' do + before do + @server = TCPServer.new('0') + end + + after do + @server.close + end + + it 'sets the port to the given argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + platform_is_not :windows do + it 'sets the hostname to 0.0.0.0 or ::' do + a = @server.local_address + a.ip_address.should == (a.ipv6? ? '::' : '0.0.0.0') + end + end + end + + describe 'with a single String argument containing a non numeric value' do + it 'raises SocketError' do + -> { TCPServer.new('cats') }.should raise_error(SocketError) + end + end + + describe 'with a String and an Integer' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + it 'sets the port to the given port argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + it 'sets the hostname to the given host argument' do + @server.local_address.ip_address.should == ip_address + end + end + end + + describe 'with a String and a custom object' do + before do + dummy = mock(:dummy) + dummy.stub!(:to_str).and_return('0') + + @server = TCPServer.new('127.0.0.1', dummy) + end + + after do + @server.close + end + + it 'sets the port to the given port argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + it 'sets the hostname to the given host argument' do + @server.local_address.ip_address.should == '127.0.0.1' + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/listen_spec.rb b/spec/ruby/library/socket/tcpserver/listen_spec.rb new file mode 100644 index 0000000000..c877fdced6 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/listen_spec.rb @@ -0,0 +1,22 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPServer#listen' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + it 'returns 0' do + @server.listen(1).should == 0 + end + + it "raises when the given argument can't be coerced to an Integer" do + -> { @server.listen('cats') }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/new_spec.rb b/spec/ruby/library/socket/tcpserver/new_spec.rb new file mode 100644 index 0000000000..dd1ba676bd --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/new_spec.rb @@ -0,0 +1,137 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer.new" do + after :each do + @server.close if @server && !@server.closed? + end + + it "binds to a host and a port" do + @server = TCPServer.new('127.0.0.1', 0) + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + # on some platforms (Mac), MRI + # returns comma at the end. + addr[2].should =~ /^#{SocketSpecs.hostname}\b/ + addr[3].should == '127.0.0.1' + end + + it "binds to localhost and a port with either IPv4 or IPv6" do + @server = TCPServer.new(SocketSpecs.hostname, 0) + addr = @server.addr + addr[1].should be_kind_of(Integer) + if addr[0] == 'AF_INET' + addr[2].should =~ /^#{SocketSpecs.hostname}\b/ + addr[3].should == '127.0.0.1' + else + addr[2].should =~ /^#{SocketSpecs.hostname('::1')}\b/ + addr[3].should == '::1' + end + end + + it "binds to INADDR_ANY if the hostname is empty" do + @server = TCPServer.new('', 0) + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "binds to INADDR_ANY if the hostname is empty and the port is a string" do + @server = TCPServer.new('', '0') + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "binds to a port if the port is explicitly nil" do + @server = TCPServer.new('', nil) + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "binds to a port if the port is an empty string" do + @server = TCPServer.new('', '') + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "coerces port to string, then determines port from that number or service name" do + -> { TCPServer.new(SocketSpecs.hostname, Object.new) }.should raise_error(TypeError) + + port = Object.new + port.should_receive(:to_str).and_return("0") + + @server = TCPServer.new(SocketSpecs.hostname, port) + addr = @server.addr + addr[1].should be_kind_of(Integer) + + # TODO: This should also accept strings like 'https', but I don't know how to + # pick such a service port that will be able to reliably bind... + end + + it "has a single argument form and treats it as a port number" do + @server = TCPServer.new(0) + addr = @server.addr + addr[1].should be_kind_of(Integer) + end + + it "coerces port to a string when it is the only argument" do + -> { TCPServer.new(Object.new) }.should raise_error(TypeError) + + port = Object.new + port.should_receive(:to_str).and_return("0") + + @server = TCPServer.new(port) + addr = @server.addr + addr[1].should be_kind_of(Integer) + end + + it "does not use the given block and warns to use TCPServer::open" do + -> { + @server = TCPServer.new(0) { raise } + }.should complain(/warning: TCPServer::new\(\) does not take block; use TCPServer::open\(\) instead/) + end + + it "raises Errno::EADDRNOTAVAIL when the address is unknown" do + -> { TCPServer.new("1.2.3.4", 0) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + # There is no way to make this fail-proof on all machines, because + # DNS servers like opendns return A records for ANY host, including + # traditionally invalidly named ones. + quarantine! do + it "raises a SocketError when the host is unknown" do + -> { + TCPServer.new("--notavalidname", 0) + }.should raise_error(SocketError) + end + end + + it "raises Errno::EADDRINUSE when address is already in use" do + @server = TCPServer.new('127.0.0.1', 0) + -> { + @server = TCPServer.new('127.0.0.1', @server.addr[1]) + }.should raise_error(Errno::EADDRINUSE) + end + + platform_is_not :windows, :aix do + # A known bug in AIX. getsockopt(2) does not properly set + # the fifth argument for SO_REUSEADDR. + it "sets SO_REUSEADDR on the resulting server" do + @server = TCPServer.new('127.0.0.1', 0) + @server.getsockopt(:SOCKET, :REUSEADDR).data.should_not == "\x00\x00\x00\x00" + @server.getsockopt(:SOCKET, :REUSEADDR).int.should_not == 0 + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb b/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb new file mode 100644 index 0000000000..bd7d33faf4 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb @@ -0,0 +1,66 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer#sysaccept" do + before :each do + @server = TCPServer.new(SocketSpecs.hostname, 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + it 'blocks if no connections' do + -> { @server.sysaccept }.should block_caller + end + + it 'returns file descriptor of an accepted connection' do + begin + sock = TCPSocket.new(SocketSpecs.hostname, @port) + + fd = @server.sysaccept + + fd.should be_kind_of(Integer) + ensure + sock.close if sock && !sock.closed? + IO.for_fd(fd).close if fd + end + end +end + +describe 'TCPServer#sysaccept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + describe 'without a connected client' do + it 'blocks the caller' do + -> { @server.sysaccept }.should block_caller + end + end + + describe 'with a connected client' do + before do + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + Socket.for_fd(@fd).close if @fd + @client.close + end + + it 'returns a new file descriptor as an Integer' do + @fd = @server.sysaccept + + @fd.should be_kind_of(Integer) + @fd.should_not == @client.fileno + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb new file mode 100644 index 0000000000..5a2c704f35 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb @@ -0,0 +1,119 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +# TODO: verify these for windows +describe "TCPSocket.gethostbyname" do + before :each do + suppress_warning do + @host_info = TCPSocket.gethostbyname(SocketSpecs.hostname) + end + end + + it "returns an array elements of information on the hostname" do + @host_info.should be_kind_of(Array) + end + + platform_is_not :windows do + it "returns the canonical name as first value" do + @host_info[0].should == SocketSpecs.hostname + end + + it "returns the address type as the third value" do + address_type = @host_info[2] + [Socket::AF_INET, Socket::AF_INET6].include?(address_type).should be_true + end + + it "returns the IP address as the fourth value" do + ip = @host_info[3] + ["127.0.0.1", "::1"].include?(ip).should be_true + end + end + + platform_is :windows do + quarantine! do # name lookup seems not working on Windows CI + it "returns the canonical name as first value" do + host = "#{ENV['COMPUTERNAME'].downcase}" + host << ".#{ENV['USERDNSDOMAIN'].downcase}" if ENV['USERDNSDOMAIN'] + @host_info[0].should == host + end + end + + it "returns the address type as the third value" do + @host_info[2].should == Socket::AF_INET + end + + it "returns the IP address as the fourth value" do + @host_info[3].should == "127.0.0.1" + end + end + + it "returns any aliases to the address as second value" do + @host_info[1].should be_kind_of(Array) + end +end + +describe 'TCPSocket.gethostbyname' do + it 'returns an Array' do + suppress_warning do + TCPSocket.gethostbyname('127.0.0.1').should be_an_instance_of(Array) + end + end + + describe 'using a hostname' do + describe 'the returned Array' do + before do + suppress_warning do + @array = TCPSocket.gethostbyname('127.0.0.1') + end + end + + it 'includes the canonical name as the 1st value' do + @array[0].should == '127.0.0.1' + end + + it 'includes an array of alternative hostnames as the 2nd value' do + @array[1].should be_an_instance_of(Array) + end + + it 'includes the address family as the 3rd value' do + @array[2].should be_kind_of(Integer) + end + + it 'includes the IP addresses as all the remaining values' do + ips = %w{::1 127.0.0.1} + + ips.include?(@array[3]).should == true + + # Not all machines might have both IPv4 and IPv6 set up, so this value is + # optional. + ips.include?(@array[4]).should == true if @array[4] + end + end + end + + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'the returned Array' do + before do + suppress_warning do + @array = TCPSocket.gethostbyname(ip_address) + end + end + + it 'includes the IP address as the 1st value' do + @array[0].should == ip_address + end + + it 'includes an empty list of aliases as the 2nd value' do + @array[1].should == [] + end + + it 'includes the address family as the 3rd value' do + @array[2].should == family + end + + it 'includes the IP address as the 4th value' do + @array[3].should == ip_address + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb new file mode 100644 index 0000000000..d7feb9751b --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb @@ -0,0 +1,100 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/new' + +describe 'TCPSocket#initialize' do + it_behaves_like :tcpsocket_new, :new + + describe "with a running server" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "does not use the given block and warns to use TCPSocket::open" do + -> { + @socket = TCPSocket.new(@hostname, @server.port, nil) { raise } + }.should complain(/warning: TCPSocket::new\(\) does not take block; use TCPSocket::open\(\) instead/) + end + end +end + +describe 'TCPSocket#initialize' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'when no server is listening on the given address' do + it 'raises Errno::ECONNREFUSED' do + -> { TCPSocket.new(ip_address, 666) }.should raise_error(Errno::ECONNREFUSED) + end + end + + describe 'when a server is listening on the given address' do + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + end + + after do + @client.close if @client + @server.close + end + + it 'returns a TCPSocket when using an Integer as the port' do + @client = TCPSocket.new(ip_address, @port) + @client.should be_an_instance_of(TCPSocket) + end + + it 'returns a TCPSocket when using a String as the port' do + @client = TCPSocket.new(ip_address, @port.to_s) + @client.should be_an_instance_of(TCPSocket) + end + + it 'raises SocketError when the port number is a non numeric String' do + -> { TCPSocket.new(ip_address, 'cats') }.should raise_error(SocketError) + end + + it 'set the socket to binmode' do + @client = TCPSocket.new(ip_address, @port) + @client.binmode?.should be_true + end + + it 'connects to the right address' do + @client = TCPSocket.new(ip_address, @port) + + @client.remote_address.ip_address.should == @server.local_address.ip_address + @client.remote_address.ip_port.should == @server.local_address.ip_port + end + + platform_is_not :windows do + it "creates a socket which is set to nonblocking" do + require 'io/nonblock' + @client = TCPSocket.new(ip_address, @port) + @client.should.nonblock? + end + end + + it "creates a socket which is set to close on exec" do + @client = TCPSocket.new(ip_address, @port) + @client.should.close_on_exec? + end + + describe 'using a local address and service' do + it 'binds the client socket to the local address and service' do + @client = TCPSocket.new(ip_address, @port, ip_address, 0) + + @client.local_address.ip_address.should == ip_address + + @client.local_address.ip_port.should > 0 + @client.local_address.ip_port.should_not == @port + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/local_address_spec.rb b/spec/ruby/library/socket/tcpsocket/local_address_spec.rb new file mode 100644 index 0000000000..ce66d5ff8f --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/local_address_spec.rb @@ -0,0 +1,73 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPSocket#local_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = TCPSocket.new(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @sock.local_address.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @sock.local_address.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + + it 'uses a randomly assigned local port' do + @sock.local_address.ip_port.should > 0 + @sock.local_address.ip_port.should_not == @port + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = TCPSocket.new(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/open_spec.rb b/spec/ruby/library/socket/tcpsocket/open_spec.rb new file mode 100644 index 0000000000..0c0b579064 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/open_spec.rb @@ -0,0 +1,6 @@ +require_relative "../../../spec_helper" +require_relative 'shared/new' + +describe "TCPSocket.open" do + it_behaves_like :tcpsocket_new, :open +end diff --git a/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb b/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb new file mode 100644 index 0000000000..d365ecd335 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' + +describe "TCPSocket partial closability" do + + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @s1 = TCPSocket.new("127.0.0.1", @server.addr[1]) + @s2 = @server.accept + end + + after :each do + @server.close + @s1.close + @s2.close + end + + it_should_behave_like :partially_closable_sockets + +end diff --git a/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb new file mode 100644 index 0000000000..6ce5a41b58 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb @@ -0,0 +1,48 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPSocket#recv_nonblock" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "returns a String read from the socket" do + @socket = TCPSocket.new @hostname, @server.port + @socket.write "TCPSocket#recv_nonblock" + + # Wait for the server to echo. This spec is testing the return + # value, not the non-blocking behavior. + # + # TODO: Figure out a good way to test non-blocking. + IO.select([@socket]) + @socket.recv_nonblock(50).should == "TCPSocket#recv_nonblock" + end + + it 'writes the read to a buffer from the socket' do + @socket = TCPSocket.new @hostname, @server.port + @socket.write "TCPSocket#recv_nonblock" + + # Wait for the server to echo. This spec is testing the return + # value, not the non-blocking behavior. + # + # TODO: Figure out a good way to test non-blocking. + IO.select([@socket]) + buffer = "".b + @socket.recv_nonblock(50, 0, buffer) + buffer.should == 'TCPSocket#recv_nonblock' + end + + it 'returns :wait_readable in exceptionless mode' do + @socket = TCPSocket.new @hostname, @server.port + @socket.recv_nonblock(50, exception: false).should == :wait_readable + end +end diff --git a/spec/ruby/library/socket/tcpsocket/recv_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_spec.rb new file mode 100644 index 0000000000..f380db670d --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/recv_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPSocket#recv' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + @client.close + @server.close + end + + it 'returns the message data' do + @client.write('hello') + + socket = @server.accept + + begin + socket.recv(5).should == 'hello' + ensure + socket.close + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb b/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb new file mode 100644 index 0000000000..eb9dabc075 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb @@ -0,0 +1,72 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPSocket#remote_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = TCPSocket.new(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @sock.remote_address.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @sock.remote_address.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.remote_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + + it 'uses the correct port' do + @sock.remote_address.ip_port.should == @port + end + + it 'uses 0 as the protocol' do + @sock.remote_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = TCPSocket.new(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb b/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb new file mode 100644 index 0000000000..8b728b7522 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb @@ -0,0 +1,45 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPSocket#setsockopt" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + @sock = TCPSocket.new @hostname, @server.port + end + + after :each do + @sock.close unless @sock.closed? + @server.shutdown + end + + describe "using constants" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1).should == 0 + end + end + + describe "using symbols" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1).should == 0 + end + + context "without prefix" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt(:TCP, :NODELAY, 1).should == 0 + end + end + end + + describe "using strings" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt('IPPROTO_TCP', 'TCP_NODELAY', 1).should == 0 + end + + context "without prefix" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt('TCP', 'NODELAY', 1).should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/shared/new.rb b/spec/ruby/library/socket/tcpsocket/shared/new.rb new file mode 100644 index 0000000000..0e405253c8 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/shared/new.rb @@ -0,0 +1,102 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :tcpsocket_new, shared: true do + it "requires a hostname and a port as arguments" do + -> { TCPSocket.send(@method) }.should raise_error(ArgumentError) + end + + it "refuses the connection when there is no server to connect to" do + -> do + TCPSocket.send(@method, SocketSpecs.hostname, SocketSpecs.reserved_unused_port) + end.should raise_error(SystemCallError) {|e| + [Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL].should include(e.class) + } + end + + it 'raises IO::TimeoutError with :connect_timeout when no server is listening on the given address' do + -> { + TCPSocket.send(@method, "192.0.2.1", 80, connect_timeout: 0) + }.should raise_error(IO::TimeoutError) + rescue Errno::ENETUNREACH + # In the case all network interfaces down. + # raise_error cannot deal with multiple expected exceptions + end + + describe "with a running server" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "silently ignores 'nil' as the third parameter" do + @socket = TCPSocket.send(@method, @hostname, @server.port, nil) + @socket.should be_an_instance_of(TCPSocket) + end + + it "connects to a listening server with host and port" do + @socket = TCPSocket.send(@method, @hostname, @server.port) + @socket.should be_an_instance_of(TCPSocket) + end + + it "connects to a server when passed local_host argument" do + @socket = TCPSocket.send(@method, @hostname, @server.port, @hostname) + @socket.should be_an_instance_of(TCPSocket) + end + + it "connects to a server when passed local_host and local_port arguments" do + retries = 0 + max_retries = 3 + + begin + retries += 1 + server = TCPServer.new(SocketSpecs.hostname, 0) + begin + available_port = server.addr[1] + ensure + server.close + end + @socket = TCPSocket.send(@method, @hostname, @server.port, + @hostname, available_port) + rescue Errno::EADDRINUSE + raise if retries >= max_retries + retry + end + @socket.should be_an_instance_of(TCPSocket) + end + + it "has an address once it has connected to a listening server" do + @socket = TCPSocket.send(@method, @hostname, @server.port) + @socket.should be_an_instance_of(TCPSocket) + + # TODO: Figure out how to abstract this. You can get AF_INET + # from 'Socket.getaddrinfo(hostname, nil)[0][3]' but socket.addr + # will return AF_INET6. At least this check will weed out clearly + # erroneous values. + @socket.addr[0].should =~ /^AF_INET6?/ + + case @socket.addr[0] + when 'AF_INET' + @socket.addr[3].should == SocketSpecs.addr(:ipv4) + when 'AF_INET6' + @socket.addr[3].should == SocketSpecs.addr(:ipv6) + end + + @socket.addr[1].should be_kind_of(Integer) + @socket.addr[2].should =~ /^#{@hostname}/ + end + + it "connects to a server when passed connect_timeout argument" do + @socket = TCPSocket.send(@method, @hostname, @server.port, connect_timeout: 1) + @socket.should be_an_instance_of(TCPSocket) + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/bind_spec.rb b/spec/ruby/library/socket/udpsocket/bind_spec.rb new file mode 100644 index 0000000000..08b386e941 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/bind_spec.rb @@ -0,0 +1,83 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket#bind" do + before :each do + @socket = UDPSocket.new + end + + after :each do + @socket.close unless @socket.closed? + end + + it "binds the socket to a port" do + @socket.bind(SocketSpecs.hostname, 0) + @socket.addr[1].should be_kind_of(Integer) + end + + it "raises Errno::EINVAL when already bound" do + @socket.bind(SocketSpecs.hostname, 0) + + -> { + @socket.bind(SocketSpecs.hostname, @socket.addr[1]) + }.should raise_error(Errno::EINVAL) + end + + it "receives a hostname and a port" do + @socket.bind(SocketSpecs.hostname, 0) + + port, host = Socket.unpack_sockaddr_in(@socket.getsockname) + + host.should == "127.0.0.1" + port.should == @socket.addr[1] + end + + it "binds to INADDR_ANY if the hostname is empty" do + @socket.bind("", 0).should == 0 + port, host = Socket.unpack_sockaddr_in(@socket.getsockname) + host.should == "0.0.0.0" + port.should == @socket.addr[1] + end +end + +describe 'UDPSocket#bind' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @socket = UDPSocket.new(family) + end + + after do + @socket.close + end + + it 'binds to an address and port' do + @socket.bind(ip_address, 0).should == 0 + + @socket.local_address.ip_address.should == ip_address + @socket.local_address.ip_port.should > 0 + end + + it 'binds to an address and port using String arguments' do + @socket.bind(ip_address, '0').should == 0 + + @socket.local_address.ip_address.should == ip_address + @socket.local_address.ip_port.should > 0 + end + + it 'can receive data after being bound to an address' do + @socket.bind(ip_address, 0) + + addr = @socket.connect_address + client = UDPSocket.new(family) + + client.connect(addr.ip_address, addr.ip_port) + client.write('hello') + + begin + @socket.recv(6).should == 'hello' + ensure + client.close + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/connect_spec.rb b/spec/ruby/library/socket/udpsocket/connect_spec.rb new file mode 100644 index 0000000000..d92bdeb981 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/connect_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#connect' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @socket = UDPSocket.new(family) + end + + after do + @socket.close + end + + it 'connects to an address even when it is not used' do + @socket.connect(ip_address, 9996).should == 0 + end + + it 'can send data after connecting' do + receiver = UDPSocket.new(family) + + receiver.bind(ip_address, 0) + + addr = receiver.connect_address + + @socket.connect(addr.ip_address, addr.ip_port) + @socket.write('hello') + + begin + receiver.recv(6).should == 'hello' + ensure + receiver.close + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/initialize_spec.rb b/spec/ruby/library/socket/udpsocket/initialize_spec.rb new file mode 100644 index 0000000000..ecf0043c10 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/initialize_spec.rb @@ -0,0 +1,53 @@ +require_relative '../spec_helper' + +describe 'UDPSocket#initialize' do + after do + @socket.close if @socket + end + + it 'initializes a new UDPSocket' do + @socket = UDPSocket.new + @socket.should be_an_instance_of(UDPSocket) + end + + it 'initializes a new UDPSocket using an Integer' do + @socket = UDPSocket.new(Socket::AF_INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'initializes a new UDPSocket using a Symbol' do + @socket = UDPSocket.new(:INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'initializes a new UDPSocket using a String' do + @socket = UDPSocket.new('INET') + @socket.should be_an_instance_of(UDPSocket) + end + + it 'sets the socket to binmode' do + @socket = UDPSocket.new(:INET) + @socket.binmode?.should be_true + end + + platform_is_not :windows do + it 'sets the socket to nonblock' do + require 'io/nonblock' + @socket = UDPSocket.new(:INET) + @socket.should.nonblock? + end + end + + it 'sets the socket to close on exec' do + @socket = UDPSocket.new(:INET) + @socket.should.close_on_exec? + end + + it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT when given an invalid address family' do + -> { + UDPSocket.new(666) + }.should raise_error(SystemCallError) { |e| + [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class) + } + end +end diff --git a/spec/ruby/library/socket/udpsocket/inspect_spec.rb b/spec/ruby/library/socket/udpsocket/inspect_spec.rb new file mode 100644 index 0000000000..e212120b14 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/inspect_spec.rb @@ -0,0 +1,17 @@ +require_relative '../spec_helper' + +describe 'UDPSocket#inspect' do + before do + @socket = UDPSocket.new + @socket.bind('127.0.0.1', 0) + end + + after do + @socket.close + end + + it 'returns a String with the fd, family, address and port' do + port = @socket.addr[1] + @socket.inspect.should == "#<UDPSocket:fd #{@socket.fileno}, AF_INET, 127.0.0.1, #{port}>" + end +end diff --git a/spec/ruby/library/socket/udpsocket/local_address_spec.rb b/spec/ruby/library/socket/udpsocket/local_address_spec.rb new file mode 100644 index 0000000000..92e4cc10c7 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/local_address_spec.rb @@ -0,0 +1,80 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#local_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM, Socket::IPPROTO_UDP) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses the correct address family' do + @sock.local_address.afamily.should == family + end + + it 'uses the correct protocol family' do + @sock.local_address.pfamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + + it 'uses a randomly assigned local port' do + @sock.local_address.ip_port.should > 0 + @sock.local_address.ip_port.should_not == @port + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/new_spec.rb b/spec/ruby/library/socket/udpsocket/new_spec.rb new file mode 100644 index 0000000000..79bfcb624d --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/new_spec.rb @@ -0,0 +1,40 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket.new' do + after :each do + @socket.close if @socket && !@socket.closed? + end + + it 'without arguments' do + @socket = UDPSocket.new + @socket.should be_an_instance_of(UDPSocket) + end + + it 'using Integer argument' do + @socket = UDPSocket.new(Socket::AF_INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'using Symbol argument' do + @socket = UDPSocket.new(:INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'using String argument' do + @socket = UDPSocket.new('INET') + @socket.should be_an_instance_of(UDPSocket) + end + + it "does not use the given block and warns to use UDPSocket::open" do + -> { + @socket = UDPSocket.new { raise } + }.should complain(/warning: UDPSocket::new\(\) does not take block; use UDPSocket::open\(\) instead/) + end + + it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT if unsupported family passed' do + -> { UDPSocket.new(-1) }.should raise_error(SystemCallError) { |e| + [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class) + } + end +end diff --git a/spec/ruby/library/socket/udpsocket/open_spec.rb b/spec/ruby/library/socket/udpsocket/open_spec.rb new file mode 100644 index 0000000000..e4dbb2ee2a --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/open_spec.rb @@ -0,0 +1,13 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket.open" do + after :each do + @socket.close if @socket && !@socket.closed? + end + + it "allows calls to open without arguments" do + @socket = UDPSocket.open + @socket.should be_kind_of(UDPSocket) + end +end diff --git a/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb new file mode 100644 index 0000000000..b804099589 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb @@ -0,0 +1,111 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#recvfrom_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(ip_address, 0) + + addr = @server.connect_address + + @client.connect(addr.ip_address, addr.ip_port) + end + + describe 'without any data available' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable with exception: false' do + @server.recvfrom_nonblock(1, exception: false).should == :wait_readable + end + end + + platform_is_not :windows do + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns an Array containing the data and an Array' do + IO.select([@server]) + @server.recvfrom_nonblock(1).should be_an_instance_of(Array) + end + + it 'writes the data to the buffer when one is present' do + buffer = "".b + IO.select([@server]) + @server.recvfrom_nonblock(1, 0, buffer) + buffer.should == 'h' + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + IO.select([@server]) + message, = @server.recvfrom_nonblock(1, 0, buffer) + + message.should.equal?(buffer) + buffer.encoding.should == Encoding::ISO_8859_1 + end + + describe 'the returned Array' do + before do + IO.select([@server]) + @array = @server.recvfrom_nonblock(1) + end + + it 'contains the data at index 0' do + @array[0].should == 'h' + end + + it 'contains an Array at index 1' do + @array[1].should be_an_instance_of(Array) + end + end + + describe 'the returned address Array' do + before do + IO.select([@server]) + @addr = @server.recvfrom_nonblock(1)[1] + end + + it 'uses the correct address family' do + @addr[0].should == family_name + end + + it 'uses the port of the client' do + @addr[1].should == @client.local_address.ip_port + end + + it 'uses the hostname of the client' do + @addr[2].should == ip_address + end + + it 'uses the IP address of the client' do + @addr[3].should == ip_address + end + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/remote_address_spec.rb b/spec/ruby/library/socket/udpsocket/remote_address_spec.rb new file mode 100644 index 0000000000..94889ce560 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/remote_address_spec.rb @@ -0,0 +1,79 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#remote_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM, Socket::IPPROTO_UDP) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses the correct address family' do + @sock.remote_address.afamily.should == family + end + + it 'uses the correct protocol family' do + @sock.remote_address.pfamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @sock.remote_address.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + + it 'uses the correct port' do + @sock.remote_address.ip_port.should == @port + end + + it 'uses 0 as the protocol' do + @sock.remote_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/send_spec.rb b/spec/ruby/library/socket/udpsocket/send_spec.rb new file mode 100644 index 0000000000..6dd5f67bea --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/send_spec.rb @@ -0,0 +1,154 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket#send" do + before :each do + @port = nil + @server_thread = Thread.new do + @server = UDPSocket.open + begin + @server.bind(nil, 0) + @port = @server.addr[1] + begin + @msg = @server.recvfrom_nonblock(64) + rescue IO::WaitReadable + IO.select([@server]) + retry + end + ensure + @server.close if !@server.closed? + end + end + Thread.pass while @server_thread.status and !@port + end + + after :each do + @server_thread.join + end + + it "sends data in ad hoc mode" do + @socket = UDPSocket.open + @socket.send("ad hoc", 0, SocketSpecs.hostname, @port) + @socket.close + @server_thread.join + + @msg[0].should == "ad hoc" + @msg[1][0].should == "AF_INET" + @msg[1][1].should be_kind_of(Integer) + @msg[1][3].should == "127.0.0.1" + end + + it "sends data in ad hoc mode (with port given as a String)" do + @socket = UDPSocket.open + @socket.send("ad hoc", 0, SocketSpecs.hostname, @port.to_s) + @socket.close + @server_thread.join + + @msg[0].should == "ad hoc" + @msg[1][0].should == "AF_INET" + @msg[1][1].should be_kind_of(Integer) + @msg[1][3].should == "127.0.0.1" + end + + it "sends data in connection mode" do + @socket = UDPSocket.open + @socket.connect(SocketSpecs.hostname, @port) + @socket.send("connection-based", 0) + @socket.close + @server_thread.join + + @msg[0].should == "connection-based" + @msg[1][0].should == "AF_INET" + @msg[1][1].should be_kind_of(Integer) + @msg[1][3].should == "127.0.0.1" + end + + it "raises EMSGSIZE if data is too big" do + @socket = UDPSocket.open + begin + -> do + @socket.send('1' * 100_000, 0, SocketSpecs.hostname, @port.to_s) + end.should raise_error(Errno::EMSGSIZE) + ensure + @socket.send("ad hoc", 0, SocketSpecs.hostname, @port) + @socket.close + @server_thread.join + end + end +end + +describe 'UDPSocket#send' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + + @server.bind(ip_address, 0) + + @addr = @server.connect_address + end + + after do + @server.close + @client.close + end + + describe 'using a disconnected socket' do + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as separate arguments' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @addr.ip_address, @addr.ip_port).should == 5 + end + + it 'does not persist the connection after sending data' do + @client.send('hello', 0, @addr.ip_address, @addr.ip_port) + + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as a single String argument' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @server.getsockname).should == 5 + end + end + end + + describe 'using a connected socket' do + describe 'without an explicit destination address' do + before do + @client.connect(@addr.ip_address, @addr.ip_port) + end + + it 'returns the amount of bytes written' do + @client.send('hello', 0).should == 5 + end + end + + describe 'with an explicit destination address' do + before do + @alt_server = UDPSocket.new(family) + + @alt_server.bind(ip_address, 0) + end + + after do + @alt_server.close + end + + it 'sends the data to the given address instead' do + @client.send('hello', 0, @alt_server.getsockname).should == 5 + + -> { @server.recv(5) }.should block_caller + + @alt_server.recv(5).should == 'hello' + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/write_spec.rb b/spec/ruby/library/socket/udpsocket/write_spec.rb new file mode 100644 index 0000000000..c971f29b62 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/write_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket#write" do + it "raises EMSGSIZE if msg is too long" do + begin + host = SocketSpecs.hostname + s1 = UDPSocket.new + s1.bind(host, 0) + s2 = UDPSocket.new + s2.connect(host, s1.addr[1]) + + -> do + s2.write('1' * 100_000) + end.should raise_error(Errno::EMSGSIZE) + ensure + s1.close if s1 && !s1.closed? + s2.close if s2 && !s2.closed? + end + end +end diff --git a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb new file mode 100644 index 0000000000..f67941b296 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb @@ -0,0 +1,87 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXServer#accept_nonblock" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + + @socket = @server.accept_nonblock + @client.send("foobar", 0) + end + + after :each do + @socket.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "accepts a connection in a non-blocking way" do + data = @socket.recvfrom(6).first + data.should == "foobar" + end + + it "returns a UNIXSocket" do + @socket.should be_kind_of(UNIXSocket) + end + + it 'returns :wait_readable in exceptionless mode' do + @server.accept_nonblock(exception: false).should == :wait_readable + end +end + +describe 'UNIXServer#accept_nonblock' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r(@path) + end + + describe 'without a client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + end + + describe 'with a client' do + before do + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @socket.close if @socket + end + + describe 'without any data' do + it 'returns a UNIXSocket' do + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(UNIXSocket) + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns a UNIXSocket' do + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(UNIXSocket) + end + + describe 'the returned UNIXSocket' do + it 'can read the data written' do + @socket = @server.accept_nonblock + @socket.recv(5).should == 'hello' + end + end + end + end +end diff --git a/spec/ruby/library/socket/unixserver/accept_spec.rb b/spec/ruby/library/socket/unixserver/accept_spec.rb new file mode 100644 index 0000000000..cc2c922b6f --- /dev/null +++ b/spec/ruby/library/socket/unixserver/accept_spec.rb @@ -0,0 +1,126 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXServer#accept" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end + + after :each do + @server.close if @server + SocketSpecs.rm_socket @path + end + + it "accepts what is written by the client" do + client = UNIXSocket.open(@path) + + client.send('hello', 0) + + sock = @server.accept + begin + data, info = sock.recvfrom(5) + + data.should == 'hello' + info.should_not be_empty + ensure + sock.close + client.close + end + end + + it "can be interrupted by Thread#kill" do + t = Thread.new { + @server.accept + } + Thread.pass while t.status and t.status != "sleep" + + # kill thread, ensure it dies in a reasonable amount of time + t.kill + a = 0 + while t.alive? and a < 5000 + sleep 0.001 + a += 1 + end + a.should < 5000 + end + + it "can be interrupted by Thread#raise" do + t = Thread.new { + -> { + @server.accept + }.should raise_error(Exception, "interrupted") + } + + Thread.pass while t.status and t.status != "sleep" + t.raise Exception, "interrupted" + t.join + end +end + +describe 'UNIXServer#accept' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r(@path) + end + + describe 'without a client' do + it 'blocks the calling thread' do + -> { @server.accept }.should block_caller + end + end + + describe 'with a client' do + before do + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @socket.close if @socket + end + + describe 'without any data' do + it 'returns a UNIXSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(UNIXSocket) + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns a UNIXSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(UNIXSocket) + end + + describe 'the returned UNIXSocket' do + it 'can read the data written' do + @socket = @server.accept + @socket.recv(5).should == 'hello' + end + + platform_is_not :windows do + it "is set to nonblocking" do + require 'io/nonblock' + @socket = @server.accept + @socket.should.nonblock? + end + end + + it "is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? + end + end + end + end +end diff --git a/spec/ruby/library/socket/unixserver/for_fd_spec.rb b/spec/ruby/library/socket/unixserver/for_fd_spec.rb new file mode 100644 index 0000000000..be1c2df4d7 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/for_fd_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXServer.for_fd" do + before :each do + @unix_path = SocketSpecs.socket_path + @unix = UNIXServer.new(@unix_path) + end + + after :each do + @unix.close if @unix + SocketSpecs.rm_socket @unix_path + end + + it "can calculate the path" do + b = UNIXServer.for_fd(@unix.fileno) + b.autoclose = false + + b.path.should == @unix_path + end +end diff --git a/spec/ruby/library/socket/unixserver/initialize_spec.rb b/spec/ruby/library/socket/unixserver/initialize_spec.rb new file mode 100644 index 0000000000..3728a307b0 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/initialize_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXServer#initialize' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close if @server + rm_r @path + end + + it 'returns a new UNIXServer' do + @server.should be_an_instance_of(UNIXServer) + end + + it 'sets the socket to binmode' do + @server.binmode?.should be_true + end + + it 'raises Errno::EADDRINUSE when the socket is already in use' do + -> { UNIXServer.new(@path) }.should raise_error(Errno::EADDRINUSE) + end +end diff --git a/spec/ruby/library/socket/unixserver/listen_spec.rb b/spec/ruby/library/socket/unixserver/listen_spec.rb new file mode 100644 index 0000000000..7938d648c4 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/listen_spec.rb @@ -0,0 +1,19 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXServer#listen' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + + rm_r(@path) + end + + it 'returns 0' do + @server.listen(1).should == 0 + end +end diff --git a/spec/ruby/library/socket/unixserver/new_spec.rb b/spec/ruby/library/socket/unixserver/new_spec.rb new file mode 100644 index 0000000000..7d0c7bf76e --- /dev/null +++ b/spec/ruby/library/socket/unixserver/new_spec.rb @@ -0,0 +1,12 @@ +require_relative '../spec_helper' +require_relative 'shared/new' + +describe "UNIXServer.new" do + it_behaves_like :unixserver_new, :new + + it "does not use the given block and warns to use UNIXServer::open" do + -> { + @server = UNIXServer.new(@path) { raise } + }.should complain(/warning: UNIXServer::new\(\) does not take block; use UNIXServer::open\(\) instead/) + end +end diff --git a/spec/ruby/library/socket/unixserver/open_spec.rb b/spec/ruby/library/socket/unixserver/open_spec.rb new file mode 100644 index 0000000000..c49df802d0 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/open_spec.rb @@ -0,0 +1,24 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/new' + +describe "UNIXServer.open" do + it_behaves_like :unixserver_new, :open + + before :each do + @path = SocketSpecs.socket_path + end + + after :each do + @server.close if @server + @server = nil + SocketSpecs.rm_socket @path + end + + it "yields the new UNIXServer object to the block, if given" do + UNIXServer.open(@path) do |unix| + unix.path.should == @path + unix.addr.should == ["AF_UNIX", @path] + end + end +end diff --git a/spec/ruby/library/socket/unixserver/shared/new.rb b/spec/ruby/library/socket/unixserver/shared/new.rb new file mode 100644 index 0000000000..b537f2a871 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/shared/new.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :unixserver_new, shared: true do + before :each do + @path = SocketSpecs.socket_path + end + + after :each do + @server.close if @server + @server = nil + SocketSpecs.rm_socket @path + end + + it "creates a new UNIXServer" do + @server = UNIXServer.send(@method, @path) + @server.path.should == @path + @server.addr.should == ["AF_UNIX", @path] + end +end diff --git a/spec/ruby/library/socket/unixserver/sysaccept_spec.rb b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb new file mode 100644 index 0000000000..c4a4ecc824 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb @@ -0,0 +1,50 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXServer#sysaccept' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + + rm_r(@path) + end + + describe 'without a client' do + it 'blocks the calling thread' do + -> { @server.sysaccept }.should block_caller + end + end + + describe 'with a client' do + before do + @client = UNIXSocket.new(@path) + end + + after do + Socket.for_fd(@fd).close if @fd + @client.close + end + + describe 'without any data' do + it 'returns an Integer' do + @fd = @server.sysaccept + @fd.should be_kind_of(Integer) + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns an Integer' do + @fd = @server.sysaccept + @fd.should be_kind_of(Integer) + end + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/addr_spec.rb b/spec/ruby/library/socket/unixsocket/addr_spec.rb new file mode 100644 index 0000000000..1afe9b12dc --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/addr_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#addr" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "returns an array" do + @client.addr.should be_kind_of(Array) + end + + it "returns the address family of this socket in an array" do + @client.addr[0].should == "AF_UNIX" + @server.addr[0].should == "AF_UNIX" + end + + it "returns the path of the socket in an array if it's a server" do + @server.addr[1].should == @path + end + + it "returns an empty string for path if it's a client" do + @client.addr[1].should == "" + end +end diff --git a/spec/ruby/library/socket/unixsocket/initialize_spec.rb b/spec/ruby/library/socket/unixsocket/initialize_spec.rb new file mode 100644 index 0000000000..5dccfcc745 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/initialize_spec.rb @@ -0,0 +1,56 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXSocket#initialize' do + describe 'using a non existing path' do + platform_is_not :windows do + it 'raises Errno::ENOENT' do + -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ENOENT) + end + end + + platform_is :windows do + # Why, Windows, why? + it 'raises Errno::ECONNREFUSED' do + -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ECONNREFUSED) + end + end + end + + describe 'using an existing socket path' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @socket = UNIXSocket.new(@path) + end + + after do + @socket.close + @server.close + rm_r(@path) + end + + it 'returns a new UNIXSocket' do + @socket.should be_an_instance_of(UNIXSocket) + end + + it 'sets the socket path to an empty String' do + @socket.path.should == '' + end + + it 'sets the socket to binmode' do + @socket.binmode?.should be_true + end + + platform_is_not :windows do + it 'sets the socket to nonblock' do + require 'io/nonblock' + @socket.should.nonblock? + end + end + + it 'sets the socket to close on exec' do + @socket.should.close_on_exec? + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/inspect_spec.rb b/spec/ruby/library/socket/unixsocket/inspect_spec.rb new file mode 100644 index 0000000000..77bb521069 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/inspect_spec.rb @@ -0,0 +1,15 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#inspect" do + it "returns sockets fd for unnamed sockets" do + begin + s1, s2 = UNIXSocket.socketpair + s1.inspect.should == "#<UNIXSocket:fd #{s1.fileno}>" + s2.inspect.should == "#<UNIXSocket:fd #{s2.fileno}>" + ensure + s1.close + s2.close + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/local_address_spec.rb b/spec/ruby/library/socket/unixsocket/local_address_spec.rb new file mode 100644 index 0000000000..0fdec38293 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/local_address_spec.rb @@ -0,0 +1,92 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXSocket#local_address' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + + rm_r(@path) + end + + it 'returns an Addrinfo' do + @client.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + platform_is_not :aix do + it 'uses AF_UNIX as the address family' do + @client.local_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @client.local_address.pfamily.should == Socket::PF_UNIX + end + end + + it 'uses SOCK_STREAM as the socket type' do + @client.local_address.socktype.should == Socket::SOCK_STREAM + end + + platform_is_not :aix do + it 'uses an empty socket path' do + @client.local_address.unix_path.should == '' + end + end + + it 'uses 0 as the protocol' do + @client.local_address.protocol.should == 0 + end + end +end + +describe 'UNIXSocket#local_address with a UNIX socket pair' do + before :each do + @sock, @sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) + end + + after :each do + @sock.close + @sock2.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_UNIX as the address family' do + @sock.local_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @sock.local_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end + + it 'raises SocketError for #ip_address' do + -> { + @sock.local_address.ip_address + }.should raise_error(SocketError, "need IPv4 or IPv6 address") + end + + it 'raises SocketError for #ip_port' do + -> { + @sock.local_address.ip_port + }.should raise_error(SocketError, "need IPv4 or IPv6 address") + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/new_spec.rb b/spec/ruby/library/socket/unixsocket/new_spec.rb new file mode 100644 index 0000000000..fea2c1e2b7 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/new_spec.rb @@ -0,0 +1,12 @@ +require_relative '../spec_helper' +require_relative 'shared/new' + +describe "UNIXSocket.new" do + it_behaves_like :unixsocket_new, :new + + it "does not use the given block and warns to use UNIXSocket::open" do + -> { + @client = UNIXSocket.new(@path) { raise } + }.should complain(/warning: UNIXSocket::new\(\) does not take block; use UNIXSocket::open\(\) instead/) + end +end diff --git a/spec/ruby/library/socket/unixsocket/open_spec.rb b/spec/ruby/library/socket/unixsocket/open_spec.rb new file mode 100644 index 0000000000..b5e8c6c23a --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/open_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/new' + +describe "UNIXSocket.open" do + it_behaves_like :unixsocket_new, :open +end + +describe "UNIXSocket.open" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end + + after :each do + @server.close + SocketSpecs.rm_socket @path + end + + it "opens a unix socket on the specified file and yields it to the block" do + UNIXSocket.open(@path) do |client| + client.addr[0].should == "AF_UNIX" + client.should_not.closed? + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/pair_spec.rb b/spec/ruby/library/socket/unixsocket/pair_spec.rb new file mode 100644 index 0000000000..9690142668 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/pair_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' +require_relative 'shared/pair' + +describe "UNIXSocket.pair" do + it_should_behave_like :unixsocket_pair + it_should_behave_like :partially_closable_sockets + + before :each do + @s1, @s2 = UNIXSocket.pair + end + + after :each do + @s1.close + @s2.close + end +end diff --git a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb new file mode 100644 index 0000000000..108a6c3063 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' + +describe "UNIXSocket partial closability" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @s1 = UNIXSocket.new(@path) + @s2 = @server.accept + end + + after :each do + @server.close + @s1.close + @s2.close + SocketSpecs.rm_socket @path + end + + it_should_behave_like :partially_closable_sockets +end diff --git a/spec/ruby/library/socket/unixsocket/path_spec.rb b/spec/ruby/library/socket/unixsocket/path_spec.rb new file mode 100644 index 0000000000..ffe7e4bea2 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/path_spec.rb @@ -0,0 +1,24 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#path" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "returns the path of the socket if it's a server" do + @server.path.should == @path + end + + it "returns an empty string for path if it's a client" do + @client.path.should == "" + end +end diff --git a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb new file mode 100644 index 0000000000..10cab13b42 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#peeraddr" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "returns the address family and path of the server end of the connection" do + @client.peeraddr.should == ["AF_UNIX", @path] + end + + it "raises an error in server sockets" do + -> { + @server.peeraddr + }.should raise_error(Errno::ENOTCONN) + end +end diff --git a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb new file mode 100644 index 0000000000..f0b408f309 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb @@ -0,0 +1,84 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +platform_is_not :windows do + describe "UNIXSocket#recv_io" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + + @send_io_path = File.expand_path('../../fixtures/send_io.txt', __FILE__) + @file = File.open(@send_io_path) + end + + after :each do + @io.close if @io + @socket.close if @socket + + @file.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "reads an IO object across the socket" do + @client.send_io(@file) + + @socket = @server.accept + @io = @socket.recv_io + + @io.read.should == File.read(@send_io_path) + end + + it "takes an optional class to use" do + @client.send_io(@file) + + @socket = @server.accept + @io = @socket.recv_io(File) + + @io.should be_an_instance_of(File) + end + end + + describe 'UNIXSocket#recv_io' do + before do + @file = File.open('/dev/null', 'w') + @client, @server = UNIXSocket.socketpair + end + + after do + @client.close + @server.close + @io.close if @io + @file.close + end + + describe 'without a custom class' do + it 'returns an IO' do + @client.send_io(@file) + + @io = @server.recv_io + @io.should be_an_instance_of(IO) + end + end + + describe 'with a custom class' do + it 'returns an instance of the custom class' do + @client.send_io(@file) + + @io = @server.recv_io(File) + @io.should be_an_instance_of(File) + end + end + + describe 'with a custom mode' do + it 'opens the IO using the given mode' do + @client.send_io(@file) + + @io = @server.recv_io(File, File::WRONLY) + @io.should be_an_instance_of(File) + end + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb new file mode 100644 index 0000000000..9ae3777961 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb @@ -0,0 +1,174 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#recvfrom" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "receives len bytes from sock, returning an array containing sent data as first element" do + @client.send("foobar", 0) + sock = @server.accept + sock.recvfrom(6).first.should == "foobar" + sock.close + end + + context "when called on a server's socket" do + platform_is_not :windows do + it "returns an array containing basic information on the client as second element" do + @client.send("foobar", 0) + sock = @server.accept + data = sock.recvfrom(6) + data.last.should == ["AF_UNIX", ""] + sock.close + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it "returns an array containing basic information on the client as second element" do + @client.send("foobar", 0) + sock = @server.accept + data = sock.recvfrom(6) + data.last.should == ["AF_UNIX", ""] + sock.close + end + end + end + + context "when called on a client's socket" do + platform_is :linux do + it "returns an array containing server's address as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + @client.recvfrom(6).last.should == ["AF_UNIX", @server.local_address.unix_path] + sock.close + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it "returns an array containing server's address as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + # This may not be correct, depends on what underlying recvfrom actually returns. + @client.recvfrom(6).last.should == ["AF_UNIX", @server.local_address.unix_path] + sock.close + end + end + + platform_is :darwin do + it "returns an array containing basic information on the server as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + @client.recvfrom(6).last.should == ["AF_UNIX", ""] + sock.close + end + end + end + + it "allows an output buffer as third argument" do + buffer = +'' + + @client.send("foobar", 0) + sock = @server.accept + message, = sock.recvfrom(6, 0, buffer) + sock.close + + message.should.equal?(buffer) + buffer.should == "foobar" + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + + @client.send("foobar", 0) + sock = @server.accept + sock.recvfrom(6, 0, buffer) + sock.close + + buffer.encoding.should == Encoding::ISO_8859_1 + end + + platform_is_not :windows do + it "uses different message options" do + @client.send("foobar", Socket::MSG_PEEK) + sock = @server.accept + peek_data = sock.recvfrom(6, Socket::MSG_PEEK) # Does not retrieve the message + real_data = sock.recvfrom(6) + + real_data.should == peek_data + peek_data.should == ["foobar", ["AF_UNIX", ""]] + sock.close + end + end +end + +describe 'UNIXSocket#recvfrom' do + describe 'using a socket pair' do + before do + @client, @server = UNIXSocket.socketpair + @client.write('hello') + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + it 'returns an Array containing the data and address information' do + @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it 'returns an Array containing the data and address information' do + @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] + end + end + end + + platform_is_not :windows do + # These specs are taken from the rdoc examples on UNIXSocket#recvfrom. + describe 'using a UNIX socket constructed using UNIXSocket.for_fd' do + before do + @path1 = SocketSpecs.socket_path + @path2 = SocketSpecs.socket_path.chop + '2' + rm_r(@path2) + + @client_raw = Socket.new(:UNIX, :DGRAM) + @client_raw.bind(Socket.sockaddr_un(@path1)) + + @server_raw = Socket.new(:UNIX, :DGRAM) + @server_raw.bind(Socket.sockaddr_un(@path2)) + + @socket = UNIXSocket.for_fd(@server_raw.fileno) + @socket.autoclose = false + end + + after do + @client_raw.close + @server_raw.close # also closes @socket + + rm_r @path1 + rm_r @path2 + end + + it 'returns an Array containing the data and address information' do + @client_raw.send('hello', 0, Socket.sockaddr_un(@path2)) + + @socket.recvfrom(5).should == ['hello', ['AF_UNIX', @path1]] + end + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/remote_address_spec.rb b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb new file mode 100644 index 0000000000..84bdff0a6a --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXSocket#remote_address' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + + rm_r(@path) + end + + it 'returns an Addrinfo' do + @client.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_UNIX as the address family' do + @client.remote_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @client.remote_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @client.remote_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct socket path' do + @client.remote_address.unix_path.should == @path + end + + it 'uses 0 as the protocol' do + @client.remote_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/send_io_spec.rb b/spec/ruby/library/socket/unixsocket/send_io_spec.rb new file mode 100644 index 0000000000..52186ec3cf --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/send_io_spec.rb @@ -0,0 +1,55 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +platform_is_not :windows do + describe "UNIXSocket#send_io" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + + @send_io_path = File.expand_path('../../fixtures/send_io.txt', __FILE__) + @file = File.open(@send_io_path) + end + + after :each do + @io.close if @io + @socket.close if @socket + + @file.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "sends the fd for an IO object across the socket" do + @client.send_io(@file) + + @socket = @server.accept + @io = @socket.recv_io + + @io.read.should == File.read(@send_io_path) + end + end + + describe 'UNIXSocket#send_io' do + before do + @file = File.open('/dev/null', 'w') + @client, @server = UNIXSocket.socketpair + end + + after do + @client.close + @server.close + @io.close if @io + @file.close + end + + it 'sends an IO object' do + @client.send_io(@file) + + @io = @server.recv_io + @io.should be_an_instance_of(IO) + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/shared/new.rb b/spec/ruby/library/socket/unixsocket/shared/new.rb new file mode 100644 index 0000000000..f075b03c5e --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/shared/new.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :unixsocket_new, shared: true do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end + + after :each do + @client.close if @client + @server.close + SocketSpecs.rm_socket @path + end + + it "opens a unix socket on the specified file" do + @client = UNIXSocket.send(@method, @path) + + @client.addr[0].should == "AF_UNIX" + @client.should_not.closed? + end +end diff --git a/spec/ruby/library/socket/unixsocket/shared/pair.rb b/spec/ruby/library/socket/unixsocket/shared/pair.rb new file mode 100644 index 0000000000..d68ee466bf --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/shared/pair.rb @@ -0,0 +1,47 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :unixsocket_pair, shared: true do + it "returns two UNIXSockets" do + @s1.should be_an_instance_of(UNIXSocket) + @s2.should be_an_instance_of(UNIXSocket) + end + + it "returns a pair of connected sockets" do + @s1.puts "foo" + @s2.gets.should == "foo\n" + end + + platform_is_not :windows do + it "sets the socket paths to empty Strings" do + @s1.path.should == "" + @s2.path.should == "" + end + + it "sets the socket addresses to empty Strings" do + @s1.addr.should == ["AF_UNIX", ""] + @s2.addr.should == ["AF_UNIX", ""] + end + + it "sets the socket peer addresses to empty Strings" do + @s1.peeraddr.should == ["AF_UNIX", ""] + @s2.peeraddr.should == ["AF_UNIX", ""] + end + end + + platform_is :windows do + it "emulates unnamed sockets with a temporary file with a path" do + @s1.addr.should == ["AF_UNIX", @s1.path] + @s2.peeraddr.should == ["AF_UNIX", @s1.path] + end + + it "sets the peer address of first socket to an empty string" do + @s1.peeraddr.should == ["AF_UNIX", ""] + end + + it "sets the address and path of second socket to an empty string" do + @s2.addr.should == ["AF_UNIX", ""] + @s2.path.should == "" + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/socketpair_spec.rb b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb new file mode 100644 index 0000000000..c61fc00be4 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' +require_relative 'shared/pair' + +describe "UNIXSocket.socketpair" do + it_should_behave_like :unixsocket_pair + it_should_behave_like :partially_closable_sockets + + before :each do + @s1, @s2 = UNIXSocket.socketpair + end + + after :each do + @s1.close + @s2.close + end +end |
