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