Home | History | Annotate | Line # | Download | only in unit-tests
check-expect.lua revision 1.16
      1   1.1  rillig #!  /usr/bin/lua
      2  1.16  rillig -- $NetBSD: check-expect.lua,v 1.16 2025/07/01 04:24:20 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.15  rillig         Each <line> must occur in the .exp file.
     13  1.15  rillig         The order in the .mk file must be the same as in the .exp file.
     14  1.15  rillig 
     15  1.15  rillig # expect[+-]offset: <message>
     16  1.15  rillig         Each <message> must occur in the .exp file and refer back to the
     17  1.15  rillig         source line in the .mk file.
     18  1.15  rillig         Each such line in the .exp file must have a corresponding expect line
     19   1.4  rillig         in the .mk file.
     20  1.15  rillig         The order in the .mk file must be the same as in the .exp file.
     21   1.4  rillig 
     22   1.4  rillig # expect-reset
     23  1.15  rillig         Search the following "expect:" and "expect[+-]offset:" comments
     24  1.15  rillig         from the top of the .exp file again.
     25   1.9  rillig 
     26   1.9  rillig # expect-not: <substring>
     27  1.15  rillig         The <substring> must not occur as part of any line in the .exp file.
     28   1.9  rillig 
     29  1.11  rillig # expect-not-matches: <pattern>
     30  1.15  rillig         The <pattern> (see https://lua.org/manual/5.4/manual.html#6.4.1)
     31  1.15  rillig         must not occur as part of any line in the .exp file.
     32   1.1  rillig ]]
     33   1.1  rillig 
     34   1.1  rillig 
     35   1.1  rillig local had_errors = false
     36   1.1  rillig ---@param fmt string
     37  1.15  rillig local function print_error(fmt, ...)
     38   1.1  rillig   print(fmt:format(...))
     39   1.1  rillig   had_errors = true
     40   1.1  rillig end
     41   1.1  rillig 
     42   1.1  rillig 
     43   1.1  rillig ---@return nil | string[]
     44   1.1  rillig local function load_lines(fname)
     45   1.1  rillig   local lines = {}
     46   1.1  rillig 
     47   1.1  rillig   local f = io.open(fname, "r")
     48  1.15  rillig   if f == nil then
     49  1.15  rillig     return nil
     50  1.15  rillig   end
     51   1.1  rillig 
     52   1.1  rillig   for line in f:lines() do
     53   1.1  rillig     table.insert(lines, line)
     54   1.1  rillig   end
     55   1.1  rillig   f:close()
     56   1.1  rillig 
     57   1.1  rillig   return lines
     58   1.1  rillig end
     59   1.1  rillig 
     60   1.1  rillig 
     61  1.15  rillig --- @shape ExpLine
     62  1.15  rillig --- @field filename string | nil
     63  1.15  rillig --- @field lineno number | nil
     64  1.15  rillig --- @field text string
     65  1.15  rillig 
     66  1.15  rillig 
     67  1.15  rillig --- @param lines string[]
     68  1.15  rillig --- @return ExpLine[]
     69  1.15  rillig local function parse_exp(lines)
     70  1.15  rillig   local exp_lines = {}
     71  1.15  rillig   for _, line in ipairs(lines) do
     72  1.15  rillig     local l_filename, l_lineno, l_text =
     73  1.15  rillig       line:match('^make: ([^:]+%.mk):(%d+):%s+(.*)')
     74  1.15  rillig     if not l_filename then
     75  1.15  rillig       l_text = line
     76  1.15  rillig     end
     77  1.15  rillig     l_text = l_text:gsub("^%s+", ""):gsub("%s+$", "")
     78  1.15  rillig     table.insert(exp_lines, {
     79  1.15  rillig       filename = l_filename,
     80  1.15  rillig       lineno = tonumber(l_lineno),
     81  1.15  rillig       text = l_text,
     82  1.15  rillig     })
     83  1.15  rillig   end
     84  1.15  rillig   return exp_lines
     85  1.15  rillig end
     86  1.15  rillig 
     87  1.15  rillig ---@param exp_lines ExpLine[]
     88  1.15  rillig local function detect_missing_expect_lines(exp_fname, exp_lines, s, e)
     89  1.15  rillig   for i = s, e do
     90  1.15  rillig     local exp_line = exp_lines[i]
     91  1.15  rillig     if exp_line.filename then
     92  1.15  rillig       print_error("error: %s:%d requires in %s:%d: # expect+1: %s",
     93  1.15  rillig         exp_fname, i, exp_line.filename, exp_line.lineno, exp_line.text)
     94  1.14  rillig     end
     95  1.14  rillig   end
     96   1.7  rillig end
     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.15  rillig   local exp_raw_lines = load_lines(exp_fname)
    102  1.15  rillig   if exp_raw_lines == nil then
    103  1.15  rillig     return
    104  1.15  rillig   end
    105  1.15  rillig   local exp_lines = parse_exp(exp_raw_lines)
    106  1.15  rillig 
    107  1.15  rillig   local exp_it = 1
    108   1.1  rillig 
    109   1.1  rillig   for mk_lineno, mk_line in ipairs(mk_lines) do
    110   1.9  rillig 
    111  1.16  rillig     mk_line:gsub("^#%s+expect%-not:%s*(.*)", function(text)
    112  1.15  rillig       for exp_lineno, exp_line in ipairs(exp_lines) do
    113  1.15  rillig         if exp_line.text:find(text, 1, true) then
    114  1.15  rillig           print_error("error: %s:%d: %s:%d must not contain '%s'",
    115  1.15  rillig             mk_fname, mk_lineno, exp_fname, exp_lineno, text)
    116  1.15  rillig         end
    117   1.9  rillig       end
    118  1.16  rillig     end)
    119   1.9  rillig 
    120  1.16  rillig     mk_line:gsub("^#%s+expect%-not%-matches:%s*(.*)", function(pattern)
    121  1.15  rillig       for exp_lineno, exp_line in ipairs(exp_lines) do
    122  1.15  rillig         if exp_line.text:find(pattern) then
    123  1.15  rillig           print_error("error: %s:%d: %s:%d must not match '%s'",
    124  1.15  rillig             mk_fname, mk_lineno, exp_fname, exp_lineno, pattern)
    125  1.15  rillig         end
    126  1.11  rillig       end
    127  1.16  rillig     end)
    128  1.11  rillig 
    129  1.16  rillig     mk_line:gsub("^#%s+expect:%s*(.*)", function(text)
    130  1.15  rillig       local i = exp_it
    131  1.15  rillig       while i <= #exp_lines and text ~= exp_lines[i].text do
    132   1.1  rillig         i = i + 1
    133   1.1  rillig       end
    134  1.15  rillig       if i <= #exp_lines then
    135  1.15  rillig         detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
    136  1.15  rillig         exp_lines[i].text = ""
    137  1.15  rillig         exp_it = i + 1
    138   1.1  rillig       else
    139   1.1  rillig         print_error("error: %s:%d: '%s:%d+' must contain '%s'",
    140  1.15  rillig           mk_fname, mk_lineno, exp_fname, exp_it, text)
    141   1.1  rillig       end
    142  1.16  rillig     end)
    143  1.15  rillig 
    144   1.2  rillig     if mk_line:match("^#%s*expect%-reset$") then
    145  1.15  rillig       exp_it = 1
    146   1.2  rillig     end
    147   1.1  rillig 
    148  1.16  rillig     mk_line:gsub("^#%s+expect([+%-]%d+):%s*(.*)", function(offset, text)
    149  1.15  rillig       local msg_lineno = mk_lineno + tonumber(offset)
    150   1.1  rillig 
    151  1.15  rillig       local i = exp_it
    152  1.15  rillig       while i <= #exp_lines and text ~= exp_lines[i].text do
    153  1.15  rillig         i = i + 1
    154   1.1  rillig       end
    155   1.1  rillig 
    156  1.15  rillig       if i <= #exp_lines and exp_lines[i].lineno == msg_lineno then
    157  1.15  rillig         detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
    158  1.15  rillig         exp_lines[i].text = ""
    159  1.15  rillig         exp_it = i + 1
    160  1.15  rillig       elseif i <= #exp_lines then
    161  1.15  rillig         print_error("error: %s:%d: expect%+d must be expect%+d",
    162  1.15  rillig           mk_fname, mk_lineno, tonumber(offset),
    163  1.15  rillig           exp_lines[i].lineno - mk_lineno)
    164  1.15  rillig       else
    165  1.15  rillig         print_error("error: %s:%d: %s:%d+ must contain '%s'",
    166  1.15  rillig           mk_fname, mk_lineno, exp_fname, exp_it, text)
    167   1.1  rillig       end
    168  1.16  rillig     end)
    169   1.4  rillig   end
    170  1.15  rillig   detect_missing_expect_lines(exp_fname, exp_lines, exp_it, #exp_lines)
    171   1.1  rillig end
    172   1.1  rillig 
    173   1.1  rillig for _, fname in ipairs(arg) do
    174   1.1  rillig   check_mk(fname)
    175   1.1  rillig end
    176   1.1  rillig os.exit(not had_errors)
    177