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