From ea618c5e257a6aabded76e63567ec0e7b99ac6c4 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Mon, 24 Aug 2026 16:21:18 -1000 Subject: Renumber the test scripts into themed ranges t000x the ground the suite stands on: git version, html validity, the cache t01xx page content, one script per page t02xx features that cut across pages: filters, submodule links, dates, limits t03xx defence, the security regressions and the $HOME promise t04xx the helper tools under tools/ --- tests/.gitignore | 2 + tests/filters/dump.lua | 4 +- tests/serve-split-check.py | 4 + tests/t0001-git-version.sh | 53 ++++++ tests/t0001-validate-git-versions.sh | 53 ------ tests/t0002-html-validity.sh | 61 ++++++ tests/t0003-cache.sh | 200 +++++++++++++++++++ tests/t0010-validate-html.sh | 61 ------ tests/t0020-validate-cache.sh | 200 ------------------- tests/t0105-commit.sh | 42 ---- tests/t0106-commit.sh | 42 ++++ tests/t0106-diff.sh | 115 ----------- tests/t0107-diff.sh | 115 +++++++++++ tests/t0107-snapshot.sh | 227 ---------------------- tests/t0108-patch.sh | 82 -------- tests/t0108-rawdiff.sh | 51 +++++ tests/t0109-gitconfig.sh | 62 ------ tests/t0109-patch.sh | 82 ++++++++ tests/t0110-rawdiff.sh | 51 ----- tests/t0110-snapshot.sh | 227 ++++++++++++++++++++++ tests/t0111-filter.sh | 70 ------- tests/t0112-stats.sh | 88 +++++++++ tests/t0112-submodule-links.sh | 109 ----------- tests/t0200-security.sh | 358 ----------------------------------- tests/t0201-filters.sh | 70 +++++++ tests/t0201-limits.sh | 182 ------------------ tests/t0202-stats.sh | 88 --------- tests/t0202-submodule-links.sh | 109 +++++++++++ tests/t0204-limits.sh | 182 ++++++++++++++++++ tests/t0300-serve.sh | 20 -- tests/t0301-security.sh | 358 +++++++++++++++++++++++++++++++++++ tests/t0302-home-access.sh | 62 ++++++ tests/t0401-serve.sh | 20 ++ 33 files changed, 1727 insertions(+), 1723 deletions(-) create mode 100755 tests/t0001-git-version.sh delete mode 100755 tests/t0001-validate-git-versions.sh create mode 100755 tests/t0002-html-validity.sh create mode 100755 tests/t0003-cache.sh delete mode 100755 tests/t0010-validate-html.sh delete mode 100755 tests/t0020-validate-cache.sh delete mode 100755 tests/t0105-commit.sh create mode 100755 tests/t0106-commit.sh delete mode 100755 tests/t0106-diff.sh create mode 100755 tests/t0107-diff.sh delete mode 100755 tests/t0107-snapshot.sh delete mode 100755 tests/t0108-patch.sh create mode 100755 tests/t0108-rawdiff.sh delete mode 100755 tests/t0109-gitconfig.sh create mode 100755 tests/t0109-patch.sh delete mode 100755 tests/t0110-rawdiff.sh create mode 100755 tests/t0110-snapshot.sh delete mode 100755 tests/t0111-filter.sh create mode 100755 tests/t0112-stats.sh delete mode 100755 tests/t0112-submodule-links.sh delete mode 100755 tests/t0200-security.sh create mode 100755 tests/t0201-filters.sh delete mode 100755 tests/t0201-limits.sh delete mode 100755 tests/t0202-stats.sh create mode 100755 tests/t0202-submodule-links.sh create mode 100755 tests/t0204-limits.sh delete mode 100755 tests/t0300-serve.sh create mode 100755 tests/t0301-security.sh create mode 100755 tests/t0302-home-access.sh create mode 100755 tests/t0401-serve.sh diff --git a/tests/.gitignore b/tests/.gitignore index 3fd2e96..f2fe0e2 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,4 @@ trash\ directory.t* test-results +__pycache__/ +.DS_Store diff --git a/tests/filters/dump.lua b/tests/filters/dump.lua index ef7f3bf..aa16dfd 100644 --- a/tests/filters/dump.lua +++ b/tests/filters/dump.lua @@ -1,4 +1,4 @@ --- Test fixture for the cgit Lua filter API, exercised by tests/t0111-filter.sh. +-- Test fixture for the cgit Lua filter API, exercised by t0201-filters.sh. -- It echoes the filter_open arguments and upper-cases the body, which lets the -- test confirm that arguments and content flow through the lua: filter path. It -- is not a production filter. Runs on Lua 5.1 through 5.4 and LuaJIT. @@ -18,5 +18,3 @@ end function filter_write(str) buffer = buffer .. string.upper(str) end - - diff --git a/tests/serve-split-check.py b/tests/serve-split-check.py index 63c8f97..3a74803 100644 --- a/tests/serve-split-check.py +++ b/tests/serve-split-check.py @@ -11,6 +11,10 @@ repository root as the only argument. import importlib.util import sys +# Loading serve.py would otherwise leave a __pycache__ directory behind in +# tools/, compiled bytecode the repository has no use for. +sys.dont_write_bytecode = True + def load_serve(root): path = root + "/tools/serve.py" diff --git a/tests/t0001-git-version.sh b/tests/t0001-git-version.sh new file mode 100755 index 0000000..2117764 --- /dev/null +++ b/tests/t0001-git-version.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +# Checks that the Git version cgit says it is built for is the one it is +# actually built against. The top-level Makefile names a version, the tree +# under vendor/git records its own, and the submodule is pinned to a tag, so +# all three have to agree or cgit is being built on something other than what +# it claims. Set CGIT_TEST_NO_GIT_VERSION to YesPlease when Git comes from +# elsewhere and the comparison has nothing to say. + +if [ "${CGIT_TEST_NO_GIT_VERSION}" = "YesPlease" ]; then + exit 0 +fi + +test_description='Check Git version is correct' +CGIT_TEST_NO_CREATE_REPOS=YesPlease +. ./setup.sh + +test_expect_success 'extract Git version from Makefile' ' + sed -n -e "/^GIT_VERSION[ ]*=/ { + s/^GIT_VERSION[ ]*=[ ]*// + p + }" ../../Makefile >makefile_version +' + +# Git's GIT-VERSION-GEN script applies "s/-/./g" to the version string on its +# way into the GIT-VERSION-FILE, so the same has to be done to the version in +# the Makefile before the two can be compared. +test_expect_success 'test Git version matches Makefile' ' + ( cat ../../vendor/git/GIT-VERSION-FILE || echo "No GIT-VERSION-FILE" ) | + sed -e "s/GIT_VERSION[ ]*=[ ]*//" -e "s/\\.dirty$//" >git_version && + sed -e "s/-/./g" makefile_version >makefile_git_version && + test_cmp git_version makefile_git_version +' + +# A tree unpacked from a tarball has no submodule pin to describe, so there is +# nothing to compare against and the test says so rather than failing. +test_expect_success 'test submodule version matches Makefile' ' + if ! test -e ../../vendor/git/.git + then + echo "vendor/git/ is not a Git repository" >&2 + else + ( + cd ../.. && + submodule_oid=$(git ls-files --stage -- vendor/git | + sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9] .*$/\\1/") && + cd vendor/git && + git describe --match "v[0-9]*" $submodule_oid + ) | sed -e "s/^v//" -e "s/-/./" >submodule_version && + test_cmp submodule_version makefile_version + fi +' + +test_done diff --git a/tests/t0001-validate-git-versions.sh b/tests/t0001-validate-git-versions.sh deleted file mode 100755 index 2117764..0000000 --- a/tests/t0001-validate-git-versions.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# Checks that the Git version cgit says it is built for is the one it is -# actually built against. The top-level Makefile names a version, the tree -# under vendor/git records its own, and the submodule is pinned to a tag, so -# all three have to agree or cgit is being built on something other than what -# it claims. Set CGIT_TEST_NO_GIT_VERSION to YesPlease when Git comes from -# elsewhere and the comparison has nothing to say. - -if [ "${CGIT_TEST_NO_GIT_VERSION}" = "YesPlease" ]; then - exit 0 -fi - -test_description='Check Git version is correct' -CGIT_TEST_NO_CREATE_REPOS=YesPlease -. ./setup.sh - -test_expect_success 'extract Git version from Makefile' ' - sed -n -e "/^GIT_VERSION[ ]*=/ { - s/^GIT_VERSION[ ]*=[ ]*// - p - }" ../../Makefile >makefile_version -' - -# Git's GIT-VERSION-GEN script applies "s/-/./g" to the version string on its -# way into the GIT-VERSION-FILE, so the same has to be done to the version in -# the Makefile before the two can be compared. -test_expect_success 'test Git version matches Makefile' ' - ( cat ../../vendor/git/GIT-VERSION-FILE || echo "No GIT-VERSION-FILE" ) | - sed -e "s/GIT_VERSION[ ]*=[ ]*//" -e "s/\\.dirty$//" >git_version && - sed -e "s/-/./g" makefile_version >makefile_git_version && - test_cmp git_version makefile_git_version -' - -# A tree unpacked from a tarball has no submodule pin to describe, so there is -# nothing to compare against and the test says so rather than failing. -test_expect_success 'test submodule version matches Makefile' ' - if ! test -e ../../vendor/git/.git - then - echo "vendor/git/ is not a Git repository" >&2 - else - ( - cd ../.. && - submodule_oid=$(git ls-files --stage -- vendor/git | - sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9] .*$/\\1/") && - cd vendor/git && - git describe --match "v[0-9]*" $submodule_oid - ) | sed -e "s/^v//" -e "s/-/./" >submodule_version && - test_cmp submodule_version makefile_version - fi -' - -test_done diff --git a/tests/t0002-html-validity.sh b/tests/t0002-html-validity.sh new file mode 100755 index 0000000..842437c --- /dev/null +++ b/tests/t0002-html-validity.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +# Runs the tidy checker over one page of each kind cgit renders, so that markup +# broken enough to confuse a browser is caught here rather than in the browser. +# Only the shape of the markup matters, since what the pages actually say is +# the business of the t01xx scripts. Tidy is optional and old versions of it +# predate the elements cgit uses, so the whole file steps aside when a usable +# one cannot be found. + +test_description='Validate html with tidy' +. ./setup.sh + +test_url() +{ + tidy_options="-eq" + test -z "$NO_TIDY_WARNINGS" || + tidy_options="$tidy_options --show-warnings no" + cgit_url "$1" >tidy-$test_count.tmp || return + # Tidy reads what it is given as a whole document, so the response + # headers are dropped before it sees the page. + strip_headers tidy-$test_count || return + "$tidy" $tidy_options tidy-$test_count + status=$? + + # Tidy leaves with 1 for warnings and 2 for errors, and only an error is + # worth failing the test over. + if test $status = 2 + then + false + else + : + fi +} + +tidy=$(which tidy 2>/dev/null) +test -n "$tidy" || { + skip_all='Skipping html validation tests: tidy not found' + test_done + exit +} + +# Releases of tidy that predate HTML5 reject the semantic elements cgit emits, +# header, nav, main and footer among them, reporting each as an error. Skip the +# validation unless tidy is new enough to understand them. +printf '\nt
x
\n' \ + | "$tidy" -q -e >/dev/null 2>&1 +test $? -ge 2 && { + skip_all='Skipping html validation tests: tidy is too old for HTML5' + test_done + exit +} + +test_expect_success 'index page' 'test_url ""' +test_expect_success 'foo' 'test_url "foo"' +test_expect_success 'foo/log' 'test_url "foo/log"' +test_expect_success 'foo/tree' 'test_url "foo/tree"' +test_expect_success 'foo/tree/file-1' 'test_url "foo/tree/file-1"' +test_expect_success 'foo/commit' 'test_url "foo/commit"' +test_expect_success 'foo/diff' 'test_url "foo/diff"' + +test_done diff --git a/tests/t0003-cache.sh b/tests/t0003-cache.sh new file mode 100755 index 0000000..ca85d97 --- /dev/null +++ b/tests/t0003-cache.sh @@ -0,0 +1,200 @@ +#!/bin/sh + +# Exercises the cache, which keeps a rendered page in a slot on disk and +# replays it for the next request that asks for the same thing. The first three +# tests set cache-size to nothing, to one slot and to the full table in turn, +# then count what the requests left behind. The rest cover the two ways a slot +# can come out wrong, a page longer than the output buffer and a key too long +# to be read back. + +test_description='Validate cache' +. ./setup.sh + +test_expect_success 'verify cache-size=0' ' + + rm -f cache/* && + sed -e "s/cache-size=1021$/cache-size=0/" cgitrc >cgitrc.tmp && + mv -f cgitrc.tmp cgitrc && + cgit_url "" && + cgit_url "foo" && + cgit_url "foo/refs" && + cgit_url "foo/tree" && + cgit_url "foo/log" && + cgit_url "foo/diff" && + cgit_url "foo/patch" && + cgit_url "bar" && + cgit_url "bar/refs" && + cgit_url "bar/tree" && + cgit_url "bar/log" && + cgit_url "bar/diff" && + cgit_url "bar/patch" && + ls cache >output && + test_line_count = 0 output +' + +test_expect_success 'verify cache-size=1' ' + + rm -f cache/* && + sed -e "s/cache-size=0$/cache-size=1/" cgitrc >cgitrc.tmp && + mv -f cgitrc.tmp cgitrc && + cgit_url "" && + cgit_url "foo" && + cgit_url "foo/refs" && + cgit_url "foo/tree" && + cgit_url "foo/log" && + cgit_url "foo/diff" && + cgit_url "foo/patch" && + cgit_url "bar" && + cgit_url "bar/refs" && + cgit_url "bar/tree" && + cgit_url "bar/log" && + cgit_url "bar/diff" && + cgit_url "bar/patch" && + ls cache >output && + test_line_count = 1 output +' + +test_expect_success 'verify cache-size=1021' ' + + rm -f cache/* && + sed -e "s/cache-size=1$/cache-size=1021/" cgitrc >cgitrc.tmp && + mv -f cgitrc.tmp cgitrc && + cgit_url "" && + cgit_url "foo" && + cgit_url "foo/refs" && + cgit_url "foo/tree" && + cgit_url "foo/log" && + cgit_url "foo/diff" && + cgit_url "foo/patch" && + cgit_url "bar" && + cgit_url "bar/refs" && + cgit_url "bar/tree" && + cgit_url "bar/log" && + cgit_url "bar/diff" && + cgit_url "bar/patch" && + ls cache >output && + test_line_count = 13 output && + cgit_url "foo/ls_cache" >output.full && + strip_headers output && + test_line_count = 13 output && + cgit_url "foo/ls_cache" >output.second && + test_cmp output.full output.second +' + +# Page output is buffered, so a slot only holds the whole page if the buffer is +# emptied before the size of what was generated is taken. The blob set up here +# is larger than one buffer, so a missing flush would leave the cached copy cut +# short. +test_expect_success 'set up a repo with a page larger than the output buffer' ' + mkrepo repos/bigpage 1 && + ( + cd repos/bigpage && + awk "BEGIN{for(i=0;i<20000;i++) print \"line \" i \" of the big file\"}" >big.txt && + git add big.txt && + git commit -m "add big.txt" + ) && + { + echo "virtual-root=/" && + echo "cache-root=$PWD/cache2" && + echo "cache-size=64" && + echo "enable-tree-linenumbers=1" && + echo "repo.url=bigpage" && + echo "repo.path=$PWD/repos/bigpage/.git" + } >bigrc && + rm -rf cache2 && mkdir cache2 +' + +bigpage_query() +{ + CGIT_CONFIG="$PWD/bigrc" QUERY_STRING="$1" cgit +} + +test_expect_success 'the first request fills the slot and the second replays it' ' + bigpage_query "url=bigpage/tree/big.txt" >big.first && + test $(wc -c big.second && + strip_headers big.first.body && + strip_headers big.second.body && + test_cmp big.first.body big.second.body +' + +test_expect_success 'the cached body matches one generated with the cache off' ' + sed -e "s/^cache-size=64$/cache-size=0/" bigrc >bignocache && + CGIT_CONFIG="$PWD/bignocache" QUERY_STRING="url=bigpage/tree/big.txt" cgit >big.nocache && + strip_headers big.nocache.body && + # The footer carries the time the page was generated, which the two runs + # cannot agree on. + sed -e "s/generated by .*//" big.nocache.body >big.nocache.timeless && + sed -e "s/generated by .*//" big.second.body >big.cached.timeless && + test_cmp big.nocache.timeless big.cached.timeless +' + +test_expect_success 'the page ends where it should, so nothing was dropped' ' + tail -c 200 big.second.body | grep "" +' + +# A slot stores its key ahead of the content and only the first few kilobytes of +# it are read back, so a key longer than that could never match. A request +# carrying one used to leave a slot behind on every visit that nothing could +# ever go on to use. +test_expect_success 'an over-long cache key leaves no slot behind' ' + rm -rf cache3 && mkdir cache3 && + sed -e "s|^cache-root=.*|cache-root=$PWD/cache3|" bigrc >bigkeyrc && + long=$(awk "BEGIN{s=\"\";for(i=0;i<6000;i++)s=s \"k\"; print s}") && + CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/&q=$long" cgit >key.out 2>key.err && + grep "" key.out && + ls cache3 >key.slots && + test_line_count = 0 key.slots +' + +test_expect_success 'an ordinary key still fills a slot' ' + rm -rf cache3 && mkdir cache3 && + CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" cgit >/dev/null && + ls cache3 >key.slots && + test_line_count = 1 key.slots +' + +# An error page fired after rendering began abandons the fill with part of +# the page already written into the lock file. That fragment must be +# discarded along with the file, not replayed to the visitor behind the +# error page. A commit whose parent object is missing renders its whole +# info table before the diff machinery fails, which makes it the trigger. +test_expect_success 'set up a repo missing a parent object' ' + mkrepo repos/broken 2 && + parent=$(git -C repos/broken rev-parse HEAD^) && + tip=$(git -C repos/broken rev-parse HEAD) && + rm "repos/broken/.git/objects/$(echo "$parent" | cut -c1-2)/$(echo "$parent" | cut -c3-)" && + { + echo "virtual-root=/" && + echo "cache-root=$PWD/cache4" && + echo "cache-size=64" && + echo "repo.url=broken" && + echo "repo.path=$PWD/repos/broken/.git" + } >brokenrc && + rm -rf cache4 && mkdir cache4 +' + +test_expect_success 'an error after output began replays none of it' ' + CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/commit/&id=$tip" \ + cgit >broken.out && + grep "Bad commit" broken.out && + test $(grep -c "^Status:" broken.out) = 1 && + ls cache4 >broken.slots && + test_line_count = 0 broken.slots +' + +# An error page reports a condition the repository may grow out of, and a +# request pinned to an object id would cache it under the never-expiring +# static ttl. Asking for a commit that does not exist yet must therefore +# leave nothing behind, or the real page could never appear once it does. +test_expect_success 'an error page leaves no slot behind' ' + rm -rf cache3 && mkdir cache3 && + CGIT_CONFIG="$PWD/bigkeyrc" \ + QUERY_STRING="url=bigpage/commit/&id=0123456789abcdef0123456789abcdef01234567" \ + cgit >error.out && + grep "Bad commit reference" error.out && + ls cache3 >error.slots && + test_line_count = 0 error.slots +' + +test_done diff --git a/tests/t0010-validate-html.sh b/tests/t0010-validate-html.sh deleted file mode 100755 index 842437c..0000000 --- a/tests/t0010-validate-html.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh - -# Runs the tidy checker over one page of each kind cgit renders, so that markup -# broken enough to confuse a browser is caught here rather than in the browser. -# Only the shape of the markup matters, since what the pages actually say is -# the business of the t01xx scripts. Tidy is optional and old versions of it -# predate the elements cgit uses, so the whole file steps aside when a usable -# one cannot be found. - -test_description='Validate html with tidy' -. ./setup.sh - -test_url() -{ - tidy_options="-eq" - test -z "$NO_TIDY_WARNINGS" || - tidy_options="$tidy_options --show-warnings no" - cgit_url "$1" >tidy-$test_count.tmp || return - # Tidy reads what it is given as a whole document, so the response - # headers are dropped before it sees the page. - strip_headers tidy-$test_count || return - "$tidy" $tidy_options tidy-$test_count - status=$? - - # Tidy leaves with 1 for warnings and 2 for errors, and only an error is - # worth failing the test over. - if test $status = 2 - then - false - else - : - fi -} - -tidy=$(which tidy 2>/dev/null) -test -n "$tidy" || { - skip_all='Skipping html validation tests: tidy not found' - test_done - exit -} - -# Releases of tidy that predate HTML5 reject the semantic elements cgit emits, -# header, nav, main and footer among them, reporting each as an error. Skip the -# validation unless tidy is new enough to understand them. -printf '\nt
x
\n' \ - | "$tidy" -q -e >/dev/null 2>&1 -test $? -ge 2 && { - skip_all='Skipping html validation tests: tidy is too old for HTML5' - test_done - exit -} - -test_expect_success 'index page' 'test_url ""' -test_expect_success 'foo' 'test_url "foo"' -test_expect_success 'foo/log' 'test_url "foo/log"' -test_expect_success 'foo/tree' 'test_url "foo/tree"' -test_expect_success 'foo/tree/file-1' 'test_url "foo/tree/file-1"' -test_expect_success 'foo/commit' 'test_url "foo/commit"' -test_expect_success 'foo/diff' 'test_url "foo/diff"' - -test_done diff --git a/tests/t0020-validate-cache.sh b/tests/t0020-validate-cache.sh deleted file mode 100755 index ca85d97..0000000 --- a/tests/t0020-validate-cache.sh +++ /dev/null @@ -1,200 +0,0 @@ -#!/bin/sh - -# Exercises the cache, which keeps a rendered page in a slot on disk and -# replays it for the next request that asks for the same thing. The first three -# tests set cache-size to nothing, to one slot and to the full table in turn, -# then count what the requests left behind. The rest cover the two ways a slot -# can come out wrong, a page longer than the output buffer and a key too long -# to be read back. - -test_description='Validate cache' -. ./setup.sh - -test_expect_success 'verify cache-size=0' ' - - rm -f cache/* && - sed -e "s/cache-size=1021$/cache-size=0/" cgitrc >cgitrc.tmp && - mv -f cgitrc.tmp cgitrc && - cgit_url "" && - cgit_url "foo" && - cgit_url "foo/refs" && - cgit_url "foo/tree" && - cgit_url "foo/log" && - cgit_url "foo/diff" && - cgit_url "foo/patch" && - cgit_url "bar" && - cgit_url "bar/refs" && - cgit_url "bar/tree" && - cgit_url "bar/log" && - cgit_url "bar/diff" && - cgit_url "bar/patch" && - ls cache >output && - test_line_count = 0 output -' - -test_expect_success 'verify cache-size=1' ' - - rm -f cache/* && - sed -e "s/cache-size=0$/cache-size=1/" cgitrc >cgitrc.tmp && - mv -f cgitrc.tmp cgitrc && - cgit_url "" && - cgit_url "foo" && - cgit_url "foo/refs" && - cgit_url "foo/tree" && - cgit_url "foo/log" && - cgit_url "foo/diff" && - cgit_url "foo/patch" && - cgit_url "bar" && - cgit_url "bar/refs" && - cgit_url "bar/tree" && - cgit_url "bar/log" && - cgit_url "bar/diff" && - cgit_url "bar/patch" && - ls cache >output && - test_line_count = 1 output -' - -test_expect_success 'verify cache-size=1021' ' - - rm -f cache/* && - sed -e "s/cache-size=1$/cache-size=1021/" cgitrc >cgitrc.tmp && - mv -f cgitrc.tmp cgitrc && - cgit_url "" && - cgit_url "foo" && - cgit_url "foo/refs" && - cgit_url "foo/tree" && - cgit_url "foo/log" && - cgit_url "foo/diff" && - cgit_url "foo/patch" && - cgit_url "bar" && - cgit_url "bar/refs" && - cgit_url "bar/tree" && - cgit_url "bar/log" && - cgit_url "bar/diff" && - cgit_url "bar/patch" && - ls cache >output && - test_line_count = 13 output && - cgit_url "foo/ls_cache" >output.full && - strip_headers output && - test_line_count = 13 output && - cgit_url "foo/ls_cache" >output.second && - test_cmp output.full output.second -' - -# Page output is buffered, so a slot only holds the whole page if the buffer is -# emptied before the size of what was generated is taken. The blob set up here -# is larger than one buffer, so a missing flush would leave the cached copy cut -# short. -test_expect_success 'set up a repo with a page larger than the output buffer' ' - mkrepo repos/bigpage 1 && - ( - cd repos/bigpage && - awk "BEGIN{for(i=0;i<20000;i++) print \"line \" i \" of the big file\"}" >big.txt && - git add big.txt && - git commit -m "add big.txt" - ) && - { - echo "virtual-root=/" && - echo "cache-root=$PWD/cache2" && - echo "cache-size=64" && - echo "enable-tree-linenumbers=1" && - echo "repo.url=bigpage" && - echo "repo.path=$PWD/repos/bigpage/.git" - } >bigrc && - rm -rf cache2 && mkdir cache2 -' - -bigpage_query() -{ - CGIT_CONFIG="$PWD/bigrc" QUERY_STRING="$1" cgit -} - -test_expect_success 'the first request fills the slot and the second replays it' ' - bigpage_query "url=bigpage/tree/big.txt" >big.first && - test $(wc -c big.second && - strip_headers big.first.body && - strip_headers big.second.body && - test_cmp big.first.body big.second.body -' - -test_expect_success 'the cached body matches one generated with the cache off' ' - sed -e "s/^cache-size=64$/cache-size=0/" bigrc >bignocache && - CGIT_CONFIG="$PWD/bignocache" QUERY_STRING="url=bigpage/tree/big.txt" cgit >big.nocache && - strip_headers big.nocache.body && - # The footer carries the time the page was generated, which the two runs - # cannot agree on. - sed -e "s/generated by .*//" big.nocache.body >big.nocache.timeless && - sed -e "s/generated by .*//" big.second.body >big.cached.timeless && - test_cmp big.nocache.timeless big.cached.timeless -' - -test_expect_success 'the page ends where it should, so nothing was dropped' ' - tail -c 200 big.second.body | grep "" -' - -# A slot stores its key ahead of the content and only the first few kilobytes of -# it are read back, so a key longer than that could never match. A request -# carrying one used to leave a slot behind on every visit that nothing could -# ever go on to use. -test_expect_success 'an over-long cache key leaves no slot behind' ' - rm -rf cache3 && mkdir cache3 && - sed -e "s|^cache-root=.*|cache-root=$PWD/cache3|" bigrc >bigkeyrc && - long=$(awk "BEGIN{s=\"\";for(i=0;i<6000;i++)s=s \"k\"; print s}") && - CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/&q=$long" cgit >key.out 2>key.err && - grep "" key.out && - ls cache3 >key.slots && - test_line_count = 0 key.slots -' - -test_expect_success 'an ordinary key still fills a slot' ' - rm -rf cache3 && mkdir cache3 && - CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" cgit >/dev/null && - ls cache3 >key.slots && - test_line_count = 1 key.slots -' - -# An error page fired after rendering began abandons the fill with part of -# the page already written into the lock file. That fragment must be -# discarded along with the file, not replayed to the visitor behind the -# error page. A commit whose parent object is missing renders its whole -# info table before the diff machinery fails, which makes it the trigger. -test_expect_success 'set up a repo missing a parent object' ' - mkrepo repos/broken 2 && - parent=$(git -C repos/broken rev-parse HEAD^) && - tip=$(git -C repos/broken rev-parse HEAD) && - rm "repos/broken/.git/objects/$(echo "$parent" | cut -c1-2)/$(echo "$parent" | cut -c3-)" && - { - echo "virtual-root=/" && - echo "cache-root=$PWD/cache4" && - echo "cache-size=64" && - echo "repo.url=broken" && - echo "repo.path=$PWD/repos/broken/.git" - } >brokenrc && - rm -rf cache4 && mkdir cache4 -' - -test_expect_success 'an error after output began replays none of it' ' - CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/commit/&id=$tip" \ - cgit >broken.out && - grep "Bad commit" broken.out && - test $(grep -c "^Status:" broken.out) = 1 && - ls cache4 >broken.slots && - test_line_count = 0 broken.slots -' - -# An error page reports a condition the repository may grow out of, and a -# request pinned to an object id would cache it under the never-expiring -# static ttl. Asking for a commit that does not exist yet must therefore -# leave nothing behind, or the real page could never appear once it does. -test_expect_success 'an error page leaves no slot behind' ' - rm -rf cache3 && mkdir cache3 && - CGIT_CONFIG="$PWD/bigkeyrc" \ - QUERY_STRING="url=bigpage/commit/&id=0123456789abcdef0123456789abcdef01234567" \ - cgit >error.out && - grep "Bad commit reference" error.out && - ls cache3 >error.slots && - test_line_count = 0 error.slots -' - -test_done diff --git a/tests/t0105-commit.sh b/tests/t0105-commit.sh deleted file mode 100755 index ca41830..0000000 --- a/tests/t0105-commit.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# The commit page shows a single commit, its message, the files it touched and -# the diff for them. Most of these checks read the markup cgit emits for the -# tree link, the parent link, the subject and the diffstat. The last few ask -# for the root commit of a repository, which has no parent and so goes through -# the code that compares a commit against an empty tree. - -test_description='Check content on commit page' -. ./setup.sh - -test_expect_success 'generate foo/commit' 'cgit_url "foo/commit" >tmp' -test_expect_success 'find tree link' 'grep "" tmp' -test_expect_success 'find parent link' 'grep -E "" tmp' - -test_expect_success 'find commit subject' ' - grep "
commit 5<" tmp -' - -test_expect_success 'find commit msg' 'grep "
" tmp' -test_expect_success 'find diffstat' 'grep "" tmp' - -test_expect_success 'find diff summary' ' - grep "1 file changed, 1 insertion, 0 deletions" tmp -' - -test_expect_success 'get root commit' ' - root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) && - cgit_url "foo/commit&id=$root" >tmp && - grep "" tmp -' - -test_expect_success 'root commit contains diffstat' ' - grep "file-1" tmp -' - -test_expect_success 'root commit contains diff' ' - grep ">diff --git a/file-1 b/file-1<" tmp && - grep "
+1
" tmp -' - -test_done diff --git a/tests/t0106-commit.sh b/tests/t0106-commit.sh new file mode 100755 index 0000000..ca41830 --- /dev/null +++ b/tests/t0106-commit.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +# The commit page shows a single commit, its message, the files it touched and +# the diff for them. Most of these checks read the markup cgit emits for the +# tree link, the parent link, the subject and the diffstat. The last few ask +# for the root commit of a repository, which has no parent and so goes through +# the code that compares a commit against an empty tree. + +test_description='Check content on commit page' +. ./setup.sh + +test_expect_success 'generate foo/commit' 'cgit_url "foo/commit" >tmp' +test_expect_success 'find tree link' 'grep "" tmp' +test_expect_success 'find parent link' 'grep -E "" tmp' + +test_expect_success 'find commit subject' ' + grep "
commit 5<" tmp +' + +test_expect_success 'find commit msg' 'grep "
" tmp' +test_expect_success 'find diffstat' 'grep "
" tmp' + +test_expect_success 'find diff summary' ' + grep "1 file changed, 1 insertion, 0 deletions" tmp +' + +test_expect_success 'get root commit' ' + root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) && + cgit_url "foo/commit&id=$root" >tmp && + grep "" tmp +' + +test_expect_success 'root commit contains diffstat' ' + grep "file-1" tmp +' + +test_expect_success 'root commit contains diff' ' + grep ">diff --git a/file-1 b/file-1<" tmp && + grep "
+1
" tmp +' + +test_done diff --git a/tests/t0106-diff.sh b/tests/t0106-diff.sh deleted file mode 100755 index 175f609..0000000 --- a/tests/t0106-diff.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/bin/sh - -# The diff page renders the change a commit made, either as unified text or, -# when dt=1 asks for it, as a side by side table. The first checks read the -# markup for one added file in the repository the shared setup builds. The -# rest build small repositories of their own, shaped so the side by side -# renderer meets the two cases it used to get wrong, a hunk covering a single -# line and a file where every line changed. - -test_description='Check content on diff page' -. ./setup.sh - -test_expect_success 'generate foo/diff' 'cgit_url "foo/diff" >tmp' -test_expect_success 'find diff header' 'grep "a/file-5 b/file-5" tmp' -test_expect_success 'find blob link' 'grep "@@ -0,0 +1 @@" tmp -' - -test_expect_success 'find added line' ' - grep "
+5
" tmp -' - -# git writes a hunk length only when the hunk spans more than one line, so the -# header for a single line reads "@@ -1 +1 @@" with no comma in it. Requiring -# that comma left those hunks numbered from zero, which dropped the line -# number link altogether. -test_expect_success 'set up a repo with single-line hunks' ' - mkrepo repos/hunk1 1 && - ( - cd repos/hunk1 && - printf "only\n" >one.txt && - git add one.txt && - git commit -m "add one.txt" && - printf "ONLY\n" >one.txt && - git commit -am "change one.txt" - ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=hunk1" && - echo "repo.path=$PWD/repos/hunk1/.git" - } >hunk1rc && - git -C repos/hunk1 diff HEAD~1 HEAD >raw.diff && - grep "^@@ -1 +1 @@" raw.diff -' - -test_expect_success 'ssdiff numbers a comma-less hunk from its real line' ' - CGIT_CONFIG="$PWD/hunk1rc" QUERY_STRING="url=hunk1/diff/&dt=1" cgit >tmp && - grep "#n1.>1
" tmp && - ! grep "#n0.>0" tmp -' - -test_expect_success 'ssdiff still numbers a hunk that carries a length' ' - cgit_query "url=foo/diff&dt=1" >tmp && - grep "#n1.>1" tmp -' - -# The character level highlight reuses one table across every changed pair, so -# these lines get shorter as the file goes on and each comparison is smaller -# than the one before it. That is where a cell left over from the previous -# pair shows itself, as a del or an add span sitting over the wrong -# characters. -test_expect_success 'set up a repo with shrinking changed lines' ' - mkrepo repos/lcs 1 && - ( - cd repos/lcs && - awk "BEGIN{for(n=120;n>0;n-=3){s=\"\"; - for(i=0;ilines.txt && - git add lines.txt && - git commit -m "add lines.txt" && - awk "BEGIN{for(n=120;n>0;n-=3){s=\"\"; - for(i=0;ilines.txt && - git commit -am "change every line" - ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=lcs" && - echo "repo.path=$PWD/repos/lcs/.git" - } >lcsrc -' - -test_expect_success 'every changed pair gets both a del and an add span' ' - CGIT_CONFIG="$PWD/lcsrc" QUERY_STRING="url=lcs/diff/&dt=1" cgit >tmp && - deletions=$(grep -o "" tmp | wc -l) && - additions=$(grep -o "" tmp | wc -l) && - test "$deletions" -gt 0 && - test "$deletions" -eq "$additions" -' - -test_expect_success 'stripping the highlight leaves every line intact' ' - sed -e "s/<[^>]*>//g" tmp >plain.out && - git -C repos/lcs show HEAD~1:lines.txt >old.txt && - git -C repos/lcs show HEAD:lines.txt >new.txt && - while read -r line - do - grep -qF "$line" plain.out || { - echo "missing old line: $line" - return 1 - } - done tmp' +test_expect_success 'find diff header' 'grep "a/file-5 b/file-5" tmp' +test_expect_success 'find blob link' 'grep "@@ -0,0 +1 @@" tmp +' + +test_expect_success 'find added line' ' + grep "
+5
" tmp +' + +# git writes a hunk length only when the hunk spans more than one line, so the +# header for a single line reads "@@ -1 +1 @@" with no comma in it. Requiring +# that comma left those hunks numbered from zero, which dropped the line +# number link altogether. +test_expect_success 'set up a repo with single-line hunks' ' + mkrepo repos/hunk1 1 && + ( + cd repos/hunk1 && + printf "only\n" >one.txt && + git add one.txt && + git commit -m "add one.txt" && + printf "ONLY\n" >one.txt && + git commit -am "change one.txt" + ) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=hunk1" && + echo "repo.path=$PWD/repos/hunk1/.git" + } >hunk1rc && + git -C repos/hunk1 diff HEAD~1 HEAD >raw.diff && + grep "^@@ -1 +1 @@" raw.diff +' + +test_expect_success 'ssdiff numbers a comma-less hunk from its real line' ' + CGIT_CONFIG="$PWD/hunk1rc" QUERY_STRING="url=hunk1/diff/&dt=1" cgit >tmp && + grep "#n1.>1
" tmp && + ! grep "#n0.>0" tmp +' + +test_expect_success 'ssdiff still numbers a hunk that carries a length' ' + cgit_query "url=foo/diff&dt=1" >tmp && + grep "#n1.>1" tmp +' + +# The character level highlight reuses one table across every changed pair, so +# these lines get shorter as the file goes on and each comparison is smaller +# than the one before it. That is where a cell left over from the previous +# pair shows itself, as a del or an add span sitting over the wrong +# characters. +test_expect_success 'set up a repo with shrinking changed lines' ' + mkrepo repos/lcs 1 && + ( + cd repos/lcs && + awk "BEGIN{for(n=120;n>0;n-=3){s=\"\"; + for(i=0;ilines.txt && + git add lines.txt && + git commit -m "add lines.txt" && + awk "BEGIN{for(n=120;n>0;n-=3){s=\"\"; + for(i=0;ilines.txt && + git commit -am "change every line" + ) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=lcs" && + echo "repo.path=$PWD/repos/lcs/.git" + } >lcsrc +' + +test_expect_success 'every changed pair gets both a del and an add span' ' + CGIT_CONFIG="$PWD/lcsrc" QUERY_STRING="url=lcs/diff/&dt=1" cgit >tmp && + deletions=$(grep -o "" tmp | wc -l) && + additions=$(grep -o "" tmp | wc -l) && + test "$deletions" -gt 0 && + test "$deletions" -eq "$additions" +' + +test_expect_success 'stripping the highlight leaves every line intact' ' + sed -e "s/<[^>]*>//g" tmp >plain.out && + git -C repos/lcs show HEAD~1:lines.txt >old.txt && + git -C repos/lcs show HEAD:lines.txt >new.txt && + while read -r line + do + grep -qF "$line" plain.out || { + echo "missing old line: $line" + return 1 + } + done tmp -' - -test_expect_success 'check html headers' ' - head -n 1 tmp | - grep "Status: 200 OK" && - - head -n 2 tmp | - grep "Content-Type: application/x-gzip" && - - head -n 3 tmp | - grep "Content-Disposition: inline; filename=.master.tar.gz." -' - -test_expect_success 'strip off the header lines' ' - strip_headers master.tar.gz -' - -test_expect_success 'verify gzip format' ' - gunzip --test master.tar.gz -' - -test_expect_success 'untar' ' - rm -rf master && - gzip -dc master.tar.gz | tar -xf - -' - -test_expect_success 'count files' ' - ls master/ >output && - test_line_count = 5 output -' - -test_expect_success 'verify untarred file-5' ' - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 -' - -if test -n "$(which lzip 2>/dev/null)"; then - test_set_prereq LZIP -else - say 'Skipping LZIP validation tests: lzip not found' -fi - -test_expect_success LZIP 'get foo/snapshot/master.tar.lz' ' - cgit_url "foo/snapshot/master.tar.lz" >tmp -' - -test_expect_success LZIP 'check html headers' ' - head -n 1 tmp | - grep "Status: 200 OK" && - - head -n 2 tmp | - grep "Content-Type: application/x-lzip" && - - head -n 3 tmp | - grep "Content-Disposition: inline; filename=.master.tar.lz." -' - -test_expect_success LZIP 'strip off the header lines' ' - strip_headers master.tar.lz -' - -test_expect_success LZIP 'verify lzip format' ' - lzip --test master.tar.lz -' - -test_expect_success LZIP 'untar' ' - rm -rf master && - lzip -dc master.tar.lz | tar -xf - -' - -test_expect_success LZIP 'count files' ' - ls master/ >output && - test_line_count = 5 output -' - -test_expect_success LZIP 'verify untarred file-5' ' - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 -' - -if test -n "$(which xz 2>/dev/null)"; then - test_set_prereq XZ -else - say 'Skipping XZ validation tests: xz not found' -fi - -test_expect_success XZ 'get foo/snapshot/master.tar.xz' ' - cgit_url "foo/snapshot/master.tar.xz" >tmp -' - -test_expect_success XZ 'check html headers' ' - head -n 1 tmp | - grep "Status: 200 OK" && - - head -n 2 tmp | - grep "Content-Type: application/x-xz" && - - head -n 3 tmp | - grep "Content-Disposition: inline; filename=.master.tar.xz." -' - -test_expect_success XZ 'strip off the header lines' ' - strip_headers master.tar.xz -' - -test_expect_success XZ 'verify xz format' ' - xz --test master.tar.xz -' - -test_expect_success XZ 'untar' ' - rm -rf master && - xz -dc master.tar.xz | tar -xf - -' - -test_expect_success XZ 'count files' ' - ls master/ >output && - test_line_count = 5 output -' - -test_expect_success XZ 'verify untarred file-5' ' - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 -' - -if test -n "$(which zstd 2>/dev/null)"; then - test_set_prereq ZSTD -else - say 'Skipping ZSTD validation tests: zstd not found' -fi - -test_expect_success ZSTD 'get foo/snapshot/master.tar.zst' ' - cgit_url "foo/snapshot/master.tar.zst" >tmp -' - -test_expect_success ZSTD 'check html headers' ' - head -n 1 tmp | - grep "Status: 200 OK" && - - head -n 2 tmp | - grep "Content-Type: application/x-zstd" && - - head -n 3 tmp | - grep "Content-Disposition: inline; filename=.master.tar.zst." -' - -test_expect_success ZSTD 'strip off the header lines' ' - strip_headers master.tar.zst -' - -test_expect_success ZSTD 'verify zstd format' ' - zstd --test master.tar.zst -' - -test_expect_success ZSTD 'untar' ' - rm -rf master && - zstd -dc master.tar.zst | tar -xf - -' - -test_expect_success ZSTD 'count files' ' - ls master/ >output && - test_line_count = 5 output -' - -test_expect_success ZSTD 'verify untarred file-5' ' - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 -' - -test_expect_success 'get foo/snapshot/master.zip' ' - cgit_url "foo/snapshot/master.zip" >tmp -' - -test_expect_success 'check HTML headers (zip)' ' - head -n 1 tmp | - grep "Status: 200 OK" && - - head -n 2 tmp | - grep "Content-Type: application/x-zip" && - - head -n 3 tmp | - grep "Content-Disposition: inline; filename=.master.zip." -' - -test_expect_success 'strip off the header lines (zip)' ' - strip_headers master.zip -' - -if test -n "$(which unzip 2>/dev/null)"; then - test_set_prereq UNZIP -else - say 'Skipping ZIP validation tests: unzip not found' -fi - -test_expect_success UNZIP 'verify zip format' ' - unzip -t master.zip -' - -test_expect_success UNZIP 'unzip' ' - rm -rf master && - unzip master.zip -' - -test_expect_success UNZIP 'count files (zip)' ' - ls master/ >output && - test_line_count = 5 output -' - -test_expect_success UNZIP 'verify unzipped file-5' ' - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 -' - -test_done diff --git a/tests/t0108-patch.sh b/tests/t0108-patch.sh deleted file mode 100755 index 3d38e55..0000000 --- a/tests/t0108-patch.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/sh - -# The patch page serves a commit as a mail ready patch, so that a change read -# from cgit can be fed straight to git am. The checks compare that output -# against git format-patch for a single commit and for a range of them, once -# the CGI headers have been stripped, which means the two have to agree line -# for line. The last one sets max-patch-count so a range wider than the limit -# comes back cut short. - -test_description='Check content on patch page' -. ./setup.sh - -test_expect_success 'generate foo/patch' ' - cgit_query "url=foo/patch" >tmp -' - -test_expect_success 'find `From:` line' ' - grep "^From: " tmp -' - -test_expect_success 'find `Date:` line' ' - grep "^Date: " tmp -' - -test_expect_success 'find `Subject:` line' ' - grep "^Subject: commit 5" tmp -' - -test_expect_success 'find `cgit` signature' ' - tail -2 tmp | head -1 | grep "^cgit" -' - -test_expect_success 'compare with output of git-format-patch(1)' ' - CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../build/VERSION) && - git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD^ >tmp2 && - strip_headers tmp_ && - test_cmp tmp_ tmp2 -' - -test_expect_success 'find initial commit' ' - root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) -' - -test_expect_success 'generate patch for initial commit' ' - cgit_query "url=foo/patch&id=$root" >tmp -' - -test_expect_success 'find `cgit` signature' ' - tail -2 tmp | head -1 | grep "^cgit" -' - -test_expect_success 'generate patches for multiple commits' ' - id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && - id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) && - cgit_query "url=foo/patch&id=$id&id2=$id2" >tmp -' - -test_expect_success 'find `cgit` signature' ' - tail -2 tmp | head -1 | grep "^cgit" -' - -test_expect_success 'compare with output of git-format-patch(1)' ' - CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../build/VERSION) && - git --git-dir="$PWD/repos/foo/.git" format-patch -N --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD~3..HEAD >tmp2 && - strip_headers tmp_ && - test_cmp tmp_ tmp2 -' - -test_expect_success 'max-patch-count bounds a patch range' ' - { - echo "cache-size=0" && - echo "max-patch-count=2" && - echo "repo.url=foo" && - echo "repo.path=$PWD/repos/foo/.git" - } >patchcountrc && - id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && - root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) && - CGIT_CONFIG="$PWD/patchcountrc" QUERY_STRING="url=foo/patch&id=$id&id2=$root" cgit >tmp && - test $(grep -c "^From " tmp) -eq 2 -' - -test_done diff --git a/tests/t0108-rawdiff.sh b/tests/t0108-rawdiff.sh new file mode 100755 index 0000000..c546993 --- /dev/null +++ b/tests/t0108-rawdiff.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# Checks the rawdiff page, which serves a diff as plain text with no markup +# around it so that the result can be fed straight to patch. Every test runs +# the git command the page is meant to mirror and compares the two byte for +# byte, covering an ordinary commit, the initial commit that has no parent, +# and a range spanning several commits. + +test_description='Check content on rawdiff page' +. ./setup.sh + +test_expect_success 'generate foo/rawdiff' ' + cgit_query "url=foo/rawdiff" >tmp +' + +# cgit writes its CGI headers and the blank line that ends them ahead of the +# body while git writes only the diff, so the head of the page comes off +# before the two are compared. +test_expect_success 'compare with output of git-diff(1)' ' + git --git-dir="$PWD/repos/foo/.git" diff HEAD^.. >tmp2 && + strip_headers tmp_ && + cmp tmp_ tmp2 +' + +test_expect_success 'find initial commit' ' + root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) +' + +test_expect_success 'generate diff for initial commit' ' + cgit_query "url=foo/rawdiff&id=$root" >tmp +' + +test_expect_success 'compare with output of git-diff-tree(1)' ' + git --git-dir="$PWD/repos/foo/.git" diff-tree -p --no-commit-id --root "$root" >tmp2 && + strip_headers tmp_ && + cmp tmp_ tmp2 +' + +test_expect_success 'generate diff for multiple commits' ' + id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && + id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) && + cgit_query "url=foo/rawdiff&id=$id&id2=$id2" >tmp +' + +test_expect_success 'compare with output of git-diff(1)' ' + git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD >tmp2 && + strip_headers tmp_ && + cmp tmp_ tmp2 +' + +test_done diff --git a/tests/t0109-gitconfig.sh b/tests/t0109-gitconfig.sh deleted file mode 100755 index 7078596..0000000 --- a/tests/t0109-gitconfig.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh - -# Guards the promise that cgit reads nothing out of the home directory of -# whichever account the web server happens to run as. Every page is fetched -# under strace with HOME pointed at a path that is known not to exist, and -# the run fails if any access call names that path, which is how a stray -# read of a personal gitconfig would show up. - -test_description='Ensure that git does not access $HOME' -. ./setup.sh - -# strace needs ptrace, which containers and hardened kernels refuse even -# where the binary is installed, so a working run is checked as well as a -# present binary. -test -n "$(which strace 2>/dev/null)" || { - skip_all='Skipping access validation tests: strace not found' - test_done - exit -} - -strace true 2>/dev/null || { - skip_all='Skipping access validation tests: strace not functional' - test_done - exit -} - -test_no_home_access () { - # A home that happened to exist would be one git may legitimately - # read, leaving the check below with nothing to catch, so extend the - # path until nothing is there. - missing_home="/path/to/some/place/that/does/not/possibly/exist" - depth=0 - while test -d "$missing_home"; do - depth=$((depth + 1)) - missing_home="$missing_home/$depth" - done && - strace \ - -E HOME="$missing_home" \ - -E CGIT_CONFIG="$PWD/cgitrc" \ - -E QUERY_STRING="url=$1" \ - -e access -f -o strace.out cgit && - ! grep "$missing_home" strace.out -} - -test_no_home_access_success() { - test_expect_success "do not access \$HOME: $1" " - test_no_home_access '$1' - " -} - -test_no_home_access_success -test_no_home_access_success foo -test_no_home_access_success foo/refs -test_no_home_access_success foo/log -test_no_home_access_success foo/tree -test_no_home_access_success foo/tree/file-1 -test_no_home_access_success foo/commit -test_no_home_access_success foo/diff -test_no_home_access_success foo/patch -test_no_home_access_success foo/snapshot/master.tar.gz - -test_done diff --git a/tests/t0109-patch.sh b/tests/t0109-patch.sh new file mode 100755 index 0000000..3d38e55 --- /dev/null +++ b/tests/t0109-patch.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +# The patch page serves a commit as a mail ready patch, so that a change read +# from cgit can be fed straight to git am. The checks compare that output +# against git format-patch for a single commit and for a range of them, once +# the CGI headers have been stripped, which means the two have to agree line +# for line. The last one sets max-patch-count so a range wider than the limit +# comes back cut short. + +test_description='Check content on patch page' +. ./setup.sh + +test_expect_success 'generate foo/patch' ' + cgit_query "url=foo/patch" >tmp +' + +test_expect_success 'find `From:` line' ' + grep "^From: " tmp +' + +test_expect_success 'find `Date:` line' ' + grep "^Date: " tmp +' + +test_expect_success 'find `Subject:` line' ' + grep "^Subject: commit 5" tmp +' + +test_expect_success 'find `cgit` signature' ' + tail -2 tmp | head -1 | grep "^cgit" +' + +test_expect_success 'compare with output of git-format-patch(1)' ' + CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../build/VERSION) && + git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD^ >tmp2 && + strip_headers tmp_ && + test_cmp tmp_ tmp2 +' + +test_expect_success 'find initial commit' ' + root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) +' + +test_expect_success 'generate patch for initial commit' ' + cgit_query "url=foo/patch&id=$root" >tmp +' + +test_expect_success 'find `cgit` signature' ' + tail -2 tmp | head -1 | grep "^cgit" +' + +test_expect_success 'generate patches for multiple commits' ' + id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && + id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) && + cgit_query "url=foo/patch&id=$id&id2=$id2" >tmp +' + +test_expect_success 'find `cgit` signature' ' + tail -2 tmp | head -1 | grep "^cgit" +' + +test_expect_success 'compare with output of git-format-patch(1)' ' + CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../build/VERSION) && + git --git-dir="$PWD/repos/foo/.git" format-patch -N --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD~3..HEAD >tmp2 && + strip_headers tmp_ && + test_cmp tmp_ tmp2 +' + +test_expect_success 'max-patch-count bounds a patch range' ' + { + echo "cache-size=0" && + echo "max-patch-count=2" && + echo "repo.url=foo" && + echo "repo.path=$PWD/repos/foo/.git" + } >patchcountrc && + id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && + root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) && + CGIT_CONFIG="$PWD/patchcountrc" QUERY_STRING="url=foo/patch&id=$id&id2=$root" cgit >tmp && + test $(grep -c "^From " tmp) -eq 2 +' + +test_done diff --git a/tests/t0110-rawdiff.sh b/tests/t0110-rawdiff.sh deleted file mode 100755 index c546993..0000000 --- a/tests/t0110-rawdiff.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# Checks the rawdiff page, which serves a diff as plain text with no markup -# around it so that the result can be fed straight to patch. Every test runs -# the git command the page is meant to mirror and compares the two byte for -# byte, covering an ordinary commit, the initial commit that has no parent, -# and a range spanning several commits. - -test_description='Check content on rawdiff page' -. ./setup.sh - -test_expect_success 'generate foo/rawdiff' ' - cgit_query "url=foo/rawdiff" >tmp -' - -# cgit writes its CGI headers and the blank line that ends them ahead of the -# body while git writes only the diff, so the head of the page comes off -# before the two are compared. -test_expect_success 'compare with output of git-diff(1)' ' - git --git-dir="$PWD/repos/foo/.git" diff HEAD^.. >tmp2 && - strip_headers tmp_ && - cmp tmp_ tmp2 -' - -test_expect_success 'find initial commit' ' - root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) -' - -test_expect_success 'generate diff for initial commit' ' - cgit_query "url=foo/rawdiff&id=$root" >tmp -' - -test_expect_success 'compare with output of git-diff-tree(1)' ' - git --git-dir="$PWD/repos/foo/.git" diff-tree -p --no-commit-id --root "$root" >tmp2 && - strip_headers tmp_ && - cmp tmp_ tmp2 -' - -test_expect_success 'generate diff for multiple commits' ' - id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && - id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) && - cgit_query "url=foo/rawdiff&id=$id&id2=$id2" >tmp -' - -test_expect_success 'compare with output of git-diff(1)' ' - git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD >tmp2 && - strip_headers tmp_ && - cmp tmp_ tmp2 -' - -test_done diff --git a/tests/t0110-snapshot.sh b/tests/t0110-snapshot.sh new file mode 100755 index 0000000..6f5f588 --- /dev/null +++ b/tests/t0110-snapshot.sh @@ -0,0 +1,227 @@ +#!/bin/sh + +# The snapshot page hands a branch back as a compressed archive. Most of the +# formats the shared setup enables are fetched here, then their headers are +# read, their bodies unpacked and the files inside compared against the +# repository that setup built. A format needing a helper program that is not +# always installed sits behind a prerequisite, so a machine without lzip, xz, +# zstd or unzip skips those checks instead of failing them. + +test_description='Verify snapshot' +. ./setup.sh + +test_expect_success 'get foo/snapshot/master.tar.gz' ' + cgit_url "foo/snapshot/master.tar.gz" >tmp +' + +test_expect_success 'check html headers' ' + head -n 1 tmp | + grep "Status: 200 OK" && + + head -n 2 tmp | + grep "Content-Type: application/x-gzip" && + + head -n 3 tmp | + grep "Content-Disposition: inline; filename=.master.tar.gz." +' + +test_expect_success 'strip off the header lines' ' + strip_headers master.tar.gz +' + +test_expect_success 'verify gzip format' ' + gunzip --test master.tar.gz +' + +test_expect_success 'untar' ' + rm -rf master && + gzip -dc master.tar.gz | tar -xf - +' + +test_expect_success 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +if test -n "$(which lzip 2>/dev/null)"; then + test_set_prereq LZIP +else + say 'Skipping LZIP validation tests: lzip not found' +fi + +test_expect_success LZIP 'get foo/snapshot/master.tar.lz' ' + cgit_url "foo/snapshot/master.tar.lz" >tmp +' + +test_expect_success LZIP 'check html headers' ' + head -n 1 tmp | + grep "Status: 200 OK" && + + head -n 2 tmp | + grep "Content-Type: application/x-lzip" && + + head -n 3 tmp | + grep "Content-Disposition: inline; filename=.master.tar.lz." +' + +test_expect_success LZIP 'strip off the header lines' ' + strip_headers master.tar.lz +' + +test_expect_success LZIP 'verify lzip format' ' + lzip --test master.tar.lz +' + +test_expect_success LZIP 'untar' ' + rm -rf master && + lzip -dc master.tar.lz | tar -xf - +' + +test_expect_success LZIP 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success LZIP 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +if test -n "$(which xz 2>/dev/null)"; then + test_set_prereq XZ +else + say 'Skipping XZ validation tests: xz not found' +fi + +test_expect_success XZ 'get foo/snapshot/master.tar.xz' ' + cgit_url "foo/snapshot/master.tar.xz" >tmp +' + +test_expect_success XZ 'check html headers' ' + head -n 1 tmp | + grep "Status: 200 OK" && + + head -n 2 tmp | + grep "Content-Type: application/x-xz" && + + head -n 3 tmp | + grep "Content-Disposition: inline; filename=.master.tar.xz." +' + +test_expect_success XZ 'strip off the header lines' ' + strip_headers master.tar.xz +' + +test_expect_success XZ 'verify xz format' ' + xz --test master.tar.xz +' + +test_expect_success XZ 'untar' ' + rm -rf master && + xz -dc master.tar.xz | tar -xf - +' + +test_expect_success XZ 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success XZ 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +if test -n "$(which zstd 2>/dev/null)"; then + test_set_prereq ZSTD +else + say 'Skipping ZSTD validation tests: zstd not found' +fi + +test_expect_success ZSTD 'get foo/snapshot/master.tar.zst' ' + cgit_url "foo/snapshot/master.tar.zst" >tmp +' + +test_expect_success ZSTD 'check html headers' ' + head -n 1 tmp | + grep "Status: 200 OK" && + + head -n 2 tmp | + grep "Content-Type: application/x-zstd" && + + head -n 3 tmp | + grep "Content-Disposition: inline; filename=.master.tar.zst." +' + +test_expect_success ZSTD 'strip off the header lines' ' + strip_headers master.tar.zst +' + +test_expect_success ZSTD 'verify zstd format' ' + zstd --test master.tar.zst +' + +test_expect_success ZSTD 'untar' ' + rm -rf master && + zstd -dc master.tar.zst | tar -xf - +' + +test_expect_success ZSTD 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success ZSTD 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +test_expect_success 'get foo/snapshot/master.zip' ' + cgit_url "foo/snapshot/master.zip" >tmp +' + +test_expect_success 'check HTML headers (zip)' ' + head -n 1 tmp | + grep "Status: 200 OK" && + + head -n 2 tmp | + grep "Content-Type: application/x-zip" && + + head -n 3 tmp | + grep "Content-Disposition: inline; filename=.master.zip." +' + +test_expect_success 'strip off the header lines (zip)' ' + strip_headers master.zip +' + +if test -n "$(which unzip 2>/dev/null)"; then + test_set_prereq UNZIP +else + say 'Skipping ZIP validation tests: unzip not found' +fi + +test_expect_success UNZIP 'verify zip format' ' + unzip -t master.zip +' + +test_expect_success UNZIP 'unzip' ' + rm -rf master && + unzip master.zip +' + +test_expect_success UNZIP 'count files (zip)' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success UNZIP 'verify unzipped file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +test_done diff --git a/tests/t0111-filter.sh b/tests/t0111-filter.sh deleted file mode 100755 index 88533f4..0000000 --- a/tests/t0111-filter.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh - -# Checks the source, about, commit and email filters, which hand a piece of -# page content to an outside program before it is written out. The whole -# body runs once per kind of filter, the exec kind always and the lua kind -# only where cgit was built with lua, because both have to produce the same -# page from the same repository. - -test_description='Check filtered content' -. ./setup.sh - -prefixes="exec" -if [ $CGIT_HAS_LUA -eq 1 ]; then - prefixes="$prefixes lua" -fi - -for prefix in $prefixes -do - test_expect_success "generate filter-$prefix/tree/a%2bb" " - cgit_url 'filter-$prefix/tree/a%2bb' >tmp - " - - test_expect_success "check whether the $prefix source filter works" ' - grep "a+b HELLO$" tmp - ' - - test_expect_success "generate filter-$prefix/about/" " - cgit_url 'filter-$prefix/about/' >tmp - " - - test_expect_success "check whether the $prefix about filter works" ' - grep "
a+b HELLO$" tmp - ' - - test_expect_success "generate filter-$prefix/commit/" " - cgit_url 'filter-$prefix/commit/' >tmp - " - - test_expect_success "check whether the $prefix commit filter works" ' - grep "
ADD A+B" tmp - ' - - test_expect_success "check whether the $prefix email filter works for authors" ' - grep " commit A U THOR <AUTHOR@EXAMPLE.COM>" tmp - ' - - test_expect_success "check whether the $prefix email filter works for committers" ' - grep " commit C O MITTER <COMMITTER@EXAMPLE.COM>" tmp - ' - - # Page output is buffered, so whatever was written before a filter - # opens has to leave the buffer before the filter takes over stdout, - # and whatever was written while it was open has to leave before - # stdout is handed back, since those bytes are meant for the filter. - # A missed flush reorders the page rather than losing any of it, - # so the two tests below confirm that the markup around the filtered - # text is still on the side of it that it belongs on. - test_expect_success "the $prefix source filter output stays inside its cell" " - cgit_url 'filter-$prefix/tree/a%2bb' >tmp && - tr -d '\n' flat.out && - grep 'a+b HELLO' flat.out - " - - test_expect_success "the $prefix about filter output stays inside its div" " - cgit_url 'filter-$prefix/about/' >tmp && - grep \"
a+b HELLO\" tmp - " -done - -test_done diff --git a/tests/t0112-stats.sh b/tests/t0112-stats.sh new file mode 100755 index 0000000..2ada672 --- /dev/null +++ b/tests/t0112-stats.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +# Checks the per repository statistics page, which stays off until a config +# turns it on and then reports commits per author beside a breakdown of the +# tree by language. A second config caps the period so that what max-stats +# takes out of the period selector is covered as well. + +test_description='Check the statistics page' +. ./setup.sh + +# The shared test config sets no enable-stats, so this is what an untouched +# install does. +test_expect_success 'stats are off by default' ' + cgit_url "foo/stats" >tmp && + grep "Status: 404" tmp && + cgit_url "foo" >tmp && + ! grep ">stats" tmp +' + +# The authors table counts only recent periods, so the fixture needs a commit +# dated now to sit beside the 2005 dates the shared repositories carry. The +# commit graph is written on purpose, because it sends the language walk down +# the graph lookup path, which once handed back another commit after the +# history walk had released its memory. +test_expect_success 'set up a stats config' ' + mkrepo repos/pulse 2 && + ( + cd repos/pulse && + GIT_AUTHOR_DATE="@$(date +%s) +0000" \ + GIT_COMMITTER_DATE="@$(date +%s) +0000" \ + git commit --allow-empty -m fresh && + git commit-graph write --reachable + ) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "enable-stats=1" && + echo "repo.url=stat" && + echo "repo.path=$PWD/repos/pulse/.git" + } >statsrc && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "enable-stats=1" && + echo "max-stats=month" && + echo "repo.url=stat" && + echo "repo.path=$PWD/repos/pulse/.git" + } >statmaxrc +' + +statq() { CGIT_CONFIG="$PWD/statsrc" QUERY_STRING="$1" cgit; } + +test_expect_success 'stats tab appears when enabled' ' + statq "url=stat/" >tmp && + grep ">stats" tmp +' + +test_expect_success 'stats page renders both sections' ' + statq "url=stat/stats/" >tmp && + grep "

Languages" tmp && + grep "

Commits per author" tmp +' + +test_expect_success 'stat options sit above the content' ' + tr -d "\n" Commits per author.*//" | grep "cgit-panel" +' + +test_expect_success 'extensionless fixture files fold into Other' ' + grep "

" tmp && + grep "class=.left.>Other" tmp +' + +test_expect_success 'all four periods offered when max-stats is unset' ' + statq "url=stat/stats/" >tmp && + grep "
LanguageSizeShare