Home | History | Annotate | Line # | Download | only in unit-tests
      1   1.1  rillig #!  /usr/bin/lua
      2  1.17  rillig -- $NetBSD: check-expect.lua,v 1.17 2025/07/01 05:03:18 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.17  rillig     local function match(pattern, action)
    112  1.17  rillig       local _, n = mk_line:gsub(pattern, action)
    113  1.17  rillig       if n > 0 then
    114  1.17  rillig         match = function() end
    115  1.17  rillig       end
    116  1.17  rillig     end
    117  1.17  rillig 
    118  1.17  rillig     match("^#%s+expect%-not:%s*(.*)", function(text)
    119  1.15  rillig       for exp_lineno, exp_line in ipairs(exp_lines) do
    120  1.15  rillig         if exp_line.text:find(text, 1, true) then
    121  1.15  rillig           print_error("error: %s:%d: %s:%d must not contain '%s'",
    122  1.15  rillig             mk_fname, mk_lineno, exp_fname, exp_lineno, text)
    123  1.15  rillig         end
    124   1.9  rillig       end
    125  1.16  rillig     end)
    126   1.9  rillig 
    127  1.17  rillig     match("^#%s+expect%-not%-matches:%s*(.*)", function(pattern)
    128  1.15  rillig       for exp_lineno, exp_line in ipairs(exp_lines) do
    129  1.15  rillig         if exp_line.text:find(pattern) then
    130  1.15  rillig           print_error("error: %s:%d: %s:%d must not match '%s'",
    131  1.15  rillig             mk_fname, mk_lineno, exp_fname, exp_lineno, pattern)
    132  1.15  rillig         end
    133  1.11  rillig       end
    134  1.16  rillig     end)
    135  1.11  rillig 
    136  1.17  rillig     match("^#%s+expect:%s*(.*)", function(text)
    137  1.15  rillig       local i = exp_it
    138  1.15  rillig       while i <= #exp_lines and text ~= exp_lines[i].text do
    139   1.1  rillig         i = i + 1
    140   1.1  rillig       end
    141  1.15  rillig       if i <= #exp_lines then
    142  1.15  rillig         detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
    143  1.15  rillig         exp_lines[i].text = ""
    144  1.15  rillig         exp_it = i + 1
    145   1.1  rillig       else
    146   1.1  rillig         print_error("error: %s:%d: '%s:%d+' must contain '%s'",
    147  1.15  rillig           mk_fname, mk_lineno, exp_fname, exp_it, text)
    148   1.1  rillig       end
    149  1.16  rillig     end)
    150  1.15  rillig 
    151  1.17  rillig     match("^#%s+expect%-reset$", function()
    152  1.15  rillig       exp_it = 1
    153  1.17  rillig     end)
    154   1.1  rillig 
    155  1.17  rillig     match("^#%s+expect([+%-]%d+):%s*(.*)", function(offset, text)
    156  1.15  rillig       local msg_lineno = mk_lineno + tonumber(offset)
    157   1.1  rillig 
    158  1.15  rillig       local i = exp_it
    159  1.15  rillig       while i <= #exp_lines and text ~= exp_lines[i].text do
    160  1.15  rillig         i = i + 1
    161   1.1  rillig       end
    162   1.1  rillig 
    163  1.15  rillig       if i <= #exp_lines and exp_lines[i].lineno == msg_lineno then
    164  1.15  rillig         detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
    165  1.15  rillig         exp_lines[i].text = ""
    166  1.15  rillig         exp_it = i + 1
    167  1.15  rillig       elseif i <= #exp_lines then
    168  1.15  rillig         print_error("error: %s:%d: expect%+d must be expect%+d",
    169  1.15  rillig           mk_fname, mk_lineno, tonumber(offset),
    170  1.15  rillig           exp_lines[i].lineno - mk_lineno)
    171  1.15  rillig       else
    172  1.15  rillig         print_error("error: %s:%d: %s:%d+ must contain '%s'",
    173  1.15  rillig           mk_fname, mk_lineno, exp_fname, exp_it, text)
    174   1.1  rillig       end
    175  1.16  rillig     end)
    176  1.17  rillig 
    177  1.17  rillig     match("^#%s+expect[+%-:]", function()
    178  1.17  rillig       print_error("error: %s:%d: invalid \"expect\" line: %s",
    179  1.17  rillig         mk_fname, mk_lineno, mk_line)
    180  1.17  rillig     end)
    181  1.17  rillig 
    182   1.4  rillig   end
    183  1.15  rillig   detect_missing_expect_lines(exp_fname, exp_lines, exp_it, #exp_lines)
    184   1.1  rillig end
    185   1.1  rillig 
    186   1.1  rillig for _, fname in ipairs(arg) do
    187   1.1  rillig   check_mk(fname)
    188   1.1  rillig end
    189   1.1  rillig os.exit(not had_errors)
    190