summaryrefslogtreecommitdiff
path: root/t/32loop-spawnchild-setup.t
diff options
context:
space:
mode:
Diffstat (limited to 't/32loop-spawnchild-setup.t')
-rw-r--r--t/32loop-spawnchild-setup.t439
1 files changed, 439 insertions, 0 deletions
diff --git a/t/32loop-spawnchild-setup.t b/t/32loop-spawnchild-setup.t
new file mode 100644
index 0000000..7ecdf85
--- /dev/null
+++ b/t/32loop-spawnchild-setup.t
@@ -0,0 +1,439 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use IO::Async::Test;
+
+use Test::More;
+use Test::Fatal;
+
+use File::Temp qw( tmpnam );
+use POSIX qw( ENOENT EBADF getcwd );
+
+use IO::Async::Loop;
+use IO::Async::OS;
+
+plan skip_all => "POSIX fork() is not available" unless IO::Async::OS->HAVE_POSIX_FORK;
+
+my $loop = IO::Async::Loop->new_builtin;
+
+testing_loop( $loop );
+
+ok( exception { $loop->spawn_child( code => sub { 1 }, setup => "hello" ); },
+ 'Bad setup type fails' );
+
+ok( exception { $loop->spawn_child( code => sub { 1 }, setup => [ 'somerandomthing' => 1 ] ); },
+ 'Setup with bad key fails' );
+
+# These tests are all very similar looking, with slightly different start and
+# code values. Easiest to wrap them up in a common testing wrapper.
+
+sub TEST
+{
+ my ( $name, %attr ) = @_;
+
+ my $exitcode;
+ my $dollarbang;
+ my $dollarat;
+
+ my ( undef, $callerfile, $callerline ) = caller;
+
+ $loop->spawn_child(
+ code => $attr{code},
+ exists $attr{setup} ? ( setup => $attr{setup} ) : (),
+ on_exit => sub { ( undef, $exitcode, $dollarbang, $dollarat ) = @_; },
+ );
+
+ wait_for { defined $exitcode };
+
+ if( exists $attr{exitstatus} ) {
+ ok( ($exitcode & 0x7f) == 0, "WIFEXITED(\$exitcode) after $name" );
+ is( ($exitcode >> 8), $attr{exitstatus}, "WEXITSTATUS(\$exitcode) after $name" );
+ }
+
+ if( exists $attr{dollarbang} ) {
+ is( $dollarbang+0, $attr{dollarbang}, "\$dollarbang numerically after $name" );
+ }
+
+ if( exists $attr{dollarat} ) {
+ is( $dollarat, $attr{dollarat}, "\$dollarat after $name" );
+ }
+}
+
+# A useful utility function like blocking read with a timeout
+sub read_timeout
+{
+ my ( $fh, undef, $len, $timeout ) = @_;
+
+ my $rvec = '';
+ vec( $rvec, fileno $fh, 1 ) = 1;
+
+ select( $rvec, undef, undef, $timeout );
+
+ return undef if !vec( $rvec, fileno $fh, 1 );
+
+ return $fh->read( $_[1], $len );
+}
+
+my $buffer;
+my $ret;
+
+{
+ my( $pipe_r, $pipe_w ) = IO::Async::OS->pipepair or die "Cannot pipepair - $!";
+
+ TEST "pipe dup to fd1",
+ setup => [ fd1 => [ 'dup', $pipe_w ] ],
+ code => sub { print "test"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to fd1' );
+ is( $buffer, 'test', '$buffer after pipe dup to fd1' );
+
+ my $pipe_w_fileno = fileno $pipe_w;
+
+ TEST "pipe dup to fd1 closes pipe",
+ setup => [ fd1 => [ 'dup', $pipe_w ] ],
+ code => sub {
+ my $f = IO::Handle->new_from_fd( $pipe_w_fileno, "w" );
+ defined $f and return 1;
+ $! == EBADF or return 1;
+ return 0;
+ },
+
+ exitstatus => 0,
+ dollarat => '';
+
+ TEST "pipe dup to stdout shortcut",
+ setup => [ stdout => $pipe_w ],
+ code => sub { print "test"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to stdout shortcut' );
+ is( $buffer, 'test', '$buffer after pipe dup to stdout shortcut' );
+
+ TEST "pipe dup to \\*STDOUT IO reference",
+ setup => [ \*STDOUT => $pipe_w ],
+ code => sub { print "test2"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 5, 0.1 );
+
+ is( $ret, 5, '$pipe_r->read after pipe dup to \\*STDOUT IO reference' );
+ is( $buffer, 'test2', '$buffer after pipe dup to \\*STDOUT IO reference' );
+
+ TEST "pipe keep open",
+ setup => [ "fd$pipe_w_fileno" => [ 'keep' ] ],
+ code => sub { $pipe_w->autoflush(1); $pipe_w->print( "test" ) },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after keep pipe open' );
+ is( $buffer, 'test', '$buffer after keep pipe open' );
+
+ TEST "pipe keep shortcut",
+ setup => [ "fd$pipe_w_fileno" => 'keep' ],
+ code => sub { $pipe_w->autoflush(1); $pipe_w->print( "test" ) },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after keep pipe open' );
+ is( $buffer, 'test', '$buffer after keep pipe open' );
+
+
+ TEST "pipe dup to stdout",
+ setup => [ stdout => [ 'dup', $pipe_w ] ],
+ code => sub { print "test"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to stdout' );
+ is( $buffer, 'test', '$buffer after pipe dup to stdout' );
+
+ TEST "pipe dup to fd2",
+ setup => [ fd2 => [ 'dup', $pipe_w ] ],
+ code => sub { print STDERR "test"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to fd2' );
+ is( $buffer, 'test', '$buffer after pipe dup to fd2' );
+
+ TEST "pipe dup to stderr",
+ setup => [ stderr => [ 'dup', $pipe_w ] ],
+ code => sub { print STDERR "test"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to stderr' );
+ is( $buffer, 'test', '$buffer after pipe dup to stderr' );
+
+ TEST "pipe dup to other FD",
+ setup => [ fd4 => [ 'dup', $pipe_w ] ],
+ code => sub {
+ close STDOUT;
+ open( STDOUT, ">&=4" ) or die "Cannot open fd4 as stdout - $!";
+ print "test";
+ },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to other FD' );
+ is( $buffer, 'test', '$buffer after pipe dup to other FD' );
+
+ TEST "pipe dup to its own FD",
+ setup => [ "fd$pipe_w_fileno" => $pipe_w ],
+ code => sub {
+ close STDOUT;
+ open( STDOUT, ">&=$pipe_w_fileno" ) or die "Cannot open fd$pipe_w_fileno as stdout - $!";
+ print "test";
+ },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after pipe dup to its own FD' );
+ is( $buffer, 'test', '$buffer after pipe dup to its own FD' );
+
+ TEST "other FD close",
+ code => sub { return $pipe_w->syswrite( "test" ); },
+
+ exitstatus => 255,
+ dollarbang => EBADF,
+ dollarat => '';
+
+ # Try to force a writepipe clash by asking to dup the pipe to lots of FDs
+ TEST "writepipe clash",
+ code => sub { print "test"; },
+ setup => [ map { +"fd$_" => $pipe_w } ( 1 .. 19 ) ],
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$pipe_r->read after writepipe clash' );
+ is( $buffer, 'test', '$buffer after writepipe clash' );
+
+ my( $pipe2_r, $pipe2_w ) = IO::Async::OS->pipepair or die "Cannot pipepair - $!";
+ $pipe2_r->blocking( 0 );
+
+ TEST "pipe dup to stdout and stderr",
+ setup => [ stdout => $pipe_w, stderr => $pipe2_w ],
+ code => sub { print "output"; print STDERR "error"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 6, 0.1 );
+
+ is( $ret, 6, '$pipe_r->read after pipe dup to stdout and stderr' );
+ is( $buffer, 'output', '$buffer after pipe dup to stdout and stderr' );
+
+ undef $buffer;
+ $ret = read_timeout( $pipe2_r, $buffer, 5, 0.1 );
+
+ is( $ret, 5, '$pipe2_r->read after pipe dup to stdout and stderr' );
+ is( $buffer, 'error', '$buffer after pipe dup to stdout and stderr' );
+
+ TEST "pipe dup to stdout and stderr same pipe",
+ setup => [ stdout => $pipe_w, stderr => $pipe_w ],
+ code => sub { print "output"; print STDERR "error"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ undef $buffer;
+ $ret = read_timeout( $pipe_r, $buffer, 11, 0.1 );
+
+ is( $ret, 11, '$pipe_r->read after pipe dup to stdout and stderr same pipe' );
+ is( $buffer, 'outputerror', '$buffer after pipe dup to stdout and stderr same pipe' );
+}
+
+{
+ my ( $child_r, $my_w, $my_r, $child_w ) = IO::Async::OS->pipequad or die "Cannot pipequad - $!";
+
+ $my_w->syswrite( "hello\n" );
+
+ TEST "pipe quad to fd0/fd1",
+ setup => [ stdin => $child_r,
+ stdout => $child_w, ],
+ code => sub { print uc scalar <STDIN>; return 0 },
+
+ exitstatus => 0,
+ dollarat => '';
+
+ my $buffer;
+ $ret = read_timeout( $my_r, $buffer, 6, 0.1 );
+
+ is( $ret, 6, '$my_r->read after pipe quad to fd0/fd1' );
+ is( $buffer, "HELLO\n", '$buffer after pipe quad to fd0/fd1' );
+}
+
+{
+ # Try to swap two filehandles and cause a dup2() collision
+ my @fhA = IO::Async::OS->pipepair or die "Cannot pipepair - $!";
+ my @fhB = IO::Async::OS->pipepair or die "Cannot pipepair - $!";
+
+ my $filenoA = $fhA[1]->fileno;
+ my $filenoB = $fhB[1]->fileno;
+
+ TEST "fd swap",
+ setup => [
+ "fd$filenoA" => $fhB[1],
+ "fd$filenoB" => $fhA[1],
+ ],
+ code => sub {
+ $fhA[1]->print( "FHA" ); $fhA[1]->autoflush(1);
+ $fhB[1]->print( "FHB" ); $fhB[1]->autoflush(1);
+ return 0;
+ },
+
+ exitstatus => 0;
+
+ my $buffer;
+
+ read_timeout( $fhA[0], $buffer, 3, 0.1 );
+ is( $buffer, "FHB", '$buffer [A] after dup2() swap' );
+
+ read_timeout( $fhB[0], $buffer, 3, 0.1 );
+ is( $buffer, "FHA", '$buffer [B] after dup2() swap' );
+}
+
+TEST "stdout close",
+ setup => [ stdout => [ 'close' ] ],
+ code => sub { print "test"; },
+
+ exitstatus => 255,
+ dollarbang => EBADF,
+ dollarat => '';
+
+TEST "stdout close shortcut",
+ setup => [ stdout => 'close' ],
+ code => sub { print "test"; },
+
+ exitstatus => 255,
+ dollarbang => EBADF,
+ dollarat => '';
+
+{
+ my $name = tmpnam;
+ END { unlink $name if defined $name and -f $name }
+
+ TEST "stdout open",
+ setup => [ stdout => [ 'open', '>', $name ] ],
+ code => sub { print "test"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ ok( -f $name, 'tmpnam file exists after stdout open' );
+
+ open( my $tmpfh, "<", $name ) or die "Cannot open '$name' for reading - $!";
+
+ undef $buffer;
+ $ret = read_timeout( $tmpfh, $buffer, 4, 0.1 );
+
+ is( $ret, 4, '$tmpfh->read after stdout open' );
+ is( $buffer, 'test', '$buffer after stdout open' );
+
+ TEST "stdout open append",
+ setup => [ stdout => [ 'open', '>>', $name ] ],
+ code => sub { print "value"; },
+
+ exitstatus => 1,
+ dollarat => '';
+
+ seek( $tmpfh, 0, 0 );
+
+ undef $buffer;
+ $ret = read_timeout( $tmpfh, $buffer, 9, 0.1 );
+
+ is( $ret, 9, '$tmpfh->read after stdout open append' );
+ is( $buffer, 'testvalue', '$buffer after stdout open append' );
+}
+
+$ENV{TESTKEY} = "parent value";
+
+TEST "environment is preserved",
+ setup => [],
+ code => sub { return $ENV{TESTKEY} eq "parent value" ? 0 : 1 },
+
+ exitstatus => 0,
+ dollarat => '';
+
+TEST "environment is overwritten",
+ setup => [ env => { TESTKEY => "child value" } ],
+ code => sub { return $ENV{TESTKEY} eq "child value" ? 0 : 1 },
+
+ exitstatus => 0,
+ dollarat => '';
+
+SKIP: {
+ # Some of the CPAN smoke testers might run test scripts under modified nice
+ # anyway. We'd better get our starting value to check for difference, not
+ # absolute
+ my $prio_now = getpriority(0,0);
+
+ # If it's already quite high, we don't want to hit the limit and be
+ # clamped. Just skip the tests if it's too high before we start.
+ skip "getpriority is already above 15, so I won't try renicing upwards", 3 if $prio_now > 15;
+
+ TEST "nice works",
+ setup => [ nice => 3 ],
+ code => sub { return getpriority(0,0) == $prio_now + 3 ? 0 : 1 },
+
+ exitstatus => 0,
+ dollarat => '';
+}
+
+TEST "chdir works",
+ setup => [ chdir => "/" ],
+ code => sub { return getcwd eq "/" ? 0 : 1 },
+
+ exitstatus => 0,
+ dollarat => '';
+
+done_testing;