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