summaryrefslogtreecommitdiff
path: root/usr.bin/make/unit-tests/check-expect.lua
blob: 8d35dfb5439a33aff2e240a58cd4b2c0d06ed0d4 (plain)
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)