Home | History | Annotate | Line # | Download | only in unit-tests
check-expect.lua revision 1.9
      1  1.1  rillig #!  /usr/bin/lua
      2  1.9  rillig -- $NetBSD: check-expect.lua,v 1.9 2024/07/20 11:05:11 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.9  rillig 
     23  1.9  rillig # expect-not: <substring>
     24  1.9  rillig         The substring must not occur as part of any line of the .exp file.
     25  1.9  rillig 
     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.5  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.7  rillig local function missing(by_location)
     76  1.7  rillig   ---@type {filename: string, lineno: number, location: string, message: string}[]
     77  1.7  rillig   local missing_expectations = {}
     78  1.7  rillig 
     79  1.7  rillig   for location, messages in pairs(by_location) do
     80  1.7  rillig     for _, message in ipairs(messages) do
     81  1.7  rillig       if message ~= "" and location:find(".mk:") then
     82  1.7  rillig         local filename, lineno = location:match("^(%S+):(%d+)$")
     83  1.7  rillig         table.insert(missing_expectations, {
     84  1.7  rillig           filename = filename,
     85  1.7  rillig           lineno = tonumber(lineno),
     86  1.7  rillig           location = location,
     87  1.7  rillig           message = message
     88  1.7  rillig         })
     89  1.7  rillig       end
     90  1.7  rillig     end
     91  1.7  rillig   end
     92  1.7  rillig   table.sort(missing_expectations, function(a, b)
     93  1.7  rillig     if a.filename ~= b.filename then
     94  1.7  rillig       return a.filename < b.filename
     95  1.7  rillig     end
     96  1.7  rillig     return a.lineno < b.lineno
     97  1.7  rillig   end)
     98  1.7  rillig   return missing_expectations
     99  1.7  rillig end
    100  1.7  rillig 
    101  1.7  rillig 
    102  1.1  rillig local function check_mk(mk_fname)
    103  1.1  rillig   local exp_fname = mk_fname:gsub("%.mk$", ".exp")
    104  1.1  rillig   local mk_lines = load_lines(mk_fname)
    105  1.1  rillig   local exp_lines = load_lines(exp_fname)
    106  1.1  rillig   if exp_lines == nil then return end
    107  1.1  rillig   local by_location = collect_lineno_diagnostics(exp_lines)
    108  1.1  rillig   local prev_expect_line = 0
    109  1.1  rillig 
    110  1.1  rillig   for mk_lineno, mk_line in ipairs(mk_lines) do
    111  1.9  rillig 
    112  1.9  rillig     for text in mk_line:gmatch("#%s*expect%-not:%s*(.*)") do
    113  1.9  rillig       local i = 1
    114  1.9  rillig       while i <= #exp_lines and not exp_lines[i]:find(text, 1, true) do
    115  1.9  rillig         i = i + 1
    116  1.9  rillig       end
    117  1.9  rillig       if i <= #exp_lines then
    118  1.9  rillig         print_error("error: %s:%d: %s must not contain '%s'",
    119  1.9  rillig           mk_fname, mk_lineno, exp_fname, text)
    120  1.9  rillig       end
    121  1.9  rillig     end
    122  1.9  rillig 
    123  1.1  rillig     for text in mk_line:gmatch("#%s*expect:%s*(.*)") do
    124  1.1  rillig       local i = prev_expect_line
    125  1.3  rillig       -- As of 2022-04-15, some lines in the .exp files contain trailing
    126  1.3  rillig       -- whitespace.  If possible, this should be avoided by rewriting the
    127  1.3  rillig       -- debug logging.  When done, the gsub can be removed.
    128  1.3  rillig       -- See deptgt-phony.exp lines 14 and 15.
    129  1.3  rillig       while i < #exp_lines and text ~= exp_lines[i + 1]:gsub("%s*$", "") do
    130  1.1  rillig         i = i + 1
    131  1.1  rillig       end
    132  1.1  rillig       if i < #exp_lines then
    133  1.1  rillig         prev_expect_line = i + 1
    134  1.1  rillig       else
    135  1.1  rillig         print_error("error: %s:%d: '%s:%d+' must contain '%s'",
    136  1.1  rillig           mk_fname, mk_lineno, exp_fname, prev_expect_line + 1, text)
    137  1.1  rillig       end
    138  1.1  rillig     end
    139  1.2  rillig     if mk_line:match("^#%s*expect%-reset$") then
    140  1.2  rillig       prev_expect_line = 0
    141  1.2  rillig     end
    142  1.1  rillig 
    143  1.1  rillig     ---@param text string
    144  1.1  rillig     for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do
    145  1.1  rillig       local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset))
    146  1.1  rillig 
    147  1.1  rillig       local found = false
    148  1.1  rillig       if by_location[location] ~= nil then
    149  1.1  rillig         for i, message in ipairs(by_location[location]) do
    150  1.8  rillig           if message == text then
    151  1.1  rillig             by_location[location][i] = ""
    152  1.1  rillig             found = true
    153  1.1  rillig             break
    154  1.1  rillig           end
    155  1.1  rillig         end
    156  1.1  rillig       end
    157  1.1  rillig 
    158  1.1  rillig       if not found then
    159  1.1  rillig         print_error("error: %s:%d: %s must contain '%s'",
    160  1.1  rillig           mk_fname, mk_lineno, exp_fname, text)
    161  1.1  rillig       end
    162  1.1  rillig     end
    163  1.4  rillig   end
    164  1.4  rillig 
    165  1.7  rillig   for _, m in ipairs(missing(by_location)) do
    166  1.7  rillig     print_error("missing: %s: # expect+1: %s", m.location, m.message)
    167  1.1  rillig   end
    168  1.1  rillig end
    169  1.1  rillig 
    170  1.1  rillig for _, fname in ipairs(arg) do
    171  1.1  rillig   check_mk(fname)
    172  1.1  rillig end
    173  1.1  rillig os.exit(not had_errors)
    174