diff options
Diffstat (limited to 't/25socket.t')
-rw-r--r-- | t/25socket.t | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/t/25socket.t b/t/25socket.t new file mode 100644 index 0000000..8da852f --- /dev/null +++ b/t/25socket.t @@ -0,0 +1,325 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use IO::Async::Test; + +use Test::More; +use Test::Fatal; +use Test::Refcount; + +use Errno qw( EAGAIN EWOULDBLOCK ECONNRESET ); + +use Socket qw( unpack_sockaddr_in ); + +use IO::Async::Loop; + +use IO::Async::OS; + +use IO::Async::Socket; + +my $loop = IO::Async::Loop->new_builtin; + +testing_loop( $loop ); + +# useful test function +sub recv_data +{ + my ( $s ) = @_; + + my $buffer; + my $ret = $s->recv( $buffer, 8192 ); + + return $buffer if defined $ret and length $buffer; + die "Socket closed" if defined $ret; + return "" if $! == EAGAIN or $! == EWOULDBLOCK; + die "Cannot recv - $!"; +} + +ok( !exception { IO::Async::Socket->new( write_handle => \*STDOUT ) }, 'Send-only Socket works' ); + +# Receiving +{ + my ( $S1, $S2 ) = IO::Async::OS->socketpair( "inet", "dgram" ) or die "Cannot socketpair - $!"; + my @S2addr = unpack_sockaddr_in $S2->sockname; + + # Need sockets in nonblocking mode + $S1->blocking( 0 ); + $S2->blocking( 0 ); + + my @received; + + my $socket = IO::Async::Socket->new( + handle => $S1, + on_recv => sub { + my $self = shift; + my ( $dgram, $sender ) = @_; + + push @received, [ $dgram, unpack_sockaddr_in $sender ]; + }, + ); + + ok( defined $socket, 'recving $socket defined' ); + isa_ok( $socket, "IO::Async::Socket", 'recving $socket isa IO::Async::Socket' ); + + is_oneref( $socket, 'recving $socket has refcount 1 initially' ); + + $loop->add( $socket ); + + is_refcount( $socket, 2, 'recving $socket has refcount 2 after adding to Loop' ); + + $S2->send( "message\n" ); + + is_deeply( \@received, [], '@received before wait' ); + + wait_for { scalar @received }; + + is_deeply( \@received, + [ [ "message\n", @S2addr ] ], + '@received after wait' ); + + undef @received; + my @new_received; + $socket->configure( + on_recv => sub { + my $self = shift; + my ( $dgram, $sender ) = @_; + push @new_received, [ $dgram, unpack_sockaddr_in $sender ]; + }, + ); + + $S2->send( "another message\n" ); + + wait_for { scalar @new_received }; + + is( scalar @received, 0, '@received still empty after on_recv replace' ); + is_deeply( \@new_received, + [ [ "another message\n", @S2addr ] ], + '@new_received after on_recv replace' ); + + is_refcount( $socket, 2, 'receiving $socket has refcount 2 before removing from Loop' ); + + $loop->remove( $socket ); + + is_oneref( $socket, 'receiving $socket refcount 1 finally' ); +} + +SKIP: { + # Don't bother with an OS constant for this as it's only used by this unit-test + skip "This OS cannot safely ->recv with truncation", 3 if $^O eq "MSWin32"; + + my ( $S1, $S2 ) = IO::Async::OS->socketpair( "inet", "dgram" ) or die "Cannot socketpair - $!"; + + # Need sockets in nonblocking mode + $S1->blocking( 0 ); + $S2->blocking( 0 ); + + my @frags; + my $socket = IO::Async::Socket->new( + handle => $S1, + recv_len => 4, + on_recv => sub { + my ( $self, $dgram ) = @_; + push @frags, $dgram; + }, + ); + + $loop->add( $socket ); + + $S2->send( "A nice long message" ); + $S2->send( "another one here" ); + $S2->send( "and again" ); + + wait_for { scalar @frags }; + + is_deeply( \@frags, [ "A ni" ], '@frags with recv_len=4 without recv_all' ); + + wait_for { @frags == 3 }; + + is_deeply( \@frags, [ "A ni", "anot", "and " ], '@frags finally with recv_len=4 without recv_all' ); + + undef @frags; + $socket->configure( recv_all => 1 ); + + $S2->send( "Long messages" ); + $S2->send( "Repeated" ); + $S2->send( "Once more" ); + + wait_for { scalar @frags }; + + is_deeply( \@frags, [ "Long", "Repe", "Once" ], '@frags with recv_len=4 with recv_all' ); + + $loop->remove( $socket ); +} + +{ + my ( $S1, $S2 ) = IO::Async::OS->socketpair( "inet", "dgram" ) or die "Cannot socketpair - $!"; + + my $no_on_recv_socket; + ok( !exception { $no_on_recv_socket = IO::Async::Socket->new( handle => $S1 ) }, + 'Allowed to construct a Socket without an on_recv handler' ); + ok( exception { $loop->add( $no_on_recv_socket ) }, + 'Not allowed to add an on_recv-less Socket to a Loop' ); + } + +# Subclass + +my @sub_received; + +{ + my ( $S1, $S2 ) = IO::Async::OS->socketpair( "inet", "dgram" ) or die "Cannot socketpair - $!"; + my @S2addr = unpack_sockaddr_in $S2->sockname; + + # Need sockets in nonblocking mode + $S1->blocking( 0 ); + $S2->blocking( 0 ); + + my $socket = TestSocket->new( + handle => $S1, + ); + + ok( defined $socket, 'receiving subclass $socket defined' ); + isa_ok( $socket, "IO::Async::Socket", 'receiving $socket isa IO::Async::Socket' ); + + is_oneref( $socket, 'subclass $socket has refcount 1 initially' ); + + $loop->add( $socket ); + + is_refcount( $socket, 2, 'subclass $socket has refcount 2 after adding to Loop' ); + + $S2->send( "message\n" ); + + is_deeply( \@sub_received, [], '@sub_received before wait' ); + + wait_for { scalar @sub_received }; + + is_deeply( \@sub_received, + [ [ "message\n", @S2addr ] ], + '@sub_received after wait' ); + + $loop->remove( $socket ); +} + +# Sending +{ + my ( $S1, $S2 ) = IO::Async::OS->socketpair( "inet", "dgram" ) or die "Cannot socketpair - $!"; + + # Need sockets in nonblocking mode + $S1->blocking( 0 ); + $S2->blocking( 0 ); + + my $empty; + + my $socket = IO::Async::Socket->new( + write_handle => $S1, + on_outgoing_empty => sub { $empty = 1 }, + ); + + ok( defined $socket, 'sending $socket defined' ); + isa_ok( $socket, "IO::Async::Socket", 'sending $socket isa IO::Async::Socket' ); + + is_oneref( $socket, 'sending $socket has refcount 1 intially' ); + + $loop->add( $socket ); + + is_refcount( $socket, 2, 'sending $socket has refcount 2 after adding to Loop' ); + + ok( !$socket->want_writeready, 'want_writeready before send' ); + $socket->send( "message\n" ); + + ok( $socket->want_writeready, 'want_writeready after send' ); + + wait_for { $empty }; + + ok( !$socket->want_writeready, 'want_writeready after wait' ); + is( $empty, 1, '$empty after writing buffer' ); + + is( recv_data( $S2 ), "message\n", 'data after writing buffer' ); + + $socket->configure( autoflush => 1 ); + $socket->send( "immediate\n" ); + + ok( !$socket->want_writeready, 'not want_writeready after autoflush send' ); + is( recv_data( $S2 ), "immediate\n", 'data after autoflush send' ); + + $socket->configure( autoflush => 0 ); + $socket->send( "First\n" ); + $socket->configure( autoflush => 1 ); + $socket->send( "Second\n" ); + + ok( !$socket->want_writeready, 'not want_writeready after split autoflush send' ); + is( recv_data( $S2 ), "First\n", 'data[0] after split autoflush send' ); + is( recv_data( $S2 ), "Second\n", 'data[1] after split autoflush send' ); + + is_refcount( $socket, 2, 'sending $socket has refcount 2 before removing from Loop' ); + + $loop->remove( $socket ); + + is_oneref( $socket, 'sending $socket has refcount 1 finally' ); +} + +# Socket errors +{ + my ( $ES1, $ES2 ) = IO::Async::OS->socketpair or die "Cannot socketpair - $!"; + $ES2->syswrite( "X" ); # ensuring $ES1 is read- and write-ready + # cheating and hackery + bless $ES1, "ErrorSocket"; + + $ErrorSocket::errno = ECONNRESET; + + my $recv_errno; + my $send_errno; + + my $socket = IO::Async::Socket->new( + read_handle => $ES1, + on_recv => sub {}, + on_recv_error => sub { ( undef, $recv_errno ) = @_ }, + ); + + $loop->add( $socket ); + + wait_for { defined $recv_errno }; + + cmp_ok( $recv_errno, "==", ECONNRESET, 'errno after failed recv' ); + + $loop->remove( $socket ); + + $socket = IO::Async::Socket->new( + write_handle => $ES1, + on_send_error => sub { ( undef, $send_errno ) = @_ }, + ); + + $loop->add( $socket ); + + $socket->send( "hello" ); + + wait_for { defined $send_errno }; + + cmp_ok( $send_errno, "==", ECONNRESET, 'errno after failed send' ); + + $loop->remove( $socket ); +} + +done_testing; + +package TestSocket; +use base qw( IO::Async::Socket ); +use Socket qw( unpack_sockaddr_in ); + +sub on_recv +{ + my $self = shift; + my ( $dgram, $sender ) = @_; + + push @sub_received, [ $dgram, unpack_sockaddr_in $sender ]; +} + +package ErrorSocket; + +use base qw( IO::Socket ); +our $errno; + +sub recv { $! = $errno; undef; } +sub send { $! = $errno; undef; } +sub close { } |