-- Unit checks for custom/extensions/syntax-highlight.lua, run under a -- standalone Lua by t0502-syntax-highlight.sh with the script path as the -- first argument. The filter remembers whether the lexers loaded for the -- life of the interpreter, so the two sides of that split are two runs of -- this file. In highlight mode the shell points CGIT_SCINTILLUA_PATH at the -- fake-lexers directory beside this file and every path through a working -- lexer is checked. In nolexers mode it points at an empty directory, so -- the escaped fallback is what everything renders through, and the run ends -- with every directory the filter probes hidden, for a server with no -- Scintillua at all. local test_directory = arg[0]:match("^(.*)/") or "." local h = dofile(test_directory .. "/harness.lua") local script = arg[1] local mode = h.mode("highlight", "nolexers") h.load(script) local function render(filename, text) return h.run({ filename }, { text }) end if mode == "nolexers" then local out, ret = render("hello.fake", "if 42") h.equals("without lexers the source passes through uncoloured", out, "if 42") h.equals("close answers zero", ret, 0) h.equals("and the error log says the lexers would not load", h.error_log(), "[cgit] Unable to load the Scintillua lexers: module 'lexer' not found:\n") out = render("hello.fake", "a & c") h.equals("the fallback escapes markup", out, "a <b> & c") h.equals("and the error log hears of it only once", h.error_log(), "") -- Large output leaves in slices, and a byte lost at a boundary would -- put the line numbers beside it out of step, so the whole escaped -- blob has to come back intact across several slices. out = render("big.fake", string.rep("x", 30000)) h.equals("slicing loses nothing at the boundaries", out, string.rep("x<y>", 30000)) out = render("hello.fake", "a\rb\vc\fd\r\ne") h.equals("the fallback escapes what a browser breaks at", out, "a\\rb\\vc" .. "\\fd\ne") -- The line separator starts on the last byte of the first slice, and -- its other two bytes would land in the next one. out = render("big.fake", string.rep("x", 64 * 1024 - 1) .. "\226\128\168y") h.equals("a separator across a slice boundary is still escaped", out, string.rep("x", 64 * 1024 - 1) .. "\\u2028y") -- With the variable unset and no lexers beside cgitrc or in any directory -- the filter probes, it finds nothing to load, the usual case on a server -- without Scintillua. local file = assert(io.open(script, "rb")) local source = file:read("*a") file:close() for dir in source:match("local scintillua_dirs = (%b{})"):gmatch('"([^"]+)"') do h.redirect_file(dir .. "/lexer.lua", "/nonexistent/lexer.lua") end h.setenv("CGIT_SCINTILLUA_PATH", nil) h.setenv("CGIT_CONFIG", nil) h.load(script) out = render("hello.fake", "a ") h.equals("with no lexers found the source is served escaped", out, "a <b>") h.equals("and the error log says none were found", h.error_log(), "[cgit] Unable to find the Scintillua lexers, so source is shown uncoloured\n") h.finish() end local out, ret = render("hello.fake", "if 42 'hi' & zz") h.equals("loaded lexers leave the error log empty", h.error_log(), "") h.contains("keywords are wrapped in their class", out, "if") h.contains("numbers are wrapped in their class", out, "42") h.contains("a dotted tag maps by its first component", out, "'hi'") h.contains("bytes between tokens are escaped", out, " & ") h.contains("later tokens still highlight", out, "zz") h.equals("close answers zero", ret, 0) out = render("hello.fake", "'&'") h.equals("markup inside a token is escaped", out, "'<b>&'") out = render("hello.fake", "$color :hover") h.contains("a css property takes the number class", out, "$color") h.contains("a css pseudo-class takes the function class", out, ":hover") out = h.run({ "split.fake" }, { "i", "f" }) h.equals("writes are joined before lexing", out, "if") -- The short lexer tags four bytes and stops, and every byte after it still -- has to reach the page. out = render("cut.short", "abcd") h.equals("a lexer stopping early leaves an escaped tail", out, "abcd<ef>") out = render("boom.badlex", "some ") h.equals("a lexer that raises falls back to escaped text", out, "some <code>") out = render("HELLO.FAKE", "if 42") h.contains("an extension in capitals finds its lexer", out, "if") out = render("post-receive", "#!/usr/bin/fake\nif") h.contains("a shebang picks the lexer of a file with no extension", out, "if") out = render("tool.py", "import ") h.equals("an extension with no lexer serves escaped text", out, "import <x>") out = render("noextension", "words ") h.equals("a file with no extension serves escaped text", out, "words <here>") -- The line numbers beside the code count line feeds alone, so each character -- a browser would start a line at is an escape, and CR LF reaches the page as -- the line feed a browser would have made of it. out = render("hello.fake", "if\r42\vzz\f'a'\r\nif") h.equals("what a browser breaks at is escaped between tokens", out, "if\\r" .. "42\\v" .. "zz\\f" .. "'a'\nif") -- The fake lexer hands back every byte of a separator as a token of its own. out = render("hello.fake", "a\194\133b\226\128\168c\226\128\169d") h.equals("a separator split between tokens is escaped whole", out, "a\\u0085" .. "b\\u2028" .. "c\\u2029" .. "d") out = render("big.fake", string.rep("a", 512 * 1024 + 1)) h.excludes("an oversized blob skips the lexing pass", out, "