about summary refs log tree commit homepage
path: root/t
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2016-12-13 21:56:39 +0000
committerEric Wong <e@80x24.org>2016-12-14 00:22:55 +0000
commit00488f0cfe9f81d04cd65d09ea783e860c937401 (patch)
tree24e4d9282d5e7098fc6cbdcc9d5615c46513661f /t
parentf9d4f28d9761011d3c7ffad9e2c9d1e54b65c519 (diff)
parent6cdb0221d18b2caed4d0caebf7c20d6eb159497d (diff)
downloadpublic-inbox-00488f0cfe9f81d04cd65d09ea783e860c937401.tar.gz
* origin/repobrowse: (98 commits)
  t/repobrowse_git_httpd.t: ensure signature exists for split
  t/repobrowse_git_tree.t: fix test for lack of bold
  repobrowse: fix alignment of gitlink entries
  repobrowse: show invalid type for tree views
  repobrowse: do not bold directory names in tree view
  repobrowse: reduce checks for response fh
  repobrowse: larger, short-lived buffer for reading patches
  repobrowse: reduce risk of callback reference cycles
  repobrowse: snapshot support for cgit compatibility
  test: disable warning for Plack::Test::Impl
  repobrowse: avoid confusing linkification for "diff"
  repobrowse: git commit view uses pi-httpd.async
  repobrowse: more consistent variable naming for /commit/
  repobrowse: show roughly equivalent "diff-tree" invocation
  repobrowse: reduce local variables for state management
  repobrowse: summary handles multiple README types
  repobrowse: remove bold decorations from diff view
  repobrowse: common git diff parsing code
  repobrowse: implement diff view for compatibility
  examples/repobrowse.psgi: disable Chunked response by default
  ...
Diffstat (limited to 't')
-rw-r--r--t/git.t23
-rw-r--r--t/hval.t20
-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.t39
-rw-r--r--t/repobrowse_git_commit.t31
-rw-r--r--t/repobrowse_git_httpd.t138
-rw-r--r--t/repobrowse_git_plain.t29
-rw-r--r--t/repobrowse_git_snapshot.t46
-rw-r--r--t/repobrowse_git_tree.t33
11 files changed, 458 insertions, 0 deletions
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/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/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..cd8ce582
--- /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::Request 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..6ae7475b
--- /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::RepobrowseGit 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..2525effd
--- /dev/null
+++ b/t/repobrowse_git_atom.t
@@ -0,0 +1,39 @@
+# 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");
+        }
+
+        $res = $cb->(GET($req . '/'));
+        my $sl = dechunk($res);
+        is($body, $sl, 'slash returned identical to non-trailing slash');
+
+        $res = $cb->(GET($req . '/foo.txt'));
+        is($res->code, 200, 'got 200');
+        $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, 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..969a0b5e
--- /dev/null
+++ b/t/repobrowse_git_commit.t
@@ -0,0 +1,31 @@
+# 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 = $cb->(GET($req . $path));
+        is($res->code, 301, 'got 301 to anchor');
+        is($res->header('Location'), "$req#path:to:something",
+                'redirected to anchor from path');
+
+        my $q = '?id=deadbeef';
+        $res = $cb->(GET($req . $path . $q));
+        is($res->code, 301, 'got 301 with query string');
+        is($res->header('Location'), "$req$q#path:to:something",
+                'redirected to anchor from path with query');
+
+        $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');
+
+        $res = $cb->(GET($req.$q));
+        is($res->code, 404, 'got 404 response for default');
+});
+
+done_testing();
diff --git a/t/repobrowse_git_httpd.t b/t/repobrowse_git_httpd.t
new file mode 100644
index 00000000..005ab173
--- /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::Parser::XS 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_plain.t b/t/repobrowse_git_plain.t
new file mode 100644
index 00000000..27347f70
--- /dev/null
+++ b/t/repobrowse_git_plain.t
@@ -0,0 +1,29 @@
+# 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/plain/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');
+
+        my $slash = $req . '/';
+        my $r2 = $cb->(GET($slash));
+        is(200, $r2->code, 'got 200 response from dir');
+        my $slash_body = dechunk($r2);
+        like($slash_body, qr{href="\./dur\">dur</a></li>}, 'path ok w/ slash');
+
+        $req = 'http://example.com/test.git/plain/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_tree.t b/t/repobrowse_git_tree.t
new file mode 100644
index 00000000..531f914c
--- /dev/null
+++ b/t/repobrowse_git_tree.t
@@ -0,0 +1,33 @@
+# 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(200, $res->code, 'got 200 response from dir');
+        my $noslash_body = dechunk($res);
+        like($noslash_body, qr{href="dir/dur\?id=\w+">dur/</a>},
+                'path ok w/o slash');
+
+        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/tree/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();