check-expect.lua revision 1.4 1 1.1 rillig #! /usr/bin/lua
2 1.4 rillig -- $NetBSD: check-expect.lua,v 1.4 2023/05/09 19:43:12 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.4 rillig
23 1.4 rillig # expect-all
24 1.4 rillig Each message from the .exp file that can be matched by an
25 1.4 rillig 'expect[+-]offset' comment must actually be matched.
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.1 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.1 rillig local function check_mk(mk_fname)
76 1.1 rillig local exp_fname = mk_fname:gsub("%.mk$", ".exp")
77 1.1 rillig local mk_lines = load_lines(mk_fname)
78 1.1 rillig local exp_lines = load_lines(exp_fname)
79 1.1 rillig if exp_lines == nil then return end
80 1.1 rillig local by_location = collect_lineno_diagnostics(exp_lines)
81 1.1 rillig local prev_expect_line = 0
82 1.4 rillig local match_all = false
83 1.1 rillig
84 1.1 rillig for mk_lineno, mk_line in ipairs(mk_lines) do
85 1.1 rillig for text in mk_line:gmatch("#%s*expect:%s*(.*)") do
86 1.1 rillig local i = prev_expect_line
87 1.3 rillig -- As of 2022-04-15, some lines in the .exp files contain trailing
88 1.3 rillig -- whitespace. If possible, this should be avoided by rewriting the
89 1.3 rillig -- debug logging. When done, the gsub can be removed.
90 1.3 rillig -- See deptgt-phony.exp lines 14 and 15.
91 1.3 rillig while i < #exp_lines and text ~= exp_lines[i + 1]:gsub("%s*$", "") do
92 1.1 rillig i = i + 1
93 1.1 rillig end
94 1.1 rillig if i < #exp_lines then
95 1.1 rillig prev_expect_line = i + 1
96 1.1 rillig else
97 1.1 rillig print_error("error: %s:%d: '%s:%d+' must contain '%s'",
98 1.1 rillig mk_fname, mk_lineno, exp_fname, prev_expect_line + 1, text)
99 1.1 rillig end
100 1.1 rillig end
101 1.2 rillig if mk_line:match("^#%s*expect%-reset$") then
102 1.2 rillig prev_expect_line = 0
103 1.2 rillig end
104 1.1 rillig
105 1.1 rillig ---@param text string
106 1.1 rillig for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do
107 1.1 rillig local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset))
108 1.1 rillig
109 1.1 rillig local found = false
110 1.1 rillig if by_location[location] ~= nil then
111 1.1 rillig for i, message in ipairs(by_location[location]) do
112 1.1 rillig if message ~= "" and message:find(text, 1, true) then
113 1.1 rillig by_location[location][i] = ""
114 1.1 rillig found = true
115 1.1 rillig break
116 1.1 rillig end
117 1.1 rillig end
118 1.1 rillig end
119 1.1 rillig
120 1.1 rillig if not found then
121 1.1 rillig print_error("error: %s:%d: %s must contain '%s'",
122 1.1 rillig mk_fname, mk_lineno, exp_fname, text)
123 1.1 rillig end
124 1.1 rillig end
125 1.4 rillig
126 1.4 rillig if mk_line:match("^#%s*expect%-all$") then
127 1.4 rillig match_all = true
128 1.4 rillig end
129 1.4 rillig end
130 1.4 rillig
131 1.4 rillig if match_all then
132 1.4 rillig -- XXX: The messages are not sorted in any meaningful way.
133 1.4 rillig for location, messages in pairs(by_location) do
134 1.4 rillig for _, message in ipairs(messages) do
135 1.4 rillig if message ~= "" then
136 1.4 rillig print_error("error: %s: missing 'expect' comment for '%s'",
137 1.4 rillig location, message)
138 1.4 rillig end
139 1.4 rillig end
140 1.4 rillig end
141 1.1 rillig end
142 1.1 rillig end
143 1.1 rillig
144 1.1 rillig for _, fname in ipairs(arg) do
145 1.1 rillig check_mk(fname)
146 1.1 rillig end
147 1.1 rillig os.exit(not had_errors)
148