]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/TestCommon.pm
eml: pure-Perl replacement for Email::MIME
[public-inbox.git] / lib / PublicInbox / TestCommon.pm
index 372cafa60d3e38b6eedc22325b2b64423185f007..600843f006663815a3b0f95b1f61a1c24dfa3899 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2019 all contributors <meta@public-inbox.org>
+# Copyright (C) 2015-2020 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
 # internal APIs used only for tests
@@ -8,8 +8,21 @@ 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: $!";
+       PublicInbox::MIME->new(\(do { local $/; <$fh> }));
+}
+
+sub eml_load ($) {
+       my ($path, $cb) = @_;
+       open(my $fh, '<', $path) or die "open $path: $!";
+       binmode $fh;
+       PublicInbox::Eml->new(\(do { local $/; <$fh> }));
+}
 
 sub tmpdir (;$) {
        my ($base) = @_;
@@ -59,9 +72,35 @@ sub require_git ($;$) {
        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/;
@@ -93,11 +132,11 @@ sub _undo_redirects ($) {
        }
 }
 
-# $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
@@ -169,21 +208,26 @@ 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: $!";
@@ -213,10 +257,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}) {
@@ -288,8 +354,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";
@@ -302,8 +369,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;