| #!perl -w |
| # Copyright (C) 2020-2021 all contributors <meta@public-inbox.org> |
| # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> |
| use strict; |
| use PublicInbox::TestCommon; |
| use Test::More; |
| use Fcntl qw(:seek); |
| use IO::Handle (); |
| use POSIX qw(_exit); |
| use Cwd qw(abs_path); |
| require_mods('PublicInbox::Gcf2'); |
| use_ok 'PublicInbox::Gcf2'; |
| use PublicInbox::Syscall qw($F_SETPIPE_SZ); |
| use PublicInbox::Import; |
| my ($tmpdir, $for_destroy) = tmpdir(); |
| |
| my $gcf2 = PublicInbox::Gcf2::new(); |
| is(ref($gcf2), 'PublicInbox::Gcf2', '::new works'); |
| my $COPYING = 'dba13ed2ddf783ee8118c6a581dbf75305f816a3'; |
| open my $agpl, '<', 'COPYING' or BAIL_OUT "AGPL-3 missing: $!"; |
| $agpl = do { local $/; <$agpl> }; |
| |
| PublicInbox::Import::init_bare($tmpdir); |
| my $fi_data = './t/git.fast-import-data'; |
| my $rdr = {}; |
| open $rdr->{0}, '<', $fi_data or BAIL_OUT $!; |
| xsys([qw(git fast-import --quiet)], { GIT_DIR => $tmpdir }, $rdr); |
| is($?, 0, 'fast-import succeeded'); |
| $gcf2->add_alternate("$tmpdir/objects"); |
| |
| { |
| my ($r, $w); |
| pipe($r, $w) or BAIL_OUT $!; |
| my $tree = 'fdbc43725f21f485051c17463b50185f4c3cf88c'; |
| $gcf2->cat_oid(fileno($w), $tree); |
| close $w; |
| is("$tree tree 30\n", <$r>, 'tree header ok'); |
| $r = do { local $/; <$r> }; |
| is(chop($r), "\n", 'got trailing newline'); |
| is(length($r), 30, 'tree length matches'); |
| } |
| |
| chomp(my $objdir = xqx([qw(git rev-parse --git-path objects)])); |
| if ($objdir =~ /\A--git-path\n/) { # git <2.5 |
| chomp($objdir = xqx([qw(git rev-parse --git-dir)])); |
| $objdir .= '/objects'; |
| } |
| if ($objdir && -d $objdir) { |
| $objdir = abs_path($objdir); |
| open my $alt, '>>', "$tmpdir/objects/info/alternates" or |
| BAIL_OUT $!; |
| print $alt $objdir, "\n" or BAIL_OUT $!; |
| close $alt or BAIL_OUT $!; |
| |
| # calling gcf2->add_alternate on an already-added path won't |
| # cause alternates to be reloaded, so we do |
| # $gcf2->add_alternate($objdir) later on instead of |
| # $gcf2->add_alternate("$tmpdir/objects"); |
| # $objdir = "$tmpdir/objects"; |
| } else { |
| $objdir = undef |
| } |
| |
| my $nr = $ENV{TEST_LEAK_NR}; |
| my $cat = $ENV{TEST_LEAK_CAT} // 10; |
| diag "checking for leaks... (TEST_LEAK_NR=$nr TEST_LEAK_CAT=$cat)" if $nr; |
| |
| SKIP: { |
| skip 'not in git worktree', 21 unless defined($objdir); |
| $gcf2->add_alternate($objdir); |
| eval { $gcf2->add_alternate($objdir) }; |
| ok(!$@, 'no error adding alternate redundantly'); |
| if ($nr) { |
| diag "adding alternate $nr times redundantly"; |
| $gcf2->add_alternate($objdir) for (1..$nr); |
| diag 'done adding redundant alternates'; |
| } |
| |
| open my $fh, '+>', undef or BAIL_OUT "open: $!"; |
| $fh->autoflush(1); |
| |
| ok(!$gcf2->cat_oid(fileno($fh), 'invalid'), 'invalid fails'); |
| seek($fh, 0, SEEK_SET) or BAIL_OUT "seek: $!"; |
| is(do { local $/; <$fh> }, '', 'nothing written'); |
| |
| open $fh, '+>', undef or BAIL_OUT "open: $!"; |
| ok(!$gcf2->cat_oid(fileno($fh), '0'x40), 'z40 fails'); |
| seek($fh, 0, SEEK_SET) or BAIL_OUT "seek: $!"; |
| is(do { local $/; <$fh> }, '', 'nothing written for z40'); |
| |
| open $fh, '+>', undef or BAIL_OUT "open: $!"; |
| my $ck_copying = sub { |
| my ($desc) = @_; |
| seek($fh, 0, SEEK_SET) or BAIL_OUT "seek: $!"; |
| is(<$fh>, "$COPYING blob 34520\n", "got expected header $desc"); |
| my $buf = do { local $/; <$fh> }; |
| is(chop($buf), "\n", 'got trailing \\n'); |
| is($buf, $agpl, "AGPL matches ($desc)"); |
| }; |
| ok($gcf2->cat_oid(fileno($fh), $COPYING), 'cat_oid normal'); |
| $ck_copying->('regular file'); |
| |
| $gcf2 = PublicInbox::Gcf2::new(); |
| $gcf2->add_alternate("$tmpdir/objects"); |
| open $fh, '+>', undef or BAIL_OUT "open: $!"; |
| ok($gcf2->cat_oid(fileno($fh), $COPYING), 'cat_oid alternate'); |
| $ck_copying->('alternates after reopen'); |
| |
| $^O eq 'linux' or skip('pipe tests are Linux-only', 14); |
| for my $blk (1, 0) { |
| my ($r, $w); |
| pipe($r, $w) or BAIL_OUT $!; |
| fcntl($w, $F_SETPIPE_SZ, 4096) or |
| skip('Linux too old for F_SETPIPE_SZ', 14); |
| $w->blocking($blk); |
| seek($fh, 0, SEEK_SET) or BAIL_OUT "seek: $!"; |
| truncate($fh, 0) or BAIL_OUT "truncate: $!"; |
| my $pid = fork // BAIL_OUT "fork: $!"; |
| if ($pid == 0) { |
| close $w; |
| tick; # wait for parent to block on writev |
| my $buf = do { local $/; <$r> }; |
| print $fh $buf or _exit(1); |
| _exit(0); |
| } |
| ok($gcf2->cat_oid(fileno($w), $COPYING), "cat blocking=$blk"); |
| close $w or BAIL_OUT "close: $!"; |
| is(waitpid($pid, 0), $pid, 'child exited'); |
| is($?, 0, 'no error in child'); |
| $ck_copying->("pipe blocking($blk)"); |
| |
| pipe($r, $w) or BAIL_OUT $!; |
| fcntl($w, $F_SETPIPE_SZ, 4096) or BAIL_OUT $!; |
| $w->blocking($blk); |
| close $r; |
| local $SIG{PIPE} = 'IGNORE'; |
| eval { $gcf2->cat_oid(fileno($w), $COPYING) }; |
| like($@, qr/writev error:/, 'got writev error'); |
| } |
| } |
| |
| if ($nr) { |
| open my $null, '>', '/dev/null' or BAIL_OUT "open /dev/null: $!"; |
| my $fd = fileno($null); |
| local $SIG{PIPE} = 'IGNORE'; |
| my ($r, $w); |
| pipe($r, $w); |
| close $r; |
| my $broken = fileno($w); |
| for (1..$nr) { |
| my $obj = PublicInbox::Gcf2::new(); |
| if (defined($objdir)) { |
| $obj->add_alternate($objdir); |
| for (1..$cat) { |
| $obj->cat_oid($fd, $COPYING); |
| eval { $obj->cat_oid($broken, $COPYING) }; |
| $obj->cat_oid($fd, '0'x40); |
| $obj->cat_oid($fd, 'invalid'); |
| } |
| } |
| } |
| } |
| done_testing; |