about summary refs log tree commit homepage
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/config.t2
-rw-r--r--t/ds-leak.t10
-rw-r--r--t/edit.t2
-rw-r--r--t/git.t17
-rw-r--r--t/hl_mod.t20
-rw-r--r--t/httpd-corner.t30
-rw-r--r--t/httpd.t8
-rw-r--r--t/indexlevels-mirror.t8
-rw-r--r--t/init.t5
-rw-r--r--t/mda.t2
-rw-r--r--t/mda_filter_rubylang.t6
-rw-r--r--t/multi-mid.t2
-rw-r--r--t/nntpd.t5
-rw-r--r--t/nulsubject.t3
-rw-r--r--t/replace.t2
-rw-r--r--t/search.t2
-rw-r--r--t/solver_git.t5
-rw-r--r--t/v2mda.t2
-rw-r--r--t/v2mirror.t4
-rw-r--r--t/v2writable.t2
-rw-r--r--t/www_listing.t19
21 files changed, 77 insertions, 79 deletions
diff --git a/t/config.t b/t/config.t
index 0db12359..1f50bb86 100644
--- a/t/config.t
+++ b/t/config.t
@@ -11,7 +11,7 @@ my ($tmpdir, $for_destroy) = tmpdir();
 {
         PublicInbox::Import::init_bare($tmpdir);
         my @cmd = ('git', "--git-dir=$tmpdir", qw(config foo.bar), "hi\nhi");
-        is(system(@cmd), 0, "set config");
+        is(xsys(@cmd), 0, "set config");
 
         my $tmp = PublicInbox::Config->new("$tmpdir/config");
 
diff --git a/t/ds-leak.t b/t/ds-leak.t
index b29d814e..72bf0379 100644
--- a/t/ds-leak.t
+++ b/t/ds-leak.t
@@ -10,7 +10,7 @@ use PublicInbox::TestCommon;
 use_ok 'PublicInbox::DS';
 
 if ('close-on-exec for epoll and kqueue') {
-        use PublicInbox::Spawn qw(spawn);
+        use PublicInbox::Spawn qw(spawn which);
         my $pid;
         my $evfd_re = qr/(?:kqueue|eventpoll)/i;
 
@@ -31,10 +31,12 @@ if ('close-on-exec for epoll and kqueue') {
         my $l = <$r>;
         is($l, undef, 'cloexec works and sleep(1) is running');
 
-        my @of = grep(/$evfd_re/, `lsof -p $pid 2>/dev/null`);
-        my $err = $?;
         SKIP: {
-                skip "lsof missing? (\$?=$err)", 1 if $err;
+                my $lsof = which('lsof') or skip 'lsof missing', 1;
+                my $rdr = { 2 => \(my $null) };
+                my @of = grep(/$evfd_re/, xqx([$lsof, '-p', $pid], {}, $rdr));
+                my $err = $?;
+                skip "lsof broken ? (\$?=$err)", 1 if $err;
                 is_deeply(\@of, [], 'no FDs leaked to subprocess');
         };
         if (defined $pid) {
diff --git a/t/edit.t b/t/edit.t
index 2803dd01..d8833f9c 100644
--- a/t/edit.t
+++ b/t/edit.t
@@ -118,7 +118,7 @@ $t = 'non-interactive editor failure'; {
 
 $t = 'mailEditor set in config'; {
         $in = $out = $err = '';
-        my $rc = system(qw(git config), "--file=$cfgfile",
+        my $rc = xsys(qw(git config), "--file=$cfgfile",
                         'publicinbox.maileditor',
                         "$^X -i -p -e 's/boolean prefix/bool pfx/'");
         is($rc, 0, 'set publicinbox.mailEditor');
diff --git a/t/git.t b/t/git.t
index 0936ac5e..b05ac123 100644
--- a/t/git.t
+++ b/t/git.t
@@ -13,9 +13,10 @@ use_ok 'PublicInbox::Git';
 {
         PublicInbox::Import::init_bare($dir);
         my $fi_data = './t/git.fast-import-data';
-        ok(-r $fi_data, "fast-import data readable (or run test at top level)");
-        local $ENV{GIT_DIR} = $dir;
-        system("git fast-import --quiet <$fi_data");
+        open my $fh, '<', $fi_data or die
+                "fast-import data readable (or run test at top level: $!";
+        my $rdr = { 0 => $fh };
+        xsys([qw(git fast-import --quiet)], { GIT_DIR => $dir }, $rdr);
         is($?, 0, 'fast-import succeeded');
 }
 
@@ -58,18 +59,14 @@ use_ok 'PublicInbox::Git';
 }
 
 if (1) {
-        my $cmd = [ 'git', "--git-dir=$dir", qw(hash-object -w --stdin) ];
-
         # need a big file, use the AGPL-3.0 :p
         my $big_data = './COPYING';
         ok(-r $big_data, 'COPYING readable');
         my $size = -s $big_data;
         ok($size > 8192, 'file is big enough');
-
-        my $buf = do {
-                local $ENV{GIT_DIR} = $dir;
-                `git hash-object -w --stdin <$big_data`;
-        };
+        open my $fh, '<', $big_data or die;
+        my $cmd = [ 'git', "--git-dir=$dir", qw(hash-object -w --stdin) ];
+        my $buf = xqx($cmd, { GIT_DIR => $dir }, { 0 => $fh });
         is(0, $?, 'hashed object successfully');
         chomp $buf;
 
diff --git a/t/hl_mod.t b/t/hl_mod.t
index a4ef4a28..95057354 100644
--- a/t/hl_mod.t
+++ b/t/hl_mod.t
@@ -4,7 +4,8 @@
 use strict;
 use warnings;
 use Test::More;
-use PublicInbox::Spawn qw(which spawn);
+use PublicInbox::Spawn qw(which);
+use PublicInbox::TestCommon;
 use IO::Handle; # ->autoflush
 use Fcntl qw(:seek);
 eval { require highlight } or
@@ -29,21 +30,14 @@ my $orig = $str;
         is($$ref, $$lref, 'do_hl_lang matches do_hl');
 
         SKIP: {
-                which('w3m') or skip 'w3m(1) missing to check output', 1;
-                my $cmd = [ qw(w3m -T text/html -dump -config /dev/null) ];
-                open my $in, '+>', undef or die;
-                open my $out, '+>', undef or die;
-                my $rdr = { 0 => fileno($in), 1 => fileno($out) };
-                $in->autoflush(1);
-                print $in '<pre>', $$ref, '</pre>' or die;
-                $in->seek(0, SEEK_SET) or die;
-                my $pid = spawn($cmd, undef, $rdr);
-                waitpid($pid, 0);
+                my $w3m = which('w3m') or
+                        skip('w3m(1) missing to check output', 1);
+                my $cmd = [ $w3m, qw(-T text/html -dump -config /dev/null) ];
+                my $in = '<pre>' . $$ref . '</pre>';
+                my $out = xqx($cmd, undef, { 0 => \$in });
                 # expand tabs and normalize whitespace,
                 # w3m doesn't preserve tabs
                 $orig =~ s/\t/        /gs;
-                $out->seek(0, SEEK_SET) or die;
-                $out = do { local $/; <$out> };
                 $out =~ s/\s*\z//sg;
                 $orig =~ s/\s*\z//sg;
                 is($out, $orig, 'w3m output matches');
diff --git a/t/httpd-corner.t b/t/httpd-corner.t
index f25a9a9c..7a6bcc66 100644
--- a/t/httpd-corner.t
+++ b/t/httpd-corner.t
@@ -6,7 +6,7 @@ use strict;
 use warnings;
 use Test::More;
 use Time::HiRes qw(gettimeofday tv_interval);
-use PublicInbox::Spawn qw(which spawn);
+use PublicInbox::Spawn qw(which spawn popen_rd);
 use PublicInbox::TestCommon;
 require_mods(qw(Plack::Util Plack::Builder HTTP::Date HTTP::Status));
 use Digest::SHA qw(sha1_hex);
@@ -26,9 +26,6 @@ my $psgi = "./t/httpd-corner.psgi";
 my $sock = tcp_server() or die;
 my @zmods = qw(PublicInbox::GzipFilter IO::Uncompress::Gunzip);
 
-# make sure stdin is not a pipe for lsof test to check for leaking pipes
-open(STDIN, '<', '/dev/null') or die 'no /dev/null: $!';
-
 # Make sure we don't clobber socket options set by systemd or similar
 # using socket activation:
 my ($defer_accept_val, $accf_arg, $TCP_DEFER_ACCEPT);
@@ -308,12 +305,12 @@ my $check_self = sub {
 };
 
 SKIP: {
-        which('curl') or skip('curl(1) missing', 4);
+        my $curl = which('curl') or skip('curl(1) missing', 4);
         my $base = 'http://' . $sock->sockhost . ':' . $sock->sockport;
         my $url = "$base/sha1";
         my ($r, $w);
         pipe($r, $w) or die "pipe: $!";
-        my $cmd = [qw(curl --tcp-nodelay --no-buffer -T- -HExpect: -sS), $url];
+        my $cmd = [$curl, qw(--tcp-nodelay -T- -HExpect: -sSN), $url];
         open my $cout, '+>', undef or die;
         open my $cerr, '>', undef or die;
         my $rdr = { 0 => $r, 1 => $cout, 2 => $cerr };
@@ -330,7 +327,7 @@ SKIP: {
         seek($cout, 0, SEEK_SET);
         is(<$cout>, sha1_hex($str), 'read expected body');
 
-        open my $fh, '-|', qw(curl -sS), "$base/async-big" or die $!;
+        my $fh = popen_rd([$curl, '-sS', "$base/async-big"]);
         my $n = 0;
         my $non_zero = 0;
         while (1) {
@@ -338,15 +335,14 @@ SKIP: {
                 $n += $r;
                 $buf =~ /\A\0+\z/ or $non_zero++;
         }
-        close $fh or die "curl errored out \$?=$?";
+        close $fh or die "close curl pipe: $!";
+        is($?, 0, 'curl succesful');
         is($n, 30 * 1024 * 1024, 'got expected output from curl');
         is($non_zero, 0, 'read all zeros');
 
-        require_mods(@zmods, 1);
-        open $fh, '-|', qw(curl -sS), "$base/psgi-return-gzip" or die;
-        binmode $fh;
-        my $buf = do { local $/; <$fh> };
-        close $fh or die "curl errored out \$?=$?";
+        require_mods(@zmods, 2);
+        my $buf = xqx([$curl, '-sS', "$base/psgi-return-gzip"]);
+        is($?, 0, 'curl succesful');
         IO::Uncompress::Gunzip::gunzip(\$buf => \(my $out));
         is($out, "hello world\n");
 }
@@ -605,12 +601,14 @@ SKIP: {
 
 SKIP: {
         skip 'only testing lsof(8) output on Linux', 1 if $^O ne 'linux';
-        skip 'no lsof in PATH', 1 unless which('lsof');
-        my @lsof = `lsof -p $td->{pid}`;
+        my $lsof = which('lsof') or skip 'no lsof in PATH', 1;
+        my $null_in = '';
+        my $rdr = { 2 => \(my $null_err), 0 => \$null_in };
+        my @lsof = xqx([$lsof, '-p', $td->{pid}], undef, $rdr);
         is_deeply([grep(/\bdeleted\b/, @lsof)], [], 'no lingering deleted inputs');
 
         # filter out pipes inherited from the parent
-        my @this = `lsof -p $$`;
+        my @this = xqx([$lsof, '-p', $$], undef, $rdr);
         my $bad;
         my $extract_inodes = sub {
                 map {;
diff --git a/t/httpd.t b/t/httpd.t
index 11511c73..61aec3b4 100644
--- a/t/httpd.t
+++ b/t/httpd.t
@@ -64,22 +64,22 @@ EOF
                 is($conn->read($buf, 1), 0, "EOF");
         }
 
-        is(system(qw(git clone -q --mirror),
+        is(xsys(qw(git clone -q --mirror),
                         "http://$host:$port/$group", "$tmpdir/clone.git"),
                 0, 'smart clone successful');
 
         # ensure dumb cloning works, too:
-        is(system('git', "--git-dir=$maindir",
+        is(xsys('git', "--git-dir=$maindir",
                 qw(config http.uploadpack false)),
                 0, 'disable http.uploadpack');
-        is(system(qw(git clone -q --mirror),
+        is(xsys(qw(git clone -q --mirror),
                         "http://$host:$port/$group", "$tmpdir/dumb.git"),
                 0, 'clone successful');
 
         ok($td->kill, 'killed httpd');
         $td->join;
 
-        is(system('git', "--git-dir=$tmpdir/clone.git",
+        is(xsys('git', "--git-dir=$tmpdir/clone.git",
                   qw(fsck --no-verbose)), 0,
                 'fsck on cloned directory successful');
 }
diff --git a/t/indexlevels-mirror.t b/t/indexlevels-mirror.t
index 77c52718..f2200306 100644
--- a/t/indexlevels-mirror.t
+++ b/t/indexlevels-mirror.t
@@ -59,7 +59,7 @@ sub import_index_incremental {
                 push @cmd, "$ibx->{inboxdir}/git/0.git", "$mirror/git/0.git";
         }
         my $fetch_dir = $cmd[-1];
-        is(system(@cmd), 0, "v$v clone OK");
+        is(xsys(@cmd), 0, "v$v clone OK");
 
         # inbox init
         local $ENV{PI_CONFIG} = "$tmpdir/.picfg";
@@ -86,7 +86,7 @@ sub import_index_incremental {
         $im->done;
 
         # mirror updates
-        is(system('git', "--git-dir=$fetch_dir", qw(fetch -q)), 0, 'fetch OK');
+        is(xsys('git', "--git-dir=$fetch_dir", qw(fetch -q)), 0, 'fetch OK');
         ok(run_script(['-index', $mirror]), "v$v index mirror again OK");
         ($nr, $msgs) = $ro_mirror->recent;
         is($nr, 2, '2nd message seen in mirror');
@@ -123,7 +123,7 @@ sub import_index_incremental {
         }
 
         # sync the mirror
-        is(system('git', "--git-dir=$fetch_dir", qw(fetch -q)), 0, 'fetch OK');
+        is(xsys('git', "--git-dir=$fetch_dir", qw(fetch -q)), 0, 'fetch OK');
         ok(run_script(['-index', $mirror]), "v$v index mirror again OK");
         ($nr, $msgs) = $ro_mirror->recent;
         is($nr, 1, '2nd message gone from mirror');
@@ -148,7 +148,7 @@ sub import_index_incremental {
                 push @expect, $i;
         }
         $im->done;
-        is(system('git', "--git-dir=$fetch_dir", qw(fetch -q)), 0, 'fetch OK');
+        is(xsys('git', "--git-dir=$fetch_dir", qw(fetch -q)), 0, 'fetch OK');
         ok(run_script(['-index', '--reindex', $mirror]),
                 "v$v index --reindex mirror OK");
         @ro_nums = map { $_->{num} } @{$ro_mirror->over->query_ts(0, 0)};
diff --git a/t/init.t b/t/init.t
index a78c2fc8..94c6184e 100644
--- a/t/init.t
+++ b/t/init.t
@@ -105,7 +105,8 @@ done_testing();
 
 sub read_indexlevel {
         my ($inbox) = @_;
-        local $ENV{GIT_CONFIG} = "$ENV{PI_DIR}/config";
-        chomp(my $lvl = `git config publicinbox.$inbox.indexlevel`);
+        my $cmd = [ qw(git config), "publicinbox.$inbox.indexlevel" ];
+        my $env = { GIT_CONFIG => "$ENV{PI_DIR}/config" };
+        chomp(my $lvl = xqx($cmd, $env));
         $lvl;
 }
diff --git a/t/mda.t b/t/mda.t
index af5e1931..dc691616 100644
--- a/t/mda.t
+++ b/t/mda.t
@@ -298,7 +298,7 @@ Subject: this message will be trained as spam
 Date: Thu, 01 Jan 1970 00:00:00 +0000
 
 EOF
-        system(qw(git config --file), $pi_config, "$cfgpfx.listid", $list_id);
+        xsys(qw(git config --file), $pi_config, "$cfgpfx.listid", $list_id);
         $? == 0 or die "failed to set listid $?";
         my $in = $simple->as_string;
         ok(run_script(['-mda'], undef, { 0 => \$in }),
diff --git a/t/mda_filter_rubylang.t b/t/mda_filter_rubylang.t
index 6f288b7e..f2cbe9d5 100644
--- a/t/mda_filter_rubylang.t
+++ b/t/mda_filter_rubylang.t
@@ -14,7 +14,7 @@ my $pi_config = "$tmpdir/pi_config";
 local $ENV{PI_CONFIG} = $pi_config;
 local $ENV{PI_EMERGENCY} = "$tmpdir/emergency";
 my @cfg = ('git', 'config', "--file=$pi_config");
-is(system(@cfg, 'publicinboxmda.spamcheck', 'none'), 0);
+is(xsys(@cfg, 'publicinboxmda.spamcheck', 'none'), 0);
 
 for my $v (qw(V1 V2)) {
         my @warn;
@@ -26,8 +26,8 @@ for my $v (qw(V1 V2)) {
                 "http://example.com/$v", $addr ];
         ok(run_script($cmd), 'public-inbox-init');
         ok(run_script(['-index', $inboxdir]), 'public-inbox-index');
-        is(system(@cfg, "$cfgpfx.filter", 'PublicInbox::Filter::RubyLang'), 0);
-        is(system(@cfg, "$cfgpfx.altid",
+        is(xsys(@cfg, "$cfgpfx.filter", 'PublicInbox::Filter::RubyLang'), 0);
+        is(xsys(@cfg, "$cfgpfx.altid",
                 'serial:alerts:file=msgmap.sqlite3'), 0);
 
         for my $i (1..2) {
diff --git a/t/multi-mid.t b/t/multi-mid.t
index 31a8fd74..5afb9693 100644
--- a/t/multi-mid.t
+++ b/t/multi-mid.t
@@ -65,7 +65,7 @@ for my $order ([$bad, $good], [$good, $bad]) {
         my @v2 = ($ibx->over->get_art(1), $ibx->over->get_art(2));
         is_deeply(\@v2, \@old, 'v2 conversion times match');
 
-        system(qw(git clone -sq --mirror), "$tmpdir/v2/git/0.git",
+        xsys(qw(git clone -sq --mirror), "$tmpdir/v2/git/0.git",
                 "$tmpdir/v2-clone/git/0.git") == 0 or die "clone: $?";
         $cmd = [ '-init', '-Lbasic', '-V2', 'v2c', "$tmpdir/v2-clone",
                 'http://example.com/v2c', 'v2c@example.com' ];
diff --git a/t/nntpd.t b/t/nntpd.t
index 826e3f3d..66aa48f1 100644
--- a/t/nntpd.t
+++ b/t/nntpd.t
@@ -48,7 +48,7 @@ $ibx = PublicInbox::Inbox->new($ibx);
         my @cmd = ('-init', $group, $inboxdir, 'http://example.com/', $addr);
         push @cmd, "-V$version", '-Lbasic';
         ok(run_script(\@cmd), 'init OK');
-        is(system(qw(git config), "--file=$home/.public-inbox/config",
+        is(xsys(qw(git config), "--file=$home/.public-inbox/config",
                         "publicinbox.$group.newsgroup", $group),
                 0, 'enabled newsgroup');
         my $len;
@@ -304,7 +304,8 @@ Date: Fri, 02 Oct 1993 00:00:00 +0000
                 if ($INC{'Search/Xapian.pm'} && ($ENV{TEST_RUN_MODE}//2)) {
                         skip 'Search/Xapian.pm pre-loaded (by t/run.perl?)', 1;
                 }
-                my @of = `lsof -p $td->{pid} 2>/dev/null`;
+                my $rdr = { 2 => \(my $null) };
+                my @of = xqx(['lsof', '-p', $td->{pid}], undef, $rdr);
                 skip('lsof broken', 1) if (!scalar(@of) || $?);
                 my @xap = grep m!Search/Xapian!, @of;
                 is_deeply(\@xap, [], 'Xapian not loaded in nntpd');
diff --git a/t/nulsubject.t b/t/nulsubject.t
index b21507c2..1ded88d3 100644
--- a/t/nulsubject.t
+++ b/t/nulsubject.t
@@ -25,7 +25,8 @@ my $git_dir = "$tmpdir/a.git";
                 body => "hello world\n",
         ));
         $im->done;
-        is(system(qw(git --git-dir), $git_dir, 'fsck', '--strict'), 0, 'git fsck ok');
+        is(xsys(qw(git --git-dir), $git_dir, 'fsck', '--strict'), 0,
+                'git fsck ok');
 }
 
 done_testing();
diff --git a/t/replace.t b/t/replace.t
index a44560fc..2efa25f1 100644
--- a/t/replace.t
+++ b/t/replace.t
@@ -95,7 +95,7 @@ EOF
 
         for my $dir (glob("$ibx->{inboxdir}/git/*.git")) {
                 my ($bn) = ($dir =~ m!([^/]+)\z!);
-                is(system(qw(git --git-dir), $dir,
+                is(xsys(qw(git --git-dir), $dir,
                                         qw(fsck --strict --no-progress)),
                         0, "git fsck is clean in epoch $bn");
         }
diff --git a/t/search.t b/t/search.t
index 101d44e9..8508f273 100644
--- a/t/search.t
+++ b/t/search.t
@@ -14,7 +14,7 @@ my $git_dir = "$tmpdir/a.git";
 my $ibx = PublicInbox::Inbox->new({ inboxdir => $git_dir });
 my ($root_id, $last_id);
 
-is(0, system(qw(git init --shared -q --bare), $git_dir), "git init (main)")
+is(0, xsys(qw(git init --shared -q --bare), $git_dir), "git init (main)")
         or BAIL_OUT("`git init --shared' failed, weird FS or seccomp?");
 eval { PublicInbox::Search->new($ibx)->xdb };
 ok($@, "exception raised on non-existent DB");
diff --git a/t/solver_git.t b/t/solver_git.t
index b5231d2c..7f79ff4c 100644
--- a/t/solver_git.t
+++ b/t/solver_git.t
@@ -8,8 +8,9 @@ use PublicInbox::TestCommon;
 require_git(2.6);
 use PublicInbox::Spawn qw(popen_rd);
 require_mods(qw(DBD::SQLite Search::Xapian Plack::Util));
-chomp(my $git_dir = `git rev-parse --git-dir 2>/dev/null`);
-plan skip_all => "$0 must be run from a git working tree" if $?;
+my $git_dir = xqx([qw(git rev-parse --git-dir)], undef, {2 => \(my $null)});
+$? == 0 or plan skip_all => "$0 must be run from a git working tree";
+chomp $git_dir;
 
 # needed for alternates, and --absolute-git-dir is only in git 2.13+
 $git_dir = abs_path($git_dir);
diff --git a/t/v2mda.t b/t/v2mda.t
index c2118a89..e9dcdf44 100644
--- a/t/v2mda.t
+++ b/t/v2mda.t
@@ -72,7 +72,7 @@ is($saved->{mime}->as_string, $mime->as_string, 'injected message');
         my $config = "$ENV{PI_DIR}/config";
         ok(-f $config, 'config exists');
         my $k = 'publicinboxmda.spamcheck';
-        is(system('git', 'config', "--file=$config", $k, 'none'), 0,
+        is(xsys('git', 'config', "--file=$config", $k, 'none'), 0,
                 'disabled spamcheck for mda');
 
         ok(run_script(['-mda'], undef, $rdr), 'mda did not die');
diff --git a/t/v2mirror.t b/t/v2mirror.t
index 2e23e763..406bbd4f 100644
--- a/t/v2mirror.t
+++ b/t/v2mirror.t
@@ -76,7 +76,7 @@ foreach my $i (0..$epoch_max) {
                 "http://$host:$port/v2/$i$sfx",
                 "$tmpdir/m/git/$i.git");
 
-        is(system(@cmd), 0, "cloned $i.git");
+        is(xsys(@cmd), 0, "cloned $i.git");
         ok(-d "$tmpdir/m/git/$i.git", "mirror $i OK");
 }
 
@@ -102,7 +102,7 @@ $ibx->cleanup;
 my $fetch_each_epoch = sub {
         foreach my $i (0..$epoch_max) {
                 my $dir = "$tmpdir/m/git/$i.git";
-                is(system('git', "--git-dir=$dir", 'fetch', '-q'), 0,
+                is(xsys('git', "--git-dir=$dir", 'fetch', '-q'), 0,
                         'fetch successful');
         }
 };
diff --git a/t/v2writable.t b/t/v2writable.t
index 66d5663e..8897062a 100644
--- a/t/v2writable.t
+++ b/t/v2writable.t
@@ -48,7 +48,7 @@ my $git0;
 if ('ensure git configs are correct') {
         my @cmd = (qw(git config), "--file=$inboxdir/all.git/config",
                 qw(core.sharedRepository 0644));
-        is(system(@cmd), 0, "set sharedRepository in all.git");
+        is(xsys(@cmd), 0, "set sharedRepository in all.git");
         $git0 = PublicInbox::Git->new("$inboxdir/git/0.git");
         chomp(my $v = $git0->qx(qw(config core.sharedRepository)));
         is($v, '0644', 'child repo inherited core.sharedRepository');
diff --git a/t/www_listing.t b/t/www_listing.t
index c132edbc..31d76356 100644
--- a/t/www_listing.t
+++ b/t/www_listing.t
@@ -24,8 +24,10 @@ is(PublicInbox::WwwListing::fingerprint($bare), undef,
         'empty repo has no fingerprint');
 {
         my $fi_data = './t/git.fast-import-data';
-        local $ENV{GIT_DIR} = $bare->{git_dir};
-        is(system("git fast-import --quiet <$fi_data"), 0, 'fast-import');
+        open my $fh, '<', $fi_data or die "open $fi_data: $!";
+        my $env = { GIT_DIR => $bare->{git_dir} };
+        is(xsys([qw(git fast-import --quiet)], $env, { 0 => $fh }), 0,
+                'fast-import');
 }
 
 like(PublicInbox::WwwListing::fingerprint($bare), qr/\A[a-f0-9]{40}\z/,
@@ -76,17 +78,17 @@ SKIP: {
         ok($sock, 'sock created');
         my ($host, $port) = ($sock->sockhost, $sock->sockport);
         my @clone = qw(git clone -q -s --bare);
-        is(system(@clone, $bare->{git_dir}, $alt), 0, 'clone shared repo');
+        is(xsys(@clone, $bare->{git_dir}, $alt), 0, 'clone shared repo');
 
         PublicInbox::Import::init_bare("$v2/all.git");
         for my $i (0..2) {
-                is(system(@clone, $alt, "$v2/git/$i.git"), 0, "clone epoch $i");
+                is(xsys(@clone, $alt, "$v2/git/$i.git"), 0, "clone epoch $i")
         }
         ok(open(my $fh, '>', "$v2/inbox.lock"), 'mock a v2 inbox');
         open $fh, '>', "$alt/description" or die;
         print $fh "we're all clones\n" or die;
         close $fh or die;
-        is(system('git', "--git-dir=$alt", qw(config gitweb.owner lorelei)), 0,
+        is(xsys('git', "--git-dir=$alt", qw(config gitweb.owner lorelei)), 0,
                 'set gitweb user');
         ok(unlink("$bare->{git_dir}/description"), 'removed bare/description');
         open $fh, '>', $cfgfile or die;
@@ -114,7 +116,8 @@ SKIP: {
 
         tiny_test($json, $host, $port);
 
-        skip 'skipping grok-pull integration test', 2 if !which('grok-pull');
+        my $grok_pull = which('grok-pull') or
+                skip('skipping grok-pull integration test', 2);
 
         ok(mkdir("$tmpdir/mirror"), 'prepare grok mirror dest');
         open $fh, '>', "$tmpdir/repos.conf" or die;
@@ -129,7 +132,7 @@ mymanifest = $tmpdir/local-manifest.js.gz
 
         close $fh or die;
 
-        system(qw(grok-pull -c), "$tmpdir/repos.conf");
+        xsys($grok_pull, '-c', "$tmpdir/repos.conf");
         is($? >> 8, 127, 'grok-pull exit code as expected');
         for (qw(alt bare v2/git/0.git v2/git/1.git v2/git/2.git)) {
                 ok(-d "$tmpdir/mirror/$_", "grok-pull created $_");
@@ -149,7 +152,7 @@ mymanifest = $tmpdir/per-inbox-manifest.js.gz
 
         close $fh or die;
         ok(mkdir("$tmpdir/per-inbox"), 'prepare single-v2-inbox mirror');
-        system(qw(grok-pull -c), "$tmpdir/per-inbox.conf");
+        xsys($grok_pull, '-c', "$tmpdir/per-inbox.conf");
         is($? >> 8, 127, 'grok-pull exit code as expected');
         for (qw(v2/git/0.git v2/git/1.git v2/git/2.git)) {
                 ok(-d "$tmpdir/per-inbox/$_", "grok-pull created $_");