aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests/check-expect.lua
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bmake/unit-tests/check-expect.lua')
-rw-r--r--contrib/bmake/unit-tests/check-expect.lua189
1 files changed, 189 insertions, 0 deletions
diff --git a/contrib/bmake/unit-tests/check-expect.lua b/contrib/bmake/unit-tests/check-expect.lua
new file mode 100644
index 000000000000..2f3adf49baa6
--- /dev/null
+++ b/contrib/bmake/unit-tests/check-expect.lua
@@ -0,0 +1,189 @@
+#! /usr/bin/lua
+-- $NetBSD: check-expect.lua,v 1.17 2025/07/01 05:03:18 rillig Exp $
+
+--[[
+
+usage: lua ./check-expect.lua *.mk
+
+Check that the various 'expect' comments in the .mk files produce the
+expected text in the corresponding .exp file.
+
+# expect: <line>
+ Each <line> must occur in the .exp file.
+ The order in the .mk file must be the same as in the .exp file.
+
+# expect[+-]offset: <message>
+ Each <message> must occur in the .exp file and refer back to the
+ source line in the .mk file.
+ Each such line in the .exp file must have a corresponding expect line
+ in the .mk file.
+ The order in the .mk file must be the same as in the .exp file.
+
+# expect-reset
+ Search the following "expect:" and "expect[+-]offset:" comments
+ from the top of the .exp file again.
+
+# expect-not: <substring>
+ The <substring> must not occur as part of any line in the .exp file.
+
+# expect-not-matches: <pattern>
+ The <pattern> (see https://lua.org/manual/5.4/manual.html#6.4.1)
+ must not occur as part of any line in the .exp file.
+]]
+
+
+local had_errors = false
+---@param fmt string
+local function print_error(fmt, ...)
+ print(fmt:format(...))
+ had_errors = true
+end
+
+
+---@return nil | string[]
+local function load_lines(fname)
+ local lines = {}
+
+ local f = io.open(fname, "r")
+ if f == nil then
+ return nil
+ end
+
+ for line in f:lines() do
+ table.insert(lines, line)
+ end
+ f:close()
+
+ return lines
+end
+
+
+--- @shape ExpLine
+--- @field filename string | nil
+--- @field lineno number | nil
+--- @field text string
+
+
+--- @param lines string[]
+--- @return ExpLine[]
+local function parse_exp(lines)
+ local exp_lines = {}
+ for _, line in ipairs(lines) do
+ local l_filename, l_lineno, l_text =
+ line:match('^make: ([^:]+%.mk):(%d+):%s+(.*)')
+ if not l_filename then
+ l_text = line
+ end
+ l_text = l_text:gsub("^%s+", ""):gsub("%s+$", "")
+ table.insert(exp_lines, {
+ filename = l_filename,
+ lineno = tonumber(l_lineno),
+ text = l_text,
+ })
+ end
+ return exp_lines
+end
+
+---@param exp_lines ExpLine[]
+local function detect_missing_expect_lines(exp_fname, exp_lines, s, e)
+ for i = s, e do
+ local exp_line = exp_lines[i]
+ if exp_line.filename then
+ print_error("error: %s:%d requires in %s:%d: # expect+1: %s",
+ exp_fname, i, exp_line.filename, exp_line.lineno, exp_line.text)
+ end
+ end
+end
+
+local function check_mk(mk_fname)
+ local exp_fname = mk_fname:gsub("%.mk$", ".exp")
+ local mk_lines = load_lines(mk_fname)
+ local exp_raw_lines = load_lines(exp_fname)
+ if exp_raw_lines == nil then
+ return
+ end
+ local exp_lines = parse_exp(exp_raw_lines)
+
+ local exp_it = 1
+
+ for mk_lineno, mk_line in ipairs(mk_lines) do
+
+ local function match(pattern, action)
+ local _, n = mk_line:gsub(pattern, action)
+ if n > 0 then
+ match = function() end
+ end
+ end
+
+ match("^#%s+expect%-not:%s*(.*)", function(text)
+ for exp_lineno, exp_line in ipairs(exp_lines) do
+ if exp_line.text:find(text, 1, true) then
+ print_error("error: %s:%d: %s:%d must not contain '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_lineno, text)
+ end
+ end
+ end)
+
+ match("^#%s+expect%-not%-matches:%s*(.*)", function(pattern)
+ for exp_lineno, exp_line in ipairs(exp_lines) do
+ if exp_line.text:find(pattern) then
+ print_error("error: %s:%d: %s:%d must not match '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_lineno, pattern)
+ end
+ end
+ end)
+
+ match("^#%s+expect:%s*(.*)", function(text)
+ local i = exp_it
+ while i <= #exp_lines and text ~= exp_lines[i].text do
+ i = i + 1
+ end
+ if i <= #exp_lines then
+ detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
+ exp_lines[i].text = ""
+ exp_it = i + 1
+ else
+ print_error("error: %s:%d: '%s:%d+' must contain '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_it, text)
+ end
+ end)
+
+ match("^#%s+expect%-reset$", function()
+ exp_it = 1
+ end)
+
+ match("^#%s+expect([+%-]%d+):%s*(.*)", function(offset, text)
+ local msg_lineno = mk_lineno + tonumber(offset)
+
+ local i = exp_it
+ while i <= #exp_lines and text ~= exp_lines[i].text do
+ i = i + 1
+ end
+
+ if i <= #exp_lines and exp_lines[i].lineno == msg_lineno then
+ detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
+ exp_lines[i].text = ""
+ exp_it = i + 1
+ elseif i <= #exp_lines then
+ print_error("error: %s:%d: expect%+d must be expect%+d",
+ mk_fname, mk_lineno, tonumber(offset),
+ exp_lines[i].lineno - mk_lineno)
+ else
+ print_error("error: %s:%d: %s:%d+ must contain '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_it, text)
+ end
+ end)
+
+ match("^#%s+expect[+%-:]", function()
+ print_error("error: %s:%d: invalid \"expect\" line: %s",
+ mk_fname, mk_lineno, mk_line)
+ end)
+
+ end
+ detect_missing_expect_lines(exp_fname, exp_lines, exp_it, #exp_lines)
+end
+
+for _, fname in ipairs(arg) do
+ check_mk(fname)
+end
+os.exit(not had_errors)