Home | History | Annotate | Line # | Download | only in unit-tests
check-expect.lua revision 1.2
      1 #!  /usr/bin/lua
      2 -- $NetBSD: check-expect.lua,v 1.2 2022/01/29 00:52:53 rillig Exp $
      3 
      4 --[[
      5 
      6 usage: lua ./check-expect.lua *.mk
      7 
      8 Check that each text from an '# expect: ...' comment in the .mk source files
      9 occurs in the corresponding .exp file, in the same order as in the .mk file.
     10 
     11 Check that each text from an '# expect[+-]offset: ...' comment in the .mk
     12 source files occurs in the corresponding .exp file and refers back to the
     13 correct line in the .mk file.
     14 
     15 ]]
     16 
     17 
     18 local had_errors = false
     19 ---@param fmt string
     20 function print_error(fmt, ...)
     21   print(fmt:format(...))
     22   had_errors = true
     23 end
     24 
     25 
     26 ---@return nil | string[]
     27 local function load_lines(fname)
     28   local lines = {}
     29 
     30   local f = io.open(fname, "r")
     31   if f == nil then return nil end
     32 
     33   for line in f:lines() do
     34     table.insert(lines, line)
     35   end
     36   f:close()
     37 
     38   return lines
     39 end
     40 
     41 
     42 ---@param exp_lines string[]
     43 local function collect_lineno_diagnostics(exp_lines)
     44   ---@type table<string, string[]>
     45   local by_location = {}
     46 
     47   for _, line in ipairs(exp_lines) do
     48     ---@type string | nil, string, string
     49     local l_fname, l_lineno, l_msg =
     50       line:match("^make: \"([^\"]+)\" line (%d+): (.*)")
     51     if l_fname ~= nil then
     52       local location = ("%s:%d"):format(l_fname, l_lineno)
     53       if by_location[location] == nil then
     54         by_location[location] = {}
     55       end
     56       table.insert(by_location[location], l_msg)
     57     end
     58   end
     59 
     60   return by_location
     61 end
     62 
     63 
     64 local function check_mk(mk_fname)
     65   local exp_fname = mk_fname:gsub("%.mk$", ".exp")
     66   local mk_lines = load_lines(mk_fname)
     67   local exp_lines = load_lines(exp_fname)
     68   if exp_lines == nil then return end
     69   local by_location = collect_lineno_diagnostics(exp_lines)
     70   local prev_expect_line = 0
     71 
     72   for mk_lineno, mk_line in ipairs(mk_lines) do
     73     for text in mk_line:gmatch("#%s*expect:%s*(.*)") do
     74       local i = prev_expect_line
     75       while i < #exp_lines and text ~= exp_lines[i + 1] do
     76         i = i + 1
     77       end
     78       if i < #exp_lines then
     79         prev_expect_line = i + 1
     80       else
     81         print_error("error: %s:%d: '%s:%d+' must contain '%s'",
     82           mk_fname, mk_lineno, exp_fname, prev_expect_line + 1, text)
     83       end
     84     end
     85     if mk_line:match("^#%s*expect%-reset$") then
     86       prev_expect_line = 0
     87     end
     88 
     89     ---@param text string
     90     for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do
     91       local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset))
     92 
     93       local found = false
     94       if by_location[location] ~= nil then
     95         for i, message in ipairs(by_location[location]) do
     96           if message ~= "" and message:find(text, 1, true) then
     97             by_location[location][i] = ""
     98             found = true
     99             break
    100           end
    101         end
    102       end
    103 
    104       if not found then
    105         print_error("error: %s:%d: %s must contain '%s'",
    106           mk_fname, mk_lineno, exp_fname, text)
    107       end
    108     end
    109   end
    110 end
    111 
    112 for _, fname in ipairs(arg) do
    113   check_mk(fname)
    114 end
    115 os.exit(not had_errors)
    116