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