check-expect.lua revision 1.16 1 1.1 rillig #! /usr/bin/lua
2 1.16 rillig -- $NetBSD: check-expect.lua,v 1.16 2025/07/01 04:24:20 rillig Exp $
3 1.1 rillig
4 1.1 rillig --[[
5 1.1 rillig
6 1.1 rillig usage: lua ./check-expect.lua *.mk
7 1.1 rillig
8 1.4 rillig Check that the various 'expect' comments in the .mk files produce the
9 1.4 rillig expected text in the corresponding .exp file.
10 1.1 rillig
11 1.4 rillig # expect: <line>
12 1.15 rillig Each <line> must occur in the .exp file.
13 1.15 rillig The order in the .mk file must be the same as in the .exp file.
14 1.15 rillig
15 1.15 rillig # expect[+-]offset: <message>
16 1.15 rillig Each <message> must occur in the .exp file and refer back to the
17 1.15 rillig source line in the .mk file.
18 1.15 rillig Each such line in the .exp file must have a corresponding expect line
19 1.4 rillig in the .mk file.
20 1.15 rillig The order in the .mk file must be the same as in the .exp file.
21 1.4 rillig
22 1.4 rillig # expect-reset
23 1.15 rillig Search the following "expect:" and "expect[+-]offset:" comments
24 1.15 rillig from the top of the .exp file again.
25 1.9 rillig
26 1.9 rillig # expect-not: <substring>
27 1.15 rillig The <substring> must not occur as part of any line in the .exp file.
28 1.9 rillig
29 1.11 rillig # expect-not-matches: <pattern>
30 1.15 rillig The <pattern> (see https://lua.org/manual/5.4/manual.html#6.4.1)
31 1.15 rillig must not occur as part of any line in the .exp file.
32 1.1 rillig ]]
33 1.1 rillig
34 1.1 rillig
35 1.1 rillig local had_errors = false
36 1.1 rillig ---@param fmt string
37 1.15 rillig local function print_error(fmt, ...)
38 1.1 rillig print(fmt:format(...))
39 1.1 rillig had_errors = true
40 1.1 rillig end
41 1.1 rillig
42 1.1 rillig
43 1.1 rillig ---@return nil | string[]
44 1.1 rillig local function load_lines(fname)
45 1.1 rillig local lines = {}
46 1.1 rillig
47 1.1 rillig local f = io.open(fname, "r")
48 1.15 rillig if f == nil then
49 1.15 rillig return nil
50 1.15 rillig end
51 1.1 rillig
52 1.1 rillig for line in f:lines() do
53 1.1 rillig table.insert(lines, line)
54 1.1 rillig end
55 1.1 rillig f:close()
56 1.1 rillig
57 1.1 rillig return lines
58 1.1 rillig end
59 1.1 rillig
60 1.1 rillig
61 1.15 rillig --- @shape ExpLine
62 1.15 rillig --- @field filename string | nil
63 1.15 rillig --- @field lineno number | nil
64 1.15 rillig --- @field text string
65 1.15 rillig
66 1.15 rillig
67 1.15 rillig --- @param lines string[]
68 1.15 rillig --- @return ExpLine[]
69 1.15 rillig local function parse_exp(lines)
70 1.15 rillig local exp_lines = {}
71 1.15 rillig for _, line in ipairs(lines) do
72 1.15 rillig local l_filename, l_lineno, l_text =
73 1.15 rillig line:match('^make: ([^:]+%.mk):(%d+):%s+(.*)')
74 1.15 rillig if not l_filename then
75 1.15 rillig l_text = line
76 1.15 rillig end
77 1.15 rillig l_text = l_text:gsub("^%s+", ""):gsub("%s+$", "")
78 1.15 rillig table.insert(exp_lines, {
79 1.15 rillig filename = l_filename,
80 1.15 rillig lineno = tonumber(l_lineno),
81 1.15 rillig text = l_text,
82 1.15 rillig })
83 1.15 rillig end
84 1.15 rillig return exp_lines
85 1.15 rillig end
86 1.15 rillig
87 1.15 rillig ---@param exp_lines ExpLine[]
88 1.15 rillig local function detect_missing_expect_lines(exp_fname, exp_lines, s, e)
89 1.15 rillig for i = s, e do
90 1.15 rillig local exp_line = exp_lines[i]
91 1.15 rillig if exp_line.filename then
92 1.15 rillig print_error("error: %s:%d requires in %s:%d: # expect+1: %s",
93 1.15 rillig exp_fname, i, exp_line.filename, exp_line.lineno, exp_line.text)
94 1.14 rillig end
95 1.14 rillig end
96 1.7 rillig end
97 1.7 rillig
98 1.1 rillig local function check_mk(mk_fname)
99 1.1 rillig local exp_fname = mk_fname:gsub("%.mk$", ".exp")
100 1.1 rillig local mk_lines = load_lines(mk_fname)
101 1.15 rillig local exp_raw_lines = load_lines(exp_fname)
102 1.15 rillig if exp_raw_lines == nil then
103 1.15 rillig return
104 1.15 rillig end
105 1.15 rillig local exp_lines = parse_exp(exp_raw_lines)
106 1.15 rillig
107 1.15 rillig local exp_it = 1
108 1.1 rillig
109 1.1 rillig for mk_lineno, mk_line in ipairs(mk_lines) do
110 1.9 rillig
111 1.16 rillig mk_line:gsub("^#%s+expect%-not:%s*(.*)", function(text)
112 1.15 rillig for exp_lineno, exp_line in ipairs(exp_lines) do
113 1.15 rillig if exp_line.text:find(text, 1, true) then
114 1.15 rillig print_error("error: %s:%d: %s:%d must not contain '%s'",
115 1.15 rillig mk_fname, mk_lineno, exp_fname, exp_lineno, text)
116 1.15 rillig end
117 1.9 rillig end
118 1.16 rillig end)
119 1.9 rillig
120 1.16 rillig mk_line:gsub("^#%s+expect%-not%-matches:%s*(.*)", function(pattern)
121 1.15 rillig for exp_lineno, exp_line in ipairs(exp_lines) do
122 1.15 rillig if exp_line.text:find(pattern) then
123 1.15 rillig print_error("error: %s:%d: %s:%d must not match '%s'",
124 1.15 rillig mk_fname, mk_lineno, exp_fname, exp_lineno, pattern)
125 1.15 rillig end
126 1.11 rillig end
127 1.16 rillig end)
128 1.11 rillig
129 1.16 rillig mk_line:gsub("^#%s+expect:%s*(.*)", function(text)
130 1.15 rillig local i = exp_it
131 1.15 rillig while i <= #exp_lines and text ~= exp_lines[i].text do
132 1.1 rillig i = i + 1
133 1.1 rillig end
134 1.15 rillig if i <= #exp_lines then
135 1.15 rillig detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
136 1.15 rillig exp_lines[i].text = ""
137 1.15 rillig exp_it = i + 1
138 1.1 rillig else
139 1.1 rillig print_error("error: %s:%d: '%s:%d+' must contain '%s'",
140 1.15 rillig mk_fname, mk_lineno, exp_fname, exp_it, text)
141 1.1 rillig end
142 1.16 rillig end)
143 1.15 rillig
144 1.2 rillig if mk_line:match("^#%s*expect%-reset$") then
145 1.15 rillig exp_it = 1
146 1.2 rillig end
147 1.1 rillig
148 1.16 rillig mk_line:gsub("^#%s+expect([+%-]%d+):%s*(.*)", function(offset, text)
149 1.15 rillig local msg_lineno = mk_lineno + tonumber(offset)
150 1.1 rillig
151 1.15 rillig local i = exp_it
152 1.15 rillig while i <= #exp_lines and text ~= exp_lines[i].text do
153 1.15 rillig i = i + 1
154 1.1 rillig end
155 1.1 rillig
156 1.15 rillig if i <= #exp_lines and exp_lines[i].lineno == msg_lineno then
157 1.15 rillig detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
158 1.15 rillig exp_lines[i].text = ""
159 1.15 rillig exp_it = i + 1
160 1.15 rillig elseif i <= #exp_lines then
161 1.15 rillig print_error("error: %s:%d: expect%+d must be expect%+d",
162 1.15 rillig mk_fname, mk_lineno, tonumber(offset),
163 1.15 rillig exp_lines[i].lineno - mk_lineno)
164 1.15 rillig else
165 1.15 rillig print_error("error: %s:%d: %s:%d+ must contain '%s'",
166 1.15 rillig mk_fname, mk_lineno, exp_fname, exp_it, text)
167 1.1 rillig end
168 1.16 rillig end)
169 1.4 rillig end
170 1.15 rillig detect_missing_expect_lines(exp_fname, exp_lines, exp_it, #exp_lines)
171 1.1 rillig end
172 1.1 rillig
173 1.1 rillig for _, fname in ipairs(arg) do
174 1.1 rillig check_mk(fname)
175 1.1 rillig end
176 1.1 rillig os.exit(not had_errors)
177