TPSGI-Server.t 3.5 KB

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