X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FTestCommon.pm;h=d952ee6d97715fc644b1dcf0e663d977821c8413;hb=0283273a14e1871955f6a9132f4f3f7884ec8a3f;hp=85cda03190ffba4b3373ac94c731e0d80d48a46b;hpb=7321c78ebdcaa7ce5f0f8383e07429827da0b718;p=public-inbox.git diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm index 85cda031..d952ee6d 100644 --- a/lib/PublicInbox/TestCommon.pm +++ b/lib/PublicInbox/TestCommon.pm @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2019 all contributors +# Copyright (C) 2015-2020 all contributors # License: AGPL-3.0+ # internal APIs used only for tests @@ -8,8 +8,23 @@ use parent qw(Exporter); use Fcntl qw(FD_CLOEXEC F_SETFD F_GETFD :seek); use POSIX qw(dup2); use IO::Socket::INET; -our @EXPORT = qw(tmpdir tcp_server tcp_connect require_git - run_script start_script key2sub); +our @EXPORT = qw(tmpdir tcp_server tcp_connect require_git require_mods + run_script start_script key2sub xsys xqx mime_load eml_load); + +sub mime_load ($) { + my ($path) = @_; + open(my $fh, '<', $path) or die "open $path: $!"; + # test should've called: require_mods('Email::MIME') + require PublicInbox::MIME; + PublicInbox::MIME->new(\(do { local $/; <$fh> })); +} + +sub eml_load ($) { + my ($path, $cb) = @_; + open(my $fh, '<', $path) or die "open $path: $!"; + require PublicInbox::Eml; + PublicInbox::Eml->new(\(do { local $/; <$fh> })); +} sub tmpdir (;$) { my ($base) = @_; @@ -53,35 +68,77 @@ sub require_git ($;$) { my $cur_int = ($cur_maj << 24) | ($cur_min << 16); if ($cur_int < $req_int) { return 0 if $maybe; - plan(skip_all => "git $req+ required, have $cur_maj.$cur_min"); + Test::More::plan(skip_all => + "git $req+ required, have $cur_maj.$cur_min"); } 1; } +sub require_mods { + my @mods = @_; + my $maybe = pop @mods if $mods[-1] =~ /\A[0-9]+\z/; + my @need; + for my $mod (@mods) { + if ($mod eq 'Search::Xapian') { + if (eval { require PublicInbox::Search } && + PublicInbox::Search::load_xapian()) { + next; + } + } elsif ($mod eq 'Search::Xapian::WritableDatabase') { + if (eval { require PublicInbox::SearchIdx } && + PublicInbox::SearchIdx::load_xapian_writable()){ + next; + } + } else { + eval "require $mod"; + } + push @need, $mod if $@; + } + return unless @need; + my $m = join(', ', @need)." missing for $0"; + Test::More::skip($m, $maybe) if $maybe; + Test::More::plan(skip_all => $m) +} + sub key2script ($) { my ($key) = @_; - return $key if (index($key, '/') >= 0); + return $key if ($key eq 'git' || index($key, '/') >= 0); # n.b. we may have scripts which don't start with "public-inbox" in # the future: $key =~ s/\A([-\.])/public-inbox$1/; 'blib/script/'.$key; } +my @io_mode = ([ *STDIN{IO}, '<&' ], [ *STDOUT{IO}, '>&' ], + [ *STDERR{IO}, '>&' ]); + sub _prepare_redirects ($) { my ($fhref) = @_; - my @x = ([ \*STDIN, '<&' ], [ \*STDOUT, '>&' ], [ \*STDERR, '>&' ]); - for (my $fd = 0; $fd <= $#x; $fd++) { + my $orig_io = []; + for (my $fd = 0; $fd <= $#io_mode; $fd++) { my $fh = $fhref->[$fd] or next; - my ($oldfh, $mode) = @{$x[$fd]}; + my ($oldfh, $mode) = @{$io_mode[$fd]}; + open my $orig, $mode, $oldfh or die "$$oldfh $mode stash: $!"; + $orig_io->[$fd] = $orig; + open $oldfh, $mode, $fh or die "$$oldfh $mode redirect: $!"; + } + $orig_io; +} + +sub _undo_redirects ($) { + my ($orig_io) = @_; + for (my $fd = 0; $fd <= $#io_mode; $fd++) { + my $fh = $orig_io->[$fd] or next; + my ($oldfh, $mode) = @{$io_mode[$fd]}; open $oldfh, $mode, $fh or die "$$oldfh $mode redirect: $!"; } } -# $opt->{run_mode} (or $ENV{TEST_RUN_MODE}) allows chosing between +# $opt->{run_mode} (or $ENV{TEST_RUN_MODE}) allows choosing between # three ways to spawn our own short-lived Perl scripts for testing: # # 0 - (fork|vfork) + execve, the most realistic but slowest -# 1 - preloading and running in a forked subprocess (fast) +# 1 - (not currently implemented) # 2 - preloading and running in current process (slightly faster than 1) # # 2 is not compatible with scripts which use "exit" (which we'll try to @@ -153,35 +210,40 @@ sub run_script ($;$$) { my $spawn_opt = {}; for my $fd (0..2) { my $redir = $opt->{$fd}; - next unless ref($redir); - open my $fh, '+>', undef or die "open: $!"; - $fhref->[$fd] = $fh; - $spawn_opt->{$fd} = fileno($fh); - next if $fd > 0; - $fh->autoflush(1); - print $fh $$redir or die "print: $!"; - seek($fh, 0, SEEK_SET) or die "seek: $!"; + my $ref = ref($redir); + if ($ref eq 'SCALAR') { + open my $fh, '+>', undef or die "open: $!"; + $fhref->[$fd] = $fh; + $spawn_opt->{$fd} = $fh; + next if $fd > 0; + $fh->autoflush(1); + print $fh $$redir or die "print: $!"; + seek($fh, 0, SEEK_SET) or die "seek: $!"; + } elsif ($ref eq 'GLOB') { + $spawn_opt->{$fd} = $fhref->[$fd] = $redir; + } elsif ($ref) { + die "unable to deal with $ref $redir"; + } } if ($run_mode == 0) { # spawn an independent new process, like real-world use cases: require PublicInbox::Spawn; my $cmd = [ key2script($key), @argv ]; my $pid = PublicInbox::Spawn::spawn($cmd, $env, $spawn_opt); - defined($pid) or die "spawn: $!"; if (defined $pid) { my $r = waitpid($pid, 0); defined($r) or die "waitpid: $!"; $r == $pid or die "waitpid: expected $pid, got $r"; } } else { # localize and run everything in the same process: - local *STDIN = *STDIN; - local *STDOUT = *STDOUT; - local *STDERR = *STDERR; + # note: "local *STDIN = *STDIN;" and so forth did not work in + # old versions of perl local %ENV = $env ? (%ENV, %$env) : %ENV; local %SIG = %SIG; local $0 = join(' ', @$cmd); - _prepare_redirects($fhref); + my $orig_io = _prepare_redirects($fhref); _run_sub($sub, $key, \@argv); + _undo_redirects($orig_io); } # slurp the redirects back into user-supplied strings @@ -197,10 +259,32 @@ sub run_script ($;$$) { sub wait_for_tail () { sleep(2) } +# like system() built-in, but uses spawn() for env/rdr + vfork +sub xsys { + my ($cmd, $env, $rdr) = @_; + if (ref($cmd)) { + $rdr ||= {}; + } else { + $cmd = [ @_ ]; + $env = undef; + $rdr = {}; + } + run_script($cmd, $env, { %$rdr, run_mode => 0 }); + $? >> 8 +} + +# like `backtick` or qx{} op, but uses spawn() for env/rdr + vfork +sub xqx { + my ($cmd, $env, $rdr) = @_; + $rdr //= {}; + run_script($cmd, $env, { %$rdr, run_mode => 0, 1 => \(my $out) }); + wantarray ? split(/^/m, $out) : $out; +} + sub start_script { my ($cmd, $env, $opt) = @_; my ($key, @argv) = @$cmd; - my $run_mode = $ENV{TEST_RUN_MODE} // $opt->{run_mode} // 1; + my $run_mode = $ENV{TEST_RUN_MODE} // $opt->{run_mode} // 2; my $sub = $run_mode == 0 ? undef : key2sub($key); my $tail_pid; if (my $tail_cmd = $ENV{TAIL}) { @@ -272,8 +356,9 @@ sub kill { } sub join { - my ($self) = @_; + my ($self, $sig) = @_; my $pid = delete $self->{pid} or return; + CORE::kill($sig, $pid) if defined $sig; my $ret = waitpid($pid, 0); defined($ret) or die "waitpid($pid): $!"; $ret == $pid or die "waitpid($pid) != $ret"; @@ -286,8 +371,7 @@ sub DESTROY { PublicInbox::TestCommon::wait_for_tail(); CORE::kill('TERM', $tail); } - my $pid = delete $self->{pid} or return; - CORE::kill('TERM', $pid); + $self->join('TERM'); } 1;