about summary refs log tree commit homepage
path: root/t/httpd-corner.t
diff options
context:
space:
mode:
Diffstat (limited to 't/httpd-corner.t')
-rw-r--r--t/httpd-corner.t121
1 files changed, 72 insertions, 49 deletions
diff --git a/t/httpd-corner.t b/t/httpd-corner.t
index 0a613a9e..7539573c 100644
--- a/t/httpd-corner.t
+++ b/t/httpd-corner.t
@@ -1,12 +1,14 @@
-# Copyright (C) 2016-2021 all contributors <meta@public-inbox.org>
+#!perl -w
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 # note: our HTTP server should be standalone and capable of running
 # generic PSGI/Plack apps.
-use strict; use v5.10.1; use PublicInbox::TestCommon;
+use v5.12; use PublicInbox::TestCommon;
 use Time::HiRes qw(gettimeofday tv_interval);
+use autodie qw(getsockopt setsockopt);
 use PublicInbox::Spawn qw(spawn popen_rd);
 require_mods(qw(Plack::Util Plack::Builder HTTP::Date HTTP::Status));
-use Digest::SHA qw(sha1_hex);
+use PublicInbox::SHA qw(sha1_hex);
 use IO::Handle ();
 use IO::Socket::UNIX;
 use Fcntl qw(:seek);
@@ -19,26 +21,27 @@ ok(defined mkfifo($fifo, 0777), 'created FIFO');
 my $err = "$tmpdir/stderr.log";
 my $out = "$tmpdir/stdout.log";
 my $psgi = "./t/httpd-corner.psgi";
-my $sock = tcp_server() or die;
+my $sock = tcp_server();
 my @zmods = qw(PublicInbox::GzipFilter IO::Uncompress::Gunzip);
 
 # Make sure we don't clobber socket options set by systemd or similar
 # using socket activation:
 my ($defer_accept_val, $accf_arg, $TCP_DEFER_ACCEPT);
-if ($^O eq 'linux') {
+SKIP: {
+        skip 'TCP_DEFER_ACCEPT is Linux-only', 1 if $^O ne 'linux';
         $TCP_DEFER_ACCEPT = eval { Socket::TCP_DEFER_ACCEPT() } // 9;
-        setsockopt($sock, IPPROTO_TCP, $TCP_DEFER_ACCEPT, 5) or die;
+        setsockopt($sock, IPPROTO_TCP, $TCP_DEFER_ACCEPT, 5);
         my $x = getsockopt($sock, IPPROTO_TCP, $TCP_DEFER_ACCEPT);
-        defined $x or die "getsockopt: $!";
         $defer_accept_val = unpack('i', $x);
-        if ($defer_accept_val <= 0) {
-                die "unexpected TCP_DEFER_ACCEPT value: $defer_accept_val";
-        }
-} elsif ($^O eq 'freebsd' && system('kldstat -m accf_data >/dev/null') == 0) {
+        ok($defer_accept_val > 0, 'TCP_DEFER_ACCEPT val non-zero') or
+                xbail "unexpected TCP_DEFER_ACCEPT value: $defer_accept_val";
+}
+SKIP: {
+        require_mods '+accf_data';
         require PublicInbox::Daemon;
         my $var = $PublicInbox::Daemon::SO_ACCEPTFILTER;
         $accf_arg = pack('a16a240', 'dataready', '');
-        setsockopt($sock, SOL_SOCKET, $var, $accf_arg) or die "setsockopt: $!";
+        setsockopt($sock, SOL_SOCKET, $var, $accf_arg);
 }
 
 sub unix_server ($) {
@@ -53,14 +56,40 @@ sub unix_server ($) {
 
 my $upath = "$tmpdir/s";
 my $unix = unix_server($upath);
+my $alt = tcp_server();
 my $td;
 my $spawn_httpd = sub {
         my (@args) = @_;
-        my $cmd = [ '-httpd', @args, "--stdout=$out", "--stderr=$err", $psgi ];
-        $td = start_script($cmd, undef, { 3 => $sock, 4 => $unix });
+        my $x = tcp_host_port($alt);
+        my $cmd = [ '-httpd', @args, "--stdout=$out", "--stderr=$err", $psgi,
+                '-l', "http://$x/?psgi=t/alt.psgi,env.PI_CONFIG=/path/to/alt".
+                        ",err=$tmpdir/alt.err" ];
+        my $env = { PI_CONFIG => '/dev/null' };
+        $td = start_script($cmd, $env, { 3 => $sock, 4 => $unix, 5 => $alt });
 };
 
 $spawn_httpd->();
+{
+        my $conn = conn_for($alt, 'alt PSGI path');
+        $conn->write("GET / HTTP/1.0\r\n\r\n");
+        $conn->read(my $buf, 4096);
+        like($buf, qr!^/path/to/alt\z!sm,
+                'alt.psgi loaded on alt socket with correct env');
+
+        $conn = conn_for($sock, 'default PSGI path');
+        $conn->write("GET /PI_CONFIG HTTP/1.0\r\n\r\n");
+        $conn->read($buf, 4096);
+        like($buf, qr!^/dev/null\z!sm,
+                'default PSGI on original socket');
+        my $log = capture("$tmpdir/alt.err");
+        ok(grep(/ALT/, @$log), 'alt psgi.errors written to');
+        $log = capture($err);
+        ok(!grep(/ALT/, @$log), 'STDERR not written to');
+        is(unlink($err, "$tmpdir/alt.err"), 2, 'unlinked stderr and alt.err');
+
+        $td->kill('USR1'); # trigger reopen_logs
+}
+
 if ('test worker death') {
         my $conn = conn_for($sock, 'killed worker');
         $conn->write("GET /pid HTTP/1.1\r\nHost:example.com\r\n\r\n");
@@ -82,6 +111,10 @@ if ('test worker death') {
         like($body, qr/\A[0-9]+\z/, '/pid response');
         isnt($body, $pid, 'respawned worker');
 }
+{ # check on prior USR1 signal
+        ok(-e $err, 'stderr recreated after USR1');
+        ok(-e "$tmpdir/alt.err", 'alt.err recreated after USR1');
+}
 {
         my $conn = conn_for($sock, 'Header spaces bogus');
         $conn->write("GET /empty HTTP/1.1\r\nSpaced-Out : 3\r\n\r\n");
@@ -289,7 +322,7 @@ sub conn_for {
         $spawn_httpd->('-W0');
 }
 
-sub delay { select(undef, undef, undef, shift || rand(0.02)) }
+sub delay { tick(shift || rand(0.02)) }
 
 my $str = 'abcdefghijklmnopqrstuvwxyz';
 my $len = length $str;
@@ -310,7 +343,7 @@ SKIP: {
         my $url = "$base/sha1";
         my ($r, $w);
         pipe($r, $w) or die "pipe: $!";
-        my $cmd = [$curl, qw(--tcp-nodelay -T- -HExpect: -sSN), $url];
+        my $cmd = [$curl, qw(--tcp-nodelay -T- -HExpect: -gsSN), $url];
         open my $cout, '+>', undef or die;
         open my $cerr, '>', undef or die;
         my $rdr = { 0 => $r, 1 => $cout, 2 => $cerr };
@@ -327,7 +360,7 @@ SKIP: {
         seek($cout, 0, SEEK_SET);
         is(<$cout>, sha1_hex($str), 'read expected body');
 
-        my $fh = popen_rd([$curl, '-sS', "$base/async-big"]);
+        my $fh = popen_rd([$curl, '-gsS', "$base/async-big"]);
         my $n = 0;
         my $non_zero = 0;
         while (1) {
@@ -335,19 +368,19 @@ SKIP: {
                 $n += $r;
                 $buf =~ /\A\0+\z/ or $non_zero++;
         }
-        close $fh or die "close curl pipe: $!";
+        $fh->close or die "close curl pipe: $!";
         is($?, 0, 'curl succesful');
         is($n, 30 * 1024 * 1024, 'got expected output from curl');
         is($non_zero, 0, 'read all zeros');
 
         require_mods(@zmods, 4);
-        my $buf = xqx([$curl, '-sS', "$base/psgi-return-gzip"]);
+        my $buf = xqx([$curl, '-gsS', "$base/psgi-yield-gzip"]);
         is($?, 0, 'curl succesful');
         IO::Uncompress::Gunzip::gunzip(\$buf => \(my $out));
         is($out, "hello world\n");
         my $curl_rdr = { 2 => \(my $curl_err = '') };
-        $buf = xqx([$curl, qw(-sSv --compressed),
-                        "$base/psgi-return-compressible"], undef, $curl_rdr);
+        $buf = xqx([$curl, qw(-gsSv --compressed),
+                        "$base/psgi-yield-compressible"], undef, $curl_rdr);
         is($?, 0, 'curl --compressed successful');
         is($buf, "goodbye world\n", 'gzipped response as expected');
         like($curl_err, qr/\bContent-Encoding: gzip\b/,
@@ -355,8 +388,8 @@ SKIP: {
 }
 
 {
-        my $conn = conn_for($sock, 'psgi_return ENOENT');
-        print $conn "GET /psgi-return-enoent HTTP/1.1\r\n\r\n" or die;
+        my $conn = conn_for($sock, 'psgi_yield ENOENT');
+        print $conn "GET /psgi-yield-enoent HTTP/1.1\r\n\r\n" or die;
         my $buf = '';
         sysread($conn, $buf, 16384, length($buf)) until $buf =~ /\r\n\r\n/;
         like($buf, qr!HTTP/1\.[01] 500\b!, 'got 500 error on ENOENT');
@@ -594,43 +627,33 @@ SKIP: {
 SKIP: {
         skip 'TCP_DEFER_ACCEPT is Linux-only', 1 if $^O ne 'linux';
         my $var = $TCP_DEFER_ACCEPT;
-        defined(my $x = getsockopt($sock, IPPROTO_TCP, $var)) or die;
+        my $x = getsockopt($sock, IPPROTO_TCP, $var);
         is(unpack('i', $x), $defer_accept_val,
                 'TCP_DEFER_ACCEPT unchanged if previously set');
 };
 SKIP: {
-        skip 'SO_ACCEPTFILTER is FreeBSD-only', 1 if $^O ne 'freebsd';
-        skip 'accf_data not loaded: kldload accf_data' if !defined $accf_arg;
+        require_mods '+accf_data';
         my $var = $PublicInbox::Daemon::SO_ACCEPTFILTER;
-        defined(my $x = getsockopt($sock, SOL_SOCKET, $var)) or die;
+        my $x = getsockopt($sock, SOL_SOCKET, $var);
         is($x, $accf_arg, 'SO_ACCEPTFILTER unchanged if previously set');
 };
 
 SKIP: {
-        skip 'only testing lsof(8) output on Linux', 1 if $^O ne 'linux';
-        my $lsof = require_cmd('lsof', 1) or skip 'no lsof in PATH', 1;
-        my $null_in = '';
-        my $rdr = { 2 => \(my $null_err), 0 => \$null_in };
-        my @lsof = xqx([$lsof, '-p', $td->{pid}], undef, $rdr);
-        my $d = [ grep(/\(deleted\)/, @lsof) ];
-        is_deeply($d, [], 'no lingering deleted inputs') or diag explain($d);
+        skip 'only testing /proc/PID/fd on Linux', 1 if $^O ne 'linux';
+        my $fd_dir = "/proc/$td->{pid}/fd";
+        -d $fd_dir or skip '/proc/$PID/fd missing', 1;
+        my @child = grep defined, map readlink, glob "$fd_dir/*";
+        my @d = grep /\(deleted\)/, @child;
+        is_deeply(\@d, [], 'no lingering deleted inputs') or diag explain(\@d);
 
         # filter out pipes inherited from the parent
-        my @this = xqx([$lsof, '-p', $$], undef, $rdr);
-        my $bad;
-        my $extract_inodes = sub {
-                map {;
-                        my @f = split(' ', $_);
-                        my $inode = $f[-2];
-                        $bad = $_ if $inode !~ /\A[0-9]+\z/;
-                        $inode => 1;
-                } grep (/\bpipe\b/, @_);
-        };
-        my %child = $extract_inodes->(@lsof);
+        my @this = grep defined, map readlink, glob "/proc/$$/fd/*";
+        my $extract_inodes = sub { map { $_ => 1 } grep /\bpipe\b/, @_ };
+        my %child = $extract_inodes->(@child);
         my %parent = $extract_inodes->(@this);
-        skip("inode not in expected format: $bad", 1) if defined($bad);
         delete @child{(keys %parent)};
-        is_deeply([], [keys %child], 'no extra pipes with -W0');
+        is_deeply([], [keys %child], 'no extra pipes with -W0') or
+                diag explain([child => \%child, parent => \%parent]);
 };
 
 # ensure compatibility with other PSGI servers
@@ -646,13 +669,13 @@ SKIP: {
         my $app = require $psgi;
         test_psgi($app, sub {
                 my ($cb) = @_;
-                my $req = GET('http://example.com/psgi-return-gzip');
+                my $req = GET('http://example.com/psgi-yield-gzip');
                 my $res = $cb->($req);
                 my $buf = $res->content;
                 IO::Uncompress::Gunzip::gunzip(\$buf => \(my $out));
                 is($out, "hello world\n", 'got expected output');
 
-                $req = GET('http://example.com/psgi-return-enoent');
+                $req = GET('http://example.com/psgi-yield-enoent');
                 $res = $cb->($req);
                 is($res->code, 500, 'got error on ENOENT');
                 seek($tmperr, 0, SEEK_SET) or die;