1-- $NetBSD: t_options.lua,v 1.4 2023/05/22 06:35:56 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>&1" 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 137end 138 139local function handle_empty_section(line) 140 if line == "" then 141 curr_empty_lines = curr_empty_lines + 1 142 else 143 if curr_empty_lines > max_empty_lines then 144 max_empty_lines = curr_empty_lines 145 end 146 if curr_empty_lines > 0 then 147 if prev_empty_lines > 1 then 148 warn(lineno - curr_empty_lines - 1, 149 prev_empty_lines .. " empty lines a few " 150 .. "lines above, should be only 1") 151 end 152 prev_empty_lines = curr_empty_lines 153 end 154 curr_empty_lines = 0 155 end 156end 157 158local function handle_indent_input() 159 if prev_empty_lines ~= 2 and seen_input_section then 160 warn(lineno, "input section needs 2 empty lines " 161 .. "above, not " .. prev_empty_lines) 162 end 163 check_empty_lines_block(2) 164 check_unused_input() 165 section = "input" 166 section_excl_comm = "" 167 section_incl_comm = "" 168 unused_input_lineno = lineno 169 seen_input_section = true 170 output_excl_comm = "" 171 output_incl_comm = "" 172 output_lineno = 0 173end 174 175local function handle_indent_run(args) 176 if section ~= "" then 177 warn(lineno, "unfinished section '" .. section .. "'") 178 end 179 check_empty_lines_block(1) 180 if prev_empty_lines ~= 1 then 181 warn(lineno, "run section needs 1 empty line above, " 182 .. "not " .. prev_empty_lines) 183 end 184 section = "run" 185 output_lineno = lineno 186 section_excl_comm = "" 187 section_incl_comm = "" 188 189 run_indent(input_excl_comm, args) 190 unused_input_lineno = 0 191end 192 193local function handle_indent_run_equals_input(args) 194 check_empty_lines_block(1) 195 run_indent(input_excl_comm, args) 196 expected_out:write(input_excl_comm) 197 unused_input_lineno = 0 198 max_empty_lines = 0 199end 200 201local function handle_indent_run_equals_prev_output(args) 202 check_empty_lines_block(1) 203 run_indent(input_excl_comm, args) 204 expected_out:write(output_excl_comm) 205 max_empty_lines = 0 206end 207 208local function handle_indent_end_input() 209 if section_incl_comm == input_incl_comm then 210 warn(lineno, "duplicate input; remove this section") 211 end 212 213 input_excl_comm = section_excl_comm 214 input_incl_comm = section_incl_comm 215 section = "" 216 max_empty_lines = 0 217end 218 219local function handle_indent_end_run() 220 if section_incl_comm == input_incl_comm then 221 warn(output_lineno, 222 "output == input; use run-equals-input") 223 end 224 if section_incl_comm == output_incl_comm then 225 warn(output_lineno, 226 "duplicate output; use run-equals-prev-output") 227 end 228 229 output_excl_comm = section_excl_comm 230 output_incl_comm = section_incl_comm 231 section = "" 232 max_empty_lines = 0 233end 234 235local function handle_indent_directive(line, command, args) 236 print(line) 237 expected_out:write(line .. "\n") 238 239 if command == "input" and args ~= "" then 240 warn(lineno, "'//indent input' does not take arguments") 241 elseif command == "input" then 242 handle_indent_input() 243 elseif command == "run" then 244 handle_indent_run(args) 245 elseif command == "run-equals-input" then 246 handle_indent_run_equals_input(args) 247 elseif command == "run-equals-prev-output" then 248 handle_indent_run_equals_prev_output(args) 249 elseif command == "end" and args ~= "" then 250 warn(lineno, "'//indent end' does not take arguments") 251 elseif command == "end" and section == "input" then 252 handle_indent_end_input() 253 elseif command == "end" and section == "run" then 254 handle_indent_end_run() 255 elseif command == "end" then 256 warn(lineno, "misplaced '//indent end'") 257 else 258 die(lineno, "invalid line '" .. line .. "'") 259 end 260 261 prev_empty_lines = 0 262 curr_empty_lines = 0 263end 264 265local function handle_line(line) 266 if section == "" then 267 handle_empty_section(line) 268 end 269 270 -- Hide comments starting with dollar from indent; they are used for 271 -- marking bugs and adding other remarks directly in the input or 272 -- output sections. 273 if line:match("^%s*/[*]%s*[$].*[*]/$") 274 or line:match("^%s*//%s*[$]") then 275 if section ~= "" then 276 section_incl_comm = section_incl_comm .. line .. "\n" 277 end 278 return 279 end 280 281 local cmd, args = line:match("^//indent%s+([^%s]+)%s*(.*)$") 282 if cmd then 283 handle_indent_directive(line, cmd, args) 284 return 285 end 286 287 if section == "input" or section == "run" then 288 section_excl_comm = section_excl_comm .. line .. "\n" 289 section_incl_comm = section_incl_comm .. line .. "\n" 290 end 291 292 if section == "run" then 293 expected_out:write(line .. "\n") 294 end 295 296 if section == "" 297 and line ~= "" 298 and line:sub(1, 1) ~= "/" 299 and line:sub(1, 2) ~= " *" then 300 warn(lineno, "non-comment line outside 'input' or 'run' " 301 .. "section") 302 end 303end 304 305local function handle_file(fname) 306 init_file(fname) 307 local f = assert(io.open(fname)) 308 for line in f:lines() do 309 lineno = lineno + 1 310 handle_line(line) 311 end 312 f:close() 313end 314 315local function main() 316 for _, arg in ipairs(arg) do 317 handle_file(arg) 318 end 319 if section ~= "" then 320 die(lineno, "still in section '" .. section .. "'") 321 end 322 check_unused_input() 323 expected_out:close() 324 os.exit(not warned) 325end 326 327main() 328