TPSGI-Server.t 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. use strict;
  2. use warnings;
  3. use Test2::V0;
  4. use Test2::Plugin::NoWarnings;
  5. use Cwd qw{abs_path};
  6. use File::Basename qw{dirname};
  7. use Test::MockModule;
  8. use lib dirname( abs_path(__FILE__) ) . '/../lib';
  9. ok( require TPSGI::Server, 'TPSGI::Server loads' ) or bail_out("Can't find SUT");
  10. sub server { return bless( { server => {@_} }, 'TPSGI::Server' ) }
  11. subtest 'child_init_hook' => sub {
  12. my $starman = Test::MockModule->new('Starman::Server');
  13. my $built = 0;
  14. $starman->redefine( child_init_hook => sub { $built++ } );
  15. local $SIG{HUP} = sub { fail('the handler Net::Server installed ran') };
  16. my $self = server( connected => 0 );
  17. $self->child_init_hook();
  18. is( $built, 1, "Starman's hook still runs, so the app is built" );
  19. $SIG{HUP}->();
  20. is( $self->{server}{SigHUPed}, 1, 'a HUP marks the worker HUPed, which done() retires it for' );
  21. is( $self->{server}{_HUP}, 1, 'and makes accept() return instead of retrying' );
  22. };
  23. subtest 'accept' => sub {
  24. my $net = Test::MockModule->new('Net::Server::PreForkSimple');
  25. # What Net::Server::accept() does when the HUP lands as it takes a
  26. # connection: it has the client, and returns 0 because _HUP is set.
  27. $net->redefine( accept => sub { $_[0]{server}{client} = 'a connection'; return 0 } );
  28. is( server()->accept(), 1, 'a connection accepted as the HUP came is answered' );
  29. # And when the HUP interrupts the wait, with nothing taken.
  30. $net->redefine( accept => sub { return 0 } );
  31. is( server( client => 'the last request' )->accept(), 0, 'a worker that took nothing retires, whatever its last request left behind' );
  32. $net->redefine( accept => sub { $_[0]{server}{client} = 'a connection'; return 1 } );
  33. is( server()->accept(), 1, 'an ordinary accept is passed through' );
  34. };
  35. subtest 'every connection is answered while the parent is HUPed' => sub {
  36. skip_all('RELEASE_TESTING not set') unless $ENV{RELEASE_TESTING};
  37. require File::Temp;
  38. require IO::Socket::UNIX;
  39. require Plack::Handler::TPSGI;
  40. my $dir = File::Temp::tempdir( CLEANUP => 1 );
  41. my $sock = "$dir/s.sock";
  42. my $server = fork() // die "fork: $!";
  43. if ( !$server ) {
  44. close(STDERR);
  45. Plack::Handler::TPSGI->new( listen => [$sock], workers => 5, pid => "$dir/pid", keepalive => 0 )
  46. ->run( sub { [ 200, [ 'Content-Type' => 'text/plain' ], ['ok'] ] } );
  47. exit 0;
  48. }
  49. for ( 1 .. 100 ) { last if -S $sock && -s "$dir/pid"; select( undef, undef, undef, 0.1 ) }
  50. ok( -S $sock, 'the server is listening' ) or return;
  51. my $parent = do { open( my $fh, '<', "$dir/pid" ) or die $!; local $/; <$fh> };
  52. chomp $parent;
  53. # Four clients, each counting the requests that got no answer.
  54. my @clients = map {
  55. my $pid = fork() // die "fork: $!";
  56. if ( !$pid ) {
  57. my $bad = 0;
  58. for ( 1 .. 1500 ) {
  59. my $c = IO::Socket::UNIX->new( Peer => $sock ) or do { $bad++; next };
  60. print $c "GET / HTTP/1.0\r\n\r\n";
  61. my $got = do { local $/; <$c> } // '';
  62. $bad++ unless $got =~ m{^HTTP/1\.\d 200}a;
  63. }
  64. exit( $bad > 255 ? 255 : $bad );
  65. }
  66. $pid
  67. } 1 .. 4;
  68. for ( 1 .. 60 ) { kill( 'HUP', $parent ); select( undef, undef, undef, 0.05 ) }
  69. my $unanswered = 0;
  70. foreach my $pid (@clients) { waitpid( $pid, 0 ); $unanswered += $? >> 8 }
  71. is( $unanswered, 0, 'no request of 6000 went unanswered across 60 restarts' );
  72. kill( 'TERM', $parent );
  73. waitpid( $server, 0 );
  74. };
  75. done_testing();