package TestApache::subprocess; use strict; use warnings FATAL => 'all'; use Apache::Test; use Apache::TestUtil; use Apache2::Build; use File::Spec::Functions qw(catfile catdir); use IO::Select (); use Apache2::Const -compile => 'OK'; use Config; use constant PERLIO_5_8_IS_ENABLED => $Config{useperlio} && $] >= 5.007; my $perl = Apache2::Build->build_config()->perl_config('perlpath'); my %scripts = ( argv => 'print STDOUT "@ARGV";', env => 'print STDOUT $ENV{SubProcess}', in_out => 'print STDOUT scalar ;', in_err => 'print STDERR scalar ;', ); sub APACHE_TEST_CONFIGURE { my ($class, $self) = @_; my $vars = $self->{vars}; my $target_dir = catdir $vars->{documentroot}, "util"; while (my ($file, $code) = each %scripts) { $file = catfile $target_dir, "$file.pl"; $self->write_perlscript($file, "$code\n"); } } sub handler { my $r = shift; my $cfg = Apache::Test::config(); my $vars = $cfg->{vars}; plan $r, tests => 5, need qw(APR::PerlIO Apache2::SubProcess); my $target_dir = catfile $vars->{documentroot}, "util"; { # test: passing argv + void context my $script = catfile $target_dir, "argv.pl"; my @argv = qw(foo bar); $r->spawn_proc_prog($perl, [$script, @argv]); # can't really test if something is still returned since it # will be no longer void context ok 1; } { # test: passing argv + scalar context my $script = catfile $target_dir, "argv.pl"; my @argv = qw(foo bar); my $out_fh = $r->spawn_proc_prog($perl, [$script, @argv]); my $output = read_data($out_fh); ok t_cmp([split / /, $output], \@argv, "passing ARGV" ); } { # test: passing env to subprocess through subprocess_env my $script = catfile $target_dir, "env.pl"; my $value = "my cool proc"; $r->subprocess_env->set(SubProcess => $value); my $out_fh = $r->spawn_proc_prog($perl, [$script]); my $output = read_data($out_fh); ok t_cmp($output, $value, "passing env via subprocess_env" ); } { # test: subproc's stdin -> stdout + list context my $script = catfile $target_dir, "in_out.pl"; my $value = "my cool proc\r\n"; # must have \n for my ($in_fh, $out_fh, $err_fh) = $r->spawn_proc_prog($perl, [$script]); print $in_fh $value; (my $output = read_data($out_fh)) =~ s/[\r\n]{1,2}/\r\n/; ok t_cmp($output, $value, "testing subproc's stdin -> stdout + list context" ); } { # test: subproc's stdin -> stderr + list context my $script = catfile $target_dir, "in_err.pl"; my $value = "my stderr\r\n"; # must have \n for my ($in_fh, $out_fh, $err_fh) = $r->spawn_proc_prog($perl, [$script]); print $in_fh $value; (my $output = read_data($err_fh)) =~ s/[\r\n]{1,2}/\r\n/; ok t_cmp($output, $value, "testing subproc's stdin -> stderr + list context" ); } # could test send_fd($out), send_fd($err), but currently it's only in # compat.pm. # these are wannabe's # ok t_cmp( # Apache2::SubProcess::spawn_proc_sub($r, $sub, \@args), # Apache2::SUCCESS, # "spawn a subprocess and run a subroutine in it" # ); # ok t_cmp( # Apache2::SubProcess::spawn_thread_prog($r, $command, \@argv), # Apache2::SUCCESS, # "spawn thread and run a program in it" # ); # ok t_cmp( # Apache2::SubProcess::spawn_thread_sub($r, $sub, \@args), # Apache2::SUCCESS, # "spawn thread and run a subroutine in it" # ); Apache2::Const::OK; } sub read_data { my ($fh) = @_; my @data = (); my $sel = IO::Select->new($fh); # here is the catch: # # non-PerlIO pipe fh needs to select if the other end is not fast # enough to send the data, since the read is non-blocking # # PerlIO-based pipe fh on the other hand does the select # internally via apr_wait_for_io_or_timeout() in # apr_file_read(). But you cannot call select() on the # PerlIO-based, because its fileno() returns (-1), remember that # apr_file_t is an opaque object, and on certain platforms # fileno() is different from unix # # so we use the following wrapper: if we are under perlio we just # go ahead and read the data, if we are under non-perlio we first # select for a few secs. (XXX: is 10 secs enough?) # # btw: we use perlIO only for perl 5.7+ # if (APR::PerlIO::PERLIO_LAYERS_ARE_ENABLED() || $sel->can_read(10)) { @data = wantarray ? (<$fh>) : <$fh>; } if (wantarray) { return @data; } else { return defined $data[0] ? $data[0] : ''; } } 1;