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