about summary refs log tree commit homepage
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/config_limiter.t1
-rw-r--r--t/git.t23
-rw-r--r--t/git_async.t142
-rw-r--r--t/git_idx.t24
-rw-r--r--t/httpd-unix.t7
-rw-r--r--t/hval.t20
-rw-r--r--t/repo_git_search_idx.t28
-rw-r--r--t/repobrowse.t21
-rw-r--r--t/repobrowse_common_git.perl67
-rw-r--r--t/repobrowse_git.t11
-rw-r--r--t/repobrowse_git_atom.t38
-rw-r--r--t/repobrowse_git_commit.t19
-rw-r--r--t/repobrowse_git_httpd.t138
-rw-r--r--t/repobrowse_git_log.t19
-rw-r--r--t/repobrowse_git_raw.t24
-rw-r--r--t/repobrowse_git_snapshot.t46
-rw-r--r--t/repobrowse_git_src.t38
-rw-r--r--t/search.t34
-rw-r--r--t/spawn.t11
-rw-r--r--t/thread-all.t5
20 files changed, 668 insertions, 48 deletions
diff --git a/t/config_limiter.t b/t/config_limiter.t
index f0b65281..04f32cbf 100644
--- a/t/config_limiter.t
+++ b/t/config_limiter.t
@@ -4,6 +4,7 @@ use strict;
 use warnings;
 use Test::More;
 use PublicInbox::Config;
+use PublicInbox::Inbox;
 my $cfgpfx = "publicinbox.test";
 {
         my $config = PublicInbox::Config->new({
diff --git a/t/git.t b/t/git.t
index d7b20d0d..e7a3c9ea 100644
--- a/t/git.t
+++ b/t/git.t
@@ -139,4 +139,27 @@ if (1) {
         ok($nl > 1, "qx returned array length of $nl");
 }
 
+{
+        my $git = PublicInbox::Git->new($dir);
+
+        my $err = $git->popen([qw(cat-file blob non-existent)], undef,
+                                { 2 => $git->err_begin });
+        my @out = <$err>;
+        my $close_ret = close $err;
+        my $close_err = $?;
+        is(join('', @out), '', 'no output on stdout on error');
+        isnt($close_err, 0, 'close set $? on bad command');
+        ok(!$close_ret, 'close returned error on bad command');
+        isnt($git->err, '', 'got stderr output');
+
+        $err = $git->popen([qw(tag -l)], undef, { 2 => $git->err_begin });
+        @out = <$err>;
+        $close_ret = close $err;
+        $close_err = $?;
+        is(join('', @out), '', 'no output on stdout on error');
+        ok(!$close_err, 'close clobbered $? on empty output');
+        ok($close_ret, 'close returned error on empty output');
+        is($git->err, '', 'no stderr output');
+}
+
 done_testing();
diff --git a/t/git_async.t b/t/git_async.t
new file mode 100644
index 00000000..ffe2b1a2
--- /dev/null
+++ b/t/git_async.t
@@ -0,0 +1,142 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+use Test::More;
+$SIG{PIPE} = 'IGNORE';
+foreach my $mod (qw(Danga::Socket)) {
+        eval "require $mod";
+        plan skip_all => "$mod missing for git_async.t" if $@;
+}
+use File::Temp qw/tempdir/;
+use Cwd qw/getcwd/;
+my $tmpdir = tempdir('git_async-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+use_ok 'PublicInbox::Git';
+my $dir = "$tmpdir/git.git";
+{
+        is(system(qw(git init -q --bare), $dir), 0, 'created git directory');
+        my @cmd = ('git', "--git-dir=$dir", 'fast-import', '--quiet');
+        my $fi_data = getcwd().'/t/git.fast-import-data';
+        ok(-r $fi_data, "fast-import data readable (or run test at top level)");
+        my $pid = fork;
+        defined $pid or die "fork failed: $!\n";
+        if ($pid == 0) {
+                open STDIN, '<', $fi_data or die "open $fi_data: $!\n";
+                exec @cmd;
+                die "failed exec: ",join(' ', @cmd),": $!\n";
+        }
+        waitpid $pid, 0;
+        is($?, 0, 'fast-import succeeded');
+}
+
+{
+        my $f = 'HEAD:foo.txt';
+        my @args;
+        my $n = 0;
+        my $git = PublicInbox::Git->new($dir);
+        Danga::Socket->SetPostLoopCallback(sub {
+                my ($fdmap) = @_;
+                foreach (values %$fdmap) {
+                        return 1 if ref($_) =~ /::GitAsync/;
+                }
+                0
+        });
+        $git->check_async_ds($f, sub {
+                $n++;
+                @args = @_;
+                $git = undef;
+        });
+        Danga::Socket->EventLoop;
+        my @exp = PublicInbox::Git->new($dir)->check($f);
+        my $exp = [ \@exp ];
+        is_deeply(\@args, $exp, 'matches regular check');
+        is($n, 1, 'callback only called once');
+        $git = PublicInbox::Git->new($dir);
+        $n = 0;
+        my $max = 100;
+        my $missing = 'm';
+        my $m = 0;
+        for my $i (0..$max) {
+                my $k = "HEAD:m$i";
+                $git->check_async_ds($k, sub {
+                        my ($info) = @_;
+                        ++$n;
+                        ++$m if $info->[1] eq 'missing' && $info->[0] eq $k;
+                });
+                if ($git->{async_c}->{wr}->{write_buf_size}) {
+                        diag("async_check capped at $i");
+                        $max = $i;
+                        last;
+                }
+        }
+        is($m, $n, 'everything expected missing is missing');
+        $git->check_async_ds($f, sub { $git = undef });
+        Danga::Socket->EventLoop;
+
+        $git = PublicInbox::Git->new($dir);
+        my $info;
+        my $str = '';
+        my @missing;
+        $git->cat_async_ds('HEAD:miss', sub {
+                my ($miss) = @_;
+                push @missing, $miss;
+        });
+        $git->cat_async_ds($f, sub {
+                my $res = $_[0];
+                if (ref($res) eq 'ARRAY') {
+                        is($info, undef, 'info unset, setting..');
+                        $info = $res;
+                } elsif (ref($res) eq 'SCALAR') {
+                        $str .= $$res;
+                        if (length($str) >= $info->[2]) {
+                                is($info->[2], length($str), 'length match');
+                                $git = undef
+                        }
+                }
+        });
+        Danga::Socket->EventLoop;
+        is_deeply(\@missing, [['HEAD:miss', 'missing']], 'missing cat OK');
+        is($git, undef, 'git undefined');
+        $git = PublicInbox::Git->new($dir);
+        my $sref = $git->cat_file($f);
+        is($str, $$sref, 'matches synchronous version');
+        $git = undef;
+        Danga::Socket->RunTimers;
+}
+
+{
+        my $git = PublicInbox::Git->new($dir);
+        foreach my $s (qw(check_async_compat cat_async_compat)) {
+                my @missing;
+                $git->check_async_compat('HED:miss1ng', sub {
+                        my ($miss) = @_;
+                        push @missing, $miss;
+                });
+                is_deeply(\@missing, [['HED:miss1ng', 'missing']],
+                        "missing $s OK");
+        }
+        my @info;
+        my $str = '';
+        my $eof_seen = 0;
+        $git->cat_async_compat('HEAD:foo.txt', sub {
+                my $ref = $_[0];
+                my $t = ref $ref;
+                if ($t eq 'ARRAY') {
+                        push @info, $ref;
+                } elsif ($t eq 'SCALAR') {
+                        $str .= $$ref;
+                } elsif ($ref == 0) {
+                        $eof_seen++;
+                } else {
+                        fail "fail type: $t";
+                }
+        });
+        is($eof_seen, 1, 'EOF seen once');
+        is_deeply(\@info, [ [ 'bf4f17855632367a160bef055fc8ba4675d10e6b',
+                               'blob', 18 ]], 'info matches compat');
+        is($str, "-----\nhello\nworld\n", 'data matches compat');
+}
+
+done_testing();
+
+1;
diff --git a/t/git_idx.t b/t/git_idx.t
new file mode 100644
index 00000000..65667cfc
--- /dev/null
+++ b/t/git_idx.t
@@ -0,0 +1,24 @@
+# Copyright (C) 2015 all contributors <meta@public-inbox.org>
+# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
+use strict;
+use warnings;
+use Test::More;
+use File::Temp qw/tempdir/;
+use Email::MIME;
+my $tmpdir = tempdir('pi-git-idx-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $git_dir = "$tmpdir/a.git";
+use_ok 'PublicInbox::Git';
+use_ok 'PublicInbox::GitIdx';
+my $git = PublicInbox::Git->new($git_dir);
+is(0, system(qw(git init -q --bare), $git_dir), "git init (main)");
+
+$git->qx(qw(config core.sharedRepository 0644));
+is(git_umask_for($git_dir), oct '022', 'umask is correct for 644');
+
+$git->qx(qw(config core.sharedRepository 0664));
+is(git_umask_for($git_dir), oct '002', 'umask is correct for 664');
+
+$git->qx(qw(config core.sharedRepository group));
+is(git_umask_for($git_dir), oct '007', 'umask is correct for "group"');
+
+done_testing();
diff --git a/t/httpd-unix.t b/t/httpd-unix.t
index 4b0f116e..5ebe2f50 100644
--- a/t/httpd-unix.t
+++ b/t/httpd-unix.t
@@ -4,6 +4,7 @@
 use strict;
 use warnings;
 use Test::More;
+use Carp qw(carp);
 
 foreach my $mod (qw(Plack::Util Plack::Builder Danga::Socket
                         HTTP::Date HTTP::Status)) {
@@ -54,7 +55,7 @@ ok(-S $unix, 'UNIX socket was bound by -httpd');
 sub check_sock ($) {
         my ($unix) = @_;
         my $sock = IO::Socket::UNIX->new(Peer => $unix, Type => SOCK_STREAM);
-        warn "E: $! connecting to $unix\n" unless defined $sock;
+        carp "E: $! connecting to $unix\n" unless defined $sock;
         ok($sock, 'client UNIX socket connected');
         ok($sock->write("GET /host-port HTTP/1.0\r\n\r\n"),
                 'wrote req to server');
@@ -95,11 +96,13 @@ SKIP: {
         eval 'require Net::Server::Daemonize';
         skip('Net::Server missing for pid-file/daemonization test', 10) if $@;
 
-        # wait for daemonization
+        # wait for daemonization, PublicInbox::Daemon should bind
+        # listener BEFORE the grandparent exits.
         $spawn_httpd->("-l$unix", '-D', '-P', "$tmpdir/pid");
         my $kpid = $pid;
         $pid = undef;
         is(waitpid($kpid, 0), $kpid, 'existing httpd terminated');
+        ok(-S $unix, 'unix socket exists');
         check_sock($unix);
 
         ok(-f "$tmpdir/pid", 'pid file written');
diff --git a/t/hval.t b/t/hval.t
new file mode 100644
index 00000000..f824752c
--- /dev/null
+++ b/t/hval.t
@@ -0,0 +1,20 @@
+# Copyright (C) 2015 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ (https://www.gnu.org/licenses/agpl-3.0.txt)
+use strict;
+use warnings;
+use Test::More;
+use PublicInbox::Hval qw(to_attr from_attr);
+
+foreach my $s ('Hello/World.pm', 'Zcat', 'hello world.c', 'Eléanor', '$at') {
+        my $attr = to_attr($s);
+        is(from_attr($attr), $s, "$s => $attr => $s round trips");
+}
+
+{
+        my $bad = eval { to_attr('foo//bar') };
+        my $err = $@;
+        ok(!$bad, 'double-slash rejected');
+        like($err, qr/invalid filename:/, 'got exception message');
+}
+
+done_testing();
diff --git a/t/repo_git_search_idx.t b/t/repo_git_search_idx.t
new file mode 100644
index 00000000..934a4e6f
--- /dev/null
+++ b/t/repo_git_search_idx.t
@@ -0,0 +1,28 @@
+# Copyright (C) 2017 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+use Test::More;
+use File::Temp qw/tempdir/;
+use_ok 'PublicInbox::RepoGitSearchIdx';
+my $test = require './t/repobrowse_common_git.perl';
+my $git_dir = $test->{git_dir};
+my $xdir = "$git_dir/rg";
+my $idx = PublicInbox::RepoGitSearchIdx->new($git_dir, $xdir);
+ok($idx->xdb && -d $xdir, 'Xapian dir created');
+$idx->index_sync;
+
+my $mset = $idx->query('bs:"add header"');
+my $doc;
+$doc = $_->get_document foreach $mset->items;
+ok($doc, 'got document');
+is('cb3b92d257e628b512a2eee0861f8935c594cd12', $doc->get_data, 'DATA OK');
+
+foreach my $q (qw(id:cb3b92d257e628b512a2eee0861f8935c594cd12 id:cb3b92d2*)) {
+        $mset = $idx->query($q);
+        $doc = undef;
+        $doc = $_->get_document foreach $mset->items;
+        ok($doc, "got document for $q");
+}
+
+done_testing();
diff --git a/t/repobrowse.t b/t/repobrowse.t
new file mode 100644
index 00000000..de8a7952
--- /dev/null
+++ b/t/repobrowse.t
@@ -0,0 +1,21 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+
+my $test = require './t/repobrowse_common_git.perl';
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+        my $req = 'http://example.com/test.git/tree/dir';
+        my $res = $cb->(GET($req . '/'));
+        is($res->code, 301, 'got 301 with trailing slash');
+        is($res->header('Location'), $req, 'redirected without tslash');
+
+        my $q = '?id=deadbeef';
+
+        $res = $cb->(GET($req . "/$q"));
+        is($res->code, 301, 'got 301 with trailing slash + query string');
+        is($res->header('Location'), $req.$q, 'redirected without tslash');
+});
+
+done_testing();
diff --git a/t/repobrowse_common_git.perl b/t/repobrowse_common_git.perl
new file mode 100644
index 00000000..de61efe6
--- /dev/null
+++ b/t/repobrowse_common_git.perl
@@ -0,0 +1,67 @@
+#!/usr/bin/perl -w
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+use Test::More;
+use File::Temp qw/tempdir/;
+use Cwd qw/getcwd/;
+my @mods = qw(HTTP::Request::Common Plack::Test URI::Escape);
+foreach my $mod (@mods) {
+        eval "require $mod";
+        plan skip_all => "$mod missing for $0" if $@;
+}
+
+sub dechunk ($) {
+        my ($res) = @_;
+        my $s = $res->content;
+        if (lc($res->header('Transfer-Encoding') || '') eq 'chunked') {
+                my $rv = '';
+                while ($s =~ s/\A([a-f0-9]+)\r\n//i) { # no comment support :x
+                        my $n = hex($1) or last;
+                        $rv .= substr($s, 0, $n);
+                        $s = substr($s, $n);
+                        $s =~ s/\A\r\n// or die "broken parsing in $s\n";
+                }
+                $s =~ s/\A\r\n// or die "broken end parsing in $s\n";
+                $s = $rv;
+        }
+        $s;
+}
+
+use_ok $_ foreach @mods;
+my $git_dir = tempdir('repobrowse-XXXXXX', CLEANUP => 1, TMPDIR => 1);
+my $psgi = "examples/repobrowse.psgi";
+my $repobrowse_config = "$git_dir/repobrowse_config";
+my $app;
+ok(-f $psgi, 'psgi example for repobrowse.psgi found');
+{
+        is(system(qw(git init -q --bare), $git_dir), 0, 'created git directory');
+        my @cmd = ('git', "--git-dir=$git_dir", 'fast-import', '--quiet');
+        my $fi_data = getcwd().'/t/git.fast-import-data';
+        ok(-r $fi_data, "fast-import data readable (or run test at top level)");
+        my $pid = fork;
+        defined $pid or die "fork failed: $!\n";
+        if ($pid == 0) {
+                open STDIN, '<', $fi_data or die "open $fi_data: $!\n";
+                exec @cmd;
+                die "failed exec: ",join(' ', @cmd),": $!\n";
+        }
+        waitpid $pid, 0;
+        is($?, 0, 'fast-import succeeded');
+        my $fh;
+        ok((open $fh, '>', $repobrowse_config and
+                print $fh '[repo "test.git"]', "\n",
+                        "\t", "path = $git_dir", "\n" and
+                close $fh), 'created repobrowse config');
+        local $ENV{REPOBROWSE_CONFIG} = $repobrowse_config;
+        ok($app = require $psgi, 'loaded PSGI app');
+}
+
+# return value
+bless {
+        psgi => $psgi,
+        git_dir => $git_dir,
+        app => $app,
+        repobrowse_config => $repobrowse_config,
+}, 'Repobrowse::TestGit';
diff --git a/t/repobrowse_git.t b/t/repobrowse_git.t
new file mode 100644
index 00000000..0ac977f3
--- /dev/null
+++ b/t/repobrowse_git.t
@@ -0,0 +1,11 @@
+# Copyright (C) 2015 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ (https://www.gnu.org/licenses/agpl-3.0.txt)
+use strict;
+use warnings;
+use Test::More;
+use PublicInbox::RepoGit qw(git_unquote);
+
+is("foo\nbar", git_unquote('"foo\\nbar"'), 'unquoted newline');
+is("Eléanor", git_unquote('"El\\303\\251anor"'), 'unquoted octal');
+
+done_testing();
diff --git a/t/repobrowse_git_atom.t b/t/repobrowse_git_atom.t
new file mode 100644
index 00000000..6769bf9f
--- /dev/null
+++ b/t/repobrowse_git_atom.t
@@ -0,0 +1,38 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+my $have_xml_feed = eval { require XML::Feed; 1 };
+my $test = require './t/repobrowse_common_git.perl';
+use Test::More;
+
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+        my $req = 'http://example.com/test.git/atom';
+        my $res = $cb->(GET($req));
+        is($res->code, 200, 'got 200');
+        is($res->header('Content-Type'), 'application/atom+xml',
+                'got correct Content-Type');
+        my $body = dechunk($res);
+        SKIP: {
+                skip 'XML::Feed missing', 2 unless $have_xml_feed;
+                my $p = XML::Feed->parse(\$body);
+                is($p->format, "Atom", "parsed atom feed");
+                is(scalar $p->entries, 6, "parsed six entries");
+        }
+        like($body, qr!<pre\s*[^>]+>\* header:\n  add header</pre>!,
+                'body wrapped in <pre>');
+
+        $res = $cb->(GET($req . '/master/foo.txt'));
+        is($res->code, 200, 'got 200');
+        $body = dechunk($res);
+        like($body, qr{\bhref="http://[^/]+/test\.git/}, 'hrefs OK');
+        SKIP: {
+                skip 'XML::Feed missing', 2 unless $have_xml_feed;
+                my $p = XML::Feed->parse(\$body);
+                is($p->format, "Atom", "parsed atom feed");
+                is(scalar $p->entries, 4, "parsed 4 entries");
+        }
+});
+
+done_testing();
diff --git a/t/repobrowse_git_commit.t b/t/repobrowse_git_commit.t
new file mode 100644
index 00000000..f5913023
--- /dev/null
+++ b/t/repobrowse_git_commit.t
@@ -0,0 +1,19 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+
+my $test = require './t/repobrowse_common_git.perl';
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+        my $path = '/path/to/something';
+        my $req = 'http://example.com/test.git/commit';
+        my $res;
+
+        $res = $cb->(GET($req));
+        is($res->code, 200, 'got proper 200 response for default');
+        my $body = dechunk($res);
+        like($body, qr!</html>\z!, 'response body finished');
+});
+
+done_testing();
diff --git a/t/repobrowse_git_httpd.t b/t/repobrowse_git_httpd.t
new file mode 100644
index 00000000..3e6c074c
--- /dev/null
+++ b/t/repobrowse_git_httpd.t
@@ -0,0 +1,138 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+#
+# Integration test for public-inbox-httpd and (git) repobrowse
+# since we may use some special APIs not available in other servers
+use strict;
+use warnings;
+use Test::More;
+foreach my $mod (qw(Danga::Socket HTTP::Date HTTP::Status
+                Plack::Test::ExternalServer)) {
+        eval "require $mod";
+        plan skip_all => "$mod missing for repobrowse_git_httpd.t" if $@;
+}
+my $test = require './t/repobrowse_common_git.perl';
+{
+        no warnings 'once';
+        $Plack::Test::Impl = 'ExternalServer';
+}
+use File::Temp qw/tempdir/;
+use Cwd qw/getcwd/;
+use IO::Socket;
+use Fcntl qw(F_SETFD);
+use POSIX qw(dup2);
+my $tmpdir = tempdir('repobrowse_git_httpd-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $err = "$tmpdir/stderr.log";
+my $out = "$tmpdir/stdout.log";
+my $httpd = 'blib/script/public-inbox-httpd';
+my $psgi = getcwd() . '/' . $test->{psgi};
+my %opts = (
+        LocalAddr => '127.0.0.1',
+        ReuseAddr => 1,
+        Proto => 'tcp',
+        Type => SOCK_STREAM,
+        Listen => 1024,
+);
+my $sock = IO::Socket::INET->new(%opts);
+my $host = $sock->sockhost;
+my $port = $sock->sockport;
+my $uri = "http://$host:$port/";
+my $pid;
+END { kill 'TERM', $pid if defined $pid };
+my $spawn_httpd = sub {
+        $pid = fork;
+        if ($pid == 0) {
+                # pretend to be systemd:
+                dup2(fileno($sock), 3) or die "dup2 failed: $!\n";
+                my $t = IO::Handle->new_from_fd(3, 'r');
+                $t->fcntl(F_SETFD, 0);
+                $ENV{REPOBROWSE_CONFIG} = $test->{repobrowse_config};
+                $ENV{LISTEN_PID} = $$;
+                $ENV{LISTEN_FDS} = 1;
+                exec $httpd, '-W0', $psgi;
+                # exec $httpd, '-W0', "--stdout=$out", "--stderr=$err", $psgi;
+                die "FAIL: $!\n";
+        }
+        ok(defined $pid, 'forked httpd process successfully');
+};
+
+$spawn_httpd->();
+
+{ # git clone tests
+        my $url = $uri . 'test.git';
+        is(system(qw(git clone -q --mirror), $url, "$tmpdir/smart.git"),
+                0, 'smart clone successful');
+        is(system('git', "--git-dir=$tmpdir/smart.git", 'fsck'), 0, 'fsck OK');
+        is(system('git', "--git-dir=$test->{git_dir}",
+                qw(config http.uploadpack 0)), 0, 'disabled smart HTTP');
+        is(system('git', "--git-dir=$test->{git_dir}",
+                qw(update-server-info)), 0, 'enable dumb HTTP');
+        is(system(qw(git clone -q --mirror), $url, "$tmpdir/dumb.git"),
+                0, 'dumb clone successful');
+        is(system('git', "--git-dir=$tmpdir/dumb.git", 'fsck'),
+                0, 'fsck dumb OK');
+}
+
+test_psgi(uri => $uri, client => sub {
+        my ($cb) = @_;
+        my $res = $cb->(GET($uri . 'test.git/info/refs'));
+        is(200, $res->code, 'got info/refs');
+
+        $res = $cb->(GET($uri . 'best.git/info/refs'));
+        is(404, $res->code, 'bad request fails');
+
+        $res = $cb->(GET($uri . 'test.git/patch'));
+        is(200, $res->code, 'got patch');
+        is('text/plain; charset=UTF-8', $res->header('Content-Type'),
+                'got proper content-type with patch');
+
+        # ignore signature from git-format-patch:
+        my ($patch, undef) = split(/\n-- \n/s, $res->content);
+
+        my $cmd = 'format-patch --signature=git -1 -M --stdout HEAD';
+        my ($exp, undef) = split(/\n-- \n/s,
+                `git --git-dir=$test->{git_dir} $cmd`);
+        is($patch, $exp, 'patch content matches expected');
+});
+
+{
+        # allow reading description file
+        my %conn = ( PeerAddr => $host, PeerPort => $port, Proto => 'tcp',
+                Type => SOCK_STREAM);
+        my $conn = IO::Socket::INET->new(%conn);
+        ok($conn, "connected for description check");
+        $conn->write("GET /test.git/description HTTP/1.0\r\n\r\n");
+        ok($conn->read(my $buf, 8192), 'read response');
+        my ($head, $body) = split(/\r\n\r\n/, $buf, 2);
+        like($head, qr!\AHTTP/1\.0 200 !s, 'got 200 response for description');
+
+        $conn = IO::Socket::INET->new(%conn);
+        ok($conn, "connected for range check");
+        $conn->write("GET /test.git/description HTTP/1.0\r\n" .
+                        "Range: bytes=5-\r\n\r\n");
+        ok($conn->read($buf, 8192), 'read partial response');
+        my ($h2, $b2) = split(/\r\n\r\n/, $buf, 2);
+        like($h2, qr!\AHTTP/1\.0 206 !s, 'got 206 response for range');
+        is($b2, substr($body, 5), 'substring matches on 206');
+}
+
+test_psgi(uri => $uri, client => sub {
+        my ($cb) = @_;
+        my $res = $cb->(GET($uri . 'test.git/snapshot/test-master.tar.gz'));
+        is(200, $res->code, 'got gzipped tarball');
+        my $got = "$tmpdir/got.tar.gz";
+        my $exp = "$tmpdir/exp.tar.gz";
+        open my $fh, '>', $got or die "open got.tar.gz: $!";
+        print $fh $res->content;
+        close $fh or die "close failed: $!";
+        $res = undef;
+        my $rc = system('git', "--git-dir=$test->{git_dir}",
+                        qw(archive --prefix=test-master/ --format=tar.gz),
+                        '-o', $exp, 'master');
+        is(0, $rc, 'git-archive generated check correctly');
+        is(0, system('cmp', $got, $exp), 'got expected gzipped tarball');
+
+});
+
+done_testing();
+1;
diff --git a/t/repobrowse_git_log.t b/t/repobrowse_git_log.t
new file mode 100644
index 00000000..2caba546
--- /dev/null
+++ b/t/repobrowse_git_log.t
@@ -0,0 +1,19 @@
+# Copyright (C) 2017 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+my $test = require './t/repobrowse_common_git.perl';
+use Test::More;
+
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+        my $req = 'http://example.com/test.git/log';
+        my $res = $cb->(GET($req));
+        is($res->code, 200, 'got 200');
+        is($res->header('Content-Type'), 'text/html; charset=UTF-8',
+                'got correct Content-Type');
+        my $body = dechunk($res);
+        like($body, qr!</html>!, 'valid HTML :)');
+});
+
+done_testing();
diff --git a/t/repobrowse_git_raw.t b/t/repobrowse_git_raw.t
new file mode 100644
index 00000000..aefe88c7
--- /dev/null
+++ b/t/repobrowse_git_raw.t
@@ -0,0 +1,24 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+my $test = require './t/repobrowse_common_git.perl';
+
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+
+        my $req = 'http://example.com/test.git/raw/master/dir';
+        my $res = $cb->(GET($req));
+        is(200, $res->code, 'got 200 response from dir');
+        my $noslash_body = dechunk($res);
+        like($noslash_body, qr{href="dir/dur">dur</a></li>},
+                'path ok w/o slash');
+
+        $req = 'http://example.com/test.git/raw/master/foo.txt';
+        my $blob = $cb->(GET($req));
+        like($blob->header('Content-Type'), qr!\Atext/plain\b!,
+                'got text/plain blob');
+        is($blob->content, "-----\nhello\nworld\n", 'raw blob passed');
+});
+
+done_testing();
diff --git a/t/repobrowse_git_snapshot.t b/t/repobrowse_git_snapshot.t
new file mode 100644
index 00000000..b608459e
--- /dev/null
+++ b/t/repobrowse_git_snapshot.t
@@ -0,0 +1,46 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+my $test = require './t/repobrowse_common_git.perl';
+
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+        my ($req, $rc, $res);
+
+        $req = 'http://example.com/test.git/snapshot/test-master.tar.gz';
+        $res = $cb->(GET($req));
+        is($res->code, 200, 'got 200 response from $NAME-master-tar.gz');
+        is($res->header('Content-Type'), 'application/x-gzip',
+                'Content-Type is as expected');
+
+        $req = 'http://example.com/test.git/snapshot/test-nonexistent.tar.gz';
+        $res = $cb->(GET($req));
+        is($res->code, 404, 'got 404 for non-existent');
+
+        $rc = system('git', "--git-dir=$test->{git_dir}", 'tag', '-a',
+                        '-m', 'annotated tag!', 'v1.0.0');
+        is($rc, 0, 'created annotated 1.0.0 tag');
+        $req = 'http://example.com/test.git/snapshot/test-1.0.0.tar.gz';
+        $res = $cb->(GET($req));
+        is($res->code, 200, 'got 200 response for tag');
+        is($res->header('Content-Type'), 'application/x-gzip',
+                'Content-Type is as expected');
+        is($res->header('Content-Disposition'),
+                'inline; filename="test-1.0.0.tar.gz"',
+                'Content-Disposition is as expected');
+
+        $rc = system('git', "--git-dir=$test->{git_dir}", 'tag',
+                        '-m', 'lightweight tag!', 'v2.0.0');
+        is($rc, 0, 'created lightweight 2.0.0 tag');
+        $req = 'http://example.com/test.git/snapshot/test-2.0.0.tar.gz';
+        $res = $cb->(GET($req));
+        is($res->code, 200, 'got 200 response for tag');
+        is($res->header('Content-Type'), 'application/x-gzip',
+                'Content-Type is as expected');
+        is($res->header('Content-Disposition'),
+                'inline; filename="test-2.0.0.tar.gz"',
+                'Content-Disposition is as expected');
+});
+
+done_testing();
diff --git a/t/repobrowse_git_src.t b/t/repobrowse_git_src.t
new file mode 100644
index 00000000..aa341d38
--- /dev/null
+++ b/t/repobrowse_git_src.t
@@ -0,0 +1,38 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+my $test = require './t/repobrowse_common_git.perl';
+
+test_psgi($test->{app}, sub {
+        my ($cb) = @_;
+
+        my $req = 'http://example.com/test.git/src/HEAD/dir';
+        my $res = $cb->(GET($req));
+        is(200, $res->code, 'got 200 response from dir');
+        my $noslash_body = dechunk($res);
+        like($noslash_body, qr{href="dir/dur">dur/</a>},
+                'path ok w/o slash');
+
+        $req = 'http://example.com/test.git/src';
+        $res = $cb->(GET($req));
+        is(302, $res->code, 'got 302 response from dir');
+        is("$req/master", $res->header('Location'), 'redirected to tip');
+
+        my $slash = $req . '/';
+        my $r2 = $cb->(GET($slash));
+        is(301, $r2->code, 'got 301 response from dir with slash');
+        is($req, $r2->header('Location'), 'redirected w/o slash');
+
+        $req = 'http://example.com/test.git/src/master/foo.txt';
+        my $blob = $cb->(GET($req));
+        is($blob->header('Content-Type'), 'text/html; charset=UTF-8',
+                'got text/html blob');
+
+        my $body = dechunk($blob);
+        foreach (qw(----- hello world)) {
+                ok(index($body, $_) >= 0, "substring $_ in body");
+        }
+});
+
+done_testing();
diff --git a/t/search.t b/t/search.t
index c9c4e346..000a0385 100644
--- a/t/search.t
+++ b/t/search.t
@@ -27,31 +27,6 @@ my $rw_commit = sub {
 };
 
 {
-        # git repository perms
-        is(PublicInbox::SearchIdx->_git_config_perm(undef),
-           &PublicInbox::SearchIdx::PERM_GROUP,
-           "undefined permission is group");
-        is(PublicInbox::SearchIdx::_umask_for(
-             PublicInbox::SearchIdx->_git_config_perm('0644')),
-           0022, "644 => umask(0022)");
-        is(PublicInbox::SearchIdx::_umask_for(
-             PublicInbox::SearchIdx->_git_config_perm('0600')),
-           0077, "600 => umask(0077)");
-        is(PublicInbox::SearchIdx::_umask_for(
-             PublicInbox::SearchIdx->_git_config_perm('0640')),
-           0027, "640 => umask(0027)");
-        is(PublicInbox::SearchIdx::_umask_for(
-             PublicInbox::SearchIdx->_git_config_perm('group')),
-           0007, 'group => umask(0007)');
-        is(PublicInbox::SearchIdx::_umask_for(
-             PublicInbox::SearchIdx->_git_config_perm('everybody')),
-           0002, 'everybody => umask(0002)');
-        is(PublicInbox::SearchIdx::_umask_for(
-             PublicInbox::SearchIdx->_git_config_perm('umask')),
-           umask, 'umask => existing umask');
-}
-
-{
         my $root = Email::MIME->create(
                 header_str => [
                         Date => 'Fri, 02 Oct 1993 00:00:00 +0000',
@@ -95,15 +70,8 @@ sub filter_mids {
         is($found->mid, 'root@s', 'mid set correctly');
         ok(int($found->thread_id) > 0, 'thread_id is an integer');
 
+        my ($res, @res);
         my @exp = sort qw(root@s last@s);
-        my $res = $ro->query("path:hello_world");
-        my @res = filter_mids($res);
-        is_deeply(\@res, \@exp, 'got expected results for path: match');
-
-        foreach my $p (qw(hello hello_ hello_world2 hello_world_)) {
-                $res = $ro->query("path:$p");
-                is($res->{total}, 0, "path variant `$p' does not match");
-        }
 
         $res = $ro->query('s:(Hello world)');
         @res = filter_mids($res);
diff --git a/t/spawn.t b/t/spawn.t
index 0f756462..2dcdf883 100644
--- a/t/spawn.t
+++ b/t/spawn.t
@@ -81,17 +81,6 @@ use PublicInbox::Spawn qw(which spawn popen_rd);
         isnt($?, 0, '$? set properly: '.$?);
 }
 
-{
-        my ($fh, $pid) = popen_rd([qw(sleep 60)], undef, { Blocking => 0 });
-        ok(defined $pid && $pid > 0, 'returned pid when array requested');
-        is(kill(0, $pid), 1, 'child process is running');
-        ok(!defined(sysread($fh, my $buf, 1)) && $!{EAGAIN},
-           'sysread returned quickly with EAGAIN');
-        is(kill(9, $pid), 1, 'child process killed early');
-        is(waitpid($pid, 0), $pid, 'child process reapable');
-        isnt($?, 0, '$? set properly: '.$?);
-}
-
 done_testing();
 
 1;
diff --git a/t/thread-all.t b/t/thread-all.t
index 8ccf4f8c..b1f9b47c 100644
--- a/t/thread-all.t
+++ b/t/thread-all.t
@@ -1,7 +1,7 @@
 # Copyright (C) 2016 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 #
-# real-world testing of search threading
+# real-world testing of search threading performance
 use strict;
 use warnings;
 use Test::More;
@@ -16,7 +16,6 @@ plan skip_all => "$pi_dir not initialized for $0" if $@;
 require PublicInbox::View;
 require PublicInbox::SearchThread;
 
-my $pfx = PublicInbox::Search::xpfx('thread');
 my $opts = { limit => 1000000, asc => 1 };
 my $t0 = clock_gettime(CLOCK_MONOTONIC);
 my $elapsed;
@@ -35,4 +34,6 @@ PublicInbox::View::thread_results($msgs);
 $elapsed = clock_gettime(CLOCK_MONOTONIC) - $t0;
 diag "thread_results $elapsed";
 
+ok(1, 'test completed without crashing :)');
+
 done_testing();