summaryrefslogtreecommitdiff
path: root/spec/ruby/library/socket/basicsocket
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/library/socket/basicsocket')
-rw-r--r--spec/ruby/library/socket/basicsocket/close_read_spec.rb43
-rw-r--r--spec/ruby/library/socket/basicsocket/close_write_spec.rb48
-rw-r--r--spec/ruby/library/socket/basicsocket/connect_address_spec.rb152
-rw-r--r--spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb103
-rw-r--r--spec/ruby/library/socket/basicsocket/for_fd_spec.rb38
-rw-r--r--spec/ruby/library/socket/basicsocket/getpeereid_spec.rb36
-rw-r--r--spec/ruby/library/socket/basicsocket/getpeername_spec.rb25
-rw-r--r--spec/ruby/library/socket/basicsocket/getsockname_spec.rb28
-rw-r--r--spec/ruby/library/socket/basicsocket/getsockopt_spec.rb188
-rw-r--r--spec/ruby/library/socket/basicsocket/ioctl_spec.rb42
-rw-r--r--spec/ruby/library/socket/basicsocket/local_address_spec.rb10
-rw-r--r--spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb74
-rw-r--r--spec/ruby/library/socket/basicsocket/read_spec.rb47
-rw-r--r--spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb172
-rw-r--r--spec/ruby/library/socket/basicsocket/recv_spec.rb250
-rw-r--r--spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb300
-rw-r--r--spec/ruby/library/socket/basicsocket/recvmsg_spec.rb281
-rw-r--r--spec/ruby/library/socket/basicsocket/remote_address_spec.rb10
-rw-r--r--spec/ruby/library/socket/basicsocket/send_spec.rb220
-rw-r--r--spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb118
-rw-r--r--spec/ruby/library/socket/basicsocket/sendmsg_spec.rb111
-rw-r--r--spec/ruby/library/socket/basicsocket/setsockopt_spec.rb334
-rw-r--r--spec/ruby/library/socket/basicsocket/shutdown_spec.rb155
-rw-r--r--spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb43
24 files changed, 2828 insertions, 0 deletions
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