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