diff options
Diffstat (limited to 't')
-rw-r--r-- | t/git.t | 23 | ||||
-rw-r--r-- | t/hval.t | 20 | ||||
-rw-r--r-- | t/repobrowse.t | 21 | ||||
-rw-r--r-- | t/repobrowse_common_git.perl | 67 | ||||
-rw-r--r-- | t/repobrowse_git.t | 11 | ||||
-rw-r--r-- | t/repobrowse_git_atom.t | 39 | ||||
-rw-r--r-- | t/repobrowse_git_commit.t | 31 | ||||
-rw-r--r-- | t/repobrowse_git_httpd.t | 138 | ||||
-rw-r--r-- | t/repobrowse_git_plain.t | 29 | ||||
-rw-r--r-- | t/repobrowse_git_snapshot.t | 46 | ||||
-rw-r--r-- | t/repobrowse_git_tree.t | 33 |
11 files changed, 458 insertions, 0 deletions
@@ -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..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..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..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_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(); |