Diff options
Diffstat (limited to '')
45 files changed, 1033 insertions, 1238 deletions
diff --git a/tests/extensions/fake-lexers/lexer.lua b/tests/extensions/fake-lexers/lexer.lua index ea7768e..bb869de 100644 --- a/tests/extensions/fake-lexers/lexer.lua +++ b/tests/extensions/fake-lexers/lexer.lua @@ -1,10 +1,10 @@ -- A miniature stand-in for the Scintillua lexer module, just enough for the -- syntax-highlight checks to drive every path through the filter without the --- real collection installed. load() hands out one of the toy lexers below by --- name and detect() recognises a single filename suffix or a shebang naming --- fake, so a check can tell the detection path apart from the extension path --- by which lexer ends up running. Anything the filter treats as optional, a lexer that will not --- load or one that raises while lexing, has a name here too. +-- real collection installed. detect() answers for the suffixes it knows, +-- matched by case as Scintillua's own does, or fake for a shebang naming it, +-- and load() hands out the toy lexer of that name. Besides fake there is one +-- that stops short and one that raises, and python, which detect() knows but +-- load() cannot find, as a collection missing a lexer would answer. local M = {} @@ -68,14 +68,13 @@ function M.load(name) return lexer end +local suffixes = { fake = "fake", short = "short", badlex = "badlex", py = "python" } + function M.detect(filename, line) - if filename:match("%.viadetect$") then - return "fake" - end if line ~= nil and line:match("^#!.*fake") then return "fake" end - return nil + return suffixes[filename:match("%.([^.]+)$") or ""] end return M diff --git a/tests/extensions/harness.lua b/tests/extensions/harness.lua index 3df4719..2fe722f 100644 --- a/tests/extensions/harness.lua +++ b/tests/extensions/harness.lua @@ -54,18 +54,6 @@ html_url_path = sink(function(s) end)) end) --- No shipped extension calls the remaining sinks, so rather than risk an --- unfaithful copy quietly passing a test, using one fails loudly until its --- escaping is mirrored from source/html.c the way the ones above are. -local function unmirrored(name) - return function() - error(name .. " is not mirrored by the test harness yet") - end -end - -html_url_arg = unmirrored("html_url_arg") -html_include = unmirrored("html_include") - function harness.reset() pieces = {} end @@ -113,7 +101,7 @@ end -- Loaders registered here win over any real module on the package path, so a -- test can hand a script a deterministic stand-in, or with a failing loader -- prove the script's fallback for a module that is not installed. -function harness.preload(name, module) +local function preload(name, module) package.preload[name] = function() return module end @@ -164,7 +152,7 @@ end -- The slice of the luaossl digest interface the avatar filters use. function harness.stub_digest() - harness.preload("openssl.digest", { + preload("openssl.digest", { new = function(algorithm) return { final = function(self, text) @@ -188,27 +176,13 @@ local function fake_crypt(password, setting) return prefix .. password end --- The slices of luaossl and luaposix the auth filters use. The link and --- unlink stubs serve the secret creation path, which the auth checks bypass --- by replacing get_secret, so they only have to exist. +-- The slices of luaossl and luaposix the auth checks reach. function harness.stub_auth_modules() local rand_counter = 0 - harness.preload("posix.sys.stat", { - umask = function(mask) - return 18 - end, - }) - harness.preload("posix.unistd", { + preload("posix.unistd", { crypt = fake_crypt, - link = function(from, to) - return nil - end, - unlink = function(path) - os.remove(path) - return 0 - end, }) - harness.preload("openssl.rand", { + preload("openssl.rand", { bytes = function(count) local out = {} for i = 1, count do @@ -218,7 +192,7 @@ function harness.stub_auth_modules() return table.concat(out) end, }) - harness.preload("openssl.hmac", { + preload("openssl.hmac", { new = function(key, algorithm) return { final = function(self, payload) diff --git a/tests/extensions/lib.sh b/tests/extensions/lib.sh index 30a9cc8..92db245 100644 --- a/tests/extensions/lib.sh +++ b/tests/extensions/lib.sh @@ -1,6 +1,6 @@ # Shared groundwork for the t05xx scripts, sourced after setup.sh from inside # the trash directory. It locates the shipped extensions and the unit checks -# beside this file, finds the standalone Lua interpreters the checks can run +# beside this file, lists the standalone Lua interpreters the checks can run # under, and offers a probe that asks the Lua inside the cgit binary itself # whether a module is available to it, since that interpreter can differ from # every standalone one on the path. @@ -8,30 +8,21 @@ EXTENSIONS_DIRECTORY=$(cd "$TEST_OUTPUT_DIRECTORY/../custom/extensions" && pwd) EXT_TEST_DIRECTORY=$(cd "$TEST_OUTPUT_DIRECTORY/extensions" && pwd) -# Prints the Lua version a binary reports, like 5.1, which is also what -# LuaJIT reports. -ext_lua_version() { - "$1" -e 'io.write(string.match(_VERSION, "%d+%.%d+"))' 2>/dev/null -} - -# Prints the interpreters found on the path whose version falls between 5.1 -# and the given 5.x ceiling, one per line, so a script is never failed on a -# version it never claimed to run on. -ext_lua_interpreters() { - ext_lua_ceiling=$1 - for ext_lua_bin in luajit lua5.1 lua5.2 lua5.3 lua5.4 lua5.5 lua - do - command -v "$ext_lua_bin" >/dev/null 2>&1 || continue - case "$(ext_lua_version "$ext_lua_bin")" in - 5.[1-9]) - ext_lua_minor=$(ext_lua_version "$ext_lua_bin") - ext_lua_minor=${ext_lua_minor#5.} - test "$ext_lua_minor" -le "$ext_lua_ceiling" && - echo "$ext_lua_bin" - ;; - esac - done -} +# The interpreters on the path whose version the extensions claim, 5.1 to +# 5.5 with LuaJIT counting as 5.1, so no script is failed on a version it +# never claimed to run on. +interpreters= +for ext_lua_bin in luajit lua5.1 lua5.2 lua5.3 lua5.4 lua5.5 lua +do + command -v "$ext_lua_bin" >/dev/null 2>&1 || continue + case "$("$ext_lua_bin" -e 'io.write(_VERSION)' 2>/dev/null)" in + "Lua 5."[1-5]) + interpreters="$interpreters $ext_lua_bin" + ;; + esac +done +test -n "$interpreters" || + say 'no standalone lua on the path, unit checks skipped' # Asks whether cgit's own Lua can require every module named, by pointing a # throwaway commit-filter at the given repository and reading its answer off diff --git a/tests/extensions/test-about-render.lua b/tests/extensions/test-about-render.lua index 88c136c..e901d28 100644 --- a/tests/extensions/test-about-render.lua +++ b/tests/extensions/test-about-render.lua @@ -219,8 +219,7 @@ out = render("README.md", "[](y)\n\n\ntext") h.contains("a paragraph of badges leaves nothing behind", out, "<div class='markdown'><p>text</p></div>") out = render("README.md", "```lua\nlocal x = 1 < 2\n```\nafter") -h.contains("a fenced block carries its language", out, - "<pre><code data-lang='lua'>local x = 1 < 2\n</code></pre>") +h.contains("a fenced block renders as code", out, "<pre><code>local x = 1 < 2\n</code></pre>") h.contains("text after the fence renders on", out, "<p>after</p>") out = render("README.md", "~~~\n**not bold**\n~~~") @@ -329,7 +328,7 @@ h.contains("an oversized readme renders as plain text", out, "<pre class='plaint -- Man pages. out = render("page.1", ".SH NAME") -h.contains("a section heading renders", out, "<div class='markdown manpage'><h2 id='name'>NAME</h2></div>") +h.contains("a section heading renders", out, "<div class='markdown'><h2 id='name'>NAME</h2></div>") out = render("page.man", '.SH "TWO WORDS"') h.contains("a quoted heading argument is unquoted", out, "<h2 id='two-words'>TWO WORDS</h2>") @@ -393,7 +392,7 @@ h.contains("text after the definitions renders", out, "<p>after</p>") out = render("page.1", ".if n \\\n\\{\\\n.ds x y\n.\\}\nafter") h.contains("a conditional block's requests and braces draw nothing", out, - "<div class='markdown manpage'><p>after</p></div>") + "<div class='markdown'><p>after</p></div>") out = render("page.1", "The \\m[blue]\\fBManual\\fR\\m[]\\s-2\\u[1]\\d\\s+2 here") h.contains("DocBook's link colours and footnote marks draw nothing", out, @@ -401,13 +400,12 @@ h.contains("DocBook's link colours and footnote marks draw nothing", out, out = render("page.1", ".de x\n.B hidden\n.. \nshown") h.contains("a definition ends at two dots with blanks after them", out, - "<div class='markdown manpage'><p>shown</p></div>") + "<div class='markdown'><p>shown</p></div>") out = render("page.1", ".TP\n.PD 0\n.B \\-a\nall\n.PD\nDon\\*(Aqt") h.excludes("a spacing macro draws nothing", out, ">0") h.contains("DocBook's apostrophe string expands", out, "Don't") - out = render("page.1", '.\\" a comment\nvisible') h.excludes("a roff comment is dropped", out, "comment") h.contains("the line after it renders", out, "visible") diff --git a/tests/extensions/test-auth.lua b/tests/extensions/test-auth.lua index 742eabe..974544d 100644 --- a/tests/extensions/test-auth.lua +++ b/tests/extensions/test-auth.lua @@ -94,7 +94,6 @@ h.equals("an account is found whatever its case", hash, stored_hash) h.equals("an unknown account is not", account_hash("nobody"), nil) local _, stand_in = account_hash("nobody") h.equals("it brings another account's hash to spend instead", stand_in, stored_hash) -h.equals("a nil user is not", account_hash(nil), nil) local userset = repo_userset("secret-repo") h.check("a protected repository lists its users", @@ -113,12 +112,14 @@ if variant == "file" then h.equals("a missing users file turns every login down", account_hash("alice"), nil) h.redirect_file("/etc/cgit-auth/users", "auth-users") -- Without the repos file nothing tells a protected repository from a - -- public one, so every repository is refused while the index stays up. + -- public one, so every page is refused, the index too, which cgit asks + -- about with an empty repository name. h.redirect_file("/etc/cgit-auth/repos", "auth-repos-missing") local refused = repo_userset("unlisted") h.check("a missing repos file refuses every repository", refused ~= nil and next(refused) == nil) - h.equals("the index stays public all the same", repo_userset(nil), nil) + local index = repo_userset("") + h.check("and the index as well", index ~= nil and next(index) == nil) h.redirect_file("/etc/cgit-auth/repos", "auth-repos") end @@ -141,7 +142,7 @@ h.equals("no header yields no cookie", get_cookie(nil, "cgitauth"), nil) h.equals("a longer name does not match", get_cookie("xcgitauth=z", "cgitauth"), nil) h.equals("a magic character in the name is taken literally", get_cookie("a-b=z", "a-b"), "z") h.equals("spaces around a pair are ignored", get_cookie(" foo=1 ; cgitauth=abc ", "cgitauth"), "abc") -h.equals("a value keeps its own equals signs", get_cookie("cgitauth=a=b", "cgitauth"), "a=b") +h.equals("a cookie value keeps its own equals signs", get_cookie("cgitauth=a=b", "cgitauth"), "a=b") -- The Cookie header is the visitor's to write, so a run of spaces in it -- must not cost the square of its length. @@ -161,8 +162,6 @@ h.check("a scheme relative target is not", not is_safe_redirect("//evil")) h.check("a backslash variant is not", not is_safe_redirect("/\\evil")) h.check("a missing target is not", not is_safe_redirect(nil)) -h.equals("control characters are stripped from header values", strip_controls("a\r\nb"), "ab") - -- Signing and verification. local now = os.time() @@ -192,6 +191,7 @@ h.equals("a value signed for one field does not serve another", validate_value(" h.equals("no cookie does not verify", validate_value("username", nil), nil) h.equals("a tiny cookie does not verify", validate_value("username", "ab"), nil) h.equals("a leading separator does not verify", validate_value("username", "|x|1|s|sig"), nil) +h.equals("a trailing field does not verify", validate_value("username", cookie .. "|x"), nil) h.equals("an unsigned cookie does not verify", validate_value("username", "username|alice|123|salt"), nil) h.equals("an exponent spelling of the expiry does not verify", @@ -237,106 +237,96 @@ local function run_action(action, opts) return h.output(), ret end -local ret +-- A login posts the form, and a visit asks about a repository, with a +-- session cookie when it is given one, and comes back with the answer. +local function login(user, pass, redirect) + local body = "username=" .. url_encode(user) .. "&password=" .. url_encode(pass) + if redirect then + body = body .. "&redirect=" .. url_encode(redirect) + end + return run_action("authenticate-post", { method = "POST", body = body }) +end -out, ret = run_action("authenticate-cookie", { repo = "unlisted" }) -h.equals("a public repository needs no cookie", ret, 1) +local function visit(repo, token) + local _, ret = run_action("authenticate-cookie", + { repo = repo, cookie = token and session_name .. "=" .. token }) + return ret +end -out, ret = run_action("authenticate-cookie", { repo = "secret-repo" }) -h.equals("a protected repository turns a bare request away", ret, 0) +-- Where the signed way back in a login form leads. +local function way_back_in(form) + local token = form:match("name='redirect' value='([^']*)'") + return token and validate_value("redirect", token) +end + +h.equals("a public repository needs no cookie", visit("unlisted"), 1) +h.equals("a protected repository turns a bare request away", visit("secret-repo"), 0) local session = secure_value("username", "Alice", now + 3600, stored_hash) -out, ret = run_action("authenticate-cookie", { repo = "secret-repo", cookie = session_name .. "=" .. session }) -h.equals("a signed session for a member is let through", ret, 1) +h.equals("a signed session for a member is let through", visit("secret-repo", session), 1) -- A session is signed with its account's hash, so a new password or a -- removed account ends it. local stale = secure_value("username", "Alice", now + 3600, "$6$rounds=300000$old$hash") -out, ret = run_action("authenticate-cookie", { repo = "secret-repo", cookie = session_name .. "=" .. stale }) -h.equals("a session from before a password change is turned away", ret, 0) +h.equals("a session from before a password change is turned away", visit("secret-repo", stale), 0) local orphan = secure_value("username", "bob", now + 3600, stored_hash) -out, ret = run_action("authenticate-cookie", { repo = "secret-repo", cookie = session_name .. "=" .. orphan }) -h.equals("a session for a member with no account is turned away", ret, 0) +h.equals("a session for a member with no account is turned away", visit("secret-repo", orphan), 0) local unbound = secure_value("username", "Alice", now + 3600) h.equals("a session signed without its binding does not verify", validate_value("username", unbound, account_hash), nil) -out, ret = run_action("authenticate-cookie", { repo = "empty-repo", cookie = session_name .. "=" .. session }) -h.equals("a memberless repository denies even a valid session", ret, 0) +h.equals("a memberless repository denies even a valid session", visit("empty-repo", session), 0) local outsider = secure_value("username", "carol", now + 3600, stored_hash) -out, ret = run_action("authenticate-cookie", { repo = "secret-repo", cookie = session_name .. "=" .. outsider }) -h.equals("a signed session for an outsider is turned away", ret, 0) +h.equals("a signed session for an outsider is turned away", visit("secret-repo", outsider), 0) local forged = session:sub(1, -2) .. (session:sub(-1) == "0" and "1" or "0") -out, ret = run_action("authenticate-cookie", { repo = "secret-repo", cookie = session_name .. "=" .. forged }) -h.equals("a forged session is turned away", ret, 0) +h.equals("a forged session is turned away", visit("secret-repo", forged), 0) -out, ret = run_action("no-such-action", {}) +local _, ret = run_action("no-such-action", {}) h.equals("an unknown action denies rather than raising", ret, 0) -out, ret = run_action("body", { url = "/repo/log/?q=x" }) +out = run_action("body", { url = "/repo/log/?q=x" }) h.contains("the login form posts to the login url", out, "<form class='login' method='post' action='/?p=login'>") h.equals("a first visit says nothing failed", out:find("login-error", 1, true), nil) -local token = out:match("name='redirect' value='([^']*)'") -h.equals("the form carries a signed way back", token and validate_value("redirect", token), "/repo/log/?q=x") +h.equals("the form carries a signed way back", way_back_in(out), "/repo/log/?q=x") -out, ret = run_action("body", { url = "//evil.example/x" }) -token = out:match("name='redirect' value='([^']*)'") -h.equals("an unsafe destination is swapped for the login page", - token and validate_value("redirect", token), "/?p=login") +out = run_action("body", { url = "//evil.example/x" }) +h.equals("an unsafe destination is swapped for the login page", way_back_in(out), "/?p=login") h.equals("and is not taken for a failed login", out:find("login-error", 1, true), nil) -out, ret = run_action("body", { url = "/repo/log/?q=x&login=failed" }) +out = run_action("body", { url = "/repo/log/?q=x&login=failed" }) h.contains("a failed login says so", out, "<p class='login-error' role='alert'>Wrong username or password.</p>") -token = out:match("name='redirect' value='([^']*)'") -h.equals("and leaves the flag out of the way back", token and validate_value("redirect", token), "/repo/log/?q=x") +h.equals("and leaves the flag out of the way back", way_back_in(out), "/repo/log/?q=x") local way_back = secure_value("redirect", "/repo/", 0) -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=" .. url_encode(password) .. "&redirect=" .. url_encode(way_back), -}) +out = login("Alice", password, way_back) h.contains("a good login redirects back", out, "Status: 302") h.contains("to where the form said", out, "Location: /repo/\n") local granted = out:match("Set%-Cookie: __Host%-cgitauth=([^;]*);") h.equals("and grants a session that verifies", granted and validate_value("username", granted, account_hash), "Alice") -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=wrong&redirect=" .. url_encode(way_back), -}) +out = login("Alice", "wrong", way_back) h.contains("a bad password redirects the same way", out, "Status: 302") h.contains("back to the page with the failure flagged", out, "Location: /repo/?login=failed\n") h.contains("but clears the session cookie", out, "Set-Cookie: " .. session_name .. "=; ") -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=nobody&password=x&redirect=" .. url_encode(way_back), -}) -h.contains("an unknown user is told nothing different", out, "Set-Cookie: " .. session_name .. "=; ") -h.contains("and lands on the same flagged page", out, "Location: /repo/?login=failed\n") - -- A failed login for a name with no account spends crypt on a configured -- hash, the same work a real check costs, so timing tells nothing. crypt_settings = {} -run_action("authenticate-post", { - method = "POST", - body = "username=nobody&password=x&redirect=" .. url_encode(way_back), -}) +out = login("nobody", "x", way_back) +h.contains("an unknown user is told nothing different", out, "Set-Cookie: " .. session_name .. "=; ") +h.contains("and lands on the same flagged page", out, "Location: /repo/?login=failed\n") h.equals("a missing account spends crypt on a configured hash", crypt_settings[1], stored_hash) if variant == "file" then h.redirect_file("/etc/cgit-auth/users", "auth-users-missing") crypt_settings = {} - run_action("authenticate-post", { - method = "POST", - body = "username=nobody&password=x&redirect=" .. url_encode(way_back), - }) + login("nobody", "x", way_back) h.equals("with no accounts at all nothing is spent", #crypt_settings, 0) h.redirect_file("/etc/cgit-auth/users", "auth-users") end @@ -344,41 +334,36 @@ end -- crypt's work grows with the password, so one past the cap is turned down -- before crypt sees it. crypt_settings = {} -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=" .. string.rep("a", 257) .. "&redirect=" .. url_encode(way_back), -}) +out = login("Alice", string.rep("a", 257), way_back) h.contains("an overlong password is turned down", out, "Set-Cookie: " .. session_name .. "=; ") h.equals("without spending crypt on it", #crypt_settings, 0) -run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=" .. string.rep("a", 256) .. "&redirect=" .. url_encode(way_back), -}) +login("Alice", string.rep("a", 256), way_back) h.equals("a password at the cap is still checked", #crypt_settings, 1) -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=" .. url_encode(password), -}) +out = login("Alice", password) h.contains("a post without the signed token is not served", out, "Status: 404") -local hijack = secure_value("redirect", "//evil.example/", 0) -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=" .. url_encode(password) .. "&redirect=" .. url_encode(hijack), -}) +out = login("Alice", password, secure_value("redirect", "//evil.example/", 0)) h.contains("even a signed unsafe destination is not followed", out, "Status: 404") -- Over plain http the cookie cannot be Secure, and so loses the __Host- name. load_script({ { "local cookie_insecure = false", "local cookie_insecure = true" } }) -out, ret = run_action("authenticate-post", { - method = "POST", - body = "username=Alice&password=" .. url_encode(password) .. "&redirect=" .. url_encode(way_back), -}) +out = login("Alice", password, way_back) h.contains("an insecure session cookie keeps its plain name", out, "Set-Cookie: cgitauth=") h.excludes("and is not marked Secure", out, "; Secure") load_script() +-- A cookie carries whole seconds, while on Lua 5.3 and later a session length +-- worked out with a fraction is a float, which tostring writes with a ".0". +load_script({ { "local session_seconds = 7 * 24 * 60 * 60", + "local session_seconds = 1.5 * 24 * 60 * 60" } }) +out = login("Alice", password, way_back) +granted = out:match("Set%-Cookie: __Host%-cgitauth=([^;]*);") +h.equals("a session a day and a half long still verifies", + granted and validate_value("username", granted, account_hash), "Alice") +h.contains("and its cookie lasts as many whole seconds", out, "; Max-Age=129600\n") +load_script() + -- The libraries load only once a request needs one, so with every one of -- them gone a public repository is still served, while a protected one -- fails closed instead of reaching its content. @@ -386,10 +371,8 @@ for _, name in ipairs({ "openssl.rand", "openssl.hmac", "posix.sys.stat", "posix package.loaded[name] = nil h.preload_failure(name) end -out, ret = run_action("authenticate-cookie", { repo = "unlisted" }) -h.equals("without the libraries a public repository is served", ret, 1) -h.check("while a protected one fails closed", not pcall(run_action, - "authenticate-cookie", { repo = "secret-repo", cookie = session_name .. "=" .. session })) +h.equals("without the libraries a public repository is served", visit("unlisted"), 1) +h.check("while a protected one fails closed", not pcall(visit, "secret-repo", session)) h.check("and so does its login form", not pcall(run_action, "body", {})) h.finish() diff --git a/tests/extensions/test-syntax-highlight.lua b/tests/extensions/test-syntax-highlight.lua index 50c7bc8..a0debc6 100644 --- a/tests/extensions/test-syntax-highlight.lua +++ b/tests/extensions/test-syntax-highlight.lua @@ -70,10 +70,9 @@ h.equals("a lexer stopping early leaves an escaped tail", out, "<span class='hl- out = render("boom.badlex", "some <code>") h.equals("a lexer that raises falls back to escaped text", out, "some <code>") --- The viadetect lexer name only resolves through detect(), so highlighted --- output proves the detection path ran ahead of the extension path. -out = render("script.viadetect", "if") -h.equals("filename detection picks the lexer", out, "<span class='hl-keyword'>if</span>") +out = render("HELLO.FAKE", "if 42") +h.contains("an extension in capitals finds its lexer", out, + "<span class='hl-keyword'>if</span>") out = render("post-receive", "#!/usr/bin/fake\nif") h.contains("a shebang picks the lexer of a file with no extension", out, @@ -111,10 +110,4 @@ out, ret = render("empty.fake", "") h.equals("an empty blob renders empty", out, "") h.equals("an empty blob still answers zero", ret, 0) --- cgit's C sink stops at the first NUL byte, which the script documents for --- binaries that slip past the text detection. -out = render("blob.bin", "ab\0cd") -h.contains("bytes before a nul survive", out, "ab") -h.excludes("bytes after a nul are dropped", out, "cd") - h.finish() diff --git a/tests/filters/dump.lua b/tests/filters/dump.lua index d654621..585538a 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 t0201-filters.sh. +-- Test fixture for the cgit Lua filter API. -- It echoes the filter_open arguments and upper-cases the body, which lets -- the test confirm arguments and content flow through the lua: filter path. diff --git a/tests/filters/dump.sh b/tests/filters/dump.sh index a4c1571..1340fe5 100755 --- a/tests/filters/dump.sh +++ b/tests/filters/dump.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Test fixture for the cgit exec filter API, exercised by t0201-filters.sh. +# Test fixture for the cgit exec filter API. # It echoes the arguments it was invoked with and upper-cases the body, which # lets the test confirm that both flow through the exec: filter path. diff --git a/tests/setup.sh b/tests/setup.sh index a1f2d15..92451b7 100755 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -1,13 +1,11 @@ # Shared groundwork sourced by every test script. It builds a handful of small # repositories under repos/ and writes a cgitrc pointing at them, then offers -# the helpers that ask cgit for a page so a test only has to look at what comes -# back. Sourcing it also brings in the test library from the bundled Git tree, -# which is where test_expect_success and the trash directory come from. Set -# CGIT_TEST_NO_CREATE_REPOS to get the helpers without paying for the fixtures. +# the helpers that write a config of a test's own and ask cgit for a page, so +# a test only has to look at what comes back. Sourcing it also brings in the +# test library from the bundled Git tree, which is where test_expect_success +# and the trash directory come from. Set CGIT_TEST_NO_CREATE_REPOS to get the +# helpers without paying for the fixtures. -# The Git test library would run every Git command under Valgrind if it saw -# --valgrind, and only cgit is worth watching, so the option is taken out -# here and acted on further down. LF=' ' # Trash directories are collected under one trash/, which keeps the ignore @@ -15,6 +13,9 @@ LF=' # one given on the command line still wins. test_argv="${LF}--root=trash" +# The Git test library would run every Git command under Valgrind if it saw +# --valgrind, and only cgit is worth watching, so the option is taken out +# here and acted on further down. while test $# != 0 do case "$1" in @@ -43,11 +44,10 @@ TEST_NO_CREATE_REPO=YesPlease # The library spells its results directory test-results and derives these # variables before it can be told otherwise, so they are re-pointed at the -# plainer results/. The --tee and --stress raw logs write earlier and keep -# the library's name. +# plainer results/. The --tee and --stress raw logs and the leak logs write +# earlier and keep the library's name. TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX" -TEST_RESULTS_SAN_DIR="$TEST_RESULTS_BASE.$TEST_RESULTS_SAN_DIR_SFX" # The library has moved into the trash directory by now, so paths outside it # are anchored to TEST_OUTPUT_DIRECTORY. The tests run cgit by name, so the @@ -69,9 +69,10 @@ FILTER_DIRECTORY=$(cd "$TEST_OUTPUT_DIRECTORY/filters" && pwd) # them have to ask the binary rather than assume. if cgit --version | grep -F -q "[+] Lua scripting" then - export CGIT_HAS_LUA=1 + CGIT_HAS_LUA=1 + test_set_prereq CGIT_LUA else - export CGIT_HAS_LUA=0 + CGIT_HAS_LUA=0 fi # Creates a repository holding the given number of one file commits. A third @@ -107,8 +108,7 @@ mkrepo() { } setup_repos() { - rm -rf cache - mkdir -p cache + mkdir cache mkrepo repos/foo 5 >/dev/null mkrepo repos/bar 50 commit-graph >/dev/null mkrepo repos/foo+bar 10 testplus >/dev/null @@ -169,12 +169,60 @@ EOF fi } +# Writes a config to the file named first that serves repos/<url> under the +# url named second, with the cache off and links rooted at /, and takes the +# rest as settings. A repository copies its defaults from the global settings +# read before its repo.url, so whatever order they come in, the repo.* ones +# go after the repository and all others ahead of it. A repo.path setting +# names a repository kept somewhere else. +cgitrc_repo() { + cgitrc_file=$1 + cgitrc_url=$2 + shift 2 + cgitrc_path="repo.path=$PWD/repos/$cgitrc_url/.git" + cgitrc_before= + cgitrc_after= + for cgitrc_setting + do + case "$cgitrc_setting" in + repo.path=*) + cgitrc_path=$cgitrc_setting + ;; + repo.*) + cgitrc_after="$cgitrc_after$cgitrc_setting$LF" + ;; + *) + cgitrc_before="$cgitrc_before$cgitrc_setting$LF" + ;; + esac + done + printf '%s' "virtual-root=/$LF" "cache-size=0$LF" "$cgitrc_before" \ + "repo.url=$cgitrc_url$LF" "$cgitrc_path$LF" "$cgitrc_after" >"$cgitrc_file" +} + +# Writes the global settings named after the file ahead of a copy of the +# shared cgitrc. The copy turns the cache off, because a page is cached under +# its request alone and would otherwise come back as the shared config made +# it. +cgitrc_copy() { + cgitrc_file=$1 + shift + { + test $# = 0 || printf '%s\n' "$@" + sed "s/^cache-size=.*/cache-size=0/" cgitrc + } >"$cgitrc_file" +} + +cgit_rc() { + CGIT_CONFIG="$PWD/$1" QUERY_STRING="$2" cgit +} + cgit_query() { - CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="$1" cgit + cgit_rc cgitrc "$1" } cgit_url() { - CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="url=$1" cgit + cgit_rc cgitrc "url=$1" } # The response headers run down to the first empty line, and a test comparing diff --git a/tests/t0002-html-validity.sh b/tests/t0002-html-validity.sh index f5e918c..bdccaf2 100755 --- a/tests/t0002-html-validity.sh +++ b/tests/t0002-html-validity.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Runs the tidy checker over one page of each kind cgit renders, caring only +# Runs the tidy checker over one page of most kinds cgit renders, caring only # about the shape of the markup. What the pages say is the business of the # t01xx scripts. Tidy is optional and old versions predate the elements cgit # uses, so the file steps aside when a usable one cannot be found. @@ -9,36 +9,21 @@ 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" # A second argument names a config of its own, for pages the shared # config keeps turned off. - if test -n "$2" - then - CGIT_CONFIG="$PWD/$2" QUERY_STRING="url=$1" cgit >tidy-$test_count.tmp || return - else - cgit_url "$1" >tidy-$test_count.tmp || return - fi + cgit_rc "${2:-cgitrc}" "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.tmp >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" -eq tidy-$test_count + test $? != 2 } tidy=$(command -v 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, @@ -49,7 +34,6 @@ printf '<!DOCTYPE html>\n<html lang="en"><head><title>t</title></head><body><hea 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 ""' @@ -65,14 +49,7 @@ test_expect_success 'error page' 'test_url "no-such-repo"' # Stats and blame stay off in the shared config, so their pages validate # through a config of their own. test_expect_success 'set up a features config' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-stats=year" && - echo "enable-blame=1" && - echo "repo.url=feat" && - echo "repo.path=$PWD/repos/foo/.git" - } >featrc + cgitrc_repo featrc feat max-stats=year enable-blame=1 "repo.path=$PWD/repos/foo/.git" ' test_expect_success 'stats page' 'test_url "feat/stats/" featrc' diff --git a/tests/t0003-cache.sh b/tests/t0003-cache.sh index 06cdd99..e07fcd0 100755 --- a/tests/t0003-cache.sh +++ b/tests/t0003-cache.sh @@ -2,8 +2,6 @@ # Exercises the cache. The first three tests set cache-size to nothing, one # slot and the full table in turn, then count what the requests left behind. -# The rest cover a page longer than the output buffer and a key too long to -# be read back. test_description='Validate cache' . ./setup.sh @@ -31,8 +29,7 @@ test_expect_success 'verify cache-size=0' ' rm -f cache/* && set_cache_size 0 && request_all_pages && - ls cache >output && - test_line_count = 0 output + test_dir_is_empty cache ' test_expect_success 'verify cache-size=1' ' @@ -79,29 +76,10 @@ test_expect_success 'set up a repo with a page larger than the output buffer' ' rm -rf cache2 && mkdir cache2 ' -# The second request lands while the first is inside its filter, which says -# so through a marker file, since a fresh binary can take a moment to start. -overlap_requests() { - n=0 - rm -f filter-running - CGIT_CONFIG="$PWD/lockrc" QUERY_STRING="url=bigpage/about/" cgit >first.out 2>first.err & - while ! test -f filter-running && test $n -lt 10 - do - sleep 1 - n=$((n + 1)) - done - CGIT_CONFIG="$PWD/lockrc" QUERY_STRING="url=bigpage/log/" cgit >second.out 2>second.err - wait -} - -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 && + cgit_rc bigrc "url=bigpage/tree/big.txt" >big.first && test $(wc -c <big.first) -gt 65536 && - bigpage_query "url=bigpage/tree/big.txt" >big.second && + cgit_rc bigrc "url=bigpage/tree/big.txt" >big.second && strip_headers <big.first >big.first.body && strip_headers <big.second >big.second.body && test_cmp big.first.body big.second.body @@ -109,7 +87,7 @@ test_expect_success 'the first request fills the slot and the second replays it' 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 && + cgit_rc bignocache "url=bigpage/tree/big.txt" >big.nocache && strip_headers <big.nocache >big.nocache.body && test_cmp big.nocache.body big.second.body ' @@ -126,15 +104,14 @@ 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 && + cgit_rc bigkeyrc "url=bigpage/&q=$long" >key.out 2>/dev/null && grep "</html>" key.out && - ls cache3 >key.slots && - test_line_count = 0 key.slots + test_dir_is_empty cache3 ' 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 && + cgit_rc bigkeyrc "url=bigpage/" >/dev/null && ls cache3 >key.slots && test_line_count = 1 key.slots ' @@ -160,11 +137,10 @@ test_expect_success 'set up a repo missing a parent object' ' ' test_expect_success 'an error after output began ends the page with it and caches nothing' ' - CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/commit/&id=$tip" cgit >broken.out && + cgit_rc brokenrc "url=broken/commit/&id=$tip" >broken.out && grep "Not a commit" broken.out && test $(grep -c "^Status:" broken.out) = 1 && - ls cache4 >broken.slots && - test_line_count = 0 broken.slots + test_dir_is_empty cache4 ' # An error page reports a condition the repository may grow out of, and a @@ -176,20 +152,18 @@ test_expect_success 'an error page leaves no slot behind' ' CGIT_CONFIG="$PWD/bigkeyrc" \ QUERY_STRING="url=bigpage/commit/&id=0123456789abcdef0123456789abcdef01234567" cgit >error.out && grep "Not a commit" error.out && - ls cache3 >error.slots && - test_line_count = 0 error.slots + test_dir_is_empty cache3 ' # The dumb transport serves files that already sit on the disk, so a copy in # a slot would only double the disk they take. test_expect_success 'a clone file leaves no slot behind' ' rm -rf cache3 && mkdir cache3 && - CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/info/refs" cgit >clone.out && + cgit_rc bigkeyrc "url=bigpage/info/refs" >clone.out && grep "refs/heads/master" clone.out && - CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/objects/info/packs" cgit >packs.out && + cgit_rc bigkeyrc "url=bigpage/objects/info/packs" >packs.out && grep "^Status: 200" packs.out && - ls cache3 >clone.slots && - test_line_count = 0 clone.slots + test_dir_is_empty cache3 ' # A response past cache-max-slot-size is served whole but kept nowhere, both @@ -202,25 +176,23 @@ test_expect_success 'a response over cache-max-slot-size is served but not kept' echo "cache-max-slot-size=32" && cat bigkeyrc } >slotsizerc && - CGIT_CONFIG="$PWD/slotsizerc" QUERY_STRING="url=bigpage/tree/big.txt" cgit >big.unkept && + cgit_rc slotsizerc "url=bigpage/tree/big.txt" >big.unkept && tail -c 200 big.unkept | grep "</html>" && - ls cache3 >unkept.slots && - test_line_count = 0 unkept.slots && - CGIT_CONFIG="$PWD/slotsizerc" QUERY_STRING="url=bigpage/" cgit >/dev/null && + test_dir_is_empty cache3 && + cgit_rc slotsizerc "url=bigpage/" >/dev/null && ls cache3 >kept.slots && test_line_count = 1 kept.slots ' test_expect_success 'an expired slot is dropped when its refill is too large' ' rm -rf cache3 && mkdir cache3 && - CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/tree/big.txt" cgit >/dev/null && + cgit_rc bigkeyrc "url=bigpage/tree/big.txt" >/dev/null && ls cache3 >filled.slots && test_line_count = 1 filled.slots && touch -t 200001010000 cache3/* && - CGIT_CONFIG="$PWD/slotsizerc" QUERY_STRING="url=bigpage/tree/big.txt" cgit >big.refill && + cgit_rc slotsizerc "url=bigpage/tree/big.txt" >big.refill && tail -c 200 big.refill | grep "</html>" && - ls cache3 >refill.slots && - test_line_count = 0 refill.slots + test_dir_is_empty cache3 ' # The summary page keeps a ttl of its own, while every other page that follows @@ -232,12 +204,11 @@ test_expect_success 'pages following a branch take the dynamic ttl' ' echo "cache-dynamic-ttl=0" && cat bigkeyrc } >dynamicrc && - CGIT_CONFIG="$PWD/dynamicrc" QUERY_STRING="url=bigpage/log/" cgit >/dev/null && - CGIT_CONFIG="$PWD/dynamicrc" QUERY_STRING="url=bigpage/log/&h=master" cgit >/dev/null && - ls cache3 >dynamic.slots && - test_line_count = 0 dynamic.slots && - CGIT_CONFIG="$PWD/dynamicrc" QUERY_STRING="url=bigpage/" cgit >/dev/null && - CGIT_CONFIG="$PWD/dynamicrc" QUERY_STRING="url=bigpage/summary/" cgit >/dev/null && + cgit_rc dynamicrc "url=bigpage/log/" >/dev/null && + cgit_rc dynamicrc "url=bigpage/log/&h=master" >/dev/null && + test_dir_is_empty cache3 && + cgit_rc dynamicrc "url=bigpage/" >/dev/null && + cgit_rc dynamicrc "url=bigpage/summary/" >/dev/null && ls cache3 >summary.slots && test_line_count = 2 summary.slots ' @@ -251,17 +222,37 @@ test_expect_success 'a failed final write still delivers the page' ' ( trap "" XFSZ && ulimit -f 2 && - CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" cgit 2>flush.err + cgit_rc bigkeyrc "url=bigpage/" 2>flush.err ) | cat >flush.out && grep "^Status: 200" flush.out && grep "</html>" flush.out && ! grep "called many times" flush.err ' +# The second request lands while the first is inside its filter, which says +# so through a marker file, since a fresh binary can take a moment to start. +start_first_request() { + n=0 + rm -f filter-running + cgit_rc lockrc "url=bigpage/about/" >first.out 2>first.err & + while ! test -f filter-running && test $n -lt 10 + do + sleep 1 + n=$((n + 1)) + done +} + +overlap_requests() { + start_first_request + cgit_rc lockrc "url=bigpage/log/" >/dev/null 2>second.err + wait +} + # The fill writes the page into the lock file through stdout, and an exec # filter puts its own pipe on stdout while it runs. The slot lock has to # outlast that, or a second request on the slot rebuilds it under the first -# and the first visitor gets the two pages spliced together. +# and the first visitor gets the two pages spliced together. The second one +# finding the lock held is ordinary contention, which logs nothing. test_expect_success 'a slot stays locked while an exec filter runs' ' rm -rf cache3 && mkdir cache3 && write_script slow-filter.sh <<-EOF && @@ -278,7 +269,153 @@ test_expect_success 'a slot stays locked while an exec filter runs' ' overlap_requests && test $(grep -c "^Status:" first.out) = 1 && grep "id=.about." first.out && - ! grep "Unable to publish" first.err + ! grep "Unable to publish" first.err && + ! grep "Unable to lock" second.err +' + +# A request that opens the lock file just before its holder renames it into +# place as the live slot goes on to lock the slot itself. strace holds the +# second request's flock back until the first has finished, and the check +# that the path still names the locked file turns that lock down, so the +# slot keeps the first request's key. strace needs ptrace and a version that +# can delay a call, which containers and older systems may not give. +test_lazy_prereq STRACE_DELAY ' + strace -o /dev/null -e inject=flock:delay_enter=1 true +' + +test_expect_success STRACE_DELAY 'a lock taken after the rename leaves the slot alone' ' + rm -rf cache3 && mkdir cache3 && + start_first_request && + strace -o /dev/null -e inject=flock:delay_enter=4000000 \ + -E CGIT_CONFIG="$PWD/lockrc" -E QUERY_STRING="url=bigpage/log/" \ + cgit >second.out 2>second.err && + wait && + grep "id=.about." first.out && + grep "</html>" second.out && + tr "\000" "\n" <"cache3/$(ls cache3)" | head -n 1 >slot.key && + grep "about" slot.key && + ! grep "log" slot.key +' + +# An flock lasts until the last copy of its descriptor closes, so a filter +# may hold the slot's lock file only as the stdout it writes the page to. A +# stray copy would keep the slot locked for as long as the filter, or +# anything it leaves running, lives on. +test_expect_success PERL 'a filter holds the lock file only as its stdout' ' + rm -rf cache3 && mkdir cache3 && + write_script fd-filter "$PERL_PATH" <<-\EOF && + use POSIX qw(fstat); + 1 while <STDIN>; + my @lock = stat $ENV{LOCK_FILE} or die "$!\n"; + my @fds = grep { + my @fd = fstat($_); + @fd && $fd[0] == $lock[0] && $fd[1] == $lock[1] + } 0 .. 255; + print "lock file on fd @fds.\n"; + EOF + { + echo "cache-size=1" && + sed "/^cache-size=/d" bigkeyrc && + echo "repo.readme=:file-1" && + echo "repo.about-filter=exec:$PWD/fd-filter" + } >fdrc && + LOCK_FILE="$PWD/cache3/00000000.lock" CGIT_CONFIG="$PWD/fdrc" \ + QUERY_STRING="url=bigpage/about/" cgit >fd.out && + grep -F "lock file on fd 1." fd.out +' + +# A refresh that cannot take the lock serves the expired copy, and says why +# unless the reason is another request doing the refresh. A line appended +# to the copy shows that it is what was served. +test_expect_success SANITY 'a refresh that cannot lock serves the expired slot and says why' ' + rm -rf cache3 && mkdir cache3 && + cgit_rc bigkeyrc "url=bigpage/" >/dev/null && + slot=$(ls cache3) && + echo "the expired copy" >>"cache3/$slot" && + touch -t 200001010000 "cache3/$slot" && + test_when_finished "chmod u+w cache3" && + chmod a-w cache3 && + cgit_rc bigkeyrc "url=bigpage/" >stale.out 2>stale.err && + grep "the expired copy" stale.out && + grep "Unable to open .*\.lock: Permission denied" stale.err +' + +# A lock file that cannot take the key goes with its lock, rather than +# staying behind locked while the page renders uncached. The limit is on +# files, so the log goes through the pipe as well. +test_expect_success 'a slot lock that cannot take its key is let go' ' + rm -rf cache3 && mkdir cache3 && + ( + trap "" XFSZ && + ulimit -f 0 && + cgit_rc bigkeyrc "url=bigpage/" 2>&1 + ) | cat >nokey.out && + grep "</html>" nokey.out && + grep "Unable to write .*\.lock" nokey.out && + test_dir_is_empty cache3 +' + +# A scan is cached in a file named after the hash of its path, so a first +# scan finds out what that name is. The index page is left uncached, which +# makes each request read the list afresh. +test_expect_success 'set up a cached scan of two repositories' ' + mkrepo scanned/one 1 && + mkrepo scanned/two 1 && + { + echo "virtual-root=/" && + echo "cache-root=$PWD/cache5" && + echo "cache-size=64" && + echo "cache-index-ttl=0" && + echo "scan-path=$PWD/scanned" + } >scanrc && + rm -rf cache5 && mkdir cache5 && + cgit_rc scanrc "url=" >/dev/null && + scanned_rc=$(ls cache5) && + test_path_is_file "cache5/$scanned_rc" +' + +# A request that finds the scan's lock held and has no list to go on scans +# in its own process, leaving the file to the holder. That is only another +# request at work and no cause for a line in the log. +test_expect_success PERL 'a scan finding its lock held runs uncached' ' + write_script hold-lock "$PERL_PATH" <<-\EOF && + use Fcntl qw(:flock); + open(my $lock, ">>", shift) or die "$!\n"; + flock($lock, LOCK_EX | LOCK_NB) or die "$!\n"; + exit(system(@ARGV) ? 1 : 0); + EOF + rm "cache5/$scanned_rc" && + CGIT_CONFIG="$PWD/scanrc" QUERY_STRING="url=" \ + ./hold-lock "cache5/$scanned_rc.lock" cgit >held.out 2>held.err && + grep ">one</a>" held.out && + grep ">two</a>" held.out && + test_path_is_missing "cache5/$scanned_rc" && + test_must_be_empty held.err +' + +# A run that died before its rename leaves a lock file behind, here longer +# than anything the next run writes and ending in a repository and a marker. +# Emptying the file before reuse keeps that tail out of both caches. +test_expect_success 'a stale lock file reaches neither the scan nor a slot' ' + { + printf "%01024d\n" 0 0 0 0 && + echo "repo.url=ghost" && + echo "repo.path=$PWD/scanned/one/.git" + } >stale.lock && + rm -f "cache5/$scanned_rc" && + cp stale.lock "cache5/$scanned_rc.lock" && + CGIT_CONFIG="$PWD/scanrc" QUERY_STRING="url=" cgit >scan.out && + grep ">one</a>" scan.out && + ! grep ">ghost</a>" scan.out && + rm -rf cache3 && mkdir cache3 && + CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" cgit >page.out && + slot=$(ls cache3) && + rm "cache3/$slot" && + { cat page.out page.out && echo "STALE-TAIL"; } >"cache3/$slot.lock" && + CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" cgit >/dev/null && + CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" cgit >replay.out && + grep "</html>" replay.out && + ! grep "STALE-TAIL" replay.out ' test_done diff --git a/tests/t0004-docs.sh b/tests/t0004-docs.sh index 1eca51e..79c36b1 100755 --- a/tests/t0004-docs.sh +++ b/tests/t0004-docs.sh @@ -2,11 +2,13 @@ # Checks that the configuration documents stay consistent with each other. # The manual documents every key the reference config sets, the reference -# config sets every key the manual documents, both settings sections of the -# manual keep their entries sorted, and the three server configs carry one -# identical content security policy. +# config sets every key the manual documents and puts scan-path after every +# global setting, both settings sections of the manual keep their entries +# sorted, and the three server configs carry one identical content security +# policy. test_description='Check the configuration documents' +CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh ROOT="$TEST_OUTPUT_DIRECTORY/.." @@ -33,6 +35,16 @@ test_expect_success 'every key the reference config sets is documented' ' test_must_be_empty undocumented ' +# A scanned repository inherits the global settings above scan-path, so the +# sample keeps none below it. +test_expect_success 'the reference config scans after every global setting' ' + sed -n "/^#\{0,1\}scan-path=/,\$p" "$ROOT/custom/cgitrc" | + sed -n "s/^#\{0,1\}\([a-z][a-zA-Z0-9.<>-]*\)=.*/\1/p" | + grep -v "^repo\." >after-scan-path && + echo scan-path >expect && + test_cmp expect after-scan-path +' + test_expect_success 'the manual lists its global settings in order' ' sed -n "/^Global settings$/,/^Repository settings$/p" "$ROOT/MANUAL.txt" | sed -n "s/^\([a-z][a-zA-Z0-9.<>-]*\)::$/\1/p" >global-order && diff --git a/tests/t0005-markup.sh b/tests/t0005-markup.sh index 5fa866d..a6dd028 100755 --- a/tests/t0005-markup.sh +++ b/tests/t0005-markup.sh @@ -5,6 +5,7 @@ # names the page it renders and the attribute or element it expects. test_description='Check the markup hooks a theme relies on' +CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh test_expect_success 'set up a repository with a file that has an extension' ' @@ -16,23 +17,13 @@ test_expect_success 'set up a repository with a file that has an extension' ' git add dir && git commit -m sources ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "enable-blame=1" && - echo "max-stats=year" && - echo "readme=:file-1" && - echo "repo.url=hooks" && - echo "repo.path=$PWD/repos/hooks/.git" - } >hooksrc + cgitrc_repo hooksrc hooks enable-blame=1 max-stats=year readme=:file-1 ' -hooks() { CGIT_CONFIG="$PWD/hooksrc" QUERY_STRING="url=$1" cgit; } - test_expect_success 'the root names the page and the repository' ' - hooks "" >tmp && + cgit_rc hooksrc "url=" >tmp && grep "<div id=.cgit. data-page=.repolist.>" tmp && - hooks "hooks/log/" >tmp && + cgit_rc hooksrc "url=hooks/log/" >tmp && grep "<div id=.cgit. data-page=.log. data-repo=.hooks.>" tmp ' @@ -45,14 +36,14 @@ test_expect_success 'the landmarks and the search form are in place' ' ' test_expect_success 'the pages name the versions only when asked to' ' - hooks "hooks/" >footer && + cgit_rc hooksrc "url=hooks/" >footer && grep "<meta name=.generator. content=.cgit.>" footer && grep "<footer class=.footer.>generated by <a href=[^>]*>cgit</a></footer>" footer && { echo "enable-version-info=1" && cat hooksrc } >versionrc && - CGIT_CONFIG="$PWD/versionrc" QUERY_STRING="url=hooks/" cgit >footer && + cgit_rc versionrc "url=hooks/" >footer && grep "<meta name=.generator. content=.cgit v[0-9][^>]*>" footer && grep "<footer class=.footer.>generated by <a href=[^>]*>cgit v[0-9][^<]*</a> (<a href=[^>]*>git [0-9][^<]*</a>)</footer>" footer && echo "<footer>our own</footer>" >footer.html && @@ -61,7 +52,7 @@ test_expect_success 'the pages name the versions only when asked to' ' echo "footer=$PWD/footer.html" && cat hooksrc } >versionrc && - CGIT_CONFIG="$PWD/versionrc" QUERY_STRING="url=hooks/" cgit >footer && + cgit_rc versionrc "url=hooks/" >footer && grep "<footer>our own</footer>" footer && ! grep "git-scm" footer ' @@ -72,58 +63,58 @@ test_expect_success 'the listings name themselves, their sections and their rows grep "<tr class=.commit.>" tmp && grep "class=.active. aria-current=.page." tmp && grep "<th class=.left. scope=.col.>" tmp && - hooks "" >tmp && + cgit_rc hooksrc "url=" >tmp && grep "<table class=.list repolist. aria-label=.Repositories.>" tmp && grep "<tbody class=.repos.>" tmp && grep "<tr class=.repo.>" tmp && - hooks "hooks/" >tmp && + cgit_rc hooksrc "url=hooks/" >tmp && grep "<table class=.list summary. aria-label=.Summary.>" tmp && grep "<tbody class=.branches.>" tmp && grep "<tr class=.branch.>" tmp && - hooks "hooks/tree/" >tmp && + cgit_rc hooksrc "url=hooks/tree/" >tmp && grep "<table class=.list tree. aria-label=.Files.>" tmp && grep "<tr class=.dir.>" tmp && grep "<tr class=.blob.>" tmp && - hooks "hooks/tree/dir/" >tmp && + cgit_rc hooksrc "url=hooks/tree/dir/" >tmp && grep "class=.ls-blob ext-c." tmp ' test_expect_success 'the diff pages wrap each file and label their controls' ' - hooks "hooks/commit/" >tmp && + cgit_rc hooksrc "url=hooks/commit/" >tmp && grep "<table class=.commit-info.>" tmp && grep "<th scope=.row.>author</th>" tmp && grep "<div class=.file. data-path=.dir/hello.c.>" tmp && grep "<label for=.diff-context.>context</label>" tmp && grep "<select id=.diff-context. name=.context.>" tmp && grep "<form method=.get. aria-label=.Diff options.>" tmp && - hooks "hooks/diff/&ss=1" >tmp && + cgit_rc hooksrc "url=hooks/diff/&ss=1" >tmp && grep "<tbody class=.file. data-path=.dir/hello.c.>" tmp && - hooks "hooks/stats/" >tmp && + cgit_rc hooksrc "url=hooks/stats/" >tmp && grep "<label for=.stats-authors.>authors</label>" tmp ' test_expect_success 'the blob, blame and about pages open with their own containers' ' - hooks "hooks/tree/dir/hello.c" >tmp && + cgit_rc hooksrc "url=hooks/tree/dir/hello.c" >tmp && grep "<div class=.blob-header.>" tmp && grep "<a id=.n1." tmp && - hooks "hooks/blame/dir/hello.c" >tmp && + cgit_rc hooksrc "url=hooks/blame/dir/hello.c" >tmp && grep "<div class=.blob-header.>" tmp && - hooks "hooks/about/" >tmp && + cgit_rc hooksrc "url=hooks/about/" >tmp && grep "<div id=.about.>" tmp ' test_expect_success 'each page names itself, then the repository and the site' ' site="Git repository browser" && - hooks "hooks/" >tmp && + cgit_rc hooksrc "url=hooks/" >tmp && grep "<title>hooks - $site</title>" tmp && for page in about log refs tree stats do - hooks "hooks/$page/" >tmp && + cgit_rc hooksrc "url=hooks/$page/" >tmp && grep "<title>$page - hooks - $site</title>" tmp || return 1 done && - hooks "hooks/tree/dir/hello.c" >tmp && + cgit_rc hooksrc "url=hooks/tree/dir/hello.c" >tmp && grep "<title>hello.c < dir - hooks - $site</title>" tmp && - hooks "hooks/blame/dir/hello.c" >tmp && + cgit_rc hooksrc "url=hooks/blame/dir/hello.c" >tmp && grep "<title>blame - hello.c < dir - hooks - $site</title>" tmp ' @@ -150,7 +141,7 @@ test_expect_success 'the page background is the same everywhere it is set' ' test_line_count = 1 expect && sed -n "/^html.cgit-standalone {\$/,/^}/s/^ background: light-dark(\(#[0-9a-f]*\), \(#[0-9a-f]*\));\$/\1 \2/p" "$css" >actual && test_cmp expect actual && - hooks "hooks/" >tmp && + cgit_rc hooksrc "url=hooks/" >tmp && light=$(sed -n "s/.*theme-color. media=.(prefers-color-scheme: light). content=.\(#[0-9a-f]*\).*/\1/p" tmp) && dark=$(sed -n "s/.*theme-color. media=.(prefers-color-scheme: dark). content=.\(#[0-9a-f]*\).*/\1/p" tmp) && echo "$light $dark" >actual && @@ -158,21 +149,33 @@ test_expect_success 'the page background is the same everywhere it is set' ' ' test_expect_success 'the script waits for the page instead of holding it up' ' - hooks "hooks/" >tmp && + cgit_rc hooksrc "url=hooks/" >tmp && grep "<script src=./cgit.js. defer></script>" tmp ' -# The rest of the hooks need a tag, a symlink, a submodule, a trailer, a lone -# carriage return, clone urls, an empty repository and a second page of -# repositories, so they get a commit and a config of their own after the checks -# above have read the first ones. +# A collection from getElementsByClassName is live, so an age the loop moves to +# another unit drops out of it at once and the age after it is skipped. +test_expect_success 'the script walks the ages from a list that holds still' ' + ! grep "getElementsByClassName" "$TEST_OUTPUT_DIRECTORY/../assets/cgit.js" +' + +# The line anchors carry a scroll margin of half the window, which centres a +# line scrolled to the top of the view. Asking scrollIntoView to centre it as +# well centres the margin too and drops a range three quarters of the way down. +test_expect_success 'a line range scrolls where a single line does' ' + grep "^ scroll-margin-top: calc(50vh - 1em);$" "$TEST_OUTPUT_DIRECTORY/../assets/cgit.css" && + grep "first.scrollIntoView();" "$TEST_OUTPUT_DIRECTORY/../assets/cgit.js" +' + +# The rest of the hooks need a tag, a symlink, a submodule, a trailer, clone +# urls, an empty repository and a second page of repositories, so they get a +# commit and a config of their own after the checks above have read the first +# ones. test_expect_success 'set up the remaining kinds of entry' ' ( cd repos/hooks && git tag -a -m "first release" v1 && test_ln_s_add file-1 link && - printf "a\\rb\\n" >cr.txt && - git add cr.txt && git update-index --add --cacheinfo "160000,$(git rev-parse HEAD),sub" && git commit -m "more kinds of entry" -m "Fixes: $(git rev-parse --short HEAD)" ) && @@ -192,36 +195,29 @@ test_expect_success 'set up the remaining kinds of entry' ' } >morerc ' -more() { CGIT_CONFIG="$PWD/morerc" QUERY_STRING="url=$1" cgit; } - test_expect_success 'the refs, summary and tree name every section and row' ' - more "hooks/refs/" >tmp && + cgit_rc morerc "url=hooks/refs/" >tmp && grep "<table class=.list refs. aria-label=.Branches and tags.>" tmp && grep "<tbody class=.tags.>" tmp && grep "<tr class=.tag.>" tmp && - more "hooks/" >tmp && + cgit_rc morerc "url=hooks/" >tmp && grep "<tbody class=.clone.>" tmp && - more "hooks/tree/" >tmp && + cgit_rc morerc "url=hooks/tree/" >tmp && grep "<tbody class=.tree.>" tmp && grep "<tr class=.link.>" tmp && grep "<tr class=.mod.>" tmp ' -test_expect_success 'a character the browser would break a line at is an escape' ' - more "hooks/tree/cr.txt" >tmp && - grep "a<span class=.escape.>" tmp -' - test_expect_success 'the commit page has its trailer table and diffstat' ' - more "hooks/commit/" >tmp && + cgit_rc morerc "url=hooks/commit/" >tmp && grep "<table class=.commit-trailers.>" tmp && grep "<table class=.diffstat.>" tmp ' test_expect_success 'the breadcrumb, the pager and the ages carry their hooks' ' - more "hooks/tree/dir/" >tmp && + cgit_rc morerc "url=hooks/tree/dir/" >tmp && grep "<nav class=.path. aria-label=.breadcrumb.>" tmp && - more "" >tmp && + cgit_rc morerc "url=" >tmp && grep "<nav class=.pager. aria-label=.Pager.>" tmp && grep "class=.current. aria-current=.page." tmp && grep "<time class=.age-[a-z]*. datetime=.[^ ]* data-ut=.[0-9]*." tmp @@ -232,13 +228,13 @@ test_expect_success 'a date shown in full is a plain time element' ' echo "enable-relative-dates=0" && cat morerc } >fullrc && - CGIT_CONFIG="$PWD/fullrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc fullrc "url=" >tmp && grep "<time datetime=" tmp && ! grep "data-ut=" tmp ' test_expect_success 'an empty repository lists its clone urls as a clone table' ' - more "empty/" >tmp && + cgit_rc morerc "url=empty/" >tmp && grep "<table class=.list clone. aria-label=.Clone urls.>" tmp && grep "<tbody class=.clone.>" tmp ' @@ -248,7 +244,7 @@ test_expect_success 'embedded mode marks the root' ' echo "embedded=1" && cat hooksrc } >embeddedrc && - CGIT_CONFIG="$PWD/embeddedrc" QUERY_STRING="url=hooks/" cgit >tmp && + cgit_rc embeddedrc "url=hooks/" >tmp && grep "<div id=.cgit. class=.cgit-embedded. data-page=.summary." tmp ' diff --git a/tests/t0006-nolua.sh b/tests/t0006-nolua.sh index c315e41..cd35fca 100755 --- a/tests/t0006-nolua.sh +++ b/tests/t0006-nolua.sh @@ -13,7 +13,6 @@ nolua="$TEST_OUTPUT_DIRECTORY/../build/nolua/cgit" test -x "$nolua" || { skip_all='Skipping the Lua-less checks: build/nolua/cgit is missing, run make test' test_done - exit } nolua_query() { CGIT_CONFIG="$PWD/$1" QUERY_STRING="$2" "$nolua"; } @@ -24,12 +23,7 @@ test_expect_success 'the binary reports no Lua' ' ' test_expect_success 'it serves a page' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=foo" && - echo "repo.path=$PWD/repos/foo/.git" - } >plainrc && + cgitrc_repo plainrc foo && nolua_query plainrc "url=foo/log/" >tmp && grep "^Status: 200" tmp && grep ">commit 5</a>" tmp diff --git a/tests/t0101-index.sh b/tests/t0101-index.sh index 8d389b6..2e49ed8 100755 --- a/tests/t0101-index.sh +++ b/tests/t0101-index.sh @@ -11,9 +11,9 @@ test_description='Check content on index page' . ./setup.sh test_expect_success 'generate index page' 'cgit_url "" >tmp' -test_expect_success 'find foo repo' 'grep "foo" tmp' +test_expect_success 'find foo repo' 'grep ">foo</a>" tmp' test_expect_success 'find foo description' 'grep "\[no description\]" tmp' -test_expect_success 'find bar repo' 'grep "bar" tmp' +test_expect_success 'find bar repo' 'grep ">bar</a>" tmp' test_expect_success 'find bar description' 'grep "the bar repo" tmp' test_expect_success 'find foo+bar repo' 'grep ">foo+bar<" tmp' test_expect_success 'verify foo+bar links' 'test $(grep -o "href=./foo%2Bbar/." tmp | wc -l) = 2' @@ -22,23 +22,15 @@ test_expect_success 'no tree-link' '! grep "foo/tree" tmp' test_expect_success 'no log-link' '! grep "foo/log" tmp' test_expect_success 'index links lead to the summary, log and tree' ' - echo "enable-index-links=1" >links-cgitrc && - sed "s/^cache-size=.*/cache-size=0/" cgitrc >>links-cgitrc && - CGIT_CONFIG="$PWD/links-cgitrc" QUERY_STRING="url=" cgit >tmp && + cgitrc_copy links-cgitrc enable-index-links=1 && + cgit_rc links-cgitrc "url=" >tmp && grep ">summary</a><a class=.button. href=./foo/log/.>log</a>" tmp && grep "href=./foo/log/.>log</a><a class=.button. href=./foo/tree/.>tree<" tmp ' test_expect_success 'each css and js entry gets a link of its own' ' - { - echo "css=/a.css" && - echo "css=/b.css" && - echo "js=/a.js" && - echo "js=" && - echo "js=/b.js" - } >headrc && - sed "s/^cache-size=.*/cache-size=0/" cgitrc >>headrc && - CGIT_CONFIG="$PWD/headrc" QUERY_STRING="url=" cgit >tmp && + cgitrc_copy headrc css=/a.css css=/b.css js=/a.js js= js=/b.js && + cgit_rc headrc "url=" >tmp && grep "<link rel=.stylesheet. href=./a.css.>" tmp && grep "<link rel=.stylesheet. href=./b.css.>" tmp && grep "<script src=" tmp >scripts && @@ -47,12 +39,6 @@ test_expect_success 'each css and js entry gets a link of its own' ' grep "/b.js" scripts ' -test_expect_success 'the feed link encodes the repository and the branch' ' - sed "s/^cache-size=.*/cache-size=0/" cgitrc >feedrc && - CGIT_CONFIG="$PWD/feedrc" HTTP_HOST=example.org QUERY_STRING="url=foo%2Bbar/log/&h=1%2B2" cgit >tmp && - grep "title=.Atom feed. href=.http://example.org/foo%2Bbar/atom/?h=1%2B2." tmp -' - # The description limit counts bytes, so a cut has to land between # characters rather than leave half of one on the page. test_expect_success 'a cut description keeps whole characters' ' @@ -63,7 +49,7 @@ test_expect_success 'a cut description keeps whole characters' ' echo "repo.path=$PWD/repos/foo/.git" && echo "repo.desc=日本語の説明文です" } >descrc && - CGIT_CONFIG="$PWD/descrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc descrc "url=" >tmp && grep ">日本語\.\.\.</a>" tmp ' diff --git a/tests/t0102-summary.sh b/tests/t0102-summary.sh index e95f270..9e0e14c 100755 --- a/tests/t0102-summary.sh +++ b/tests/t0102-summary.sh @@ -11,7 +11,7 @@ test_description='Check content on summary page' test_expect_success 'generate foo summary' 'cgit_url "foo" >tmp' test_expect_success 'find commit 1' 'grep "commit 1" tmp' test_expect_success 'find commit 5' 'grep "commit 5" tmp' -test_expect_success 'find branch master' 'grep "master" tmp' +test_expect_success 'find branch master' 'grep "<tr class=.branch.><td><a[^>]*>master</a>" tmp' test_expect_success 'no tags' '! grep "Tag</th>" tmp' test_expect_success 'clone-url expanded correctly' ' grep "git://example.org/foo.git" tmp @@ -24,11 +24,6 @@ test_expect_success 'repo description reaches the meta tag' ' test_expect_success 'no commit 45' '! grep "commit 45" tmp' test_expect_success 'find commit 46' 'grep "commit 46" tmp' test_expect_success 'find commit 50' 'grep "commit 50" tmp' -test_expect_success 'find branch master' 'grep "master" tmp' -test_expect_success 'no tags' '! grep "Tag</th>" tmp' -test_expect_success 'clone-url expanded correctly' ' - grep "git://example.org/bar.git" tmp -' # A clone url is shown as written, but a browser cannot follow the ssh, scp # or git forms, so those rows link to the first http url in the list, or to @@ -38,17 +33,40 @@ test_expect_success 'a git clone url row falls back to the page url' ' ' test_expect_success 'an scp clone url row links to the http url' ' + cgitrc_repo clonerc foo "repo.clone-url=git@example.org:foo https://example.org/foo" && + cgit_rc clonerc "url=foo" >tmp && + grep "title=.foo Git repository. href=.https://example.org/foo.>git@example.org:foo</a>" tmp && + grep "vcs-git. href=.https://example.org/foo." tmp && + ! grep "vcs-git. href=.git@" tmp +' + +# A repository can turn the global clone-url off with an empty one of its own. +# Neither its summary nor the page an empty repository gets then draws a Clone +# heading over no rows. +test_expect_success 'an empty clone url draws no clone heading' ' + git init -q --bare repos/empty.git && { echo "virtual-root=/" && echo "cache-size=0" && + echo "clone-url=https://example.org/\$CGIT_REPO_URL" && echo "repo.url=foo" && echo "repo.path=$PWD/repos/foo/.git" && - echo "repo.clone-url=git@example.org:foo https://example.org/foo" - } >clonerc && - CGIT_CONFIG="$PWD/clonerc" QUERY_STRING="url=foo" cgit >tmp && - grep "title=.foo Git repository. href=.https://example.org/foo.>git@example.org:foo</a>" tmp && - grep "vcs-git. href=.https://example.org/foo." tmp && - ! grep "vcs-git. href=.git@" tmp + echo "repo.clone-url=" && + echo "repo.url=empty" && + echo "repo.path=$PWD/repos/empty.git" && + echo "repo.clone-url=" && + echo "repo.url=cloned" && + echo "repo.path=$PWD/repos/empty.git" + } >emptyrc && + cgit_rc emptyrc "url=foo" >tmp && + grep "commit 1" tmp && + ! grep "Clone</th>" tmp && + cgit_rc emptyrc "url=empty" >tmp && + grep "Repository seems to be empty" tmp && + ! grep "Clone</th>" tmp && + cgit_rc emptyrc "url=cloned" >tmp && + grep "Clone</th>" tmp && + grep "https://example.org/cloned</a>" tmp ' # Prints how many columns each row of the page's list table spans, one row to @@ -74,7 +92,7 @@ row_widths() { # Fails unless every row of the list table at the url is as many columns wide # as the second argument says. A third names a config other than colsrc. test_widths() { - CGIT_CONFIG="$PWD/${3:-colsrc}" QUERY_STRING="url=$1" cgit >tmp && + cgit_rc "${3:-colsrc}" "url=$1" >tmp && row_widths tmp | sort -u >actual && echo "$2" >expect && test_cmp expect actual @@ -86,7 +104,7 @@ test_widths() { # Each repo below shows the same history with different counts, and every # section is cut short so that its [...] row is drawn too. test_expect_success 'set up a repo cut short in every section' ' - mkrepo repos/cols 3 >/dev/null && + mkrepo repos/cols 3 && git -C repos/cols branch other HEAD~1 && git -C repos/cols tag one HEAD~1 && git -C repos/cols tag -a -m two two && @@ -108,7 +126,7 @@ test_expect_success 'set up a repo cut short in every section' ' echo "repo.enable-log-filecount=1" && echo "repo.enable-log-linecount=1" } >colsrc && - CGIT_CONFIG="$PWD/colsrc" QUERY_STRING="url=none" cgit >tmp && + cgit_rc colsrc "url=none" >tmp && grep "<tbody class=.tags.>" tmp && grep "<tbody class=.clone.>" tmp && test $(grep -c ">\[...\]</a>" tmp) = 3 diff --git a/tests/t0103-log.sh b/tests/t0103-log.sh index d7e745d..9796394 100755 --- a/tests/t0103-log.sh +++ b/tests/t0103-log.sh @@ -12,7 +12,7 @@ test_expect_success 'find commit 1' 'grep "commit 1" tmp' test_expect_success 'find commit 5' 'grep "commit 5" tmp' test_expect_success 'generate bar/log' 'cgit_url "bar/log" >tmp' -test_expect_success 'find commit 1' 'grep "commit 1" tmp' +test_expect_success 'find commit 1' 'grep ">commit 1<" tmp' test_expect_success 'find commit 50' 'grep "commit 50" tmp' # The switch button submits the select as h, so whatever head the request @@ -38,7 +38,7 @@ test_expect_success 'a branch head keeps its plain option' ' test_expect_success 'generate "with%20space/log?qt=grep&q=commit+1"' ' cgit_url "with+space/log&qt=grep&q=commit+1" >tmp ' -test_expect_success 'find commit 1' 'grep "commit 1" tmp' +test_expect_success 'find commit 1' 'grep ">commit 1<" tmp' test_expect_success 'find link with %20 in path' 'grep "/with%20space/log/?qt=grep" tmp' test_expect_success 'find link with + in arg' 'grep "/log/?qt=grep&q=commit+1" tmp' test_expect_success 'no links with space in path' '! grep "href=./with space/" tmp' @@ -68,24 +68,17 @@ test_expect_success 'set up a renamed file to follow' ' git commit -am "edit b $i" || return 1 done ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "enable-follow-links=1" && - echo "max-commit-count=3" && - echo "repo.url=fol" && - echo "repo.path=$PWD/repos/fol/.git" - } >folrc + cgitrc_repo folrc fol enable-follow-links=1 max-commit-count=3 ' test_expect_success 'a followed log reaches the root commit' ' - CGIT_CONFIG="$PWD/folrc" QUERY_STRING="url=fol/log/b.txt&follow=1&ofs=6" cgit >tmp && + cgit_rc folrc "url=fol/log/b.txt&follow=1&ofs=6" >tmp && grep ">edit a 2<" tmp && grep ">create a<" tmp ' test_expect_success 'the pager of a followed log keeps the name followed' ' - CGIT_CONFIG="$PWD/folrc" QUERY_STRING="url=fol/log/b.txt&follow=1&ofs=3" cgit >tmp && + cgit_rc folrc "url=fol/log/b.txt&follow=1&ofs=3" >tmp && grep ">edit a 3<" tmp && grep "href=./fol/log/b.txt?follow=1.>\[prev\]" tmp && grep "href=./fol/log/b.txt?ofs=6&follow=1.>\[next\]" tmp @@ -93,24 +86,30 @@ test_expect_success 'the pager of a followed log keeps the name followed' ' test_expect_success 'a cut subject keeps whole characters' ' git -C repos/foo commit --allow-empty -m "日本語の説明文です日本語の説明文です" && - { - echo "max-message-length=20" && - sed "s/^cache-size=.*/cache-size=0/" cgitrc - } >subjectrc && - CGIT_CONFIG="$PWD/subjectrc" QUERY_STRING="url=foo/log/" cgit >tmp && + cgitrc_copy subjectrc max-message-length=20 && + cgit_rc subjectrc "url=foo/log/" >tmp && grep ">日本語の説\.\.\.</a>" tmp ' test_expect_success 'the log draws the commit graph when enabled' ' - { - echo "enable-commit-graph=1" && - sed "s/^cache-size=.*/cache-size=0/" cgitrc - } >graphrc && - CGIT_CONFIG="$PWD/graphrc" QUERY_STRING="url=foo/log/" cgit >tmp && + cgitrc_copy graphrc enable-commit-graph=1 && + cgit_rc graphrc "url=foo/log/" >tmp && grep "class=.commitgraph." tmp && grep "<th scope=.col. aria-label=.Graph.></th>" tmp ' +# A followed log is walked without the graph, so its first column holds the +# age whatever enable-commit-graph says. +test_expect_success 'a followed log heads its age column with the graph on' ' + { + echo "enable-commit-graph=1" && + cat folrc + } >folgraphrc && + cgit_rc folgraphrc "url=fol/log/b.txt&follow=1" >tmp && + grep "<th class=.left. scope=.col.>Age</th><th class=.left. scope=.col.>Commit message" tmp && + ! grep "aria-label=.Graph." tmp +' + # The search is literal, so text that is no valid regular expression still # finds the commit holding it. test_expect_success 'a search for brackets matches them literally' ' @@ -120,4 +119,18 @@ test_expect_success 'a search for brackets matches them literally' ' grep "fix \\[brackets\\] in docs" tmp ' +# A range is walked in full before the first row, so a missing parent fails +# the walk while the page can still answer with an error of its own. +test_expect_success 'a walk that cannot start answers one error page' ' + git clone -q repos/foo gap && + old=$(git -C gap rev-parse HEAD~4) && + parent=$(git -C gap rev-parse HEAD~2) && + rm gap/.git/objects/$(echo $parent | cut -c1-2)/$(echo $parent | cut -c3-) && + cgitrc_repo gaprc gap "repo.path=$PWD/gap/.git" && + cgit_rc gaprc "url=gap/log/&qt=range&q=$old..master" >tmp 2>err && + test $(grep -c "^Status:" tmp) = 1 && + grep "^Status: 500" tmp && + grep "Unable to read the history" tmp +' + test_done diff --git a/tests/t0104-tree.sh b/tests/t0104-tree.sh index 41134a4..26a0cb4 100755 --- a/tests/t0104-tree.sh +++ b/tests/t0104-tree.sh @@ -9,7 +9,7 @@ test_description='Check content on tree page' . ./setup.sh test_expect_success 'generate bar/tree' 'cgit_url "bar/tree" >tmp' -test_expect_success 'find file-1' 'grep "file-1" tmp' +test_expect_success 'find file-1' 'grep ">file-1<" tmp' test_expect_success 'find file-50' 'grep "file-50" tmp' test_expect_success 'the links column has a name' ' grep "<th class=.ls-links. scope=.col. aria-label=.Links.></th>" tmp @@ -44,17 +44,11 @@ test_expect_success 'set up a file holding the characters a browser breaks at' ' git add breaks.txt && git commit -m breaks ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "enable-blame=1" && - echo "repo.url=breaks" && - echo "repo.path=$PWD/repos/breaks/.git" - } >breaksrc + cgitrc_repo breaksrc breaks enable-blame=1 ' test_expect_success 'each of them is shown as an escape in the tree' ' - CGIT_CONFIG="$PWD/breaksrc" QUERY_STRING="url=breaks/tree/breaks.txt" cgit >tmp && + cgit_rc breaksrc "url=breaks/tree/breaks.txt" >tmp && grep "<code>a<span class=.escape.>\\\\r</span>b<span class=.escape.>\\\\v</span>c" tmp && grep "c<span class=.escape.>\\\\f</span>d<span class=.escape.>\\\\u0085</span>e" tmp && grep "e<span class=.escape.>\\\\u2028</span>f<span class=.escape.>\\\\u2029</span>g$" tmp @@ -73,7 +67,7 @@ test_expect_success 'the numbers count only the lines git counts' ' ' test_expect_success 'the blame page escapes them the same way' ' - CGIT_CONFIG="$PWD/breaksrc" QUERY_STRING="url=breaks/blame/breaks.txt" cgit >tmp && + cgit_rc breaksrc "url=breaks/blame/breaks.txt" >tmp && grep "<code>a<span class=.escape.>\\\\r</span>b<span class=.escape.>\\\\v</span>c" tmp && grep "e<span class=.escape.>\\\\u2028</span>f<span class=.escape.>\\\\u2029</span>g$" tmp && grep "^i<span class=.escape.>\\\\r</span></code>" tmp @@ -105,17 +99,11 @@ test_expect_success 'set up a repo with a nested submodule' ' git update-index --add --cacheinfo 160000,$sub,vendor/sub && git commit -m gitlink ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "enable-blame=1" && - echo "repo.url=submodules" && - echo "repo.path=$PWD/repos/submodules/.git" - } >submodrc + cgitrc_repo submodrc submodules enable-blame=1 ' test_expect_success 'generate submodules/tree/vendor' ' - CGIT_CONFIG="$PWD/submodrc" QUERY_STRING="url=submodules/tree/vendor" cgit >tmp + cgit_rc submodrc "url=submodules/tree/vendor" >tmp ' test_expect_success 'a submodule row shows the entry name alone' ' @@ -134,14 +122,13 @@ test_expect_success 'a submodule row shows no size' ' ' test_expect_success 'a submodule row offers log but not blame or plain' ' - grep "ls-mod" tmp >row && grep ">log</a>" row && ! grep ">blame</a>" row && ! grep ">plain</a>" row ' test_expect_success 'a file row still offers blame' ' - CGIT_CONFIG="$PWD/submodrc" QUERY_STRING="url=submodules/tree/" cgit >tmp && + cgit_rc submodrc "url=submodules/tree/" >tmp && grep ">blame</a>" tmp ' @@ -159,13 +146,8 @@ test_expect_success 'only a link target inside the tree is linked' ' git update-index --add --cacheinfo 120000,$abs,abs && git commit -m links ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=links" && - echo "repo.path=$PWD/repos/links/.git" - } >linksrc && - CGIT_CONFIG="$PWD/linksrc" QUERY_STRING="url=links/tree/" cgit >tmp && + cgitrc_repo linksrc links && + cgit_rc linksrc "url=links/tree/" >tmp && grep " -> <a class=.ls-blob. href=./links/tree/target.txt.>target.txt</a>" tmp && grep " -> /etc/passwd</td>" tmp && ! grep "href=./links/tree/etc/passwd" tmp @@ -187,17 +169,34 @@ test_expect_success 'a long directory chain ends on a link' ' git add . && git commit -m deep ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=deep" && - echo "repo.path=$PWD/repos/deep/.git" - } >deeprc && - CGIT_CONFIG="$PWD/deeprc" QUERY_STRING="url=deep/tree/" cgit >tmp && + cgitrc_repo deeprc deep && + cgit_rc deeprc "url=deep/tree/" >tmp && grep ">d15</a></td>" tmp && ! grep " / </td>" tmp ' +# Git holds one object per id, as the type it was first looked up as, so a +# folder the commit also names as its parent cannot be read as a tree and the +# chain stops on it. The rest of the row still belongs to the first folder. +test_expect_success 'a broken chain keeps the row links on the first folder' ' + test_create_repo repos/cut && + ( + cd repos/cut && + mkdir -p a/b && + echo x >a/b/file && + git add a && + git commit -m base && + odd=$(printf "tree %s\\nparent %s\\nauthor A <a@x> 1700000000 +0000\\ncommitter A <a@x> 1700000000 +0000\\n\\nodd\\n" \ + $(git rev-parse HEAD^{tree}) $(git rev-parse HEAD:a/b) | + git hash-object -t commit -w --stdin --literally) && + git update-ref refs/heads/odd $odd + ) && + cgitrc_repo cutrc cut && + cgit_rc cutrc "url=cut/tree/&h=odd" >tmp && + grep ">b</a></td>" tmp && + grep "href=./cut/log/a?h=odd.>log</a>" tmp +' + test_expect_success 'a binary file in the tree shows as a hex dump' ' test_create_repo repos/bin && ( @@ -206,13 +205,8 @@ test_expect_success 'a binary file in the tree shows as a hex dump' ' git add blob.bin && git commit -m binary ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=bin" && - echo "repo.path=$PWD/repos/bin/.git" - } >binrc && - CGIT_CONFIG="$PWD/binrc" QUERY_STRING="url=bin/tree/blob.bin" cgit >tmp && + cgitrc_repo binrc bin && + cgit_rc binrc "url=bin/tree/blob.bin" >tmp && grep "<table class=.bin-blob.>" tmp && grep "<td class=.right.>0000</td><td class=.hex.> 00 01 02 62 69 6e" tmp ' diff --git a/tests/t0105-plain.sh b/tests/t0105-plain.sh index 4615dc5..6a96b61 100755 --- a/tests/t0105-plain.sh +++ b/tests/t0105-plain.sh @@ -65,9 +65,9 @@ test_expect_success 'only text and xml types carry a charset' ' echo "mimetype.svg=image/svg+xml" && cat cgitrc } >typesrc && - CGIT_CONFIG="$PWD/typesrc" QUERY_STRING="url=foo/plain/doc.docx" cgit >tmp && + cgit_rc typesrc "url=foo/plain/doc.docx" >tmp && sed "/^$/q" tmp | grep "^Content-Type: $docx$" && - CGIT_CONFIG="$PWD/typesrc" QUERY_STRING="url=foo/plain/pic.svg" cgit >tmp && + cgit_rc typesrc "url=foo/plain/pic.svg" >tmp && sed "/^$/q" tmp | grep "^Content-Type: image/svg+xml; charset=UTF-8$" ' @@ -83,14 +83,8 @@ test_expect_success 'plain answers once for duplicate tree entries' ' c=$(echo dup | git commit-tree $top) && git update-ref refs/heads/master $c ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "mimetype.png=image/png" && - echo "repo.url=dup" && - echo "repo.path=$PWD/repos/dup/.git" - } >duprc && - CGIT_CONFIG="$PWD/duprc" QUERY_STRING="url=dup/plain/x.png" cgit >tmp && + cgitrc_repo duprc dup mimetype.png=image/png && + cgit_rc duprc "url=dup/plain/x.png" >tmp && test $(grep -c "^Status:" tmp) = 1 && test $(grep -c "^Content-Type:" tmp) = 1 && grep "^Content-Type: image/png" tmp @@ -106,7 +100,7 @@ test_expect_success 'a control character in a file name stays out of the headers c=$(echo ctl | git commit-tree $top) && git update-ref refs/heads/ctl $c ) && - CGIT_CONFIG="$PWD/duprc" QUERY_STRING="url=dup/plain/ctl%01name.txt&h=ctl" cgit >tmp && + cgit_rc duprc "url=dup/plain/ctl%01name.txt&h=ctl" >tmp && grep "^Content-Disposition: inline; filename=.ctl?name.txt.$" tmp ' diff --git a/tests/t0106-commit.sh b/tests/t0106-commit.sh index 5564038..9a0a7e1 100755 --- a/tests/t0106-commit.sh +++ b/tests/t0106-commit.sh @@ -1,8 +1,7 @@ #!/bin/sh # The commit page. Most checks read the markup for the tree link, the parent -# link, the subject and the diffstat. The last few use a root commit, which -# has no parent and diffs against the empty tree. +# link, the subject and the diffstat. test_description='Check content on commit page' . ./setup.sh @@ -38,8 +37,9 @@ test_expect_success 'a commit that changes no files draws no diffstat table' ' ! grep "<table class=.diffstat.>" tmp ' +# A root commit has no parent, so it diffs against the empty tree. test_expect_success 'get root commit' ' - root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) && + root=$(git -C repos/foo rev-list --max-parents=0 HEAD) && cgit_url "foo/commit&id=$root" >tmp && grep "</html>" tmp ' @@ -66,7 +66,7 @@ test_expect_success 'a message that is not UTF-8 has its stray bytes replaced' ' cid=$(git hash-object --literally -t commit -w raw) && git update-ref refs/heads/latin "$cid" ) && - cgit_query "url=foo/commit&h=latin" >tmp && + cgit_url "foo/commit&h=latin" >tmp && grep "<div class=.commit-subject.>caf? subject<" tmp && grep "body ? end" tmp && ! grep "caf$(printf "\351")" tmp @@ -78,9 +78,9 @@ test_expect_success 'an octopus merge, which draws no diff, offers no diff optio cid=$(git commit-tree -p HEAD -p HEAD~1 -p HEAD~2 -m octopus HEAD^{tree}) && git update-ref refs/heads/octopus "$cid" ) && - cgit_query "url=foo/commit&h=octopus" >tmp && + cgit_url "foo/commit&h=octopus" >tmp && ! grep "cgit-panel" tmp && - cgit_query "url=foo/commit" >tmp && + cgit_url "foo/commit" >tmp && grep "cgit-panel" tmp ' diff --git a/tests/t0107-diff.sh b/tests/t0107-diff.sh index f058246..e173d00 100755 --- a/tests/t0107-diff.sh +++ b/tests/t0107-diff.sh @@ -1,8 +1,8 @@ #!/bin/sh -# The diff page, unified or side by side under dt=1. The first checks read +# The diff page, unified or side-by-side under dt=1. The first checks read # the markup for one added file in the fixture repository. The rest build -# repositories of their own, shaped for the side by side renderer's two +# repositories of their own, shaped for the side-by-side renderer's two # trickiest cases, a hunk covering a single line and a file where every # line changed. @@ -22,6 +22,16 @@ test_expect_success 'find added line' ' grep "<div class=.add.>+5</div>" tmp ' +# A revision that names no commit is quoted the way the request wrote it, as +# the commit and patch pages quote it, on either side of the diff. +test_expect_success 'a tree on either side is named as the request gave it' ' + tree=$(git -C repos/foo rev-parse --short HEAD^{tree}) && + cgit_url "foo/diff/&id=$tree" >tmp && + grep "<div class=.error.>Not a commit: $tree</div>" tmp && + cgit_url "foo/diff/&id2=$tree" >tmp && + grep "<div class=.error.>Not a commit: $tree</div>" 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 @@ -36,24 +46,19 @@ test_expect_success 'set up a repo with single-line hunks' ' 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 && + cgitrc_repo hunk1rc hunk1 && 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 && + cgit_rc hunk1rc "url=hunk1/diff/&dt=1" >tmp && grep "#n1.>1</a>" tmp && ! grep "#n0.>0</a>" tmp ' test_expect_success 'ssdiff still numbers a hunk that carries a length' ' - cgit_query "url=foo/diff&dt=1" >tmp && + cgit_url "foo/diff&dt=1" >tmp && grep "#n1.>1</a>" tmp ' @@ -75,16 +80,11 @@ test_expect_success 'set up a repo with shrinking changed lines' ' s=s sprintf(\"%c\",c)} print s}}" >lines.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 + cgitrc_repo lcsrc lcs ' 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 && + cgit_rc lcsrc "url=lcs/diff/&dt=1" >tmp && deletions=$(grep -o "<span class=.del.>" tmp | wc -l) && additions=$(grep -o "<span class=.add.>" tmp | wc -l) && test "$deletions" -gt 0 && @@ -93,22 +93,15 @@ test_expect_success 'every changed pair gets both a del and an add span' ' 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 <old.txt && + git -C repos/lcs show HEAD~1:lines.txt >lines && + git -C repos/lcs show HEAD:lines.txt >>lines && while read -r line do grep -qF "$line" plain.out || { - echo "missing new line: $line" + echo "missing line: $line" return 1 } - done <new.txt + done <lines ' test_done diff --git a/tests/t0108-rawdiff.sh b/tests/t0108-rawdiff.sh index f68628a..9b4eb37 100755 --- a/tests/t0108-rawdiff.sh +++ b/tests/t0108-rawdiff.sh @@ -8,51 +8,48 @@ test_description='Check content on rawdiff page' . ./setup.sh test_expect_success 'generate foo/rawdiff' ' - cgit_query "url=foo/rawdiff" >tmp + cgit_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 >tmp_ && - cmp tmp_ tmp2 + git -C repos/foo diff HEAD^.. >expect && + strip_headers <tmp >actual && + test_cmp expect actual ' test_expect_success 'find initial commit' ' - root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) + root=$(git -C repos/foo rev-list --max-parents=0 HEAD) ' test_expect_success 'generate diff for initial commit' ' - cgit_query "url=foo/rawdiff&id=$root" >tmp + cgit_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 >tmp_ && - cmp tmp_ tmp2 + git -C repos/foo diff-tree -p --no-commit-id --root "$root" >expect && + strip_headers <tmp >actual && + test_cmp expect actual ' 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 + id=$(git -C repos/foo rev-parse HEAD) && + id2=$(git -C repos/foo rev-parse HEAD~3) && + cgit_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 >tmp_ && - cmp tmp_ tmp2 + git -C repos/foo diff HEAD~3..HEAD >expect && + strip_headers <tmp >actual && + test_cmp expect actual ' test_expect_success 'a path limits the diff to that file' ' - cgit_query "url=foo/rawdiff/file-4&id=$id&id2=$id2" >tmp && - git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD -- file-4 >tmp2 && - strip_headers <tmp >tmp_ && - cmp tmp_ tmp2 && - grep "file-4" tmp_ && - ! grep "file-5" tmp_ + cgit_url "foo/rawdiff/file-4&id=$id&id2=$id2" >tmp && + git -C repos/foo diff HEAD~3..HEAD -- file-4 >expect && + strip_headers <tmp >actual && + test_cmp expect actual && + grep "file-4" actual && + ! grep "file-5" actual ' test_done diff --git a/tests/t0109-patch.sh b/tests/t0109-patch.sh index 6840f0c..bc942fe 100755 --- a/tests/t0109-patch.sh +++ b/tests/t0109-patch.sh @@ -2,8 +2,7 @@ # The patch page serves mail ready patches, compared line for line against # git format-patch for a single commit and for a range, once the CGI headers -# are stripped. The last check sets max-patch-count so a range wider than -# the limit comes back cut short. +# are stripped. test_description='Check content on patch page' . ./setup.sh @@ -19,38 +18,22 @@ check_cgit_signature() { } 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' ' - check_cgit_signature + cgit_url "foo/patch" >tmp ' test_expect_success 'compare with output of git-format-patch(1)' ' - git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" \ - --signature="cgit" --stdout HEAD^ >tmp2 && - strip_headers <tmp >tmp_ && - test_cmp tmp_ tmp2 + git -C repos/foo format-patch --subject-prefix="" \ + --signature="cgit" --stdout HEAD^ >expect && + strip_headers <tmp >actual && + test_cmp expect actual ' test_expect_success 'find initial commit' ' - root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) + root=$(git -C repos/foo rev-list --max-parents=0 HEAD) ' test_expect_success 'generate patch for initial commit' ' - cgit_query "url=foo/patch&id=$root" >tmp + cgit_url "foo/patch&id=$root" >tmp ' test_expect_success 'find `cgit` signature on the initial commit' ' @@ -58,20 +41,16 @@ test_expect_success 'find `cgit` signature on the initial commit' ' ' 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 on the range' ' - check_cgit_signature + id=$(git -C repos/foo rev-parse HEAD) && + id2=$(git -C repos/foo rev-parse HEAD~3) && + cgit_url "foo/patch&id=$id&id2=$id2" >tmp ' test_expect_success 'compare the range with git-format-patch(1)' ' - git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" \ - -N --signature="cgit" --stdout HEAD~3..HEAD >tmp2 && - strip_headers <tmp >tmp_ && - test_cmp tmp_ tmp2 + git -C repos/foo format-patch --subject-prefix="" \ + -N --signature="cgit" --stdout HEAD~3..HEAD >expect && + strip_headers <tmp >actual && + test_cmp expect actual ' test_expect_success 'max-patch-count bounds a patch range' ' @@ -81,18 +60,13 @@ test_expect_success 'max-patch-count bounds a patch range' ' 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 && + cgit_rc patchcountrc "url=foo/patch&id=$id&id2=$root" >tmp && test $(grep -c "^From " tmp) -eq 2 ' test_expect_success 'the signature names the version when asked to' ' - { - echo "enable-version-info=1" && - sed "s/^cache-size=.*/cache-size=0/" cgitrc - } >versionrc && - CGIT_CONFIG="$PWD/versionrc" QUERY_STRING="url=foo/patch" cgit >tmp && + cgitrc_copy versionrc enable-version-info=1 && + cgit_rc versionrc "url=foo/patch" >tmp && check_cgit_signature "$CGIT_VERSION" ' diff --git a/tests/t0110-snapshot.sh b/tests/t0110-snapshot.sh index bec0e3d..faebf5e 100755 --- a/tests/t0110-snapshot.sh +++ b/tests/t0110-snapshot.sh @@ -1,38 +1,43 @@ #!/bin/sh # The snapshot page hands a branch back as an archive. One function below -# registers the same checks for every tar based format, and each format +# registers the same checks for every compressed tar format, and each format # needs the same compressor cgit pipes through on the server, so one missing -# tool skips that format on both grounds at once. zip follows at the end, -# since unzip shares no flags with the rest. +# tool skips that format on both grounds at once. Plain tar and zip get checks +# of their own, since cgit runs no compressor for either and unzip shares no +# flags with the rest. test_description='Verify snapshot' . ./setup.sh -# Registers the checks for one tar based format. Takes the archive suffix, -# the content type the response must carry, the compressor to unpack with and -# the name of the prerequisite guarding the checks. +check_snapshot_headers() { + head -n 1 tmp | grep "Status: 200 OK" && + head -n 2 tmp | grep "^Content-Type: $2$" && + head -n 3 tmp | grep "Content-Disposition: inline; filename=.master.$1." +} + +check_snapshot_content() { + ls master/ >output && + test_line_count = 5 output && + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +} + +# Registers the checks for one compressed tar format. test_tar_snapshot() { ext=$1 type=$2 tool=$3 prereq=$4 - if command -v "$tool" >/dev/null 2>&1 - then - test_set_prereq $prereq - else - say "Skipping $ext validation tests: $tool not found" - fi + test_lazy_prereq $prereq "command -v $tool" test_expect_success $prereq "get foo/snapshot/master.$ext" " cgit_url 'foo/snapshot/master.$ext' >tmp " test_expect_success $prereq "check $ext response headers" " - head -n 1 tmp | grep 'Status: 200 OK' && - head -n 2 tmp | grep '^Content-Type: $type\$' && - head -n 3 tmp | grep 'Content-Disposition: inline; filename=.master.$ext.' + check_snapshot_headers $ext $type " test_expect_success $prereq "verify $ext format" " @@ -43,10 +48,7 @@ test_tar_snapshot() { test_expect_success $prereq "untar $ext and verify the content" " rm -rf master && '$tool' -dc 'master.$ext' | tar -xf - && - ls master/ >output && - test_line_count = 5 output && - grep '^5\$' master/file-5 && - test_line_count = 1 master/file-5 + check_snapshot_content " } @@ -57,19 +59,14 @@ test_expect_success 'get foo/snapshot/master.tar' ' ' test_expect_success 'check tar response headers' ' - head -n 1 tmp | grep "Status: 200 OK" && - head -n 2 tmp | grep "^Content-Type: application/x-tar$" && - head -n 3 tmp | grep "Content-Disposition: inline; filename=.master.tar." + check_snapshot_headers tar application/x-tar ' test_expect_success 'untar and verify the content' ' strip_headers <tmp >master.tar && rm -rf master && tar -xf master.tar && - ls master/ >output && - test_line_count = 5 output && - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 + check_snapshot_content ' test_tar_snapshot tar.gz application/gzip gzip GZIP @@ -78,21 +75,12 @@ test_tar_snapshot tar.lz application/x-lzip lzip LZIP test_tar_snapshot tar.xz application/x-xz xz XZ test_tar_snapshot tar.zst application/zstd zstd ZSTD -if command -v unzip >/dev/null 2>&1 -then - test_set_prereq UNZIP -else - say 'Skipping zip validation tests: unzip not found' -fi - test_expect_success 'get foo/snapshot/master.zip' ' cgit_url "foo/snapshot/master.zip" >tmp ' test_expect_success 'check zip response headers' ' - head -n 1 tmp | grep "Status: 200 OK" && - head -n 2 tmp | grep "^Content-Type: application/zip$" && - head -n 3 tmp | grep "Content-Disposition: inline; filename=.master.zip." + check_snapshot_headers zip application/zip ' test_expect_success 'strip off the header lines' ' @@ -100,16 +88,13 @@ test_expect_success 'strip off the header lines' ' ' test_expect_success UNZIP 'verify zip format' ' - unzip -t master.zip + "$GIT_UNZIP" -t master.zip ' test_expect_success UNZIP 'unzip and verify the content' ' rm -rf master && - unzip master.zip && - ls master/ >output && - test_line_count = 5 output && - grep "^5$" master/file-5 && - test_line_count = 1 master/file-5 + "$GIT_UNZIP" master.zip && + check_snapshot_content ' # A tag named v1.0 sheds its v in the snapshot name unless dropping it would @@ -120,17 +105,11 @@ test_expect_success UNZIP 'unzip and verify the content' ' test_expect_success 'set up a repo with a versioned tag' ' mkrepo repos/vtags 2 && git -C repos/vtags update-ref refs/tags/v1.0 HEAD && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "snapshots=tar.gz" && - echo "repo.url=vtags" && - echo "repo.path=$PWD/repos/vtags/.git" - } >vtagsrc + cgitrc_repo vtagsrc vtags snapshots=tar.gz ' test_expect_success 'a loose v tag still sheds its v in the snapshot name' ' - CGIT_CONFIG="$PWD/vtagsrc" QUERY_STRING="url=vtags/refs/" cgit >tmp && + cgit_rc vtagsrc "url=vtags/refs/" >tmp && grep "vtags-1.0.tar.gz" tmp && ! grep "vtags-v1.0.tar.gz" tmp ' @@ -140,7 +119,7 @@ test_expect_success 'a loose v tag still sheds its v in the snapshot name' ' test_expect_success 'a real case collision keeps the v on both tags' ' git -C repos/vtags pack-refs --all && git -C repos/vtags update-ref refs/tags/V1.0 HEAD~1 && - CGIT_CONFIG="$PWD/vtagsrc" QUERY_STRING="url=vtags/refs/" cgit >tmp && + cgit_rc vtagsrc "url=vtags/refs/" >tmp && grep "vtags-v1.0.tar.gz" tmp && grep "vtags-V1.0.tar.gz" tmp && ! grep "vtags-1.0.tar.gz" tmp @@ -151,7 +130,7 @@ test_expect_success 'a real case collision keeps the v on both tags' ' test_expect_success 'a snapshot name with nothing before its suffix is refused' ' git -C repos/vtags branch v HEAD && sed "s/^snapshots=tar.gz$/snapshots=tar/" vtagsrc >vtagstarrc && - CGIT_CONFIG="$PWD/vtagstarrc" QUERY_STRING="url=vtags/snapshot/.tar" cgit >tmp && + cgit_rc vtagstarrc "url=vtags/snapshot/.tar" >tmp && grep "^Status: 404" tmp ' @@ -160,17 +139,47 @@ test_expect_success 'an empty snapshot-prefix puts no slash ahead of the members cat vtagstarrc && echo "repo.snapshot-prefix=" } >noprefixrc && - CGIT_CONFIG="$PWD/noprefixrc" QUERY_STRING="url=vtags/snapshot/vtags.tar&id=master" cgit >tmp && + cgit_rc noprefixrc "url=vtags/snapshot/vtags.tar&id=master" >tmp && strip_headers <tmp >noprefix.tar && tar -tf noprefix.tar >members && grep "^file-1$" members && ! grep "^/" members ' +# A snapshot is named after the last component of the url once its .git +# suffix is gone, so a url of .git alone leaves the ref as the whole name. +test_expect_success 'set up repositories whose urls end in .git' ' + mkrepo repos/dotgit 1 && + git -C repos/dotgit tag v1.0 && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "snapshots=tar" && + echo "repo.url=.git" && + echo "repo.path=$PWD/repos/dotgit/.git" && + echo "repo.url=sub/.git" && + echo "repo.path=$PWD/repos/dotgit/.git" + } >dotgitrc +' + +test_expect_success 'a url of .git names a snapshot after the ref alone' ' + cgit_rc dotgitrc "url=.git/refs/" >tmp && + grep ">v1.0.tar<" tmp && + cgit_rc dotgitrc "url=.git/snapshot/v1.0.tar" >tmp && + strip_headers <tmp >dotgit.tar && + tar -tf dotgit.tar >members && + grep "^v1.0/file-1$" members +' + +test_expect_success 'a url ending in /.git names a snapshot after its directory' ' + cgit_rc dotgitrc "url=sub/.git/refs/" >tmp && + grep ">sub-1.0.tar<" tmp +' + # A server may run cgit with no PATH at all, and the compressor is then looked # for on the default path execvp searches. test_expect_success GZIP 'a compressed snapshot is made without a PATH' ' - sed "s/^cache-size=.*/cache-size=0/" cgitrc >nopathrc && + cgitrc_copy nopathrc && cgit_bin=$(command -v cgit) && ( unset PATH && @@ -183,9 +192,9 @@ test_expect_success GZIP 'a compressed snapshot is made without a PATH' ' # A link offers the snapshot of a tag, and the name on it has to lead back to # that tag even when a branch is spelled like the tag's stem. -test_expect_success 'a snapshot name leads to the tag ahead of a branch' ' +test_expect_success GZIP 'a snapshot name leads to the tag ahead of a branch' ' git -C repos/vtags update-ref refs/heads/1.0 $(git -C repos/vtags commit-tree -m shadow HEAD^{tree}) && - CGIT_CONFIG="$PWD/vtagsrc" QUERY_STRING="url=vtags/snapshot/vtags-1.0.tar.gz" cgit >tmp && + cgit_rc vtagsrc "url=vtags/snapshot/vtags-1.0.tar.gz" >tmp && strip_headers <tmp >tag.tar.gz && test "$(gzip -dc tag.tar.gz | git -C repos/vtags get-tar-commit-id)" = "$(git -C repos/vtags rev-parse refs/tags/v1.0^{commit})" ' @@ -198,7 +207,7 @@ test_expect_success 'a signature past max-blob-size is refused' ' echo "max-blob-size=1" && cat vtagsrc } >sigrc && - CGIT_CONFIG="$PWD/sigrc" QUERY_STRING="url=vtags/snapshot/vtags-1.0.tar.gz.asc" cgit >tmp && + cgit_rc sigrc "url=vtags/snapshot/vtags-1.0.tar.gz.asc" >tmp && grep "^Status: 413" tmp ' @@ -213,14 +222,8 @@ test_expect_success 'a snapshot and its signature are linked as one unit' ' sig=$(echo signature | git hash-object -w --stdin) && git notes --ref signatures/tar.gz add -C $sig one ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "snapshots=tar.gz" && - echo "repo.url=signed" && - echo "repo.path=$PWD/repos/signed/.git" - } >signedrc && - CGIT_CONFIG="$PWD/signedrc" QUERY_STRING="url=signed/refs/" cgit >tmp && + cgitrc_repo signedrc signed snapshots=tar.gz && + cgit_rc signedrc "url=signed/refs/" >tmp && grep "<span class=.snapshot.><a href=./signed/snapshot/signed-one.tar.gz.>signed-one.tar.gz</a> (<a href=./signed/snapshot/signed-one.tar.gz.asc.>sig</a>)</span>" tmp ' diff --git a/tests/t0111-atom.sh b/tests/t0111-atom.sh index 48448ef..608ab78 100755 --- a/tests/t0111-atom.sh +++ b/tests/t0111-atom.sh @@ -34,24 +34,13 @@ test_expect_success 'ten entries is the default ceiling' ' ' test_expect_success 'max-atom-items overrides the ceiling' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-atom-items=3" && - echo "repo.url=bar" && - echo "repo.path=$PWD/repos/bar/.git" - } >atomrc && - CGIT_CONFIG="$PWD/atomrc" QUERY_STRING="url=bar/atom" cgit >tmp && + cgitrc_repo atomrc bar max-atom-items=3 && + cgit_rc atomrc "url=bar/atom" >tmp && strip_headers <tmp >feed && test $(grep -c "<entry>" feed) -eq 3 ' -if command -v xmllint >/dev/null 2>&1 -then - test_set_prereq XMLLINT -else - say 'Skipping well-formedness check: xmllint not found' -fi +test_lazy_prereq XMLLINT 'command -v xmllint' test_expect_success XMLLINT 'the feed is well-formed xml' ' cgit_url "foo/atom" >tmp && @@ -68,4 +57,10 @@ test_expect_success 'a path with pathspec magic is refused' ' grep "^Status: 200" tmp ' +test_expect_success 'the feed link encodes the repository and the branch' ' + cgitrc_copy feedrc && + CGIT_CONFIG="$PWD/feedrc" HTTP_HOST=example.org QUERY_STRING="url=foo%2Bbar/log/&h=1%2B2" cgit >tmp && + grep "title=.Atom feed. href=.http://example.org/foo%2Bbar/atom/?h=1%2B2." tmp +' + test_done diff --git a/tests/t0112-stats.sh b/tests/t0112-stats.sh index 7b08a6b..722a61c 100755 --- a/tests/t0112-stats.sh +++ b/tests/t0112-stats.sh @@ -28,50 +28,32 @@ test_expect_success 'set up a stats config' ' git commit --allow-empty -m fresh && git commit-graph write --reachable ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-stats=year" && - echo "repo.url=stat" && - echo "repo.path=$PWD/repos/pulse/.git" - } >statsrc && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-stats=month" && - echo "repo.url=stat" && - echo "repo.path=$PWD/repos/pulse/.git" - } >statmaxrc + cgitrc_repo statsrc stat max-stats=year "repo.path=$PWD/repos/pulse/.git" && + cgitrc_repo statmaxrc stat max-stats=month "repo.path=$PWD/repos/pulse/.git" ' -statq() { CGIT_CONFIG="$PWD/statsrc" QUERY_STRING="$1" cgit; } - test_expect_success 'stats tab appears when enabled' ' - statq "url=stat/" >tmp && + cgit_rc statsrc "url=stat/" >tmp && grep ">stats</a>" tmp ' test_expect_success 'stats page renders the authors table' ' - statq "url=stat/stats/" >tmp && + cgit_rc statsrc "url=stat/stats/" >tmp && grep "<h2>Commits per author" tmp ' -test_expect_success 'no language breakdown is rendered' ' - ! grep -i "language" tmp -' - test_expect_success 'stat options sit above the content' ' tr -d "\n" <tmp | sed "s/<h2>Commits per author.*//" | grep "cgit-panel" ' test_expect_success 'stats below a path stay a normal page' ' - statq "url=stat/stats/file-1" >tmp && + cgit_rc statsrc "url=stat/stats/file-1" >tmp && grep "Status: 200" tmp && grep "<h2>Commits per author" tmp ' test_expect_success 'max-stats=year offers all four periods' ' - statq "url=stat/stats/" >tmp && + cgit_rc statsrc "url=stat/stats/" >tmp && grep "<option value=.w." tmp && grep "<option value=.m." tmp && grep "<option value=.q." tmp && @@ -79,21 +61,52 @@ test_expect_success 'max-stats=year offers all four periods' ' ' test_expect_success 'max-stats bounds the selectable periods' ' - CGIT_CONFIG="$PWD/statmaxrc" QUERY_STRING="url=stat/stats/&period=y" cgit >tmp && + cgit_rc statmaxrc "url=stat/stats/&period=y" >tmp && grep "Statistics type disabled" tmp && - CGIT_CONFIG="$PWD/statmaxrc" QUERY_STRING="url=stat/stats/" cgit >tmp && + cgit_rc statmaxrc "url=stat/stats/" >tmp && ! grep "<option value=.q." tmp ' # Each period buckets commits and labels its columns its own way, so every one # gets a page of its own here. test_expect_success 'every period renders its own columns' ' - statq "url=stat/stats/&period=m" >tmp && + cgit_rc statsrc "url=stat/stats/&period=m" >tmp && grep -E "<th[^>]*>[A-Z][a-z][a-z] [0-9]{4}</th>" tmp && - statq "url=stat/stats/&period=q" >tmp && + cgit_rc statsrc "url=stat/stats/&period=q" >tmp && grep -E "<th[^>]*>Q[1-4] [0-9]{4}</th>" tmp && - statq "url=stat/stats/&period=y" >tmp && + cgit_rc statsrc "url=stat/stats/&period=y" >tmp && grep -E "<th[^>]*>[0-9]{4}</th>" tmp ' +# The yearly window opens on the first of January three years back. cgit reads +# the real clock, so the two commits sit either side of midnight on that day. +test_expect_success 'the window opens at midnight on its first day' ' + first=$(($(date -u +%Y) - 3)) && + test_create_repo repos/edge && + ( + cd repos/edge && + GIT_AUTHOR_NAME="Too Early" \ + GIT_AUTHOR_DATE="$((first - 1))-12-31 23:59:59 +0000" \ + GIT_COMMITTER_DATE="$((first - 1))-12-31 23:59:59 +0000" \ + git commit --allow-empty -m before && + GIT_AUTHOR_NAME="Early Bird" \ + GIT_AUTHOR_DATE="$first-01-01 00:00:00 +0000" \ + GIT_COMMITTER_DATE="$first-01-01 00:00:00 +0000" \ + git commit --allow-empty -m first + ) && + cgitrc_repo edgerc edge max-stats=year && + cgit_rc edgerc "url=edge/stats/&period=y" >tmp && + grep ">Early Bird<" tmp && + ! grep ">Too Early<" tmp +' + +# A path starting with a colon would reach git as pathspec magic, which the +# log, the feed and the patch page refuse as well. +test_expect_success 'a path with pathspec magic is refused' ' + cgit_rc statsrc "url=stat/stats/%3A%28icase%29FILE-1" >tmp && + grep "^Status: 400" tmp && + cgit_rc statsrc "url=stat/stats/file-1" >tmp && + grep "^Status: 200" tmp +' + test_done diff --git a/tests/t0113-tag.sh b/tests/t0113-tag.sh index 2043385..8aea27e 100755 --- a/tests/t0113-tag.sh +++ b/tests/t0113-tag.sh @@ -54,9 +54,7 @@ test_expect_success 'a tag of anything but a commit offers no download' ' ! grep ">download<" tmp && cgit_url "foo/tag/&h=blob-tag" >tmp && grep "tagged object" tmp && - ! grep ">download<" tmp && - cgit_url "foo/tag/&h=v1.0" >tmp && - grep ">download<" tmp + ! grep ">download<" tmp ' # git reads the signature off the end of a tag's message, so a block shaped @@ -82,8 +80,7 @@ test_expect_success 'a signed tag shows its signature in the message' ' ) && cgit_url "foo/tag/&h=signed" >tmp && tr -d "\n" <tmp >flat && - grep "<div class=.commit-msg.>Notes.-----BEGIN PGP SIGNATURE-----iQEzBAABCAAdFiEE-----END PGP SIGNATURE-----</div>" flat && - ! grep "<details" tmp + grep "<div class=.commit-msg.>Notes.-----BEGIN PGP SIGNATURE-----iQEzBAABCAAdFiEE-----END PGP SIGNATURE-----</div>" flat ' test_expect_success 'a tag page names the tag in its title' ' diff --git a/tests/t0201-filters.sh b/tests/t0201-filters.sh index 6d33803..8d8743a 100755 --- a/tests/t0201-filters.sh +++ b/tests/t0201-filters.sh @@ -1,8 +1,8 @@ #!/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 +# page content to an outside program before it is written out. The checks in +# the loop run 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. @@ -21,8 +21,14 @@ do cgit_url 'filter-$prefix/tree/a%2bb' >tmp " + # Page output is buffered and has to be flushed as stdout is handed + # to a filter and back. A missed flush reorders the page rather than + # losing any of it, so the test below watches which side of the + # filtered text the surrounding markup lands on. test_expect_success "check whether the $prefix source filter works" ' - grep "<code>a+b HELLO$" tmp + grep "<code>a+b HELLO$" tmp && + tr -d "\n" <tmp >flat.out && + grep "<code>a+b HELLO</code>" flat.out ' test_expect_success "generate filter-$prefix/about/" " @@ -48,27 +54,11 @@ do test_expect_success "check whether the $prefix email filter works for committers" ' grep "<committer@example.com> commit C O MITTER <COMMITTER@EXAMPLE.COM>" tmp ' - - # Page output is buffered and has to be flushed as stdout is handed - # to a filter and back. A missed flush reorders the page rather than - # losing any of it, so the two tests below watch which side of the - # filtered text the surrounding markup lands on. - test_expect_success "the $prefix source filter output stays inside its cell" " - cgit_url 'filter-$prefix/tree/a%2bb' >tmp && - tr -d '\n' <tmp >flat.out && - grep '<code>a+b HELLO</code>' flat.out - " - - test_expect_success "the $prefix about filter output stays inside its div" " - cgit_url 'filter-$prefix/about/' >tmp && - grep \"<div id='about'>a+b /filter-$prefix/tree/ HELLO\" tmp - " done test_expect_success 'the blame page goes through the source filter too' ' - echo "enable-blame=1" >blamerc && - sed "s/^cache-size=.*/cache-size=0/" cgitrc >>blamerc && - CGIT_CONFIG="$PWD/blamerc" QUERY_STRING="url=filter-exec/blame/a%2bb" cgit >tmp && + cgitrc_copy blamerc enable-blame=1 && + cgit_rc blamerc "url=filter-exec/blame/a%2bb" >tmp && grep "<td class=.linenumbers.>" tmp && grep "<code>a+b HELLO$" tmp ' @@ -76,22 +66,14 @@ test_expect_success 'the blame page goes through the source filter too' ' # A readme read at another branch links into the tree at that branch, which # the filter is told as the query to follow a path with. test_expect_success 'the about filter gets the tree url at the readme branch' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "readme=:a+b" && - echo "about-filter=exec:$FILTER_DIRECTORY/dump.sh" && - echo "root-readme=$PWD/root.txt" && - echo "repo.url=filtered" && - echo "repo.path=$PWD/repos/filter/.git" - } >aboutargsrc && - CGIT_CONFIG="$PWD/aboutargsrc" QUERY_STRING="url=filtered/about/&h=1%2b2" cgit >tmp && + cgitrc_repo aboutargsrc filtered readme=:a+b "about-filter=exec:$FILTER_DIRECTORY/dump.sh" "root-readme=$PWD/root.txt" "repo.path=$PWD/repos/filter/.git" && + cgit_rc aboutargsrc "url=filtered/about/&h=1%2b2" >tmp && grep "<div id=.about.>a+b /filtered/tree/ ?h=1%2B2 HELLO" tmp ' test_expect_success 'the site readme leaves the tree url empty' ' echo site >root.txt && - CGIT_CONFIG="$PWD/aboutargsrc" QUERY_STRING="p=about" cgit >tmp && + cgit_rc aboutargsrc "p=about" >tmp && grep "root.txt SITE" tmp ' @@ -101,15 +83,12 @@ test_expect_success 'only a repository page hands filters its variables' ' echo "repo url: $CGIT_REPO_URL." EOF sed "s|^about-filter=.*|about-filter=exec:$PWD/env.sh|" aboutargsrc >envrc && - CGIT_CONFIG="$PWD/envrc" QUERY_STRING="url=filtered/about/" cgit >tmp && + cgit_rc envrc "url=filtered/about/" >tmp && grep "repo url: filtered\." tmp && - CGIT_CONFIG="$PWD/envrc" QUERY_STRING="p=about" cgit >tmp && + cgit_rc envrc "p=about" >tmp && grep "repo url: \." tmp ' -test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA -test "$CGIT_HAS_LUA" -eq 1 || say 'cgit built without lua, error page check skipped' - # A die inside a lua filter has to reach the visitor as an error page rather # than being fed back into the filter that just failed, and the reason goes to # the log. @@ -125,7 +104,7 @@ test_expect_success CGIT_LUA 'a failing lua filter still renders an error page' echo "repo.path=$PWD/repos/foo/.git" && echo "auth-filter=lua:$PWD/broken.lua" } >brokenrc && - CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=foo/commit" cgit >tmp 2>err && + cgit_rc brokenrc "url=foo/commit" >tmp 2>err && grep "Status: 500" tmp && grep "Lua error in" err ' @@ -138,7 +117,7 @@ test_expect_success CGIT_LUA 'a lua filter raising a table is named by type' ' function filter_close() return 0 end EOF sed "s|broken.lua|tabled.lua|" brokenrc >tabledrc && - CGIT_CONFIG="$PWD/tabledrc" QUERY_STRING="url=foo/commit" cgit >tmp 2>err && + cgit_rc tabledrc "url=foo/commit" >tmp 2>err && grep "Status: 500" tmp && grep "Lua error in .*tabled.lua: (error object is a table value)" err ' @@ -158,7 +137,7 @@ test_expect_success CGIT_LUA 'a lua filter that writes while loading still rende echo "repo.path=$PWD/repos/foo/.git" && echo "repo.commit-filter=lua:$PWD/loud.lua" } >loudrc && - CGIT_CONFIG="$PWD/loudrc" QUERY_STRING="url=foo/commit" cgit >tmp && + cgit_rc loudrc "url=foo/commit" >tmp && grep "written while loading" tmp && grep "</html>" tmp ' @@ -184,13 +163,8 @@ test_expect_success CGIT_LUA 'a lua filter can encode an argument and include a end function filter_close() return 0 end EOF - { - echo "cache-size=0" && - echo "repo.url=foo" && - echo "repo.path=$PWD/repos/foo/.git" && - echo "repo.commit-filter=lua:$PWD/apis.lua" - } >apisrc && - CGIT_CONFIG="$PWD/apisrc" QUERY_STRING="url=foo/commit" cgit >tmp && + sed "s|loud.lua|apis.lua|" loudrc >apisrc && + cgit_rc apisrc "url=foo/commit" >tmp && grep "a+b%26c|included <b>bold</b>" tmp ' diff --git a/tests/t0202-submodule-links.sh b/tests/t0202-submodule-links.sh index 6db164f..a5810a0 100755 --- a/tests/t0202-submodule-links.sh +++ b/tests/t0202-submodule-links.sh @@ -11,9 +11,9 @@ test_description='Check submodule links derived from .gitmodules' test_expect_success 'set up a parent repo with submodules' ' mkrepo repos/subtarget 1 && mkrepo repos/subparent 1 && + sub=$(git -C repos/subparent rev-parse HEAD) && ( cd repos/subparent && - sub=$(git rev-parse HEAD) && git update-index --add --cacheinfo 160000,$sub,local-ssh && git update-index --add --cacheinfo 160000,$sub,sibling && git update-index --add --cacheinfo 160000,$sub,forge-https && @@ -55,21 +55,18 @@ test_expect_success 'set up a parent repo with submodules' ' ' test_expect_success 'generate subparent/tree' ' - CGIT_CONFIG="$PWD/subrc" QUERY_STRING="url=subparent/tree/" cgit >tmp + cgit_rc subrc "url=subparent/tree/" >tmp ' test_expect_success 'an ssh url served here links to the local repo' ' - sub=$(git -C repos/subparent rev-parse HEAD~1) && grep "href=./subtarget/tree/?id=$sub.>local-ssh</a>" tmp ' test_expect_success 'its hash links to the local commit page' ' - sub=$(git -C repos/subparent rev-parse HEAD~1) && grep "href=./subtarget/commit/?id=$sub." tmp ' test_expect_success 'a relative url resolves against this repository' ' - sub=$(git -C repos/subparent rev-parse HEAD~1) && grep "href=./subtarget/tree/?id=$sub.>sibling</a>" tmp ' @@ -78,18 +75,15 @@ test_expect_success 'a web url is linked as it is' ' ' test_expect_success 'a known forge hash links to its commit page' ' - sub=$(git -C repos/subparent rev-parse HEAD~1) && grep "href=.https://github.com/example/project/commit/$sub." tmp ' test_expect_success 'an ssh url to a known forge is rewritten' ' - sub=$(git -C repos/subparent rev-parse HEAD~1) && grep "href=.https://github.com/example/other.>forge-ssh</a>" tmp && grep "href=.https://github.com/example/other/commit/$sub." tmp ' test_expect_success 'a cgit host keeps its .git suffix in the commit link' ' - sub=$(git -C repos/subparent rev-parse HEAD~1) && grep "href=.https://git.kernel.org/pub/scm/git/git.git/commit/?id=$sub." tmp ' @@ -99,7 +93,7 @@ test_expect_success 'an unresolvable url stays unlinked with a tooltip' ' test_expect_success 'the links stay off without the flag' ' sed "/enable-gitmodules-links/d" subrc >subrcoff && - CGIT_CONFIG="$PWD/subrcoff" QUERY_STRING="url=subparent/tree/" cgit >tmp && + cgit_rc subrcoff "url=subparent/tree/" >tmp && ! grep "subtarget/tree" tmp && grep "class=.ls-mod.>local-ssh</span>" tmp ' diff --git a/tests/t0203-dates.sh b/tests/t0203-dates.sh index 3c8ecab..5c3567f 100755 --- a/tests/t0203-dates.sh +++ b/tests/t0203-dates.sh @@ -24,8 +24,6 @@ test_expect_success 'set up a dated repo' ' ) ' -# Writes the config afresh, one extra line per argument, which is what lets a -# test below name a setting or two and say nothing about the rest. daterc() { { echo "virtual-root=/" @@ -36,66 +34,64 @@ daterc() { } >daterc } -dateq() { CGIT_CONFIG="$PWD/daterc" QUERY_STRING="$1" cgit; } - test_expect_success 'relative dates are on by default' ' daterc && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep "class=.age-" tmp ' test_expect_success 'enable-relative-dates=0 shows a calendar date' ' daterc "enable-relative-dates=0" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && ! grep "class=.age-" tmp && grep "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]<" tmp ' test_expect_success 'the index and log columns follow the same flag' ' daterc "enable-relative-dates=0" && - dateq "" >tmp && + cgit_rc daterc "" >tmp && ! grep "class=.age-" tmp && - dateq "url=dated/log/" >tmp && + cgit_rc daterc "url=dated/log/" >tmp && ! grep "class=.age-" tmp ' test_expect_success 'date-format selects the calendar format' ' daterc "enable-relative-dates=0" "date-format=iso8601" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep ">[0-9-]* [0-9][0-9]:[0-9][0-9]:[0-9][0-9] +0000<" tmp ' test_expect_success 'date-format accepts a strftime format' ' daterc "enable-relative-dates=0" "date-format=format:%Y/%m/%d" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep ">[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]<" tmp ' test_expect_success 'an unknown date-format falls back to short' ' daterc "enable-relative-dates=0" "date-format=nonsense" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]<" tmp && daterc "enable-relative-dates=0" "date-format=format:" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]<" tmp ' test_expect_success 'a date-format git would refuse falls back to short' ' daterc "enable-relative-dates=0" "date-format=local-local" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep "^Status: 200" tmp && grep "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]<" tmp ' test_expect_success 'date-format=local is the default format in local time' ' daterc "enable-relative-dates=0" "date-format=local" && - dateq "url=dated/refs/" >tmp && + cgit_rc daterc "url=dated/refs/" >tmp && grep ">[A-Z][a-z][a-z] [A-Z][a-z][a-z] [0-9]* [0-9:]* [0-9]*<" tmp ' test_expect_success 'date-format still formats the log fallback' ' daterc "date-format=rfc2822" && - dateq "url=dated/log/" >tmp && + cgit_rc daterc "url=dated/log/" >tmp && grep "class=.age-" tmp && grep ">[A-Z][a-z][a-z], [0-9]* Apr 2005 " tmp ' @@ -103,7 +99,7 @@ test_expect_success 'date-format still formats the log fallback' ' test_expect_success 'date-format leaves the commit page on ISO 8601' ' daterc "date-format=rfc2822" && sha=$(git -C repos/dated rev-parse HEAD~1) && - dateq "url=dated/commit/&id=$sha" >tmp && + cgit_rc daterc "url=dated/commit/&id=$sha" >tmp && grep "2005-04-07 [0-9:]* +0000" tmp && ! grep "Apr 2005" tmp ' diff --git a/tests/t0204-limits.sh b/tests/t0204-limits.sh index 2d33368..ecc33bf 100755 --- a/tests/t0204-limits.sh +++ b/tests/t0204-limits.sh @@ -4,17 +4,10 @@ # on the lines and files a diff renders inline, on the size of a blob any # view will inflate, and the clamp on index query length. Crossing one has # to trim the page or point somewhere better suited, never drop the request. -# The last case is the same idea applied to a filter, which degrades to -# escaped text when its highlighting library is absent. test_description='Check the ref listing and diff size limits' . ./setup.sh -if test "$CGIT_HAS_LUA" -eq 1 -then - test_set_prereq LUA -fi - # The limits only show themselves against content that crosses them, so the # fixture carries more branches and tags than max-ref-count allows and a # commit whose oversized file diff sits beside a small one, which is what @@ -36,33 +29,16 @@ test_expect_success 'set up limit fixtures' ' git tag tag-$i || exit 1 done ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-ref-count=2" && - echo "max-diff-lines=20" && - echo "max-diff-files=0" && - echo "repo.url=limits" && - echo "repo.path=$PWD/repos/limits/.git" - } >limitrc && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-diff-files=1" && - echo "repo.url=limits" && - echo "repo.path=$PWD/repos/limits/.git" - } >limitfilesrc + cgitrc_repo limitrc limits max-ref-count=2 max-diff-lines=20 max-diff-files=0 && + cgitrc_repo limitfilesrc limits max-diff-files=1 ' -limitq() { CGIT_CONFIG="$PWD/limitrc" QUERY_STRING="$1" cgit; } - # An index query is compared against every repository the listing holds, so # an enormous one multiplies out across the whole index. Clamping it rather # than refusing it keeps the cost bounded without making an ordinary search # behave any differently. test_expect_success 'an enormous query is clamped, not rejected' ' long=$(awk "BEGIN{s=\"\";for(i=0;i<4000;i++)s=s \"a\"; print s}") && - test ${#long} -eq 4000 && cgit_query "q=$long" >tmp && grep "</html>" tmp && longest=$(grep -o "aaaa*" tmp | awk "{print length}" | sort -n | tail -1) && @@ -71,7 +47,7 @@ test_expect_success 'an enormous query is clamped, not rejected' ' test_expect_success 'an ordinary query still filters the index' ' cgit_query "q=foo" >tmp && - grep "foo" tmp && + grep ">foo</a>" tmp && ! grep ">bar<" tmp ' @@ -79,7 +55,7 @@ test_expect_success 'an ordinary query still filters the index' ' # hands the overflow to page on their own, so a limit that leaked between the # two sections would show up as the wrong link or the wrong count here. test_expect_success 'refs page lists max-ref-count branches and tags' ' - limitq "url=limits/refs/" >tmp && + cgit_rc limitrc "url=limits/refs/" >tmp && test $(grep -c "log/?h=" tmp) -eq 2 && test $(grep -c "/tag/?h=tag-" tmp) -eq 2 ' @@ -90,16 +66,16 @@ test_expect_success 'refs page links each overflow to its own category' ' ' test_expect_success 'branch page paginates independently' ' - limitq "url=limits/refs/heads/" >tmp && + cgit_rc limitrc "url=limits/refs/heads/" >tmp && test $(grep -c "log/?h=" tmp) -eq 2 && grep "\[next\]" tmp && - limitq "url=limits/refs/heads/&ofs=2" >tmp && + cgit_rc limitrc "url=limits/refs/heads/&ofs=2" >tmp && grep "\[prev\]" tmp && ! grep "/tag/?h=tag-" tmp ' test_expect_success 'tag page paginates independently' ' - limitq "url=limits/refs/tags/&ofs=2" >tmp && + cgit_rc limitrc "url=limits/refs/tags/&ofs=2" >tmp && grep "/tag/?h=tag-" tmp && grep "\[prev\]" tmp && ! grep "log/?h=" tmp @@ -115,8 +91,8 @@ test_expect_success 'a negative ref count lists every ref' ' echo "summary-tags=$n" && sed "s/^max-ref-count=2$/max-ref-count=$n/" limitrc } >count$n.rc && - CGIT_CONFIG="$PWD/count$n.rc" QUERY_STRING="url=limits/refs/" cgit >refs$n && - CGIT_CONFIG="$PWD/count$n.rc" QUERY_STRING="url=limits/" cgit >summary$n || + cgit_rc count$n.rc "url=limits/refs/" >refs$n && + cgit_rc count$n.rc "url=limits/" >summary$n || return 1 done && grep "log/?h=branch-3" refs0 && @@ -130,7 +106,7 @@ test_expect_success 'a negative ref count lists every ref' ' # The rest of the commit still renders, so one huge file cannot hide the # small change beside it. test_expect_success 'oversized file diff is replaced by a link' ' - limitq "url=limits/commit/" >tmp && + cgit_rc limitrc "url=limits/commit/" >tmp && grep "too large to be rendered inline" tmp && grep "View it on its own page" tmp ' @@ -140,7 +116,7 @@ test_expect_success 'small file in the same commit still renders inline' ' ' test_expect_success 'the single-file diff page always renders in full' ' - limitq "url=limits/diff/big.c" >tmp && + cgit_rc limitrc "url=limits/diff/big.c" >tmp && grep "class=.hunk." tmp && ! grep "too large to be rendered inline" tmp ' @@ -149,13 +125,13 @@ test_expect_success 'the single-file diff page always renders in full' ' # so it drops the whole body back to the diffstat while a request naming a # single file stays unaffected. test_expect_success 'a commit changing too many files shows stat only' ' - CGIT_CONFIG="$PWD/limitfilesrc" QUERY_STRING="url=limits/commit/" cgit >tmp && + cgit_rc limitfilesrc "url=limits/commit/" >tmp && grep "too large to be rendered inline" tmp && ! grep "class=.hunk." tmp ' test_expect_success 'the single-file page is not limited by max-diff-files' ' - CGIT_CONFIG="$PWD/limitfilesrc" QUERY_STRING="url=limits/diff/small.c" cgit >tmp && + cgit_rc limitfilesrc "url=limits/diff/small.c" >tmp && grep "class=.hunk." tmp ' @@ -172,15 +148,9 @@ test_expect_success 'a diff of an oversized blob is not inlined' ' awk "BEGIN{for(i=0;i<401;i++)print \"aaaaaaaa\"}" >big.txt && git commit -am change ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-blob-size=1" && - echo "repo.url=blobdiff" && - echo "repo.path=$PWD/repos/blobdiff/.git" - } >blobdiffrc && + cgitrc_repo blobdiffrc blobdiff max-blob-size=1 && sha=$(git -C repos/blobdiff rev-parse HEAD) && - CGIT_CONFIG="$PWD/blobdiffrc" QUERY_STRING="url=blobdiff/commit/&id=$sha" cgit >tmp && + cgit_rc blobdiffrc "url=blobdiff/commit/&id=$sha" >tmp && grep "Binary files differ" tmp && ! grep "aaaaaaaa" tmp ' @@ -192,28 +162,12 @@ test_expect_success 'every page showing a blob refuses an oversized one alike' ' } >blobpagesrc && for page in tree plain blob blame do - CGIT_CONFIG="$PWD/blobpagesrc" QUERY_STRING="url=blobdiff/$page/big.txt" cgit >tmp && + cgit_rc blobpagesrc "url=blobdiff/$page/big.txt" >tmp && grep "^Status: 413 Content Too Large" tmp && grep "Blob size (3 KB) exceeds the limit (1 KB)" tmp || return 1 done ' -# A missing scintillua leaves the shipped filter with nothing to highlight -# with, and a filter that failed there would take the whole page down with -# it, so it has to hand the source back escaped instead. -test_expect_success LUA 'highlight filter passes text through without scintillua' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "source-filter=lua:$(cd "$TEST_OUTPUT_DIRECTORY/../custom/extensions" && pwd)/syntax-highlight.lua" && - echo "repo.url=limits" && - echo "repo.path=$PWD/repos/limits/.git" - } >hlrc && - CGIT_SCINTILLUA_PATH=/nonexistent CGIT_CONFIG="$PWD/hlrc" \ - QUERY_STRING="url=limits/tree/small.c" cgit >tmp && - grep "int y;" tmp -' - # The patch and rawdiff pages hand the work to git, whose own limit on an # inlined blob is far past max-blob-size, so the cap is passed through to it. test_expect_success 'patch and rawdiff show an oversized blob as binary' ' @@ -226,7 +180,7 @@ test_expect_success 'patch and rawdiff show an oversized blob as binary' ' } >blobrc && for p in patch rawdiff do - CGIT_CONFIG="$PWD/blobrc" QUERY_STRING="url=limits/$p/" cgit >tmp && + cgit_rc blobrc "url=limits/$p/" >tmp && grep "^Binary files" tmp && ! grep "^+1999$" tmp || return 1 done diff --git a/tests/t0205-config.sh b/tests/t0205-config.sh index 4536e49..32c260e 100755 --- a/tests/t0205-config.sh +++ b/tests/t0205-config.sh @@ -1,43 +1,35 @@ #!/bin/sh -# Checks what the configuration parser reports on stderr. A key it does not -# know, a repo key before any repository and a filter set by a scanned -# repository without trust-scan-config are each ignored with a warning naming -# the key, a repo filter in the main cgitrc passes without one, a repository -# whose path is missing answers 404 on every page, and a config cgit fully -# understands stays silent. +# Checks what the configuration parser reports on stderr. test_description='Check configuration diagnostics' . ./setup.sh test_expect_success 'set up configs' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "no-such-key=1" && - echo "repo.url=diag" && - echo "repo.path=$PWD/repos/foo/.git" && - echo "repo.no-such-repo-key=1" - } >diagrc && + write_script drain-filter <<-\EOF && + cat >/dev/null + EOF + cgitrc_repo diagrc diag no-such-key=1 "repo.path=$PWD/repos/foo/.git" repo.no-such-repo-key=1 && { echo "virtual-root=/" && echo "cache-size=0" && echo "repo.desc=early" && echo "repo.url=diag" && echo "repo.path=$PWD/repos/foo/.git" && - echo "repo.about-filter=exec:/bin/true" + echo "repo.readme=:file-1" && + echo "repo.about-filter=exec:$PWD/drain-filter" } >earlyrc ' test_expect_success 'unknown keys are reported and the page still renders' ' - CGIT_CONFIG="$PWD/diagrc" QUERY_STRING="url=diag/" cgit >tmp 2>err && + cgit_rc diagrc "url=diag/" >tmp 2>err && grep "Status: 200" tmp && grep "Unknown config key: no-such-key" err && grep "Unknown repo config key: no-such-repo-key" err ' test_expect_success 'a repo key before any repo.url is reported' ' - CGIT_CONFIG="$PWD/earlyrc" QUERY_STRING="url=diag/" cgit >tmp 2>err && + cgit_rc earlyrc "url=diag/" >tmp 2>err && grep "Status: 200" tmp && grep "Ignoring repo.desc before any repo.url" err ' @@ -50,7 +42,7 @@ test_expect_success 'a filter in a scanned repository is reported without its fl echo "cache-size=0" && echo "scan-path=$PWD/scan" } >scanrc && - CGIT_CONFIG="$PWD/scanrc" QUERY_STRING="url=foo.git/commit/" cgit >tmp 2>err && + cgit_rc scanrc "url=foo.git/commit/" >tmp 2>err && grep "<div class=.commit-subject.>commit 5" tmp && grep "Ignoring commit-filter in $PWD/scan/foo.git/: trust-scan-config is not set" err ' @@ -60,14 +52,17 @@ test_expect_success 'the flag lets a scanned repository set its filters' ' echo "trust-scan-config=1" && cat scanrc } >scanrc-on && - CGIT_CONFIG="$PWD/scanrc-on" QUERY_STRING="url=foo.git/commit/" cgit >tmp 2>err && + cgit_rc scanrc-on "url=foo.git/commit/" >tmp 2>err && grep "<div class=.commit-subject.>COMMIT 5" tmp && ! grep "Ignoring" err ' +# The filter prints nothing, so an empty about div shows that it ran in place +# of the readme. It reads its input first, so its exit cannot race cgit's +# write. test_expect_success 'a repo filter in the main cgitrc needs no flag' ' - ! grep "about-filter" err && - CGIT_CONFIG="$PWD/earlyrc" QUERY_STRING="url=diag/" cgit >tmp 2>err && + cgit_rc earlyrc "url=diag/about/" >tmp 2>err && + grep "<div id=.about.></div>" tmp && ! grep "about-filter" err ' @@ -82,7 +77,7 @@ test_expect_success 'a repository whose path is missing answers 404' ' mkdir -p gone-cache && for page in "" log/ commit/ "tag/&h=x" atom/ plain/f do - CGIT_CONFIG="$PWD/gonerc" QUERY_STRING="url=gone/$page" cgit >tmp 2>err && + cgit_rc gonerc "url=gone/$page" >tmp 2>err && grep "^Status: 404 Not Found" tmp && grep "Failed to open gone: No such file or directory" tmp || return 1 done && @@ -106,7 +101,7 @@ test_expect_success 'a NUL inside a comment does not end it' ' echo "repo.path=$PWD/repos/foo/.git" } >nulrc && grep -c Smuggled nulrc && - CGIT_CONFIG="$PWD/nulrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc nulrc "url=" >tmp && grep "</html>" tmp && ! grep Smuggled tmp ' diff --git a/tests/t0206-trailers.sh b/tests/t0206-trailers.sh index 61fba94..15c3600 100755 --- a/tests/t0206-trailers.sh +++ b/tests/t0206-trailers.sh @@ -87,7 +87,7 @@ test_expect_success 'a message with no block gets no table' ' test_expect_success 'with the flag off the message stays whole' ' sed "s/^enable-commit-trailers=1/enable-commit-trailers=0/" cgitrc >cgitrc-off && - CGIT_CONFIG="$PWD/cgitrc-off" QUERY_STRING="url=trailers/commit/" cgit >tmp && + cgit_rc cgitrc-off "url=trailers/commit/" >tmp && ! grep "commit-trailers" tmp && grep "Signed-off-by: A U Thor" tmp ' diff --git a/tests/t0208-mailmap.sh b/tests/t0208-mailmap.sh index 86f6d50..9e9ae0f 100755 --- a/tests/t0208-mailmap.sh +++ b/tests/t0208-mailmap.sh @@ -62,9 +62,9 @@ test_expect_success 'the atom feed uses the mapped name and address' ' ' test_expect_success 'the author search matches the mapped name' ' - cgit_query "url=mapped/log/&qt=author&q=New+Name" >tmp && + cgit_url "mapped/log/&qt=author&q=New+Name" >tmp && grep ">first commit</a>" tmp && - cgit_query "url=mapped/log/&qt=author&q=Nobody" >tmp && + cgit_url "mapped/log/&qt=author&q=Nobody" >tmp && ! grep ">first commit</a>" tmp ' @@ -73,7 +73,7 @@ test_expect_success 'with the flag off the old ident shows' ' echo "enable-mailmap=0" && cat cgitrc } >cgitrc-off && - CGIT_CONFIG="$PWD/cgitrc-off" QUERY_STRING="url=mapped/log/" cgit >tmp && + cgit_rc cgitrc-off "url=mapped/log/" >tmp && grep "<td class=.col-author.>Old Name</td>" tmp ' diff --git a/tests/t0301-security.sh b/tests/t0301-security.sh index d9ec265..b62fc62 100755 --- a/tests/t0301-security.sh +++ b/tests/t0301-security.sh @@ -8,15 +8,13 @@ test_description='Check security fixes and fork-specific behaviour' . ./setup.sh -# Most of what follows shares one repository and one config, so the fixture -# carries everything they need at once, a blob over the size limit, readmes -# holding markup that must not reach the page as markup, and a subdirectory -# to sort ahead of the files. +# Some cases below share one repository and one config, so the fixture +# carries everything they need at once, readmes holding markup that must not +# reach the page as markup and a subdirectory to sort ahead of the files. test_expect_success 'set up security fixtures' ' mkrepo repos/sec 1 && ( cd repos/sec && - dd if=/dev/zero bs=1024 count=4 2>/dev/null | tr "\0" "X" >big.txt && printf "# Title\n<script>alert(1)</script>\n" >README.md && printf "<script>alert(2)</script>\n" >readme.txt && printf "top\n" >afile && @@ -25,25 +23,14 @@ test_expect_success 'set up security fixtures' ' git add -A && git commit -m fixtures ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "max-blob-size=1" && - echo "enable-blame=1" && - echo "enable-tree-group-dirs=1" && - echo "repo.url=sec" && - echo "repo.path=$PWD/repos/sec/.git" - } >seccgitrc + cgitrc_repo seccgitrc sec max-blob-size=1 enable-tree-group-dirs=1 ' -secq() { CGIT_CONFIG="$PWD/seccgitrc" QUERY_STRING="$1" cgit; } - # A revision beginning with a dash reaches git as an option rather than as a # tip, so a request for id=--output=<path> could create or truncate any file # the server is able to write. test_expect_success 'log id=--output does not write a file' ' - rm -f pwned && - cgit_query "url=foo/log&id=--output=$PWD/pwned" >tmp 2>&1 && + cgit_url "foo/log&id=--output=$PWD/pwned" >tmp 2>&1 && ! test -e pwned ' @@ -51,34 +38,15 @@ test_expect_success 'log id=--output is rejected as an invalid revision' ' grep -i "invalid revision" tmp ' -test_expect_success 'a normal log still renders' ' - cgit_query "url=foo/log" >tmp && - grep -i "commit 5" tmp -' - test_expect_success 'a valid id= still renders the log' ' sha=$(git -C repos/foo rev-parse HEAD) && - cgit_query "url=foo/log&id=$sha" >tmp && + cgit_url "foo/log&id=$sha" >tmp && grep -i "commit 5" tmp ' -# max-blob-size is enforced before the object is read, so every view that -# would otherwise inline a file has to turn the same one away rather than -# inflate it first and think better of it afterwards. -test_expect_success 'tree view refuses an oversized blob' ' - secq "url=sec/tree/big.txt" | grep -iE "exceeds|too large" -' - -test_expect_success 'plain view refuses an oversized blob' ' - secq "url=sec/plain/big.txt" | grep -iE "exceeds|too large|413" -' - -test_expect_success 'blame view refuses an oversized blob' ' - secq "url=sec/blame/big.txt" | grep -iE "exceeds|too large" -' - test_expect_success 'a small blob is still served' ' - secq "url=sec/plain/afile" | grep -F "top" + cgit_rc seccgitrc "url=sec/plain/afile" >tmp && + grep -F top tmp ' # A readme is repository content, so with no about filter configured it has @@ -90,31 +58,24 @@ test_expect_success 'markdown readme without a filter is escaped as plain text' echo "cache-size=0" && echo "repo.url=md" && echo "repo.path=$PWD/repos/sec/.git" && - echo "repo.readme=master:README.md" - } >secmdrc && - CGIT_CONFIG="$PWD/secmdrc" QUERY_STRING="url=md/about/" cgit >tmp && + echo "repo.readme=master:README.md" && + echo "repo.url=txt" && + echo "repo.path=$PWD/repos/sec/.git" && + echo "repo.readme=master:readme.txt" + } >secreadmerc && + cgit_rc secreadmerc "url=md/about/" >tmp && grep "pre class=.plaintext." tmp && grep "<script>" tmp && ! grep "<script>alert(1)</script>" tmp ' -test_expect_success 'non-markdown readme without a filter is escaped' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=txt" && - echo "repo.path=$PWD/repos/sec/.git" && - echo "repo.readme=master:readme.txt" - } >sectxtrc && - CGIT_CONFIG="$PWD/sectxtrc" QUERY_STRING="url=txt/about/" cgit >tmp && +test_expect_success 'non-markdown readme without a filter is escaped as plain text' ' + cgit_rc secreadmerc "url=txt/about/" >tmp && + grep "pre class=.plaintext." tmp && grep "<script>" tmp && ! grep "<script>alert(2)</script>" tmp ' -test_expect_success 'non-markdown readme keeps its line structure' ' - grep "pre class=.plaintext." tmp -' - # root-readme is the site's own file rather than repository content, so it # goes out as written, on an about page of its own. test_expect_success 'root-readme without a filter is its own page, as written' ' @@ -124,10 +85,10 @@ test_expect_success 'root-readme without a filter is its own page, as written' ' echo "cache-size=0" && echo "root-readme=$PWD/root.html" } >secrootrc && - CGIT_CONFIG="$PWD/secrootrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc secrootrc "url=" >tmp && grep "<li><a title=.About this site. href=./?p=about.>about</a>" tmp && ! grep "<b>site</b>" tmp && - CGIT_CONFIG="$PWD/secrootrc" QUERY_STRING="p=about" cgit >tmp && + cgit_rc secrootrc "p=about" >tmp && grep "^<b>site</b>$" tmp ' @@ -136,7 +97,7 @@ test_expect_success 'root-readme without a filter is its own page, as written' ' # button and its selects carry no handlers at all. test_expect_success 'diff options submit through a button' ' sha=$(git -C repos/foo rev-parse HEAD) && - cgit_query "url=foo/commit&id=$sha" >tmp && + cgit_url "foo/commit&id=$sha" >tmp && grep "type=.submit. value=.reload." tmp && ! grep "onchange" tmp ' @@ -144,7 +105,7 @@ test_expect_success 'diff options submit through a button' ' # Grouping directories ahead of files is behaviour this fork adds, so nothing # upstream covers it. test_expect_success 'tree groups directories before files' ' - secq "url=sec/tree/" >tmp && + cgit_rc seccgitrc "url=sec/tree/" >tmp && dirline=$(grep -n "tree/zsub" tmp | head -1 | cut -d: -f1) && fileline=$(grep -n "tree/afile" tmp | head -1 | cut -d: -f1) && test -n "$dirline" && @@ -153,7 +114,7 @@ test_expect_success 'tree groups directories before files' ' ' # A file name is repository content and may hold a quote, which would break -# out of the href attribute on the line number links of a side by side diff, +# out of the href attribute on the line number links of a side-by-side diff, # so the path is percent-encoded on its way into them. test_expect_success 'ssdiff percent-encodes a quoted file path' ' mkrepo repos/xss 1 && @@ -166,14 +127,9 @@ test_expect_success 'ssdiff percent-encodes a quoted file path' ' printf "a\nc\n" >"$name" && git commit -am change ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=xss" && - echo "repo.path=$PWD/repos/xss/.git" - } >xssrc && + cgitrc_repo xssrc xss && sha=$(git -C repos/xss rev-parse HEAD) && - CGIT_CONFIG="$PWD/xssrc" QUERY_STRING="url=xss/diff/&id=$sha&ss=1" cgit >tmp && + cgit_rc xssrc "url=xss/diff/&id=$sha&ss=1" >tmp && grep "tree/x%27y.txt" tmp && ! grep "href=.[^>]*x.y.txt.[^>]*>" tmp ' @@ -189,15 +145,10 @@ test_expect_success 'a message-less commit renders without crashing' ' cid=$(git hash-object -t commit -w raw) && git update-ref refs/heads/master "$cid" ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=nomsg" && - echo "repo.path=$PWD/repos/nomsg/.git" - } >nomsgrc && - CGIT_CONFIG="$PWD/nomsgrc" QUERY_STRING="url=nomsg/log/&showmsg=1" cgit >tmp && + cgitrc_repo nomsgrc nomsg && + cgit_rc nomsgrc "url=nomsg/log/&showmsg=1" >tmp && grep "no commit message" tmp && - CGIT_CONFIG="$PWD/nomsgrc" QUERY_STRING="url=nomsg/" cgit >tmp && + cgit_rc nomsgrc "url=nomsg/" >tmp && grep "no commit message" tmp ' @@ -218,42 +169,21 @@ test_expect_success 'set up a repo whose branches point at odd objects' ' grep -q "tree.refs/heads/points-at-tree" refs.out && grep -q "tag.refs/heads/points-at-tag" refs.out ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "branch-sort=age" && - echo "repo.url=oddref" && - echo "repo.path=$PWD/repos/oddref/.git" - } >oddrefrc -' - -test_expect_success 'refs page sorts such branches without crashing' ' - CGIT_CONFIG="$PWD/oddrefrc" QUERY_STRING="url=oddref/refs/" cgit >tmp && - grep "points-at-tree" tmp && - grep "</html>" tmp -' - -test_expect_success 'the branch page sorts them without crashing' ' - CGIT_CONFIG="$PWD/oddrefrc" QUERY_STRING="url=oddref/refs/heads/" cgit >tmp && - grep "points-at-tree" tmp && - grep "</html>" tmp + cgitrc_repo oddrefrc oddref branch-sort=age ' -test_expect_success 'the summary page sorts them without crashing' ' - CGIT_CONFIG="$PWD/oddrefrc" QUERY_STRING="url=oddref/" cgit >tmp && - grep "points-at-tree" tmp && - grep "</html>" tmp +test_expect_success 'the refs, branch and summary pages sort such branches' ' + for page in refs/ refs/heads/ "" + do + cgit_rc oddrefrc "url=oddref/$page" >tmp && + grep "points-at-tree" tmp && + grep "</html>" tmp || return 1 + done ' test_expect_success 'name-sorted branches are unaffected' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "branch-sort=name" && - echo "repo.url=oddref" && - echo "repo.path=$PWD/repos/oddref/.git" - } >oddrefnamerc && - CGIT_CONFIG="$PWD/oddrefnamerc" QUERY_STRING="url=oddref/refs/heads/" cgit >tmp && + sed "s/^branch-sort=age$/branch-sort=name/" oddrefrc >oddrefnamerc && + cgit_rc oddrefnamerc "url=oddref/refs/heads/" >tmp && grep "points-at-tree" tmp && grep "</html>" tmp ' @@ -266,9 +196,9 @@ test_expect_success 'name-sorted branches are unaffected' ' # working. test_expect_success 'set up a submodule fixture with a hostile module-link' ' mkrepo repos/modlink 1 && + sub=$(git -C repos/modlink rev-parse HEAD) && ( cd repos/modlink && - sub=$(git rev-parse HEAD) && git update-index --add --cacheinfo 160000,$sub,submod && git commit -m gitlink ) && @@ -284,7 +214,7 @@ test_expect_success 'set up a submodule fixture with a hostile module-link' ' ' test_expect_success 'a surplus conversion does not crash the tree view' ' - CGIT_CONFIG="$PWD/modlinkrc" QUERY_STRING="url=modlink/tree/" cgit >tmp && + cgit_rc modlinkrc "url=modlink/tree/" >tmp && grep "ls-mod" tmp ' @@ -293,41 +223,20 @@ test_expect_success 'a surplus conversion is shown literally, not filled' ' ' test_expect_success 'a well-formed module-link still takes name and sha1' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "module-link=/mod/%s/commit/?id=%s" && - echo "repo.url=modlink" && - echo "repo.path=$PWD/repos/modlink/.git" - } >modlinkokrc && - sub=$(git -C repos/modlink rev-parse HEAD~1) && - CGIT_CONFIG="$PWD/modlinkokrc" QUERY_STRING="url=modlink/tree/" cgit >tmp && + cgitrc_repo modlinkokrc modlink "module-link=/mod/%s/commit/?id=%s" && + cgit_rc modlinkokrc "url=modlink/tree/" >tmp && grep "href=./mod/submod/commit/?id=$sub." tmp ' test_expect_success 'a per-path module-link takes only the sha1' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=modlink" && - echo "repo.path=$PWD/repos/modlink/.git" && - echo "repo.module-link.submod=https://example.com/s/?id=%s" - } >modlinkpathrc && - sub=$(git -C repos/modlink rev-parse HEAD~1) && - CGIT_CONFIG="$PWD/modlinkpathrc" QUERY_STRING="url=modlink/tree/" cgit >tmp && + cgitrc_repo modlinkpathrc modlink "repo.module-link.submod=https://example.com/s/?id=%s" && + cgit_rc modlinkpathrc "url=modlink/tree/" >tmp && grep "href=.https://example.com/s/?id=$sub." tmp ' test_expect_success 'a doubled percent in a module-link renders as one' ' - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "module-link=/m/%s/%%/%s" && - echo "repo.url=modlink" && - echo "repo.path=$PWD/repos/modlink/.git" - } >modlinkpctrc && - sub=$(git -C repos/modlink rev-parse HEAD~1) && - CGIT_CONFIG="$PWD/modlinkpctrc" QUERY_STRING="url=modlink/tree/" cgit >tmp && + cgitrc_repo modlinkpctrc modlink "module-link=/m/%s/%%/%s" && + cgit_rc modlinkpctrc "url=modlink/tree/" >tmp && grep "href=./m/submod/%/$sub." tmp ' @@ -345,7 +254,7 @@ test_expect_success 'a clone endpoint with http clone off answers the same way' echo "enable-http-clone=0" && cat cgitrc } >noclonerc && - CGIT_CONFIG="$PWD/noclonerc" QUERY_STRING="url=foo/info/refs" cgit >tmp && + cgit_rc noclonerc "url=foo/info/refs" >tmp && grep "^Status: 404 Not Found" tmp && grep "<select name=.h. " tmp ' @@ -369,17 +278,11 @@ test_expect_success 'set up a hostile .gitmodules' ' git add .gitmodules && git commit -m hostile ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "enable-gitmodules-links=1" && - echo "repo.url=modlink" && - echo "repo.path=$PWD/repos/modlink/.git" - } >modlinkgmrc + cgitrc_repo modlinkgmrc modlink enable-gitmodules-links=1 ' test_expect_success 'a javascript url never reaches an href' ' - CGIT_CONFIG="$PWD/modlinkgmrc" QUERY_STRING="url=modlink/tree/" cgit >tmp && + cgit_rc modlinkgmrc "url=modlink/tree/" >tmp && ! grep "href=.javascript:" tmp && grep "class=.ls-mod. title=.javascript:alert(1).>submod</span>" tmp ' @@ -412,8 +315,7 @@ test_expect_success 'set up the advisory fixtures' ' ) && mkdir -p readme && printf "readme text\n" >readme/README && - cat >auth.sh <<-\EOF && - #!/bin/sh + write_script auth.sh <<-\EOF && case "$1" in authenticate-post) cat >/dev/null @@ -422,22 +324,11 @@ test_expect_success 'set up the advisory fixtures' ' esac exit 1 EOF - chmod +x auth.sh && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "auth-filter=exec:$PWD/auth.sh" && - echo "repo.url=cve" && - echo "repo.path=$PWD/repos/cve/.git" && - echo "repo.readme=$PWD/readme/README" && - echo "repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh" - } >cverc + cgitrc_repo cverc cve "auth-filter=exec:$PWD/auth.sh" "repo.readme=$PWD/readme/README" "repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh" ' -cveq() { CGIT_CONFIG="$PWD/cverc" QUERY_STRING="$1" cgit; } - test_expect_success 'a newline in a file name cannot split the headers' ' - cveq "url=cve/plain/crlf%0d%0aX-Injected:%201.txt" >tmp && + cgit_rc cverc "url=cve/plain/crlf%0d%0aX-Injected:%201.txt" >tmp && grep "^Status: 200" tmp && ! grep "^X-Injected" tmp && grep "^Content-Disposition: inline; filename=.crlf\\\\r\\\\nX-Injected: 1.txt.$" tmp @@ -451,48 +342,48 @@ test_expect_success 'a posted length past any buffer is clamped' ' ' test_expect_success 'the dumb transport refuses a path that climbs out' ' - cveq "url=cve/objects/../config" >tmp && + cgit_rc cverc "url=cve/objects/../config" >tmp && grep "^Status: 404" tmp && ! grep "repositoryformatversion" tmp && - cveq "url=cve/objects/info/../../config" >tmp && + cgit_rc cverc "url=cve/objects/info/../../config" >tmp && grep "^Status: 404" tmp && ! grep "repositoryformatversion" tmp ' test_expect_success 'the about page serves nothing above the readme directory' ' - cveq "url=cve/about/../cverc" >tmp && + cgit_rc cverc "url=cve/about/../cverc" >tmp && grep "^Status: 404" tmp && ! grep "virtual-root" tmp && - cveq "url=cve/about/" >tmp && + cgit_rc cverc "url=cve/about/" >tmp && grep "readme text" tmp ' test_expect_success 'a commit with an empty author renders everywhere' ' - cveq "url=cve/log/&h=noname" >tmp && + cgit_rc cverc "url=cve/log/&h=noname" >tmp && grep ">empty author</a>" tmp && - cveq "url=cve/commit/&h=noname" >tmp && + cgit_rc cverc "url=cve/commit/&h=noname" >tmp && grep "<div class=.commit-subject.>empty author<" tmp && - cveq "url=cve/atom/&h=noname" >tmp && + cgit_rc cverc "url=cve/atom/&h=noname" >tmp && grep "<name>unknown</name>" tmp && ! grep "<email></email>" tmp ' test_expect_success 'a percent sign without digits behind it is read as itself' ' - cveq "url=cve/log/&q=%" >tmp && + cgit_rc cverc "url=cve/log/&q=%" >tmp && grep "^Status: 200" tmp && - cveq "url=cve/log/&q=%zz%2" >tmp && + cgit_rc cverc "url=cve/log/&q=%zz%2" >tmp && grep "^Status: 200" tmp && grep "value=.%zz%2." tmp ' test_expect_success 'script in a file name is escaped on the diff page' ' - cveq "url=cve/diff/" >tmp && + cgit_rc cverc "url=cve/diff/" >tmp && ! grep "<script>alert" tmp && grep "<script>alert(1)x.txt" tmp ' test_expect_success 'shell syntax in a file name reaches a filter as a plain argument' ' - cveq "url=cve/tree/%24(touch%20pwned).c" >tmp && + cgit_rc cverc "url=cve/tree/%24(touch%20pwned).c" >tmp && grep "^Status: 200" tmp && grep "pwned).c" tmp && ! test -e pwned && diff --git a/tests/t0302-home-access.sh b/tests/t0302-home-access.sh index df55e0d..e6faa45 100755 --- a/tests/t0302-home-access.sh +++ b/tests/t0302-home-access.sh @@ -16,26 +16,15 @@ test_description='Ensure that git does not access $HOME' command -v strace >/dev/null 2>&1 || { 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 && + missing_home=/path/to/some/place/that/does/not/possibly/exist && strace \ -E HOME="$missing_home" \ -E CGIT_CONFIG="$PWD/cgitrc" \ diff --git a/tests/t0303-robustness.sh b/tests/t0303-robustness.sh index 07f7c09..c881045 100755 --- a/tests/t0303-robustness.sh +++ b/tests/t0303-robustness.sh @@ -10,17 +10,9 @@ test_description='Check the audit regressions' test_expect_success 'set up a repository and a config to vary' ' mkrepo repos/rob 3 && sha=$(git -C repos/rob rev-parse HEAD~1) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "snapshots=tar.gz" && - echo "repo.url=rob" && - echo "repo.path=$PWD/repos/rob/.git" - } >robrc + cgitrc_repo robrc rob snapshots=tar.gz ' -robq() { CGIT_CONFIG="$PWD/robrc" QUERY_STRING="$1" cgit; } - test_expect_success 'a repository without a path answers 404' ' { echo "virtual-root=/" && @@ -31,7 +23,7 @@ test_expect_success 'a repository without a path answers 404' ' } >nopathrc && for r in nopath emptypath do - CGIT_CONFIG="$PWD/nopathrc" QUERY_STRING="url=$r/log/" cgit >tmp && + cgit_rc nopathrc "url=$r/log/" >tmp && grep "^Status: 404 Not Found" tmp && grep "Failed to open $r: Not a valid git repository" tmp || return 1 done @@ -42,7 +34,7 @@ test_expect_success 'a repository without a path answers 404' ' test_expect_success 'a repository without a path has no age on the index' ' mkdir -p "(null)/refs/heads" && >"(null)/refs/heads/master" && - CGIT_CONFIG="$PWD/nopathrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc nopathrc "url=" >tmp && grep "</html>" tmp && ! grep "<time " tmp ' @@ -57,12 +49,12 @@ test_expect_success 'a repository with an empty url is skipped with a warning' ' echo "repo.url=rob" && echo "repo.path=$PWD/repos/rob/.git" } >nourlrc && - CGIT_CONFIG="$PWD/nourlrc" QUERY_STRING="url=rob/" cgit >tmp 2>err && + cgit_rc nourlrc "url=rob/" >tmp 2>err && grep "^Status: 200" tmp && grep "Ignoring repository with an empty url" err && - CGIT_CONFIG="$PWD/nourlrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc nourlrc "url=" >tmp && ! grep nourl tmp && - CGIT_CONFIG="$PWD/nourlrc" QUERY_STRING="r=" cgit >tmp && + cgit_rc nourlrc "r=" >tmp && ! grep nourl tmp ' @@ -73,7 +65,7 @@ test_expect_success 'a scan path that is itself a repository is named after it' echo "remove-suffix=1" && echo "scan-path=$PWD/repos/rob" } >rootrc && - CGIT_CONFIG="$PWD/rootrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc rootrc "url=" >tmp && grep "toplevel-repo.><a href=./rob/.>rob</a>" tmp ' @@ -86,7 +78,7 @@ test_expect_success 'a repo key after scan-path is reported instead of applied' echo "scan-path=$PWD/repos" && echo "repo.desc=misplaced" } >stalerc && - CGIT_CONFIG="$PWD/stalerc" QUERY_STRING="url=" cgit >tmp 2>err && + cgit_rc stalerc "url=" >tmp 2>err && ! grep "misplaced" tmp && grep "Ignoring repo.desc before any repo.url" err ' @@ -100,7 +92,7 @@ test_expect_success 'a valueless or broken git config in a scanned repository is echo "enable-git-config=1" && echo "scan-path=$PWD/scan" } >gitcfgrc && - CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp 2>err && + cgit_rc gitcfgrc "url=" >tmp 2>err && grep "toplevel-repo.><a href=./a.git/.>" tmp && grep "Ignoring unreadable config in $PWD/scan/a.git/config" err ' @@ -113,7 +105,7 @@ test_expect_success 'only descriptive keys are taken from a scanned repository' echo "head-content=<script>alert(1)</script>" && echo "logo-link=javascript:alert(2)" } >scan/b.git/cgitrc && - CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err && + cgit_rc gitcfgrc "url=b.git/about/" >tmp 2>err && grep "from the repo" tmp && grep "pre class=.plaintext." tmp && ! grep "alert(" tmp && @@ -123,7 +115,7 @@ test_expect_success 'only descriptive keys are taken from a scanned repository' test_expect_success 'a filesystem readme from a scanned repository is refused' ' echo "readme=/etc/hosts" >scan/b.git/cgitrc && - CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err && + cgit_rc gitcfgrc "url=b.git/about/" >tmp 2>err && grep "Ignoring readme in " err ' @@ -132,7 +124,7 @@ test_expect_success 'a filesystem readme from a scanned repository is refused' ' test_expect_success 'a readme ending in a colon is refused like a path on disk' ' echo "outside the repository" >"scan/outside:" && echo "readme=../outside:" >scan/b.git/cgitrc && - CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err && + cgit_rc gitcfgrc "url=b.git/about/" >tmp 2>err && grep "Ignoring readme in " err && ! grep "outside the repository" tmp ' @@ -143,7 +135,7 @@ test_expect_success 'a readme set after scan-path reaches a scanned repository' rm scan/b.git/cgitrc && cat gitcfgrc >laterrc && echo "readme=master:file-1" >>laterrc && - CGIT_CONFIG="$PWD/laterrc" QUERY_STRING="url=b.git/about/" cgit >tmp && + cgit_rc laterrc "url=b.git/about/" >tmp && grep "<pre class=.plaintext.>1$" tmp ' @@ -167,7 +159,7 @@ test_expect_success 'html serving from a scanned repository waits on trust' ' echo "mimetype.html=text/html" && echo "scan-path=$PWD/scan2" } >htmlrc && - CGIT_CONFIG="$PWD/htmlrc" QUERY_STRING="url=c.git/plain/page.html" cgit >tmp 2>err && + cgit_rc htmlrc "url=c.git/plain/page.html" >tmp 2>err && grep "^Content-Type: text/plain" tmp && grep "^X-Content-Type-Options: nosniff" tmp && grep "Ignoring enable-html-serving in $PWD/scan2/c.git/: trust-scan-config is not set" err @@ -178,41 +170,46 @@ test_expect_success 'with trust-scan-config the same repository serves html' ' echo "trust-scan-config=1" && cat htmlrc } >htmltrustrc && - CGIT_CONFIG="$PWD/htmltrustrc" QUERY_STRING="url=c.git/plain/page.html" cgit >tmp && + cgit_rc htmltrustrc "url=c.git/plain/page.html" >tmp && grep "^Content-Type: text/html" tmp && ! grep "^X-Content-Type-Options" tmp ' test_expect_success SYMLINKS 'a symlink cycle under the scan path is entered once' ' ln -s . scan/loop && - CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc gitcfgrc "url=" >tmp && test $(grep -c "toplevel-repo" tmp) = 2 ' test_expect_success 'a bare query parameter does not swallow the next one' ' - robq "url=rob/commit/&x&id=$sha" >tmp && + cgit_rc robrc "url=rob/commit/&x&id=$sha" >tmp && grep "<div class=.commit-subject.>commit 2" tmp ' +# Whether cgit first sees the filter's exit status or its closed pipe +# depends on which side runs first, so either message will do. test_expect_success 'a filter that cannot run answers one error page' ' { echo "commit-filter=exec:$PWD/no/such/filter" && cat robrc } >badfilterrc && - CGIT_CONFIG="$PWD/badfilterrc" QUERY_STRING="url=rob/commit/" cgit >tmp 2>err && + cgit_rc badfilterrc "url=rob/commit/" >tmp 2>err && test $(grep -c "^Status:" tmp) = 1 && grep "Unable to complete the request" tmp && - grep "Unable to run filter $PWD/no/such/filter" err && + grep -e "Unable to run filter $PWD/no/such/filter" -e "Unable to write the page" err && grep "Unable to exec filter" err ' +# The filter may exit before or after cgit writes to it, so the page either +# completes or ends where the write failed. Either way cgit answers once and +# exits cleanly rather than dying of SIGPIPE. test_expect_success 'a filter that exits without reading does not end cgit' ' { - echo "email-filter=exec:/usr/bin/true" && + echo "email-filter=exec:true" && cat robrc } >truerc && - CGIT_CONFIG="$PWD/truerc" QUERY_STRING="url=rob/log/" cgit >tmp && - grep "^Status:" tmp + cgit_rc truerc "url=rob/log/" >tmp && + test $(grep -c "^Status:" tmp) = 1 ' test_expect_success 'a commit encoding header is read without its newline' ' @@ -222,11 +219,13 @@ test_expect_success 'a commit encoding header is read without its newline' ' git add file-1 && git -c i18n.commitEncoding=ISO-8859-1 commit -m "$(printf "caf\351")" ) && - robq "url=rob/commit/" >tmp && + cgit_rc robrc "url=rob/commit/" >tmp && grep "<div class=.commit-subject.>caf$(printf "\303\251")<" tmp ' -test_expect_success 'a submodule below the about path does not end the request' ' +# The about page shows the readme alone and its links lead into the tree, +# so a path below it names nothing, the site about page included. +test_expect_success 'a path below the about page answers 404' ' ( cd repos/rob && git update-index --add --cacheinfo 160000,$sha,sub && @@ -237,45 +236,45 @@ test_expect_success 'a submodule below the about path does not end the request' cat robrc && echo "repo.readme=master:file-1" } >aboutrc && - CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/sub" cgit >tmp 2>err && - grep "^Status: 404" tmp && - ! grep "fatal" err -' - -test_expect_success 'a missing image on the about page errors as html' ' - CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/missing.png" cgit >tmp && - grep "^Status: 404" tmp && - grep "^Content-Type: text/html" tmp + for path in sub sub/ missing.png file-1 ../robrc + do + cgit_rc aboutrc "url=rob/about/$path" >tmp 2>err && + grep "^Status: 404" tmp && + grep "^Content-Type: text/html" tmp && + ! grep fatal err || return 1 + done && + cgit_rc aboutrc "p=about&path=x" >tmp && + grep "^Status: 404" tmp ' test_expect_success 'a snapshot name holding a slash is refused' ' - robq "url=rob/snapshot/:/../../...tar.gz" >tmp && + cgit_rc robrc "url=rob/snapshot/:/../../...tar.gz" >tmp && grep "^Status: 404" tmp && - robq "url=rob/snapshot/rob-master.tar.gz" >tmp && + cgit_rc robrc "url=rob/snapshot/rob-master.tar.gz" >tmp && grep "^Status: 200" tmp ' test_expect_success 'a path opening with a colon is refused on the log and patch pages' ' - robq "url=rob/log/:%25x" >tmp && + cgit_rc robrc "url=rob/log/:%25x" >tmp && grep "^Status: 400" tmp && - robq "url=rob/patch/:%25x" >tmp && + cgit_rc robrc "url=rob/patch/:%25x" >tmp && grep "^Status: 400" tmp && test $(grep -c "^Status:" tmp) = 1 ' test_expect_success 'a revision spelled like an option is refused' ' git -C repos/rob update-ref refs/heads/--stdin HEAD && - robq "url=rob/log/&h=--stdin" >tmp && + cgit_rc robrc "url=rob/log/&h=--stdin" >tmp && grep "^Status: 404" tmp && - robq "url=rob/blame/file-1&id=--stdin" >tmp && + cgit_rc robrc "url=rob/blame/file-1&id=--stdin" >tmp && grep "^Status: 400" tmp && - robq "url=rob/atom/&h=--stdin" >tmp && + cgit_rc robrc "url=rob/atom/&h=--stdin" >tmp && grep "^Status: 404" tmp ' test_expect_success 'the blob page reads a file through an annotated tag' ' git -C repos/rob -c tag.gpgsign=false tag -a -m note ann HEAD && - robq "url=rob/blob/&h=ann&path=file-1" >tmp && + cgit_rc robrc "url=rob/blob/&h=ann&path=file-1" >tmp && strip_headers <tmp >body && printf "more\n" >want && test_cmp want body @@ -285,14 +284,14 @@ test_expect_success 'the blob page reads a file through an annotated tag' ' # blob page used to answer such a request with the raw commit object. test_expect_success 'the blob page walks a path out of the commit or tag the id names' ' tip=$(git -C repos/rob rev-parse HEAD) && - robq "url=rob/blob/file-1&id=$tip" >tmp && + cgit_rc robrc "url=rob/blob/file-1&id=$tip" >tmp && strip_headers <tmp >body && test_cmp want body && tag=$(git -C repos/rob rev-parse ann) && - robq "url=rob/blob/file-1&id=$tag" >tmp && + cgit_rc robrc "url=rob/blob/file-1&id=$tag" >tmp && strip_headers <tmp >body && test_cmp want body && - robq "url=rob/blob/missing&id=$tip" >tmp && + cgit_rc robrc "url=rob/blob/missing&id=$tip" >tmp && grep "^Status: 404" tmp ' @@ -316,31 +315,24 @@ test_expect_success 'the commit page shows a message whole when text follows its echo "enable-commit-trailers=1" && cat robrc } >trailrc && - CGIT_CONFIG="$PWD/trailrc" QUERY_STRING="url=rob/commit/" cgit >tmp && + cgit_rc trailrc "url=rob/commit/" >tmp && ! grep "commit-trailers" tmp && grep "Conflicts:" tmp ' test_expect_success 'a revision expression that walks the history is refused' ' - robq "url=rob/log/&h=:/zzzz" >tmp && + cgit_rc robrc "url=rob/log/&h=:/zzzz" >tmp && grep "^Status: 404" tmp && - robq "url=rob/commit/&id=HEAD^{/zzzz}" >tmp && + cgit_rc robrc "url=rob/commit/&id=HEAD^{/zzzz}" >tmp && grep "^Status: 400" tmp && - robq "url=rob/log/&qt=range&q=:/zzzz" >tmp && + cgit_rc robrc "url=rob/log/&qt=range&q=:/zzzz" >tmp && grep "^Status: 400" tmp && - robq "url=rob/log/&qt=range&q=master~1..master" >tmp && + cgit_rc robrc "url=rob/log/&qt=range&q=master~1..master" >tmp && grep "^Status: 400" tmp && - robq "url=rob/log/&qt=range&q=$sha..master" >tmp && + cgit_rc robrc "url=rob/log/&qt=range&q=$sha..master" >tmp && grep "^Status: 200" tmp ' -test_expect_success 'the log search is literal' ' - robq "url=rob/log/&qt=grep&q=commit.1" >tmp && - ! grep ">commit 1</a>" tmp && - robq "url=rob/log/&qt=grep&q=commit%201" >tmp && - grep ">commit 1</a>" tmp -' - test_expect_success 'a commit with a broken tree line does not crash the blob page' ' git clone -q repos/rob broken && ( @@ -349,20 +341,14 @@ test_expect_success 'a commit with a broken tree line does not crash the blob pa git hash-object -t commit -w --stdin --literally) && echo $bad >.git/refs/heads/bad ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=broken" && - echo "repo.path=$PWD/broken/.git" && - echo "repo.readme=:file-1" - } >brokenrc && - CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/blob/&h=bad&path=file-1" cgit >tmp && + cgitrc_repo brokenrc broken "repo.path=$PWD/broken/.git" repo.readme=:file-1 && + cgit_rc brokenrc "url=broken/blob/&h=bad&path=file-1" >tmp && grep "^Status: 404" tmp ' test_expect_success 'a die inside git answers one 500 page and logs the reason' ' printf "[core\n" >>broken/.git/config && - CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/log/" cgit >tmp 2>err && + cgit_rc brokenrc "url=broken/log/" >tmp 2>err && grep "^Status: 500" tmp && test $(grep -c "^Status:" tmp) = 1 && grep "bad config line" err && @@ -374,17 +360,12 @@ test_expect_success 'a history with a missing parent renders up to the gap' ' older=$(git -C gap rev-parse HEAD~3) && parent=$(git -C gap rev-parse HEAD~2) && rm gap/.git/objects/$(echo $parent | cut -c1-2)/$(echo $parent | cut -c3-) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=gap" && - echo "repo.path=$PWD/gap/.git" - } >gaprc && - CGIT_CONFIG="$PWD/gaprc" QUERY_STRING="url=gap/log/" cgit >tmp 2>err && + cgitrc_repo gaprc gap "repo.path=$PWD/gap/.git" && + cgit_rc gaprc "url=gap/log/" >tmp 2>err && test $(grep -c "^Status:" tmp) = 1 && grep "Unable to complete the request" tmp && grep "Failed to traverse parents" err && - CGIT_CONFIG="$PWD/gaprc" QUERY_STRING="url=gap/patch/&id2=$older" cgit >tmp && + cgit_rc gaprc "url=gap/patch/&id2=$older" >tmp && test $(grep -c "^Status:" tmp) = 1 && grep "^Status: 500" tmp ' @@ -407,20 +388,14 @@ test_expect_success 'a corrupt blob in a collected diff answers the error' ' test_copy_bytes $((size / 2)) <$obj >half && mv half $obj ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=corrupt" && - echo "repo.path=$PWD/corrupt/.git" - } >corruptrc && - CGIT_CONFIG="$PWD/corruptrc" QUERY_STRING="url=corrupt/commit/" cgit >tmp 2>err && + cgitrc_repo corruptrc corrupt "repo.path=$PWD/corrupt/.git" && + cgit_rc corruptrc "url=corrupt/commit/" >tmp 2>err && grep "is corrupt" err && grep "Unable to complete the request" tmp ' test_expect_success 'a filter pipeline sees the default SIGPIPE disposition' ' - cat >pipe.sh <<-\EOF && - #!/bin/sh + write_script pipe.sh <<-\EOF && n=0 while test $n -lt 20000 do @@ -428,12 +403,11 @@ test_expect_success 'a filter pipeline sees the default SIGPIPE disposition' ' n=$((n + 1)) done | head -c 5 EOF - chmod +x pipe.sh && { echo "source-filter=exec:$PWD/pipe.sh" && cat robrc } >piperc && - CGIT_CONFIG="$PWD/piperc" QUERY_STRING="url=rob/tree/file-1" cgit >tmp 2>err && + cgit_rc piperc "url=rob/tree/file-1" >tmp 2>err && grep "^Status: 200" tmp && ! grep -i "broken pipe" err ' @@ -441,7 +415,7 @@ test_expect_success 'a filter pipeline sees the default SIGPIPE disposition' ' test_expect_success 'the fallback branch skips a name spelled like an option' ' git -C repos/rob update-ref refs/heads/-first HEAD && git -C repos/rob symbolic-ref HEAD refs/heads/gone && - robq "url=rob/" >tmp && + cgit_rc robrc "url=rob/" >tmp && git -C repos/rob symbolic-ref HEAD refs/heads/master && grep "^Status: 200" tmp ' @@ -465,18 +439,17 @@ test_expect_success 'a blank line in the mimetype file is skipped' ' echo "mimetype-file=$PWD/mime.types" && cat robrc } >mimerc && - CGIT_CONFIG="$PWD/mimerc" QUERY_STRING="url=rob/plain/note.cgt" cgit >tmp && + cgit_rc mimerc "url=rob/plain/note.cgt" >tmp && grep "^Content-Type: image/x-cgit" tmp ' test_expect_success 'an indented comment in the mimetype file is skipped' ' printf " # cgt used to be image/x-old\nimage/x-cgit cgt\n" >mime.types && - CGIT_CONFIG="$PWD/mimerc" QUERY_STRING="url=rob/plain/note.cgt" cgit >tmp && + cgit_rc mimerc "url=rob/plain/note.cgt" >tmp && grep "^Content-Type: image/x-cgit" tmp ' test_expect_success 'a refs page named with an id is not cached for ever' ' - mkdir -p cache && { echo "cache-size=10" && echo "cache-root=$PWD/cache" && @@ -486,9 +459,9 @@ test_expect_success 'a refs page named with an id is not cached for ever' ' grep -v "^cache-size" robrc } >ttlrc && full=$(git -C repos/rob rev-parse HEAD) && - CGIT_CONFIG="$PWD/ttlrc" QUERY_STRING="url=rob/refs/&id=$full" cgit >tmp && + cgit_rc ttlrc "url=rob/refs/&id=$full" >tmp && git -C repos/rob tag later HEAD && - CGIT_CONFIG="$PWD/ttlrc" QUERY_STRING="url=rob/refs/&id=$full" cgit >tmp && + cgit_rc ttlrc "url=rob/refs/&id=$full" >tmp && grep ">later<" tmp ' @@ -507,15 +480,15 @@ test_expect_success 'the diff caps hold when the path names a directory' ' echo "max-diff-files=2" && cat robrc } >caprc && - CGIT_CONFIG="$PWD/caprc" QUERY_STRING="url=rob/diff/dir" cgit >tmp && + cgit_rc caprc "url=rob/diff/dir" >tmp && grep "too large to be rendered inline" tmp && - CGIT_CONFIG="$PWD/caprc" QUERY_STRING="url=rob/diff/dir/f1" cgit >tmp && + cgit_rc caprc "url=rob/diff/dir/f1" >tmp && ! grep "too large to be rendered inline" tmp ' test_expect_success 'a snapshot of a tag spelled like an option is archived' ' git -C repos/rob update-ref refs/tags/-l HEAD && - robq "url=rob/snapshot/-l.tar.gz" >tmp && + cgit_rc robrc "url=rob/snapshot/-l.tar.gz" >tmp && grep "^Status: 200" tmp && strip_headers <tmp | gzip -dc | tar -tf - >list && grep "file-1" list @@ -542,9 +515,9 @@ test_expect_success 'a disk readme in the repository directory serves no sibling cat robrc && echo "repo.readme=README" } >diskrc && - CGIT_CONFIG="$PWD/diskrc" QUERY_STRING="url=rob/about/" cgit >tmp && + cgit_rc diskrc "url=rob/about/" >tmp && grep "hello" tmp && - CGIT_CONFIG="$PWD/diskrc" QUERY_STRING="url=rob/about/config" cgit >tmp && + cgit_rc diskrc "url=rob/about/config" >tmp && grep "^Status: 404" tmp && ! grep "repositoryformatversion" tmp ' @@ -552,23 +525,18 @@ test_expect_success 'a disk readme in the repository directory serves no sibling test_expect_success 'a tag pointing at a tag links the inner tag by id' ' git -C repos/rob -c tag.gpgsign=false tag -a -m outer outer ann && inner=$(git -C repos/rob rev-parse ann) && - robq "url=rob/tag/&id=$inner" >tmp && + cgit_rc robrc "url=rob/tag/&id=$inner" >tmp && grep "^Status: 200" tmp && grep "tagged object" tmp ' test_expect_success 'the dumb transport serves an empty repository' ' git init -q --bare empty.git && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=empty" && - echo "repo.path=$PWD/empty.git" - } >emptyrc && - CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/info/refs" cgit >tmp && + cgitrc_repo emptyrc empty "repo.path=$PWD/empty.git" && + cgit_rc emptyrc "url=empty/info/refs" >tmp && grep "^Status: 200" tmp && grep "^Content-Type: text/plain" tmp && - CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/objects/info" cgit >tmp && + cgit_rc emptyrc "url=empty/objects/info" >tmp && grep "^Status: 404" tmp ' @@ -576,10 +544,10 @@ test_expect_success 'the dumb transport serves an empty repository' ' # its size up front and compare what arrived against it. test_expect_success 'the dumb transport sends a file with its size' ' git -C repos/rob repack -q -d && - robq "url=rob/objects/info/packs" >tmp && + cgit_rc robrc "url=rob/objects/info/packs" >tmp && pack=$(strip_headers <tmp | sed -n "s/^P //p" | head -1) && test -n "$pack" && - robq "url=rob/objects/pack/$pack" >tmp && + cgit_rc robrc "url=rob/objects/pack/$pack" >tmp && grep "^Status: 200" tmp && size=$(wc -c <repos/rob/.git/objects/pack/$pack | tr -d " ") && grep "^Content-Length: $size$" tmp && @@ -595,43 +563,38 @@ test_expect_success 'the dumb transport withholds the alternates file' ' for path in info/alternates info//alternates info/./alternates ./info/alternates \ info/Alternates /info/alternates do - robq "url=rob/objects/$path" >tmp && + cgit_rc robrc "url=rob/objects/$path" >tmp && grep "^Status: 404" tmp && ! grep "repos/foo" tmp || return 1 done && rm repos/rob/.git/objects/info/alternates && - robq "url=rob/objects/info/packs" >tmp && + cgit_rc robrc "url=rob/objects/info/packs" >tmp && grep "^Status: 200" tmp ' # A reftable repository keeps only a placeholder in its HEAD file, so the # branch a client is to check out has to come from the refs instead. test_expect_success 'the dumb transport answers HEAD from the refs' ' - robq "url=rob/HEAD" >tmp && + cgit_rc robrc "url=rob/HEAD" >tmp && grep "^Status: 200" tmp && strip_headers <tmp >head && echo "ref: refs/heads/master" >expect && test_cmp expect head && git init -q --bare --ref-format=reftable -b trunk rt.git && git -C repos/rob push -q "$PWD/rt.git" master:trunk && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=rt" && - echo "repo.path=$PWD/rt.git" - } >rtrc && - CGIT_CONFIG="$PWD/rtrc" QUERY_STRING="url=rt/HEAD" cgit >tmp && + cgitrc_repo rtrc rt "repo.path=$PWD/rt.git" && + cgit_rc rtrc "url=rt/HEAD" >tmp && strip_headers <tmp >head && echo "ref: refs/heads/trunk" >expect && test_cmp expect head && - CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/HEAD" cgit >tmp && + cgit_rc emptyrc "url=empty/HEAD" >tmp && strip_headers <tmp >head && echo "ref: refs/heads/master" >expect && test_cmp expect head ' test_expect_success 'a reftable repository shows its age on the index' ' - CGIT_CONFIG="$PWD/rtrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc rtrc "url=" >tmp && grep "<td class=.col-idle.><time " tmp ' @@ -645,7 +608,7 @@ test_expect_success 'a client that disconnects ends the request quietly' ' git add long.txt && git commit -m long ) && - { robq "url=rob/tree/long.txt" 2>err; echo $? >status; } | head -c 1 >/dev/null && + { cgit_rc robrc "url=rob/tree/long.txt" 2>err; echo $? >status; } | head -c 1 >/dev/null && test "$(cat status)" = 0 && ! grep -i "broken pipe" err && ! grep "die()" err @@ -655,20 +618,18 @@ test_expect_success 'a client that disconnects ends the request quietly' ' # shared with other visitors has to be told, while a site without a filter # keeps its pages free of any such header. test_expect_success 'pages behind an auth filter are marked private' ' - cat >letin.sh <<-\EOF && - #!/bin/sh + write_script letin.sh <<-\EOF && exit 1 EOF - chmod +x letin.sh && { echo "auth-filter=exec:$PWD/letin.sh" && cat robrc } >letinrc && - CGIT_CONFIG="$PWD/letinrc" QUERY_STRING="url=rob/log/" cgit >tmp && + cgit_rc letinrc "url=rob/log/" >tmp && grep "^Status: 200" tmp && grep "^Cache-Control: private$" tmp && grep "^Vary: Cookie$" tmp && - robq "url=rob/log/" >tmp && + cgit_rc robrc "url=rob/log/" >tmp && ! grep "^Cache-Control" tmp && ! grep "^Vary" tmp ' @@ -677,10 +638,10 @@ test_expect_success 'pages behind an auth filter are marked private' ' # the branch asked for has to survive that hop. test_expect_success 'the about redirect keeps the query string' ' git -C repos/rob branch other HEAD && - robq "url=rob/about&h=other&x=1" >tmp && + cgit_rc robrc "url=rob/about&h=other&x=1" >tmp && grep "^Status: 302" tmp && grep "^Location: /rob/?h=other&x=1$" tmp && - CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about&h=other" cgit >tmp && + cgit_rc aboutrc "url=rob/about&h=other" >tmp && grep "^Status: 200" tmp ' @@ -692,9 +653,9 @@ test_expect_success 'the readme follows an annotated tag as the head' ' cat robrc && echo "repo.readme=:file-1" } >tagreadmerc && - CGIT_CONFIG="$PWD/tagreadmerc" QUERY_STRING="url=rob/about/&h=v1" cgit >tmp && + cgit_rc tagreadmerc "url=rob/about/&h=v1" >tmp && grep "^Status: 200" tmp && - CGIT_CONFIG="$PWD/tagreadmerc" QUERY_STRING="url=rob/tree/&h=v1" cgit >tmp && + cgit_rc tagreadmerc "url=rob/tree/&h=v1" >tmp && grep ">about<" tmp ' @@ -704,18 +665,6 @@ test_expect_success 'the about redirect encodes the repository url once' ' grep "^Location: /foo%2Bbar/$" tmp ' -# The about page shows the readme alone and its links lead into the tree, -# so a path below it names nothing, the site about page included. -test_expect_success 'a path below the about page answers 404' ' - for path in file-1 sub/ ../robrc - do - CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/$path" cgit >tmp && - grep "^Status: 404" tmp || return 1 - done && - CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="p=about&path=x" cgit >tmp && - grep "^Status: 404" tmp -' - test_expect_success 'a symlink whose target is a large blob is listed without it' ' big=$(head -c 5000 /dev/zero | tr "\0" a | git -C repos/rob hash-object -w --stdin) && ( @@ -723,7 +672,7 @@ test_expect_success 'a symlink whose target is a large blob is listed without it git update-index --add --cacheinfo 120000,$big,biglink && git commit -m biglink ) && - robq "url=rob/tree/" >tmp && + cgit_rc robrc "url=rob/tree/" >tmp && grep "biglink" tmp && ! grep "aaaaaaaaaa" tmp ' @@ -737,7 +686,7 @@ test_expect_success 'a chain of single directories is followed a bounded number git add d && git commit -m deep ) && - robq "url=rob/tree/" >tmp && + cgit_rc robrc "url=rob/tree/" >tmp && test $(grep -o "class=.ls-dir." tmp | wc -l) -le 17 ' @@ -752,7 +701,7 @@ test_expect_success 'an unreadable project list is named with its error' ' echo "project-list=$PWD/projdir" && echo "scan-path=$PWD/repos" } >projrc && - CGIT_CONFIG="$PWD/projrc" QUERY_STRING="url=" cgit >tmp 2>err && + cgit_rc projrc "url=" >tmp 2>err && grep "project list .*projdir: Is a directory" err ' @@ -765,7 +714,7 @@ test_expect_success SANITY 'a scan entry that cannot be searched is named in ful echo "cache-size=0" && echo "scan-path=$PWD/scanperm" } >scanpermrc && - CGIT_CONFIG="$PWD/scanpermrc" QUERY_STRING="url=" cgit >tmp 2>err && + cgit_rc scanpermrc "url=" >tmp 2>err && grep "Unable to stat .*scanperm/locked/objects: Permission denied" err ' @@ -780,7 +729,7 @@ test_expect_success 'a readme past max-blob-size is reported on the about page' cat robrc && echo "repo.readme=:big-readme" } >bigreadmerc && - CGIT_CONFIG="$PWD/bigreadmerc" QUERY_STRING="url=rob/about/" cgit >tmp && + cgit_rc bigreadmerc "url=rob/about/" >tmp && grep "The readme is larger than max-blob-size (1 KB)" tmp ' @@ -788,10 +737,9 @@ test_expect_success 'a readme past max-blob-size is reported on the about page' # that was just refused must not be looked up to get there, since the wider # revision syntax walks the whole history for a match. test_expect_success 'the error page does not resolve the id it refused' ' - sha=$(git -C repos/rob rev-parse HEAD~1) && - robq "url=rob/log/&id=$sha" >tmp && + cgit_rc robrc "url=rob/log/&id=$sha" >tmp && grep "<title>.* @ " tmp && - robq "url=rob/log/&id=%3A/commit" >tmp && + cgit_rc robrc "url=rob/log/&id=%3A/commit" >tmp && grep "^Status: 400" tmp && ! grep "<title>.* @ " tmp ' @@ -816,22 +764,17 @@ test_expect_success 'odd dates and zones in pushed objects render' ' git hash-object -t commit -w --literally --stdin) && git update-ref refs/heads/zone $zone ) && - { - echo "virtual-root=/" && - echo "cache-size=0" && - echo "repo.url=dates" && - echo "repo.path=$PWD/repos/dates/.git" - } >datesrc && + cgitrc_repo datesrc dates && for q in "url=dates/" "url=dates/refs/" "url=dates/tag/&h=early" \ "url=dates/log/&h=far" "url=dates/commit/&h=far" "url=dates/commit/&h=zone" \ "url=dates/atom/&h=far" do - CGIT_CONFIG="$PWD/datesrc" QUERY_STRING="$q" cgit >tmp 2>err && + cgit_rc datesrc "$q" >tmp 2>err && grep "^Status: 200" tmp && ! grep "Unable to complete" tmp && ! grep "Timestamp" err || return 1 done && - CGIT_CONFIG="$PWD/datesrc" QUERY_STRING="url=dates/commit/&h=far" cgit >tmp && + cgit_rc datesrc "url=dates/commit/&h=far" >tmp && grep "1970-01-01" tmp ' @@ -848,7 +791,7 @@ test_expect_success 'a notes ref that names no tree is passed over' ' for q in "url=dates/" "url=dates/log/" "url=dates/refs/" "url=dates/tree/" \ "url=dates/snapshot/dates-base.tar.asc" do - CGIT_CONFIG="$PWD/notesrc" QUERY_STRING="$q" cgit >tmp 2>err && + cgit_rc notesrc "$q" >tmp 2>err && ! grep "Unable to complete" tmp && ! grep "notes tree" err || return 1 done && @@ -861,7 +804,7 @@ test_expect_success 'a scanned defbranch cannot name a path outside the reposito mkdir -p secret && touch -t 200001010000 secret/file && echo "defbranch=../../../../secret/file" >scan/b.git/cgitrc && - CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp && + cgit_rc gitcfgrc "url=" >tmp && grep ">b.git</a>" tmp && ! grep "data-ut=.946" tmp ' diff --git a/tests/t0401-serve.sh b/tests/t0401-serve.sh index 5a2b9b6..341bbb6 100755 --- a/tests/t0401-serve.sh +++ b/tests/t0401-serve.sh @@ -1,11 +1,9 @@ #!/bin/sh # Checks how tools/serve.py, the preview server that runs the built binary and -# hands its output back over HTTP, tells the CGI headers from the body. That -# split once keyed on the wrong blank line and swallowed the document head, -# which left a page that still validated but arrived with no stylesheet and no -# chrome. The cases live in serve-split-check.py beside this file, and nothing -# here touches a repository, so the fixtures stay unbuilt. +# hands its output back over HTTP, tells the CGI headers from the body. The +# cases live in serve-split-check.py beside this file, and nothing here touches +# a repository, so the fixtures stay unbuilt. test_description='Check the preview server splits CGI output correctly' CGIT_TEST_NO_CREATE_REPOS=YesPlease @@ -14,7 +12,6 @@ CGIT_TEST_NO_CREATE_REPOS=YesPlease command -v python3 >/dev/null 2>&1 || { skip_all='Skipping preview server tests: python3 not found' test_done - exit } test_expect_success 'CGI headers and body are split at the first blank line' ' diff --git a/tests/t0501-about-render.sh b/tests/t0501-about-render.sh index 339c5e2..e4eaf5c 100755 --- a/tests/t0501-about-render.sh +++ b/tests/t0501-about-render.sh @@ -13,10 +13,6 @@ CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" -interpreters=$(ext_lua_interpreters 5) -test -z "$interpreters" && - say 'no standalone lua on the path, unit checks skipped' - for lua in $interpreters do test_expect_success "unit checks under $lua" " @@ -77,18 +73,13 @@ test_expect_success 'point cgit at it through the about filter' ' EOF ' -if test "$CGIT_HAS_LUA" -eq 1 +if cgit_lua_probe "$PWD/repos/md/.git" lpeg +then + test_set_prereq CGIT_LUA_LPEG +elif test "$CGIT_HAS_LUA" -eq 1 then - test_set_prereq CGIT_LUA - if cgit_lua_probe "$PWD/repos/md/.git" lpeg - then - test_set_prereq CGIT_LUA_LPEG - else - test_set_prereq CGIT_LUA_NOLPEG - say 'cgit lua has no lpeg, expecting the plain text fallback' - fi -else - say 'cgit built without lua, filter checks through cgit skipped' + test_set_prereq CGIT_LUA_NOLPEG + say 'cgit lua has no lpeg, expecting the plain text fallback' fi test_expect_success CGIT_LUA_LPEG 'the about page renders the markdown' ' @@ -109,13 +100,13 @@ test_expect_success CGIT_LUA_LPEG 'a relative link leads into the tree' ' ' test_expect_success CGIT_LUA_LPEG 'the link stays on the branch shown' ' - cgit_query "url=md/about/&h=next" >tmp && + cgit_url "md/about/&h=next" >tmp && grep "<a href=./md/tree/ROADMAP.md?h=next#plan.>roadmap</a>" tmp ' test_expect_success CGIT_LUA_LPEG 'the about page renders a man page' ' cgit_url "man/about/" >tmp && - grep "<div class=.markdown manpage.><h2 id=.name.>NAME</h2>" tmp && + grep "<div class=.markdown.><h2 id=.name.>NAME</h2>" tmp && grep "tool - does one thing — well" tmp && grep "<strong>-v</strong><br>" tmp && grep "<pre><code>tool -v <file>" tmp && diff --git a/tests/t0502-syntax-highlight.sh b/tests/t0502-syntax-highlight.sh index ced9edd..2ca3c9f 100755 --- a/tests/t0502-syntax-highlight.sh +++ b/tests/t0502-syntax-highlight.sh @@ -7,17 +7,14 @@ # fake lexers found and once with an empty directory so the escaped fallback # is what everything renders through. The run through cgit itself places the # fake lexers beside the config file, which is the probe the filter documents -# for a scintillua directory next to cgitrc. +# for a scintillua directory next to cgitrc, and then points the lexer path +# at nothing to see the page fall back to escaped text. test_description='Check the shipped syntax-highlight extension' CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" -interpreters=$(ext_lua_interpreters 5) -test -z "$interpreters" && - say 'no standalone lua on the path, unit checks skipped' - for lua in $interpreters do test_expect_success "highlighting checks under $lua" " @@ -34,10 +31,6 @@ do " done -test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA -test "$CGIT_HAS_LUA" -eq 1 || - say 'cgit built without lua, filter checks through cgit skipped' - test_expect_success 'create a repository holding a lexable file' ' test_create_repo repos/fake && ( @@ -70,6 +63,16 @@ test_expect_success CGIT_LUA 'bytes between tokens reach the page escaped' ' grep "<span class=.hl-number.>42</span> & " tmp ' +# A missing scintillua leaves the filter with nothing to highlight with, and +# a filter that failed there would take the whole page down with it, so it +# has to hand the source back escaped instead. +test_expect_success CGIT_LUA 'without scintillua the source is served escaped' ' + CGIT_SCINTILLUA_PATH=/nonexistent CGIT_CONFIG="$PWD/cgitrc" \ + QUERY_STRING="url=fake/tree/hello.fake" cgit >tmp && + grep "if 42 & zz" tmp && + ! grep "hl-keyword" tmp +' + test_expect_success CGIT_LUA 'a lone carriage return reaches the page as an escape' ' ( cd repos/fake && diff --git a/tests/t0503-link-commits.sh b/tests/t0503-link-commits.sh index e6e8816..d38093a 100755 --- a/tests/t0503-link-commits.sh +++ b/tests/t0503-link-commits.sh @@ -11,10 +11,6 @@ CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" -interpreters=$(ext_lua_interpreters 5) -test -z "$interpreters" && - say 'no standalone lua on the path, unit checks skipped' - for lua in $interpreters do test_expect_success "unit checks under $lua" " @@ -22,10 +18,6 @@ do " done -test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA -test "$CGIT_HAS_LUA" -eq 1 || - say 'cgit built without lua, filter checks through cgit skipped' - test_expect_success 'create a repository whose commit message holds matches' ' test_create_repo repos/linky && ( diff --git a/tests/t0504-email-avatar.sh b/tests/t0504-email-avatar.sh index ec1f5da..60db6d6 100755 --- a/tests/t0504-email-avatar.sh +++ b/tests/t0504-email-avatar.sh @@ -14,10 +14,6 @@ CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" -interpreters=$(ext_lua_interpreters 5) -test -z "$interpreters" && - say 'no standalone lua on the path, unit checks skipped' - for lua in $interpreters do for script in email-gravatar email-libravatar @@ -48,18 +44,14 @@ test_expect_success 'create a repository with one commit' ' ) ' -if test "$CGIT_HAS_LUA" -eq 1 && - cgit_lua_probe "$PWD/repos/avatar/.git" openssl.digest +if cgit_lua_probe "$PWD/repos/avatar/.git" openssl.digest then test_set_prereq CGIT_LUA_OSSL elif test "$CGIT_HAS_LUA" -eq 1 then test_set_prereq CGIT_LUA_NOOSSL say 'cgit lua has no luaossl, expecting names without icons' -else - say 'cgit built without lua, filter checks through cgit skipped' fi -test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA test_expect_success CGIT_LUA 'point cgit at it through the email filter' ' cat >cgitrc <<-EOF diff --git a/tests/t0505-auth.sh b/tests/t0505-auth.sh index be0f0ce..7dd4a36 100755 --- a/tests/t0505-auth.sh +++ b/tests/t0505-auth.sh @@ -13,10 +13,6 @@ CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" -interpreters=$(ext_lua_interpreters 5) -test -z "$interpreters" && - say 'no standalone lua on the path, unit checks skipped' - for lua in $interpreters do for variant in inline file @@ -38,17 +34,11 @@ test_expect_success 'create a repository with one commit' ' ' if test "$CGIT_HAS_LUA" -eq 1 && - cgit_lua_probe "$PWD/repos/authy/.git" openssl.rand openssl.hmac posix.sys.stat posix.unistd -then - test_set_prereq CGIT_LUA_AUTH -elif test "$CGIT_HAS_LUA" -eq 1 + ! cgit_lua_probe "$PWD/repos/authy/.git" openssl.rand openssl.hmac posix.sys.stat posix.unistd then test_set_prereq CGIT_LUA_NOAUTH say 'cgit lua lacks luaossl or luaposix, expecting protected pages refused' -else - say 'cgit built without lua, checks through cgit skipped' fi -test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA test_expect_success CGIT_LUA 'point cgit at the unedited auth filter' ' cat >cgitrc <<-EOF @@ -79,7 +69,7 @@ test_expect_success CGIT_LUA_NOAUTH 'without the modules a protected repository repo.url=authy repo.path=$PWD/repos/authy/.git EOF - CGIT_CONFIG="$PWD/guardedrc" QUERY_STRING="url=authy/log/" cgit >tmp && + cgit_rc guardedrc "url=authy/log/" >tmp && ! grep "guarded commit" tmp && grep "Unable to complete the request" tmp ' diff --git a/tests/t0506-link-trailers.sh b/tests/t0506-link-trailers.sh index c2e4f91..03db63a 100755 --- a/tests/t0506-link-trailers.sh +++ b/tests/t0506-link-trailers.sh @@ -11,10 +11,6 @@ CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh . "$TEST_OUTPUT_DIRECTORY/extensions/lib.sh" -interpreters=$(ext_lua_interpreters 5) -test -z "$interpreters" && - say 'no standalone lua on the path, unit checks skipped' - for lua in $interpreters do test_expect_success "unit checks under $lua" " @@ -22,10 +18,6 @@ do " done -test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA -test "$CGIT_HAS_LUA" -eq 1 || - say 'cgit built without lua, filter checks through cgit skipped' - test_expect_success 'create a repository whose commit carries linkable trailers' ' test_create_repo repos/linky && ( diff --git a/tests/t0507-hooks.sh b/tests/t0507-hooks.sh index 8a45f81..ec31546 100755 --- a/tests/t0507-hooks.sh +++ b/tests/t0507-hooks.sh @@ -32,8 +32,8 @@ test_expect_success 'set up a cache of two repositories and the index' ' CGIT_CONFIG="$PWD/cachedrc" PATH_INFO="$page" QUERY_STRING= cgit >/dev/null || return 1 done && - CGIT_CONFIG="$PWD/cachedrc" QUERY_STRING="url=a.git/refs/" cgit >/dev/null && - CGIT_CONFIG="$PWD/cachedrc" QUERY_STRING="url=b.git/refs/" cgit >/dev/null && + cgit_rc cachedrc "url=a.git/refs/" >/dev/null && + cgit_rc cachedrc "url=b.git/refs/" >/dev/null && echo writer >slots/0123abcd.lock && cp -R slots pristine && sed -e "s|^cache_root=.*|cache_root='\''$PWD/slots'\''|" \ @@ -115,6 +115,29 @@ do " done +for shell in $shells +do + test_expect_success SANITY "the age hook shows a pusher no error under $shell" " + echo kept >kept && + cp kept plain.git/info/web/last-modified && + test_when_finished 'chmod 755 plain.git/info/web' && + chmod 555 plain.git/info/web && + (cd plain.git && $shell \"\$HOOK_DIRECTORY/post-receive.cgit-age\" 2>../err) && + test_must_be_empty err && + test_cmp kept plain.git/info/web/last-modified + " +done + +test_expect_success SANITY 'the age hook shows no error when it cannot make its directory' ' + git init -q --bare closed.git && + git -C work push -q ../closed.git HEAD:refs/heads/main && + test_when_finished "chmod 755 closed.git/info" && + chmod 555 closed.git/info && + (cd closed.git && sh "$HOOK_DIRECTORY/post-receive.cgit-age" 2>../err) && + test_must_be_empty err && + test_path_is_missing closed.git/info/web +' + # git keeps a shared repository group writable whatever the pusher's umask, # and the next member to push has to be able to replace the file. test_expect_success SANITY 'the age hook keeps a shared directory group writable' ' |
