spec/tcpsocket_spec.rb in em-synchrony-1.0.1 vs spec/tcpsocket_spec.rb in em-synchrony-1.0.2

- old
+ new

@@ -1,30 +1,413 @@ require "spec/helper/all" -describe EventMachine::Synchrony::TCPSocket do - it 'connects to a TCP port' do - EventMachine.synchrony do - @socket = EventMachine::Synchrony::TCPSocket.new 'eventmachine.rubyforge.org', 80 - @socket.should_not be_error - EM.stop - end +module SendAndClose + def post_init + send_data "1234" + close_connection_after_writing end +end - it 'errors on connection failure' do +module SendAndTimedClose + def post_init + send_data "1234" + EM.add_timer(0.05) { self.close_connection_after_writing } + end +end + +module SendAndKeepOpen + def post_init + send_data "1234" + end +end + +def tcp_test(server_type, ops={}, &block) + Proc.new do EventMachine.synchrony do - proc { - EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 - }.should raise_error(SocketError) - EM.stop + ops = {:stop => true}.merge ops + EM::start_server('localhost', 12345, server_type) + @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 + @socket.close if ops[:close] + block.call + EM.stop if ops[:stop] end end +end - it 'should accept "send" when wrapped in a connection pool' do - EventMachine.synchrony do - @socket = EventMachine::Synchrony::ConnectionPool.new(size: 1) do - EventMachine::Synchrony::TCPSocket.new 'eventmachine.rubyforge.org', 80 +describe EventMachine::Synchrony::TCPSocket do + context '.new' do + context 'to an open TCP port on an resolvable host' do + it 'succeeds' do + EventMachine.synchrony do + EM::start_server('localhost', 12345) + @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 + @socket.should_not be_error + EM.stop + end end - @socket.send("GET / HTTP1.1\r\n\r\n").class.should be(Fixnum) - EM.stop + end + + context 'to an unresolvable host' do + it 'raises SocketError' do + EventMachine.synchrony do + proc { + EventMachine::Synchrony::TCPSocket.new 'xxxyyyzzz', 12345 + }.should raise_error(SocketError) + EM.stop + end + end + end + + context 'to a closed TCP port' do + it 'raises Errno::ECONNREFUSED' do + EventMachine.synchrony do + proc { + EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 + }.should raise_error(Errno::ECONNREFUSED) + EM.stop + end + end + end + end + + context '#closed?' do + context 'after calling #close' do + it 'returns true' do + tcp_test(SendAndKeepOpen, :close => true) do + @socket.should be_closed + end + end + end + context 'after the peer has closed the connection' do + context 'when we\'ve not yet read EOF' do + it 'returns false' do + tcp_test(SendAndClose) do + @socket.read(2).size.should eq 2 + @socket.should_not be_closed + end + end + end + context 'when we\'ve read EOF' do + it 'returns false' do + tcp_test(SendAndClose) do + @socket.read(10).size.should < 10 + @socket.read(10).should be_nil + @socket.should_not be_closed + end + end + end + end + end + + context '#read' do + context 'with a length argument' do + context 'with a possitive length argument' do + context 'when the connection is open' do + context 'with greater or equal than the requested data buffered' do + it 'returns the requested data and no more' do + tcp_test(SendAndKeepOpen) do + @socket.read(2).size.should eq 2 + @socket.read(1).size.should eq 1 + end + end + end + context 'with less than the requested data buffered' do + it 'blocks' do + tcp_test(SendAndKeepOpen, :stop => false) do + @blocked = true + EM.next_tick { @blocked.should eq true; EM.next_tick { EM.stop } } + @socket.read(10) + @blocked = false + end + end + end + end + context 'when the peer has closed the connection' do + context 'with no data buffered' do + it 'returns nil' do + tcp_test(SendAndClose) do + @socket.read(4).size.should eq 4 + @socket.read(1).should be_nil + end + end + end + context 'with less than the requested data buffered' do + it 'returns the buffered data' do + tcp_test(SendAndClose) do + @socket.read(50).size.should eq 4 + end + end + end + context 'with greater or equal than the requested data buffered' do + it 'returns the requested data and no more' do + tcp_test(SendAndClose) do + @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 + @socket.read(2).size.should eq 2 + end + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.read(4) + }.should raise_error(IOError) + end + end + end + end + context 'with a negative length argument' do + it 'raises ArgumentError' do + tcp_test(SendAndKeepOpen) do + proc { + @socket.read(-10) + }.should raise_error(ArgumentError) + end + end + end + context 'with a zero length argument' do + context 'when the connection is open' do + it 'returns an empty string' do + tcp_test(SendAndKeepOpen) do + @socket.read(0).should eq "" + end + end + end + context 'when the peer has closed the connection' do + it 'returns an empty string' do + tcp_test(SendAndClose) do + @socket.read(0).should eq "" + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.read(0) + }.should raise_error(IOError) + end + end + end + end + end + context 'without a length argument' do + context 'when the connection is open' do + it 'blocks until the peer closes the connection and returns all data sent' do + tcp_test(SendAndTimedClose) do + @blocked = true + EM.next_tick { @blocked.should eq true } + @socket.read(10).should eq '1234' + @blocked = false + end + end + end + context 'when the peer has closed the connection' do + context 'with no data buffered' do + it 'returns an empty string' do + tcp_test(SendAndClose) do + @socket.read() + @socket.read().should eq "" + end + end + end + context 'with data buffered' do + it 'returns the buffered data' do + tcp_test(SendAndClose) do + @socket.read().should eq "1234" + end + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.read() + }.should raise_error(IOError) + end + end + end + end + end + + context '#recv' do + context 'with a length argument' do + context 'with a possitive length argument' do + context 'when the connection is open' do + context 'with greater or equal than the requested data buffered' do + it 'returns the requested data and no more' do + tcp_test(SendAndKeepOpen) do + @socket.recv(2).size.should eq 2 + @socket.recv(1).size.should eq 1 + end + end + end + context 'with less than the requested data buffered' do + it 'return the buffered data' do + tcp_test(SendAndKeepOpen) do + @socket.recv(50).size.should eq 4 + end + end + end + context 'with no buffered data' do + it 'blocks' do + tcp_test(SendAndKeepOpen, :stop => false) do + @socket.recv(10) + @blocked = true + EM.next_tick { @blocked.should eq true; EM.next_tick { EM.stop } } + @socket.recv(10) + @blocked = false + end + end + end + end + context 'when the peer has closed the connection' do + context 'with no data buffered' do + it 'returns an empty string' do + tcp_test(SendAndClose) do + @socket.read(4).size.should eq 4 + @socket.recv(1).should eq "" + end + end + end + context 'with less than the requested data buffered' do + it 'returns the buffered data' do + tcp_test(SendAndClose) do + @socket.recv(50).size.should eq 4 + end + end + end + context 'with greater or equal than the requested data buffered' do + it 'returns the requested data and no more' do + tcp_test(SendAndClose) do + @socket.recv(2).size.should eq 2 + end + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.recv(4) + }.should raise_error(IOError) + end + end + end + end + context 'with a negative length argument' do + it 'raises ArgumentError' do + tcp_test(SendAndKeepOpen) do + proc { + @socket.recv(-10) + }.should raise_error(ArgumentError) + end + end + end + context 'with a zero length argument' do + context 'when the connection is open' do + it 'returns an empty string' do + tcp_test(SendAndKeepOpen) do + @socket.recv(0).should eq "" + end + end + end + context 'when the peer has closed the connection' do + it 'returns an empty string' do + tcp_test(SendAndClose) do + @socket.recv(0).should eq "" + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.recv(0) + }.should raise_error(IOError) + end + end + end + end + end + context 'without a length argument' do + it 'raises ArgumentError' do + tcp_test(SendAndKeepOpen) do + proc { + @socket.recv() + }.should raise_error(ArgumentError) + end + end + end + end + + context '#write' do + context 'when the peer has closed the connection' do + it 'raises Errno::EPIPE' do + tcp_test(SendAndClose, :stop => false) do + EM.add_timer(0.01) do + proc { + @socket.write("foo") + }.should raise_error(Errno::EPIPE) + EM.stop + end + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.write("foo") + }.should raise_error(IOError) + end + end + end + end + + context '#send' do + context 'when the peer has closed the connection' do + it 'raises Errno::EPIPE' do + tcp_test(SendAndClose, :stop => false) do + EM.add_timer(0.01) do + proc { + @socket.send("foo",0) + }.should raise_error(Errno::EPIPE) + EM.stop + end + end + end + end + context 'when we closed the connection' do + it 'raises IOError' do + tcp_test(SendAndKeepOpen, :close => true) do + proc { + @socket.send("foo",0) + }.should raise_error(IOError) + end + end + end + context 'without a flags argument' do + it 'raises ArgumentError' do + tcp_test(SendAndKeepOpen) do + proc { + @socket.send('foo') + }.should raise_error(ArgumentError) + end + end + end + end + + context 'when wrapped in a connection pool' do + it 'accepts "send"' do + EventMachine.synchrony do + @socket = EventMachine::Synchrony::ConnectionPool.new(size: 1) do + EventMachine::Synchrony::TCPSocket.new 'eventmachine.rubyforge.org', 80 + end + @socket.send("GET / HTTP1.1\r\n\r\n",0).class.should be(Fixnum) + EM.stop + end end end end