diff options
Diffstat (limited to 'spec/ruby/library/socket/basicsocket')
24 files changed, 2183 insertions, 164 deletions
diff --git a/spec/ruby/library/socket/basicsocket/close_read_spec.rb b/spec/ruby/library/socket/basicsocket/close_read_spec.rb index c71e1acaf9..35bec203d7 100644 --- a/spec/ruby/library/socket/basicsocket/close_read_spec.rb +++ b/spec/ruby/library/socket/basicsocket/close_read_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "Socket::BasicSocket#close_read" do before :each do @@ -12,32 +12,32 @@ describe "Socket::BasicSocket#close_read" do it "closes the reading end of the socket" do @server.close_read - lambda { @server.read }.should raise_error(IOError) + -> { @server.read }.should.raise(IOError) end - it "it works on sockets with closed ends" do + it 'does not raise when called on a socket already closed for reading' do @server.close_read - lambda { @server.close_read }.should_not raise_error(Exception) - lambda { @server.read }.should raise_error(IOError) + @server.close_read + -> { @server.read }.should.raise(IOError) end - it "does not close the socket" do + it 'does not fully close the socket' do @server.close_read - @server.closed?.should be_false + @server.closed?.should == 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 + @server.closed?.should == true end - it "raises IOError on closed socket" do + it 'raises IOError when called on a fully closed socket' do @server.close - lambda { @server.close_read }.should raise_error(IOError) + -> { @server.close_read }.should.raise(IOError) end it "returns nil" do - @server.close_read.should be_nil + @server.close_read.should == 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 index a00f5d5870..c1b6d9e9ef 100644 --- a/spec/ruby/library/socket/basicsocket/close_write_spec.rb +++ b/spec/ruby/library/socket/basicsocket/close_write_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "Socket::BasicSocket#close_write" do before :each do @@ -12,18 +12,18 @@ describe "Socket::BasicSocket#close_write" do it "closes the writing end of the socket" do @server.close_write - lambda { @server.write("foo") }.should raise_error(IOError) + -> { @server.write("foo") }.should.raise(IOError) end - it "works on sockets with closed write ends" do + it 'does not raise when called on a socket already closed for writing' do @server.close_write - lambda { @server.close_write }.should_not raise_error(Exception) - lambda { @server.write("foo") }.should raise_error(IOError) + @server.close_write + -> { @server.write("foo") }.should.raise(IOError) end - it "does not close the socket" do + it 'does not fully close the socket' do @server.close_write - @server.closed?.should be_false + @server.closed?.should == false end it "does not prevent reading" do @@ -34,15 +34,15 @@ describe "Socket::BasicSocket#close_write" do it "fully closes the socket if it was already closed for reading" do @server.close_read @server.close_write - @server.closed?.should be_true + @server.closed?.should == true end - it "raises IOError on closed socket" do + it 'raises IOError when called on a fully closed socket' do @server.close - lambda { @server.close_write }.should raise_error(IOError) + -> { @server.close_write }.should.raise(IOError) end it "returns nil" do - @server.close_write.should be_nil + @server.close_write.should == 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..e330b6e1be --- /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(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.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.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(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.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 index 3ef3a686e2..36338bfd59 100644 --- a/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb +++ b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "BasicSocket.do_not_reverse_lookup" do before :each do @@ -16,7 +16,7 @@ describe "BasicSocket.do_not_reverse_lookup" do end it "defaults to true" do - BasicSocket.do_not_reverse_lookup.should be_true + BasicSocket.do_not_reverse_lookup.should == true end it "causes 'peeraddr' to avoid name lookups" do @@ -37,3 +37,67 @@ describe "BasicSocket.do_not_reverse_lookup" do @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 index 164e4dc93c..8d5b71cfa9 100644 --- a/spec/ruby/library/socket/basicsocket/for_fd_spec.rb +++ b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb @@ -1,21 +1,38 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) - -describe "BasicSocket#for_fd" do +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.should.is_a?(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.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 == 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..b9851bae15 --- /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(NoMethodError) + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/getpeername_spec.rb b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb index cecf590092..7e2f6f2e7b 100644 --- a/spec/ruby/library/socket/basicsocket/getpeername_spec.rb +++ b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "Socket::BasicSocket#getpeername" do @@ -19,8 +19,7 @@ describe "Socket::BasicSocket#getpeername" do @client.getpeername.should == server_sockaddr end - # Catch general exceptions to prevent NotImplementedError - it "raises an error if socket's not connected" do - lambda { @server.getpeername }.should raise_error(Exception) + it 'raises Errno::ENOTCONN for a disconnected socket' do + -> { @server.getpeername }.should.raise(Errno::ENOTCONN) end end diff --git a/spec/ruby/library/socket/basicsocket/getsockname_spec.rb b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb index cb3a45eb5f..a54626647e 100644 --- a/spec/ruby/library/socket/basicsocket/getsockname_spec.rb +++ b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb @@ -1,13 +1,13 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "Socket::BasicSocket#getsockname" do after :each do - @socket.closed?.should be_false + @socket.closed?.should == false @socket.close end - it "returns the sockaddr associacted with the socket" do + 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"] @@ -16,11 +16,11 @@ describe "Socket::BasicSocket#getsockname" do 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 + ["::", "0.0.0.0", "::ffff:0.0.0.0"].include?(sockaddr[1]).should == true sockaddr[0].should == @socket.addr[1] end - it "returns empty sockaddr for unbinded sockets" do + 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"] diff --git a/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb index dc4fffa5c1..744297ad02 100644 --- a/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb +++ b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "BasicSocket#getsockopt" do before :each do @@ -7,7 +7,7 @@ describe "BasicSocket#getsockopt" do end after :each do - @sock.closed?.should be_false + @sock.closed?.should == false @sock.close end @@ -41,6 +41,148 @@ describe "BasicSocket#getsockopt" do end it "raises a SystemCallError with an invalid socket option" do - lambda { @sock.getsockopt Socket::SOL_SOCKET, -1 }.should raise_error(Errno::ENOPROTOOPT) + -> { @sock.getsockopt Socket::SOL_SOCKET, -1 }.should.raise(Errno::ENOPROTOOPT) + end + + it 'returns a Socket::Option using a constant' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE) + + opt.should.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.is_a?(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(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.is_a?(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 index 9a7f535317..615d92bea8 100644 --- a/spec/ruby/library/socket/basicsocket/ioctl_spec.rb +++ b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb @@ -1,5 +1,4 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require 'socket' +require_relative '../spec_helper' describe "Socket::BasicSocket#ioctl" do platform_is :linux do 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 index 2c948eaa2f..8aca7d0332 100644 --- a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb @@ -1,7 +1,142 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../shared/recv_nonblock', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "Socket::BasicSocket#recv_nonblock" do - it_behaves_like :socket_recv_nonblock, :recv_nonblock + 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(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(IO::WaitReadable) { |e| + platform_is_not :windows do + e.should.is_a?(Errno::EAGAIN) + end + platform_is :windows do + e.should.is_a?(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(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 { |e| + [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class) + } + -> { @server.recv_nonblock(1, exception: false) }.should.raise { |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 + + 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 == nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should == nil + end + end + end end diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb index 5891bf9c87..5c393218bc 100644 --- a/spec/ruby/library/socket/basicsocket/recv_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb @@ -1,6 +1,6 @@ -# -*- encoding: binary -*- -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +# encoding: binary +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "BasicSocket#recv" do @@ -22,7 +22,7 @@ describe "BasicSocket#recv" do client.close end Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + t.status.should_not == nil socket = TCPSocket.new('127.0.0.1', @port) socket.send('hello', 0) @@ -32,28 +32,26 @@ describe "BasicSocket#recv" do ScratchPad.recorded.should == 'hello' end - platform_is_not :solaris do - it "accepts flags to specify unusual receiving behaviour" do - t = Thread.new do - client = @server.accept + 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) + # 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' + # 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 == 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 @@ -66,7 +64,7 @@ describe "BasicSocket#recv" do client.close end Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + t.status.should_not == nil socket = TCPSocket.new('127.0.0.1', @port) socket.write("firstline\377secondline\377") @@ -76,21 +74,156 @@ describe "BasicSocket#recv" do ScratchPad.recorded.should == "firstline\377" end - ruby_version_is "2.3" do - it "allows an output buffer as third argument" do - socket = TCPSocket.new('127.0.0.1', @port) - socket.write("data") + it "allows an output buffer as third argument" do + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("data") - client = @server.accept - buf = "foo" - begin - client.recv(4, 0, buf) - ensure - client.close + 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 - buf.should == "data" + end - socket.close + 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 + + 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 == nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should == nil + 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..9d77f5df6b --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb @@ -0,0 +1,267 @@ +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(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(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.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.instance_of?(Addrinfo) + end + + platform_is_not :windows do + it 'stores the flags at index 2' do + @array[2].should.is_a?(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(Errno::ENOTCONN) + -> { @server.recvmsg_nonblock(exception: false) }.should.raise(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(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.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.instance_of?(Addrinfo) + end + + it 'stores the flags at index 2' do + @array[2].should.is_a?(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(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(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 + + 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 == nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should == nil + 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..eabfb9dd18 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb @@ -0,0 +1,257 @@ +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.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.instance_of?(Addrinfo) + end + + platform_is_not :windows do + it 'stores the flags at index 2' do + @array[2].should.is_a?(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.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.instance_of?(Addrinfo) + end + + it 'stores the flags at index 2' do + @array[2].should.is_a?(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(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(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 + + 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 == nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should == 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.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 index 4df0d04a10..a2c2ee8de9 100644 --- a/spec/ruby/library/socket/basicsocket/send_spec.rb +++ b/spec/ruby/library/socket/basicsocket/send_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "BasicSocket#send" do before :each do @@ -9,36 +9,36 @@ describe "BasicSocket#send" do end after :each do - @server.closed?.should be_false - @socket.closed?.should be_false + @server.closed?.should == false + @socket.closed?.should == 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.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 :solaris, :windows do + 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 == 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 @@ -50,7 +50,7 @@ describe "BasicSocket#send" do client.close end Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + t.status.should_not == nil @socket.send('helloU', Socket::MSG_PEEK | Socket::MSG_OOB).should == 6 @socket.shutdown # indicate, that we are done sending @@ -62,24 +62,159 @@ describe "BasicSocket#send" do 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.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' + 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 == 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(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(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..4cd6e8bdca --- /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(SocketSpecs.dest_addr_req_error) + -> { + @client.sendmsg_nonblock('hello', exception: false) + }.should.raise(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(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..dc999b32df --- /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(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 index 523a22d957..e306581aa3 100644 --- a/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb +++ b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' describe "BasicSocket#setsockopt" do @@ -38,9 +38,9 @@ describe "BasicSocket#setsockopt" do platform_is_not :windows do it "raises EINVAL if passed wrong linger value" do - lambda do + -> do @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, 0) - end.should raise_error(Errno::EINVAL) + end.should.raise(Errno::EINVAL) end end @@ -70,9 +70,9 @@ describe "BasicSocket#setsockopt" do n.should_not == [0].pack("i") platform_is_not :windows do - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) end @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "blah").should == 0 @@ -80,9 +80,9 @@ describe "BasicSocket#setsockopt" do n.should_not == [0].pack("i") platform_is_not :windows do - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "0") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) end @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00\x00").should == 0 @@ -90,15 +90,15 @@ describe "BasicSocket#setsockopt" do n.should == [0].pack("i") platform_is_not :windows do - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "1") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) end platform_is_not :windows do - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) end @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1].pack('i')).should == 0 @@ -125,9 +125,9 @@ describe "BasicSocket#setsockopt" do n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s n.unpack('i')[0].should >= 1 - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, nil).should == 0 - }.should raise_error(TypeError) + }.should.raise(TypeError) @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 1).should == 0 n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s @@ -137,25 +137,25 @@ describe "BasicSocket#setsockopt" do n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s n.unpack('i')[0].should >= 2 - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "bla") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "0") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "1") - }.should raise_error(SystemCallError) + }.should.raise(SystemCallError) - lambda { + -> { @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x00") - }.should raise_error(SystemCallError) + }.should.raise(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 @@ -207,7 +207,128 @@ describe "BasicSocket#setsockopt" do 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) + [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(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(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(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(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 index c874f08697..c96c62abe1 100644 --- a/spec/ruby/library/socket/basicsocket/shutdown_spec.rb +++ b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb @@ -1,6 +1,155 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../spec_helper' +require_relative '../fixtures/classes' -describe "Socket::BasicSocket#shutdown" do +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.empty? + end + + it 'shuts down a socket for writing' do + @client.shutdown(Socket::SHUT_WR) + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'shuts down a socket for reading and writing' do + @client.shutdown(Socket::SHUT_RDWR) + + @client.recv(1).to_s.should.empty? + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown(666) }.should.raise(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.empty? + end + + it 'shuts down a socket for reading using :SHUT_RD' do + @client.shutdown(:SHUT_RD) + + @client.recv(1).to_s.should.empty? + end + + it 'shuts down a socket for writing using :WR' do + @client.shutdown(:WR) + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'shuts down a socket for writing using :SHUT_WR' do + @client.shutdown(:SHUT_WR) + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'shuts down a socket for reading and writing' do + @client.shutdown(:RDWR) + + @client.recv(1).to_s.should.empty? + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown(:Nope) }.should.raise(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.empty? + end + + it 'shuts down a socket for reading using "SHUT_RD"' do + @client.shutdown('SHUT_RD') + + @client.recv(1).to_s.should.empty? + end + + it 'shuts down a socket for writing using "WR"' do + @client.shutdown('WR') + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'shuts down a socket for writing using "SHUT_WR"' do + @client.shutdown('SHUT_WR') + + -> { @client.write('hello') }.should.raise(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown('Nope') }.should.raise(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.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.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.empty? + + -> { @client.write('hello') }.should.raise(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(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 |
