]> Sergey Matveev's repositories - public-inbox.git/blobdiff - t/spawn.t
send and receive all 3 FDs at once
[public-inbox.git] / t / spawn.t
index c31c4f193a4fd2379df35252cec28aab425dc754..891a370211e28ff17035dc12d07d8b8bbe09021a 100644 (file)
--- a/t/spawn.t
+++ b/t/spawn.t
@@ -1,9 +1,39 @@
-# Copyright (C) 2015-2019 all contributors <meta@public-inbox.org>
+# Copyright (C) 2015-2021 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 use strict;
 use warnings;
 use Test::More;
 use PublicInbox::Spawn qw(which spawn popen_rd);
+use PublicInbox::Sigfd;
+use Socket qw(AF_UNIX SOCK_STREAM);
+
+SKIP: {
+       my $recv_3fds = PublicInbox::Spawn->can('recv_3fds');
+       my $send_3fds = PublicInbox::Spawn->can('send_3fds');
+       skip 'Inline::C not enabled', 3 unless $send_3fds && $recv_3fds;
+       my ($s1, $s2);
+       socketpair($s1, $s2, AF_UNIX, SOCK_STREAM, 0) or BAIL_OUT $!;
+       pipe(my ($r, $w)) or BAIL_OUT $!;
+       my @orig = ($r, $w, $s2);
+       my @fd = map { fileno($_) } @orig;
+       ok($send_3fds->(fileno($s1), $fd[0], $fd[1], $fd[2]),
+               'FDs sent');
+       my (@fds) = $recv_3fds->(fileno($s2));
+       is(scalar(@fds), 3, 'got 3 fds');
+       use Data::Dumper; diag Dumper(\@fds);
+       is(scalar(grep(/\A\d+\z/, @fds)), 3, 'all valid FDs');
+       my $i = 0;
+       my @cmp = map {
+               open my $new, $_, shift(@fds) or BAIL_OUT "open $! $i => $_";
+               ($new, shift(@orig), $i++);
+       } (qw(<&= >&= +<&=));
+       while (my ($new, $old, $fd) = splice(@cmp, 0, 3)) {
+               my @new = stat($new);
+               my @old = stat($old);
+               is("$old[0]\0$old[1]", "$new[0]\0$new[1]",
+                       "device/inode matches on received FD:$fd");
+       }
+}
 
 {
        my $true = which('true');
@@ -17,6 +47,32 @@ use PublicInbox::Spawn qw(which spawn popen_rd);
        is($?, 0, 'true exited successfully');
 }
 
+{ # ensure waitpid(-1, 0) and SIGCHLD works in spawned process
+       my $script = <<'EOF';
+$| = 1; # unbuffer stdout
+defined(my $pid = fork) or die "fork: $!";
+if ($pid == 0) { exit }
+elsif ($pid > 0) {
+       my $waited = waitpid(-1, 0);
+       $waited == $pid or die "mismatched child $pid != $waited";
+       $? == 0 or die "child err: $>";
+       $SIG{CHLD} = sub { print "HI\n"; exit };
+       print "RDY $$\n";
+       select(undef, undef, undef, 0.01) while 1;
+}
+EOF
+       my $oldset = PublicInbox::Sigfd::block_signals();
+       my $rd = popen_rd([$^X, '-e', $script]);
+       diag 'waiting for child to reap grandchild...';
+       chomp(my $line = readline($rd));
+       my ($rdy, $pid) = split(' ', $line);
+       is($rdy, 'RDY', 'got ready signal, waitpid(-1) works in child');
+       ok(kill('CHLD', $pid), 'sent SIGCHLD to child');
+       is(readline($rd), "HI\n", '$SIG{CHLD} works in child');
+       ok(close $rd, 'popen_rd close works');
+       PublicInbox::Sigfd::sig_setmask($oldset);
+}
+
 {
        my ($r, $w);
        pipe $r, $w or die "pipe failed: $!";
@@ -71,6 +127,44 @@ use PublicInbox::Spawn qw(which spawn popen_rd);
        isnt($?, 0, '$? set properly: '.$?);
 }
 
+{ # ->CLOSE vs ->DESTROY waitpid caller distinction
+       my @c;
+       my $fh = popen_rd(['true'], undef, { cb => sub { @c = caller } });
+       ok(close($fh), '->CLOSE fired and successful');
+       ok(scalar(@c), 'callback fired by ->CLOSE');
+       ok(grep(!m[/PublicInbox/DS\.pm\z], @c), 'callback not invoked by DS');
+
+       @c = ();
+       $fh = popen_rd(['true'], undef, { cb => sub { @c = caller } });
+       undef $fh; # ->DESTROY
+       ok(scalar(@c), 'callback fired by ->DESTROY');
+       ok(grep(!m[/PublicInbox/ProcessPipe\.pm\z], @c),
+               'callback not invoked by ProcessPipe');
+}
+
+{ # children don't wait on siblings
+       use POSIX qw(_exit);
+       pipe(my ($r, $w)) or BAIL_OUT $!;
+       my $cb = sub { warn "x=$$\n" };
+       my $fh = popen_rd(['cat'], undef, { 0 => $r, cb => $cb });
+       my $pp = tied *$fh;
+       my $pid = fork // BAIL_OUT $!;
+       local $SIG{__WARN__} = sub { _exit(1) };
+       if ($pid == 0) {
+               local $SIG{__DIE__} = sub { _exit(2) };
+               undef $fh;
+               _exit(0);
+       }
+       waitpid($pid, 0);
+       is($?, 0, 'forked process exited');
+       my @w;
+       local $SIG{__WARN__} = sub { push @w, @_ };
+       close $w;
+       close $fh;
+       is($?, 0, 'cat exited');
+       is_deeply(\@w, [ "x=$$\n" ], 'callback fired from owner');
+}
+
 SKIP: {
        eval {
                require BSD::Resource;