1-- $NetBSD: t_options.lua,v 1.7 2023/06/26 12:21:18 rillig Exp $ 2-- 3-- Copyright (c) 2023 The NetBSD Foundation, Inc. 4-- All rights reserved. 5-- 6-- Redistribution and use in source and binary forms, with or without 7-- modification, are permitted provided that the following conditions 8-- are met: 9-- 1. Redistributions of source code must retain the above copyright 10-- notice, this list of conditions and the following disclaimer. 11-- 2. Redistributions in binary form must reproduce the above copyright 12-- notice, this list of conditions and the following disclaimer in the 13-- documentation and/or other materials provided with the distribution. 14-- 15-- THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 16-- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 19-- BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25-- POSSIBILITY OF SUCH DAMAGE. 26 27-- usage: [INDENT=...] lua t_options.lua <file>... 28-- 29-- Test driver for indent that runs indent on several inputs, checks the 30-- output and can run indent with different command line options on the same 31-- input. 32-- 33-- The test files contain the input to be formatted, the formatting options 34-- and the output, all as close together as possible. The test files use the 35-- following directives: 36-- 37-- //indent input 38-- Specifies the input to be formatted. 39-- //indent run [options] 40-- Runs indent on the input, using the given options. 41-- //indent end 42-- Finishes an '//indent input' or '//indent run' section. 43-- //indent run-equals-input [options] 44-- Runs indent on the input, expecting unmodified output. 45-- //indent run-equals-prev-output [options] 46-- Runs indent on the input, expecting the same output as from 47-- the previous run. 48-- 49-- All text outside input..end or run..end directives is not passed to indent. 50-- 51-- Inside the input..end or run..end sections, comments that start with '$' 52-- are filtered out, they can be used for remarks near the affected code. 53-- 54-- The actual output from running indent is written to stdout, the expected 55-- test output is written to 'expected.out'. 56 57local warned = false 58 59local filename = "" 60local lineno = 0 61 62local prev_empty_lines = 0 -- the finished "block" of empty lines 63local curr_empty_lines = 0 -- the ongoing "block" of empty lines 64local max_empty_lines = 0 -- between sections 65local seen_input_section = false-- the first input section is not checked 66 67local section = "" -- "", "input" or "run" 68local section_excl_comm = "" -- without dollar comments 69local section_incl_comm = "" -- with dollar comments 70 71local input_excl_comm = "" -- the input text for indent 72local input_incl_comm = "" -- used for duplicate checks 73local unused_input_lineno = 0 74 75local output_excl_comm = "" -- expected output 76local output_incl_comm = "" -- used for duplicate checks 77local output_lineno = 0 78 79local expected_out = assert(io.open("expected.out", "w")) 80 81local function die(ln, msg) 82 io.stderr:write(("%s:%d: error: %s\n"):format(filename, ln, msg)) 83 os.exit(false) 84end 85 86local function warn(ln, msg) 87 io.stderr:write(("%s:%d: warning: %s\n"):format(filename, ln, msg)) 88 warned = true 89end 90 91local function init_file(fname) 92 filename = fname 93 lineno = 0 94 95 prev_empty_lines = 0 96 curr_empty_lines = 0 97 max_empty_lines = 0 98 seen_input_section = false 99 100 section = "" 101 section_excl_comm = "" 102 section_incl_comm = "" 103 104 input_excl_comm = "" 105 input_incl_comm = "" 106 unused_input_lineno = 0 107 108 output_excl_comm = "" 109 output_incl_comm = "" 110 output_lineno = 0 111end 112 113local function check_empty_lines_block(n) 114 if max_empty_lines ~= n and seen_input_section then 115 local lines = n ~= 1 and "lines" or "line" 116 warn(lineno, ("expecting %d empty %s, got %d") 117 :format(n, lines, max_empty_lines)) 118 end 119end 120 121local function check_unused_input() 122 if unused_input_lineno ~= 0 then 123 warn(unused_input_lineno, "input is not used") 124 end 125end 126 127local function run_indent(inp, args) 128 local indent = os.getenv("INDENT") or "indent" 129 local cmd = indent .. " " .. args .. " 2>t_options.err" 130 131 local indent_in = assert(io.popen(cmd, "w")) 132 indent_in:write(inp) 133 local ok, kind, info = indent_in:close() 134 if not ok then 135 print("// " .. kind .. " " .. info) 136 end 137 for line in io.lines("t_options.err") do 138 print("// " .. line) 139 end 140end 141 142local function handle_empty_section(line) 143 if line == "" then 144 curr_empty_lines = curr_empty_lines + 1 145 else 146 if curr_empty_lines > max_empty_lines then 147 max_empty_lines = curr_empty_lines 148 end 149 if curr_empty_lines > 0 then 150 if prev_empty_lines > 1 then 151 warn(lineno - curr_empty_lines - 1, 152 prev_empty_lines .. " empty lines a few " 153 .. "lines above, should be only 1") 154 end 155 prev_empty_lines = curr_empty_lines 156 end 157 curr_empty_lines = 0 158 end 159end 160 161local function handle_indent_input() 162 if prev_empty_lines ~= 2 and seen_input_section then 163 warn(lineno, "input section needs 2 empty lines " 164 .. "above, not " .. prev_empty_lines) 165 end 166 check_empty_lines_block(2) 167 check_unused_input() 168 section = "input" 169 section_excl_comm = "" 170 section_incl_comm = "" 171 unused_input_lineno = lineno 172 seen_input_section = true 173 output_excl_comm = "" 174 output_incl_comm = "" 175 output_lineno = 0 176end 177 178local function handle_indent_run(args) 179 if section ~= "" then 180 warn(lineno, "unfinished section '" .. section .. "'") 181 end 182 check_empty_lines_block(1) 183 if prev_empty_lines ~= 1 then 184 warn(lineno, "run section needs 1 empty line above, " 185 .. "not " .. prev_empty_lines) 186 end 187 section = "run" 188 output_lineno = lineno 189 section_excl_comm = "" 190 section_incl_comm = "" 191 192 run_indent(input_excl_comm, args) 193 unused_input_lineno = 0 194end 195 196local function handle_indent_run_equals_input(args) 197 check_empty_lines_block(1) 198 run_indent(input_excl_comm, args) 199 expected_out:write(input_excl_comm) 200 unused_input_lineno = 0 201 max_empty_lines = 0 202 output_incl_comm = "" 203 output_excl_comm = "" 204end 205 206local function handle_indent_run_equals_prev_output(args) 207 if output_incl_comm == "" then 208 warn(lineno, 209 "no previous output; use run-equals-input instead") 210 end 211 check_empty_lines_block(1) 212 run_indent(input_excl_comm, args) 213 expected_out:write(output_excl_comm) 214 max_empty_lines = 0 215end 216 217local function handle_indent_end_input() 218 if section_incl_comm == input_incl_comm then 219 warn(lineno, "duplicate input; remove this section") 220 end 221 222 input_excl_comm = section_excl_comm 223 input_incl_comm = section_incl_comm 224 section = "" 225 max_empty_lines = 0 226end 227 228local function handle_indent_end_run() 229 if section_incl_comm == input_incl_comm then 230 warn(output_lineno, 231 "output == input; use run-equals-input") 232 end 233 if section_incl_comm == output_incl_comm then 234 warn(output_lineno, 235 "duplicate output; use run-equals-prev-output") 236 end 237 238 output_excl_comm = section_excl_comm 239 output_incl_comm = section_incl_comm 240 section = "" 241 max_empty_lines = 0 242end 243 244local function handle_indent_directive(line, command, args) 245 print(line) 246 expected_out:write(line .. "\n") 247 248 if command == "input" and args ~= "" then 249 warn(lineno, "'//indent input' does not take arguments") 250 elseif command == "input" then 251 handle_indent_input() 252 elseif command == "run" then 253 handle_indent_run(args) 254 elseif command == "run-equals-input" then 255 handle_indent_run_equals_input(args) 256 elseif command == "run-equals-prev-output" then 257 handle_indent_run_equals_prev_output(args) 258 elseif command == "end" and args ~= "" then 259 warn(lineno, "'//indent end' does not take arguments") 260 elseif command == "end" and section == "input" then 261 handle_indent_end_input() 262 elseif command == "end" and section == "run" then 263 handle_indent_end_run() 264 elseif command == "end" then 265 warn(lineno, "misplaced '//indent end'") 266 else 267 die(lineno, "invalid line '" .. line .. "'") 268 end 269 270 prev_empty_lines = 0 271 curr_empty_lines = 0 272end 273 274local function handle_line(line) 275 if section == "" then 276 handle_empty_section(line) 277 end 278 279 -- Hide comments starting with dollar from indent; they are used for 280 -- marking bugs and adding other remarks directly in the input or 281 -- output sections. 282 if line:match("^%s*/[*]%s*[$].*[*]/$") 283 or line:match("^%s*//%s*[$]") then 284 if section ~= "" then 285 section_incl_comm = section_incl_comm .. line .. "\n" 286 end 287 return 288 end 289 290 local cmd, args = line:match("^//indent%s+([^%s]+)%s*(.*)$") 291 if cmd then 292 handle_indent_directive(line, cmd, args) 293 return 294 end 295 296 if section == "input" or section == "run" then 297 section_excl_comm = section_excl_comm .. line .. "\n" 298 section_incl_comm = section_incl_comm .. line .. "\n" 299 end 300 301 if section == "run" then 302 expected_out:write(line .. "\n") 303 end 304 305 if section == "" 306 and line ~= "" 307 and line:sub(1, 1) ~= "/" 308 and line:sub(1, 2) ~= " *" then 309 warn(lineno, "non-comment line outside 'input' or 'run' " 310 .. "section") 311 end 312end 313 314local function handle_file(fname) 315 init_file(fname) 316 local f = assert(io.open(fname)) 317 for line in f:lines() do 318 lineno = lineno + 1 319 handle_line(line) 320 end 321 f:close() 322end 323 324local function main() 325 for _, arg in ipairs(arg) do 326 handle_file(arg) 327 end 328 if section ~= "" then 329 die(lineno, "still in section '" .. section .. "'") 330 end 331 check_unused_input() 332 expected_out:close() 333 os.exit(not warned) 334end 335 336main() 337