-- Unit checks for custom/extensions/link-commits.lua, run under a standalone -- Lua by t0503-link-commits.sh with the script path as the argument. The -- shipped configuration links bare object names to ./?id= and leaves issue -- marks as text. The rest runs with the commented-out issue rule switched on, -- linking marks like #123 to bugs.example.com, and checks that shape, the -- length bounds and frontier on hash recognition, and the rule that no run -- of text is ever wrapped twice. local test_directory = arg[0]:match("^(.*)/") or "." local h = dofile(test_directory .. "/harness.lua") local script = arg[1] h.load(script) local function render(text) return h.run({}, { text }) end local out = render("see #123 near deadbee") h.equals("an unedited copy leaves issue marks as text", out, "see #123 near deadbee") h.load_with(script, { { "-- { pattern = \"#(%d+)\"", "{ pattern = \"#(%d+)\"" } }) local ret out, ret = render("see #123 for details") h.equals("issue mark becomes a link amid its text", out, "see #123 for details") h.equals("close answers zero", ret, 0) out = render("fixed in deadbee") h.contains("seven hex characters link", out, "deadbee") out = render("fixed in deadbe") h.equals("six hex characters stay text", out, "fixed in deadbe") out = render("fixed in 1234567") h.contains("an all digit run still links", out, "1234567") local sha1 = string.rep("0123456789abcdef", 2) .. "01234567" out = render("commit " .. sha1) h.contains("a full sha1 name links", out, "'./?id=" .. sha1 .. "'") local sha256 = string.rep("0123456789abcdef", 4) out = render("commit " .. sha256) h.contains("a full sha256 name links", out, "'./?id=" .. sha256 .. "'") out = render("commit " .. sha256 .. "0") h.equals("sixty five hex characters stay text", out, "commit " .. sha256 .. "0") out = render("prefixdeadbeef") h.equals("a hex run inside a word stays text", out, "prefixdeadbeef") out = render("deadbeefy") h.equals("a hex run ending inside a word stays text", out, "deadbeefy") -- The issue rule wins the overlap because it starts first, and the digit run -- inside it must not be wrapped a second time. out = render("see #1234567 for details") h.contains("the issue rule wins an overlap", out, "#1234567") h.excludes("the overlapped hash is dropped", out, "./?id=") out = render("#12 and cafebabe12") h.contains("both rules fire on one message", out, "?bug=12'>#12") h.contains("the object link also fires", out, "cafebabe12") -- One request is delivered as several writes, and a match split across two of -- them has to be seen whole. out = h.run({}, { "see #45", "6 and be done" }) h.contains("a match split across writes still links", out, "?bug=456'>#456") out, ret = render("") h.equals("an empty message stays empty", out, "") h.equals("an empty message still answers zero", ret, 0) out = render("plain words only") h.equals("a message with no matches passes through", out, "plain words only") -- cgit hands the message over already escaped. out = render("a & b <deadbeef>") h.equals("escaped text keeps its entities around a link", out, "a & b <deadbeef>") -- A rule broad enough to capture a quote or a percent sign still has to -- leave a url that stays inside its attribute and means what it says. h.load_with(script, { { "local rules = {", "local rules = { { pattern = \"T:(%S+)\", url = \"https://t/%s\" }," } }) out = render("T:a'b%c") h.equals("a captured value is percent-encoded into the url", out, "T:a'b%c") -- A malformed pattern is the operator's error, so that rule is skipped and -- the others still run. h.load_with(script, { { "local rules = {", "local rules = { { pattern = \"(%d+\", url = \"https://x/%s\" }," } }) out = render("fixed 42 in deadbee") h.equals("a malformed rule is skipped and object names still link", out, "fixed 42 in deadbee") -- Two rules matching at the same place leave it to the one listed first. h.load_with(script, { { "local rules = {", "local rules = { { pattern = \"#(%d+)\", url = \"https://a/%s\" }, { pattern = \"#(%d%d)\", url = \"https://b/%s\" }," } }) out = render("#12") h.equals("the rule listed first wins a tie", out, "#12") -- A pattern that can match nothing still moves the scan on, or the page -- would never finish. h.load_with(script, { { "local rules = {", "local rules = { { pattern = \"x*\", url = \"https://x/%s\" }," } }) out = render("abc") h.equals("a rule matching the empty string still reaches the end", (out:gsub("<[^>]*>", "")), "abc") h.load_with(script, { { "local rules = {", "local rules = { { pattern = \"RFC%d+\", url = \"https://r/%s\" }," } }) out = render("see RFC123") h.equals("a rule with no capture puts the whole match in the url", out, "see RFC123") h.load_with(script, { { "enabled = true,", "enabled = false," } }) out = render("fixed in deadbee") h.equals("object names can be left unlinked", out, "fixed in deadbee") h.load_with(script, { { "-- { pattern = \"CVE", "{ pattern = \"CVE" }, { "-- url = \"https://www.cve.org", " url = \"https://www.cve.org" }, { "-- { pattern = \"!", "{ pattern = \"!" }, { "-- url = \"https://gitlab", " url = \"https://gitlab" }, { "-- { pattern = \"RFC", "{ pattern = \"RFC" }, { "-- url = \"https://www.rfc-editor", " url = \"https://www.rfc-editor" }, }) out = render("CVE-2024-12345, !42 and RFC 9110") h.equals("the shipped examples link what they describe", out, "CVE-2024-12345, " .. "!42 and " .. "RFC 9110") -- A message near cgit's size limit with a match every few bytes still has -- to come out in linear time. h.load_with(script, { { "-- { pattern = \"#(%d+)\"", "{ pattern = \"#(%d+)\"" } }) h.stays_linear("a long message full of matches is linked in linear time", function(text) return (render(text)) end, function(count) return string.rep("deadbeef #1 ", count) end, 10000) h.finish()