check-expect.lua revision 1.9 1 1.1 rillig #! /usr/bin/lua
2 1.9 rillig -- $NetBSD: check-expect.lua,v 1.9 2024/07/20 11:05:11 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.4 rillig All of these lines must occur in the .exp file, in the same order as
13 1.4 rillig in the .mk file.
14 1.4 rillig
15 1.4 rillig # expect-reset
16 1.4 rillig Search the following 'expect:' comments from the top of the .exp
17 1.4 rillig file again.
18 1.4 rillig
19 1.4 rillig # expect[+-]offset: <message>
20 1.4 rillig Each message must occur in the .exp file and refer back to the
21 1.4 rillig source line in the .mk file.
22 1.9 rillig
23 1.9 rillig # expect-not: <substring>
24 1.9 rillig The substring must not occur as part of any line of the .exp file.
25 1.9 rillig
26 1.1 rillig ]]
27 1.1 rillig
28 1.1 rillig
29 1.1 rillig local had_errors = false
30 1.1 rillig ---@param fmt string
31 1.1 rillig function print_error(fmt, ...)
32 1.1 rillig print(fmt:format(...))
33 1.1 rillig had_errors = true
34 1.1 rillig end
35 1.1 rillig
36 1.1 rillig
37 1.1 rillig ---@return nil | string[]
38 1.1 rillig local function load_lines(fname)
39 1.1 rillig local lines = {}
40 1.1 rillig
41 1.1 rillig local f = io.open(fname, "r")
42 1.1 rillig if f == nil then return nil end
43 1.1 rillig
44 1.1 rillig for line in f:lines() do
45 1.1 rillig table.insert(lines, line)
46 1.1 rillig end
47 1.1 rillig f:close()
48 1.1 rillig
49 1.1 rillig return lines
50 1.1 rillig end
51 1.1 rillig
52 1.1 rillig
53 1.1 rillig ---@param exp_lines string[]
54 1.1 rillig local function collect_lineno_diagnostics(exp_lines)
55 1.1 rillig ---@type table<string, string[]>
56 1.1 rillig local by_location = {}
57 1.1 rillig
58 1.1 rillig for _, line in ipairs(exp_lines) do
59 1.1 rillig ---@type string | nil, string, string
60 1.1 rillig local l_fname, l_lineno, l_msg =
61 1.5 rillig line:match('^make: "([^"]+)" line (%d+): (.*)')
62 1.1 rillig if l_fname ~= nil then
63 1.1 rillig local location = ("%s:%d"):format(l_fname, l_lineno)
64 1.1 rillig if by_location[location] == nil then
65 1.1 rillig by_location[location] = {}
66 1.1 rillig end
67 1.1 rillig table.insert(by_location[location], l_msg)
68 1.1 rillig end
69 1.1 rillig end
70 1.1 rillig
71 1.1 rillig return by_location
72 1.1 rillig end
73 1.1 rillig
74 1.1 rillig
75 1.7 rillig local function missing(by_location)
76 1.7 rillig ---@type {filename: string, lineno: number, location: string, message: string}[]
77 1.7 rillig local missing_expectations = {}
78 1.7 rillig
79 1.7 rillig for location, messages in pairs(by_location) do
80 1.7 rillig for _, message in ipairs(messages) do
81 1.7 rillig if message ~= "" and location:find(".mk:") then
82 1.7 rillig local filename, lineno = location:match("^(%S+):(%d+)$")
83 1.7 rillig table.insert(missing_expectations, {
84 1.7 rillig filename = filename,
85 1.7 rillig lineno = tonumber(lineno),
86 1.7 rillig location = location,
87 1.7 rillig message = message
88 1.7 rillig })
89 1.7 rillig end
90 1.7 rillig end
91 1.7 rillig end
92 1.7 rillig table.sort(missing_expectations, function(a, b)
93 1.7 rillig if a.filename ~= b.filename then
94 1.7 rillig return a.filename < b.filename
95 1.7 rillig end
96 1.7 rillig return a.lineno < b.lineno
97 1.7 rillig end)
98 1.7 rillig return missing_expectations
99 1.7 rillig end
100 1.7 rillig
101 1.7 rillig
102 1.1 rillig local function check_mk(mk_fname)
103 1.1 rillig local exp_fname = mk_fname:gsub("%.mk$", ".exp")
104 1.1 rillig local mk_lines = load_lines(mk_fname)
105 1.1 rillig local exp_lines = load_lines(exp_fname)
106 1.1 rillig if exp_lines == nil then return end
107 1.1 rillig local by_location = collect_lineno_diagnostics(exp_lines)
108 1.1 rillig local prev_expect_line = 0
109 1.1 rillig
110 1.1 rillig for mk_lineno, mk_line in ipairs(mk_lines) do
111 1.9 rillig
112 1.9 rillig for text in mk_line:gmatch("#%s*expect%-not:%s*(.*)") do
113 1.9 rillig local i = 1
114 1.9 rillig while i <= #exp_lines and not exp_lines[i]:find(text, 1, true) do
115 1.9 rillig i = i + 1
116 1.9 rillig end
117 1.9 rillig if i <= #exp_lines then
118 1.9 rillig print_error("error: %s:%d: %s must not contain '%s'",
119 1.9 rillig mk_fname, mk_lineno, exp_fname, text)
120 1.9 rillig end
121 1.9 rillig end
122 1.9 rillig
123 1.1 rillig for text in mk_line:gmatch("#%s*expect:%s*(.*)") do
124 1.1 rillig local i = prev_expect_line
125 1.3 rillig -- As of 2022-04-15, some lines in the .exp files contain trailing
126 1.3 rillig -- whitespace. If possible, this should be avoided by rewriting the
127 1.3 rillig -- debug logging. When done, the gsub can be removed.
128 1.3 rillig -- See deptgt-phony.exp lines 14 and 15.
129 1.3 rillig while i < #exp_lines and text ~= exp_lines[i + 1]:gsub("%s*$", "") do
130 1.1 rillig i = i + 1
131 1.1 rillig end
132 1.1 rillig if i < #exp_lines then
133 1.1 rillig prev_expect_line = i + 1
134 1.1 rillig else
135 1.1 rillig print_error("error: %s:%d: '%s:%d+' must contain '%s'",
136 1.1 rillig mk_fname, mk_lineno, exp_fname, prev_expect_line + 1, text)
137 1.1 rillig end
138 1.1 rillig end
139 1.2 rillig if mk_line:match("^#%s*expect%-reset$") then
140 1.2 rillig prev_expect_line = 0
141 1.2 rillig end
142 1.1 rillig
143 1.1 rillig ---@param text string
144 1.1 rillig for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do
145 1.1 rillig local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset))
146 1.1 rillig
147 1.1 rillig local found = false
148 1.1 rillig if by_location[location] ~= nil then
149 1.1 rillig for i, message in ipairs(by_location[location]) do
150 1.8 rillig if message == text then
151 1.1 rillig by_location[location][i] = ""
152 1.1 rillig found = true
153 1.1 rillig break
154 1.1 rillig end
155 1.1 rillig end
156 1.1 rillig end
157 1.1 rillig
158 1.1 rillig if not found then
159 1.1 rillig print_error("error: %s:%d: %s must contain '%s'",
160 1.1 rillig mk_fname, mk_lineno, exp_fname, text)
161 1.1 rillig end
162 1.1 rillig end
163 1.4 rillig end
164 1.4 rillig
165 1.7 rillig for _, m in ipairs(missing(by_location)) do
166 1.7 rillig print_error("missing: %s: # expect+1: %s", m.location, m.message)
167 1.1 rillig end
168 1.1 rillig end
169 1.1 rillig
170 1.1 rillig for _, fname in ipairs(arg) do
171 1.1 rillig check_mk(fname)
172 1.1 rillig end
173 1.1 rillig os.exit(not had_errors)
174