xref: /netbsd-src/tests/bin/sh/t_option.sh (revision f42f89fd6fc4d08451450f9b7ffa731678160f1c)
1# $NetBSD: t_option.sh,v 1.9 2022/05/22 11:27:36 andvar Exp $
2#
3# Copyright (c) 2016 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# the implementation of "sh" to test
28: ${TEST_SH:="/bin/sh"}
29
30# The standard
31# http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
32# says:
33#	...[lots]
34
35test_option_on_off()
36{
37	atf_require_prog tr
38
39	for opt
40	do
41				# t is needed, as inside $()` $- appears to lose
42				# the 'e' option if it happened to already be
43				# set.  Must check if that is what should
44				# happen, but that is a different issue.
45
46		test -z "${opt}" && continue
47
48		# if we are playing with more that one option at a
49		# time, the code below requires that we start with no
50		# options set, or it will mis-diagnose the situation
51		CLEAR=''
52		test "${#opt}" -gt 1 &&
53  CLEAR='xx="$-" && xx=$(echo "$xx" | tr -d cs) && test -n "$xx" && set +"$xx";'
54
55		atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
56			"opt=${opt}"'
57			x() {
58				echo "ERROR: Unable to $1 option $2" >&2
59				exit 1
60			}
61			s() {
62				set -"$1"
63				t="$-"
64				x=$(echo "$t" | tr -d "$1")
65				test "$t" = "$x" && x set "$1"
66				return 0
67			}
68			c() {
69				set +"$1"
70				t="$-"
71				x=$(echo "$t" | tr -d "$1")
72				test "$t" != "$x" && x clear "$1"
73				return 0
74			}
75			'"${CLEAR}"'
76
77			# if we do not do this, -x tracing splatters stderr
78			# for some shells, -v does as well (is that correct?)
79			case "${opt}" in
80			(*[xXv]*)	exec 2>/dev/null;;
81			esac
82
83			o="$-"
84			x=$(echo "$o" | tr -d "$opt")
85
86			if [ "$o" = "$x" ]; then	# option was off
87				s "${opt}"
88				c "${opt}"
89			else
90				c "${opt}"
91				s "${opt}"
92			fi
93		'
94	done
95}
96
97test_optional_on_off()
98{
99	RET=0
100	OPTS=
101	for opt
102	do
103		test "${opt}" = n && continue
104		${TEST_SH} -c "set -${opt}" 2>/dev/null  &&
105			OPTS="${OPTS} ${opt}" || RET=1
106	done
107
108	test -n "${OPTS}" && test_option_on_off ${OPTS}
109
110	return "${RET}"
111}
112
113atf_test_case set_a
114set_a_head() {
115	atf_set "descr" "Tests that 'set -a' turns on all var export " \
116	                "and that it behaves as defined by the standard"
117}
118set_a_body() {
119	atf_require_prog env
120	atf_require_prog grep
121
122	test_option_on_off a
123
124	# without -a, new variables should not be exported (so grep "fails")
125	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -ce \
126		'unset VAR; set +a; VAR=value; env | grep "^VAR="'
127
128	# with -a, they should be
129	atf_check -s exit:0 -o match:VAR=value -e empty ${TEST_SH} -ce \
130		'unset VAR; set -a; VAR=value; env | grep "^VAR="'
131}
132
133atf_test_case set_C
134set_C_head() {
135	atf_set "descr" "Tests that 'set -C' turns on no clobber mode " \
136	                "and that it behaves as defined by the standard"
137}
138set_C_body() {
139	atf_require_prog ls
140
141	test_option_on_off C
142
143	# Check that the environment to use for the tests is sane ...
144	# we assume current dir is a new tempory directory & is empty
145
146	test -z "$(ls)" || atf_skip "Test execution directory not clean"
147	test -c "/dev/null" || atf_skip "Problem with /dev/null"
148
149	echo Dummy_Content > Junk_File
150	echo Precious_Content > Important_File
151
152	# Check that we can redirect onto file when -C is not set
153	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
154		'
155		D=$(ls -l Junk_File) || exit 1
156		set +C
157		echo "Overwrite it now" > Junk_File
158		A=$(ls -l Junk_File) || exit 1
159		test "${A}" != "${D}"
160		'
161
162	# Check that we cannot redirect onto file when -C is set
163	atf_check -s exit:0 -o empty -e not-empty ${TEST_SH} -c \
164		'
165		D=$(ls -l Important_File) || exit 1
166		set -C
167		echo "Fail to Overwrite it now" > Important_File
168		A=$(ls -l Important_File) || exit 1
169		test "${A}" = "${D}"
170		'
171
172	# Check that we can append to file, even when -C is set
173	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
174		'
175		D=$(ls -l Junk_File) || exit 1
176		set -C
177		echo "Append to it now" >> Junk_File
178		A=$(ls -l Junk_File) || exit 1
179		test "${A}" != "${D}"
180		'
181
182	# Check that we abort on attempt to redirect onto file when -Ce is set
183	atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
184		'
185		set -Ce
186		echo "Fail to Overwrite it now" > Important_File
187		echo "Should not reach this point"
188		'
189
190	# Last check that we can override -C for when we really need to
191	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
192		'
193		D=$(ls -l Junk_File) || exit 1
194		set -C
195		echo "Change the poor bugger again" >| Junk_File
196		A=$(ls -l Junk_File) || exit 1
197		test "${A}" != "${D}"
198		'
199}
200
201atf_test_case set_e
202set_e_head() {
203	atf_set "descr" "Tests that 'set -e' turns on error detection " \
204		"and that a simple case behaves as defined by the standard"
205}
206set_e_body() {
207	test_option_on_off e
208
209	# Check that -e does nothing if no commands fail
210	atf_check -s exit:0 -o match:I_am_OK -e empty \
211	    ${TEST_SH} -c \
212		'false; printf "%s" I_am; set -e; true; printf "%s\n" _OK'
213
214	# and that it (silently, but with exit status) aborts if cmd fails
215	atf_check -s not-exit:0 -o match:I_am -o not-match:Broken -e empty \
216	    ${TEST_SH} -c \
217		'false; printf "%s" I_am; set -e; false; printf "%s\n" _Broken'
218
219	# same, except -e this time is on from the beginning
220	atf_check -s not-exit:0 -o match:I_am -o not-match:Broken -e empty \
221	    ${TEST_SH} -ec 'printf "%s" I_am; false; printf "%s\n" _Broken'
222
223	# More checking of -e in other places, there is lots to deal with.
224}
225
226atf_test_case set_f
227set_f_head() {
228	atf_set "descr" "Tests that 'set -f' turns off pathname expansion " \
229	                "and that it behaves as defined by the standard"
230}
231set_f_body() {
232	atf_require_prog ls
233
234	test_option_on_off f
235
236	# Check that the environment to use for the tests is sane ...
237	# we assume current dir is a new tempory directory & is empty
238
239	test -z "$(ls)" || atf_skip "Test execution directory not clean"
240
241	# we will assume that atf will clean up this junk directory
242	# when we are done.   But for testing pathname expansion
243	# we need files
244
245	for f in a b c d e f aa ab ac ad ae aaa aab aac aad aba abc bbb ccc
246	do
247		echo "$f" > "$f"
248	done
249
250	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -ec \
251	    'X=$(echo b*); Y=$(echo b*); test "${X}" != "a*";
252		test "${X}" = "${Y}"'
253
254	# now test expansion is different when -f is set
255	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -ec \
256	   'X=$(echo b*); Y=$(set -f; echo b*); test "${X}" != "${Y}"'
257}
258
259atf_test_case set_n
260set_n_head() {
261	atf_set "descr" "Tests that 'set -n' suppresses command execution " \
262	                "and that it behaves as defined by the standard"
263}
264set_n_body() {
265	# pointless to test this, if it turns on, it stays on...
266	# test_option_on_off n
267	# so just allow the tests below to verify it can be turned on
268
269	# nothing should be executed, hence no output...
270	atf_check -s exit:0 -o empty -e empty \
271		${TEST_SH} -enc 'echo ABANDON HOPE; echo ALL YE; echo ...'
272
273	# this is true even when the "commands" do not exist
274	atf_check -s exit:0 -o empty -e empty \
275		${TEST_SH} -enc 'ERR; FAIL; ABANDON HOPE'
276
277	# but if there is a syntax error, it should be detected (w or w/o -e)
278	atf_check -s not-exit:0 -o empty -e not-empty \
279		${TEST_SH} -enc 'echo JUMP; for frogs swim; echo in puddles'
280	atf_check -s not-exit:0 -o empty -e not-empty \
281		${TEST_SH} -nc 'echo ABANDON HOPE; echo "ALL YE; echo ...'
282	atf_check -s not-exit:0 -o empty -e not-empty \
283		${TEST_SH} -enc 'echo ABANDON HOPE;; echo ALL YE; echo ...'
284	atf_check -s not-exit:0 -o empty -e not-empty \
285		${TEST_SH} -nc 'do YOU ABANDON HOPE; for all eternity?'
286
287	# now test enabling -n in the middle of a script
288	# note that once turned on, it cannot be turned off again.
289	#
290	# omit more complex cases, as those can send some shells
291	# into infinite loops, and believe it or not, that might be OK!
292
293	atf_check -s exit:0 -o match:first -o not-match:second -e empty \
294		${TEST_SH} -c 'echo first; set -n; echo second'
295	atf_check -s exit:0 -o match:first -o not-match:third -e empty \
296	    ${TEST_SH} -c 'echo first; set -n; echo second; set +n; echo third'
297	atf_check -s exit:0 -o inline:'a\nb\n' -e empty \
298	    ${TEST_SH} -c 'for x in a b c d
299			   do
300				case "$x" in
301				     a);; b);; c) set -n;; d);;
302				esac
303				printf "%s\n" "$x"
304			   done'
305
306	# This last one is a bit more complex to explain, so I will not try
307
308	# First, we need to know what signal number is used for SIGUSR1 on
309	# the local (testing) system (signal number is $(( $XIT - 128 )) )
310
311	# this will take slightly over 1 second elapsed time (the sleep 1)
312	# The "10" for the first sleep just needs to be something big enough
313	# that the rest of the commands have time to complete, even on
314	# very slow testing systems.  10 should be enough.  Otherwise irrelevant
315
316	# The shell will usually blather to stderr about the sleep 10 being
317	# killed, but it affects nothing, so just allow it to cry.
318
319	(sleep 10 & sleep 1; kill -USR1 $!; wait $!)
320	XIT="$?"
321
322	# The exit value should be an integer > 128 and < 256 (often 158)
323	# If it is not just skip the test
324
325	# If we do run the test, it should take (slightly over) either 1 or 2
326	# seconds to complete, depending upon the shell being tested.
327
328	case "${XIT}" in
329	( 129 | 1[3-9][0-9] | 2[0-4][0-9] | 25[0-5] )
330
331		# The script below should exit with the same code - no output
332
333		# Or that is the result that seems best explanable.
334		# "set -n" in uses like this is not exactly well defined...
335
336		# This script comes from a member of the austin group
337		# (they author changes to the posix shell spec - and more.)
338		# The author is also an (occasional?) NetBSD user.
339		atf_check -s exit:${XIT} -o empty -e empty ${TEST_SH} -c '
340			trap "set -n" USR1
341			{ sleep 1; kill -USR1 $$; sleep 1; } &
342			false
343			wait && echo t || echo f
344			wait
345			echo foo
346		'
347		;;
348	esac
349}
350
351atf_test_case set_u
352set_u_head() {
353	atf_set "descr" "Tests that 'set -u' turns on unset var detection " \
354	                "and that it behaves as defined by the standard"
355}
356set_u_body() {
357	test_option_on_off u
358
359	unset ENV	# make sure there is nothing there to cause problems
360
361	# first make sure it is OK to unset an unset variable
362	atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
363		'unset _UNSET_VARIABLE_; echo OK'
364	# even if -u is set
365	atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -cue \
366		'unset _UNSET_VARIABLE_; echo OK'
367
368	# and that without -u accessing an unset variable is harmless
369	atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
370		'unset X; echo ${X}; echo OK'
371	# and that the unset variable test expansion works properly
372	atf_check -s exit:0 -o match:OKOK -e empty ${TEST_SH} -ce \
373		'unset X; printf "%s" ${X-OK}; echo OK'
374
375	# Next test that with -u set, the shell aborts on access to unset var
376	# do not use -e, want to make sure it is -u that causes abort
377	atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
378		'unset X; set -u; echo ${X}; echo ERR'
379	# quoting should make no difference...
380	atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
381		'unset X; set -u; echo "${X}"; echo ERR'
382
383	# Now a bunch of accesses to unset vars, with -u, in ways that are OK
384	atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
385		'unset X; set -u; echo ${X-GOOD}; echo OK'
386	atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
387		'unset X; set -u; echo ${X-OK}'
388	atf_check -s exit:0 -o not-match:ERR -o match:OK -e empty \
389		${TEST_SH} -ce 'unset X; set -u; echo ${X+ERR}; echo OK'
390
391	# and some more ways that are not OK
392	atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
393		'unset X; set -u; echo ${X#foo}; echo ERR'
394	atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
395		'unset X; set -u; echo ${X%%bar}; echo ERR'
396
397	# lastly, just while we are checking unset vars, test aborts w/o -u
398	atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
399		'unset X; echo ${X?}; echo ERR'
400	atf_check -s not-exit:0 -o not-match:ERR -e match:X_NOT_SET \
401		${TEST_SH} -c 'unset X; echo ${X?X_NOT_SET}; echo ERR'
402}
403
404atf_test_case set_v
405set_v_head() {
406	atf_set "descr" "Tests that 'set -v' turns on input read echoing " \
407	                "and that it behaves as defined by the standard"
408}
409set_v_body() {
410	test_option_on_off v
411
412	# check that -v does nothing if no later input line is read
413	atf_check -s exit:0 \
414			-o match:OKOK -o not-match:echo -o not-match:printf \
415			-e empty \
416		${TEST_SH} -ec 'printf "%s" OK; set -v; echo OK; exit 0'
417
418	# but that it does when there are multiple lines
419	cat <<- 'EOF' |
420		set -v
421		printf %s OK
422		echo OK
423		exit 0
424	EOF
425	atf_check -s exit:0 \
426			-o match:OKOK -o not-match:echo -o not-match:printf \
427			-e match:printf -e match:OK -e match:echo \
428			-e not-match:set ${TEST_SH}
429
430	# and that it can be disabled again
431	cat <<- 'EOF' |
432		set -v
433		printf %s OK
434		set +v
435		echo OK
436		exit 0
437	EOF
438	atf_check -s exit:0 \
439			-o match:OKOK -o not-match:echo -o not-match:printf \
440			-e match:printf -e match:OK -e not-match:echo \
441				${TEST_SH}
442
443	# and lastly, that shell keywords do get output when "read"
444	cat <<- 'EOF' |
445		set -v
446		for i in 111 222 333
447		do
448			printf %s $i
449		done
450		exit 0
451	EOF
452	atf_check -s exit:0 \
453			-o match:111222333 -o not-match:printf \
454			-o not-match:for -o not-match:do -o not-match:done \
455			-e match:printf -e match:111 -e not-match:111222 \
456			-e match:for -e match:do -e match:done \
457				${TEST_SH} ||
458		atf_fail '111 222 333 test failure'
459}
460
461atf_test_case set_x
462set_x_head() {
463	atf_set "descr" "Tests that 'set -x' turns on command exec logging " \
464	                "and that it behaves as defined by the standard"
465}
466set_x_body() {
467	test_option_on_off x
468
469	# check that cmd output appears after -x is enabled
470	atf_check -s exit:0 \
471			-o match:OKOK -o not-match:echo -o not-match:printf \
472			-e not-match:printf -e match:OK -e match:echo \
473		${TEST_SH} -ec 'printf "%s" OK; set -x; echo OK; exit 0'
474
475	# and that it stops again afer -x is disabled
476	atf_check -s exit:0 \
477			-o match:OKOK -o not-match:echo -o not-match:printf \
478			-e match:printf -e match:OK -e not-match:echo \
479	    ${TEST_SH} -ec 'set -x; printf "%s" OK; set +x; echo OK; exit 0'
480
481	# also check that PS4 is output correctly
482	atf_check -s exit:0 \
483			-o match:OK -o not-match:echo \
484			-e match:OK -e match:Run:echo \
485		${TEST_SH} -ec 'PS4=Run:; set -x; echo OK; exit 0'
486
487	return 0
488
489	# This one seems controversial... I suspect it is NetBSD's sh
490	# that is wrong to not output "for" "while" "if" ... etc
491
492	# and lastly, that shell keywords do not get output when "executed"
493	atf_check -s exit:0 \
494			-o match:111222333 -o not-match:printf \
495			-o not-match:for \
496			-e match:printf -e match:111 -e not-match:111222 \
497			-e not-match:for -e not-match:do -e not-match:done \
498		${TEST_SH} -ec \
499	   'set -x; for i in 111 222 333; do printf "%s" $i; done; echo; exit 0'
500}
501
502atf_test_case set_X
503set_X_head() {
504	atf_set "descr" "Tests that 'set -X' turns on command exec logging " \
505	                "and that it enables set -x and retains a single fd"
506}
507set_X_body() {
508
509	# First we need to verify that $TEST_SH supports -X
510	test_optional_on_off X					||
511		atf_skip "$TEST_SH does not support -X"
512
513	# and that the -X it implements is the -X we expect
514	$TEST_SH -c 'exec 2>/dev/null;
515		set +x; set -X;
516		case "$-" in (*x*) exit 0;; esac;
517		exit 1'						||
518			atf_skip "$TEST_SH supports -X but not 'the' -X"
519
520	# Above has already tested that set -X => set -x
521	# Now test that set +X => set +x
522	# and that set -x and set +x do not affect -X
523
524	atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
525		'set -x; set +X; case "$-" in (*x*) echo FAIL; exit 1;; esac'
526
527	atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
528		'set -X; set +x;
529		 case "$-" in (*x*) echo FAIL; exit 1;; esac
530		 case "$-" in (*X*) exit 0;; esac; echo ERROR; exit 2'
531
532	atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
533		'set -X; set +x; set -x;
534		 case "$-" in (*x*X*|*X*x*) exit 0;; esac; echo ERROR; exit 2'
535
536	atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
537		'set +X; set -x;
538		 case "$-" in (*X*) echo FAIL; exit 1;; esac
539		 case "$-" in (*x*) exit 0;; esac; echo ERROR; exit 2'
540
541	atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
542		'set +X; set -x; set +x;
543		 case "$-" in (*[xX]*) echo FAULT; exit 3;; esac'
544
545	# The following just verify regular tracing using -X instead of -x
546	# These are the same tests as the -x test (set_x) performs.
547
548	# check that cmd output appears after -X is enabled
549	atf_check -s exit:0 \
550			-o match:OKOK -o not-match:echo -o not-match:printf \
551			-e not-match:printf -e match:OK -e match:echo \
552		${TEST_SH} -ec 'printf "%s" OK; set -X; echo OK; exit 0'
553
554	# and that it stops again afer -X is disabled
555	atf_check -s exit:0 \
556			-o match:OKOK -o not-match:echo -o not-match:printf \
557			-e match:printf -e match:OK -e not-match:echo \
558	    ${TEST_SH} -ec 'set -X; printf "%s" OK; set +X; echo OK; exit 0'
559
560	# also check that PS4 is output correctly
561	atf_check -s exit:0 \
562			-o match:OK -o not-match:echo \
563			-e match:OK -e match:Run:echo \
564		${TEST_SH} -ec 'PS4=Run:; set -X; echo OK; exit 0'
565
566	# end copies of -x tests ...
567
568	# now check that we can move stderr around without affecting -X output
569
570	atf_check -s exit:0 \
571			-o match:OKOK -o not-match:echo -o not-match:printf \
572			-e match:printf -e match:OK -e match:echo \
573		${TEST_SH} -ecX 'printf "%s" OK; exec 2>/dev/null; echo OK'
574	atf_check -s exit:0 \
575			-o match:OKOK -o not-match:echo -o not-match:printf \
576			-e match:printf -e match:OK -e match:echo \
577		${TEST_SH} -ecX 'printf "%s" OK; exec 2>&1; echo OK'
578	atf_check -s exit:0 \
579			-o match:OKOK -o not-match:echo -o not-match:printf \
580			-e match:printf -e match:OK -e match:echo \
581		${TEST_SH} -ecX 'printf "%s" OK; exec 2>&-; echo OK'
582
583	# and that we can put tracing on an external file, leaving stderr alone
584
585	atf_require_prog grep
586
587	rm -f X-trace
588	atf_check -s exit:0 \
589			-o match:OKOK -o not-match:echo -o not-match:printf \
590			-e empty \
591		${TEST_SH} -ec 'PS4=; set -X 2>X-trace; printf "%s" OK; echo OK'
592	test -s X-trace || atf_fail "T1: Failed to create trace output file"
593	grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
594		atf_fail "T1: -X tracing missing printf"
595	grep >/dev/null 2>&1 'echo.*OK' X-trace ||
596		atf_fail "T1: -X tracing missing echo"
597
598	rm -f X-trace
599	atf_check -s exit:0 \
600			-o match:OKOK -o not-match:echo -o not-match:printf \
601			-e empty \
602		${TEST_SH} -ec \
603			'PS4=; set -X 2>X-trace;
604			printf "%s" OK;
605			exec 2>/dev/null;
606			echo OK'
607	test -s X-trace || atf_fail "T2: Failed to create trace output file"
608	grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
609		atf_fail "T2: -X tracing missing printf"
610	grep >/dev/null 2>&1 'exec' X-trace ||
611		atf_fail "T2: -X tracing missing exec"
612	grep >/dev/null 2>&1 'echo.*OK' X-trace ||
613		atf_fail "T2: -X tracing missing echo after stderr redirect"
614
615	rm -f X-trace
616	atf_check -s exit:0 \
617			-o match:OKOK -o not-match:echo -o not-match:printf \
618			-e empty \
619		${TEST_SH} -ec \
620			'PS4=; set -X 2>X-trace;
621			printf "%s" OK;
622			set -X 2>/dev/null;
623			echo OK'
624	test -s X-trace || atf_fail "T3: Failed to create trace output file"
625	grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
626		atf_fail "T3: -X tracing missing printf"
627	grep >/dev/null 2>&1 'set.*-X' X-trace ||
628		atf_fail "T3: -X tracing missing set -X"
629	grep >/dev/null 2>&1 'echo.*OK' X-trace &&
630		atf_fail "T3: -X tracing included echo after set -X redirect"
631
632	rm -f X-trace
633	atf_check -s exit:0 \
634			-o match:OKOK -o not-match:echo -o not-match:printf \
635			-e match:echo -e match:OK -e not-match:printf \
636		${TEST_SH} -ec \
637			'PS4=; set -X 2>X-trace;
638			printf "%s" OK;
639			set -X;
640			echo OK'
641	test -s X-trace || atf_fail "T4: Failed to create trace output file"
642	grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
643		atf_fail "T4: -X tracing missing printf"
644	grep >/dev/null 2>&1 'set.*-X' X-trace ||
645		atf_fail "T4: -X tracing missing set -X"
646	grep >/dev/null 2>&1 'echo.*OK' X-trace &&
647		atf_fail "T4: -X tracing included echo after set -X redirect"
648
649	# Now check that -X and the tracing files work properly wrt functions
650
651	# a shell that supports -X should support "local -" ... but verify
652
653	( ${TEST_SH} -c 'fn() { local - || exit 2; set -f; }; set +f; fn;
654		case "$-" in ("*f*") exit 1;; esac; exit 0' ) 2>/dev/null ||
655			atf_skip "-X function test: 'local -' unsupported"
656
657	rm -f X-trace X-trace-fn
658	atf_check -s exit:0 \
659			-o match:OKhelloGOOD		\
660			-e empty			\
661		${TEST_SH} -c '
662			say() {
663				printf "%s" "$*"
664			}
665			funct() {
666				local -
667
668				set -X 2>X-trace-fn
669				say hello
670			}
671
672			set -X 2>X-trace
673
674			printf OK
675			funct
676			echo GOOD
677		'
678	test -s X-trace || atf_fail "T5: Failed to create trace output file"
679	test -s X-trace-fn || atf_fail "T5: Failed to create fn trace output"
680	grep >/dev/null 2>&1 'printf.*OK' X-trace ||
681		atf_fail "T5: -X tracing missing printf"
682	grep >/dev/null 2>&1 funct X-trace ||
683		atf_fail "T5: -X tracing missing funct"
684	grep >/dev/null 2>&1 'set.*-X' X-trace ||
685		atf_fail "T5: -X tracing missing set -X from in funct"
686	grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
687		atf_fail "T5: -X tracing missing echo after funct redirect"
688	grep >/dev/null 2>&1 'say.*hello' X-trace &&
689		atf_fail "T5: -X tracing included 'say' after funct redirect"
690	grep >/dev/null 2>&1 'say.*hello' X-trace-fn ||
691		atf_fail "T5: -X funct tracing missed 'say'"
692
693	rm -f X-trace X-trace-fn
694
695	atf_check -s exit:0 \
696			-o match:OKhelloGOOD		\
697			-e empty			\
698		${TEST_SH} -c '
699			say() {
700				printf "%s" "$*"
701			}
702			funct() {
703				local -
704
705				set +X
706				say hello
707			}
708
709			set -X 2>X-trace
710
711			printf OK
712			funct
713			echo GOOD
714		'
715	test -s X-trace || atf_fail "T6: Failed to create trace output file"
716	grep >/dev/null 2>&1 'printf.*OK' X-trace ||
717		atf_fail "T6: -X tracing missing printf"
718	grep >/dev/null 2>&1 funct X-trace ||
719		atf_fail "T6: -X tracing missing funct"
720	grep >/dev/null 2>&1 'set.*+X' X-trace ||
721		atf_fail "T6: -X tracing missing set +X from in funct"
722	grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
723		atf_fail "T6: -X tracing missing echo after funct redirect"
724	grep >/dev/null 2>&1 'say.*hello' X-trace &&
725		atf_fail "T6: -X tracing included 'say' after funct redirect"
726
727	rm -f X-trace
728
729	atf_check -s exit:0 \
730			-o match:OKtracednotraceGOOD \
731			-e match:say -e match:traced -e not-match:notrace \
732		${TEST_SH} -c '
733			say() {
734				printf "%s" "$*"
735			}
736			funct() {
737				local -
738
739				set +X -x
740
741				say traced
742				exec 2>/dev/null
743				say notrace
744
745			}
746
747			set -X 2>X-trace
748
749			printf OK
750			funct
751			echo GOOD
752		'
753	test -s X-trace || atf_fail "T7: Failed to create trace output file"
754	grep >/dev/null 2>&1 'printf.*OK' X-trace ||
755		atf_fail "T7: -X tracing missing printf"
756	grep >/dev/null 2>&1 funct X-trace ||
757		atf_fail "T7: -X tracing missing funct"
758	grep >/dev/null 2>&1 'set.*+X.*-x' X-trace ||
759		atf_fail "T7: -X tracing missing set +X -x from in funct"
760	grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
761		atf_fail "T7: -X tracing missing echo after funct +X"
762	grep >/dev/null 2>&1 'say.*hello' X-trace &&
763		atf_fail "T7: -X tracing included 'say' after funct +X"
764
765	rm -f X-trace X-trace-fn
766	atf_check -s exit:0 \
767			-o "match:OKg'daybye-bye.*hello.*GOOD"		\
768			-e empty 					\
769		${TEST_SH} -c '
770			say() {
771				printf "%s" "$*"
772			}
773			fn1() {
774				local -
775
776				set -X 2>>X-trace-fn
777				say "g'\''day"
778				"$@"
779				say bye-bye
780			}
781			fn2() {
782				set +X
783				say hello
784				"$@"
785				say goodbye
786			}
787
788			set -X 2>X-trace
789
790			printf OK
791			fn1
792			fn1 fn2
793			fn1 fn1 fn2
794			fn1 fn2 fn1 fn2 fn1
795			fn1 fn1 fn2 fn2 fn1
796			echo GOOD
797		'
798
799	# That test generally succeeds if the earlier ones did
800	# and if it did not dump core!
801
802	# But we can check a few things...
803
804	test -s X-trace || atf_fail "T8: Failed to create trace output file"
805	test -s X-trace-fn || atf_fail "T8: Failed to create trace output file"
806	grep >/dev/null 2>&1 'printf.*OK' X-trace ||
807		atf_fail "T8: -X tracing missing printf"
808	grep >/dev/null 2>&1 fn1 X-trace ||
809		atf_fail "T8: -X tracing missing fn1"
810	grep >/dev/null 2>&1 'set.*-X' X-trace ||
811		atf_fail "T8: -X tracing missing set -X from in fn1"
812	grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
813		atf_fail "T8: -X tracing missing echo after fn1 redirect"
814	grep >/dev/null 2>&1 'say.*hello' X-trace &&
815		atf_fail "T8: -X tracing included 'say' after fn2 +X"
816	grep >/dev/null 2>&1 'say.*hello' X-trace-fn &&
817		atf_fail "T8: -X fn tracing included 'say' after fn2 +X"
818
819
820	rm -f X-trace
821
822	return 0
823}
824
825opt_test_setup()
826{
827	test -n "$1" || { echo >&2 "Internal error"; exit 1; }
828
829	cat > "$1" << 'END_OF_FUNCTIONS'
830local_opt_check()
831{
832	local -
833}
834
835instr()
836{
837	expr "$2" : "\(.*$1\)" >/dev/null
838}
839
840save_opts()
841{
842	local -
843
844	set -e
845	set -u
846
847	instr e "$-" && instr u "$-" && return 0
848	echo ERR
849}
850
851fiddle_opts()
852{
853	set -e
854	set -u
855
856	instr e "$-" && instr u "$-" && return 0
857	echo ERR
858}
859
860local_test()
861{
862	set +eu
863
864	save_opts
865	instr '[eu]' "$-" || printf %s "OK"
866
867	fiddle_opts
868	instr e "$-" && instr u "$-" && printf %s "OK"
869
870	set +eu
871}
872END_OF_FUNCTIONS
873}
874
875atf_test_case restore_local_opts
876restore_local_opts_head() {
877	atf_set "descr" "Tests that 'local -' saves and restores options.  " \
878			"Note that "local" is a local shell addition"
879}
880restore_local_opts_body() {
881	atf_require_prog cat
882	atf_require_prog expr
883
884	FN="test-funcs.$$"
885	opt_test_setup "${FN}" || atf_skip "Cannot setup test environment"
886
887	${TEST_SH} -ec ". './${FN}'; local_opt_check" 2>/dev/null ||
888		atf_skip "sh extension 'local -' not supported by ${TEST_SH}"
889
890	atf_check -s exit:0 -o match:OKOK -o not-match:ERR -e empty \
891		${TEST_SH} -ec ". './${FN}'; local_test"
892}
893
894atf_test_case vi_emacs_VE_toggle
895vi_emacs_VE_toggle_head() {
896	atf_set "descr" "Tests enabling vi disables emacs (and v.v - but why?)"\
897			"  Note that -V and -E are local shell additions"
898}
899vi_emacs_VE_toggle_body() {
900
901	test_optional_on_off V E ||
902	  atf_skip "One or both V & E opts unsupported by ${TEST_SH}"
903
904	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
905		q() {
906			eval "case \"$-\" in
907			(*${2}*)	return 1;;
908			(*${1}*)	return 0;;
909			esac"
910			return 1
911		}
912		x() {
913			echo >&2 "Option set or toggle failure:" \
914					" on=$1 off=$2 set=$-"
915			exit 1
916		}
917		set -V; q V E || x V E
918		set -E; q E V || x E V
919		set -V; q V E || x V E
920		set +EV; q "" "[VE]" || x "" VE
921		exit 0
922	'
923}
924
925atf_test_case pipefail
926pipefail_head() {
927	atf_set "descr" "Basic tests of the pipefail option"
928}
929pipefail_body() {
930	${TEST_SH} -c 'set -o pipefail' 2>/dev/null ||
931		atf_skip "pipefail option not supported by ${TEST_SH}"
932
933	atf_check -s exit:0 -o match:'pipefail.*off' -e empty ${TEST_SH} -c \
934		'set -o | grep pipefail'
935	atf_check -s exit:0 -o match:'pipefail.*on' -e empty ${TEST_SH} -c \
936		'set -o pipefail; set -o | grep pipefail'
937
938	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
939		'(exit 1) | (exit 2) | (exit 0)'
940	atf_check -s exit:2 -o empty -e empty ${TEST_SH} -c \
941		'set -o pipefail; (exit 1) | (exit 2) | (exit 0)'
942	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
943		'set -o pipefail; (exit 1) | (exit 0) | (exit 0)'
944	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
945		'set -o pipefail; (exit 0) | (exit 0) | (exit 0)'
946
947	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
948		'! (exit 1) | (exit 2) | (exit 0)'
949	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
950		'set -o pipefail; ! (exit 1) | (exit 2) | (exit 0)'
951	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
952		'set -o pipefail; ! (exit 1) | (exit 0) | (exit 0)'
953	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
954		'set -o pipefail; ! (exit 0) | (exit 0) | (exit 0)'
955
956	atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
957		'(exit 1) | (exit 2) | (exit 0); echo $?'
958	atf_check -s exit:0 -o inline:'2\n' -e empty ${TEST_SH} -c \
959		'set -o pipefail; (exit 1) | (exit 2) | (exit 0); echo $?'
960	atf_check -s exit:0 -o inline:'1\n' -e empty ${TEST_SH} -c \
961		'set -o pipefail; (exit 1) | (exit 0) | (exit 0); echo $?'
962	atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
963		'set -o pipefail; (exit 0) | (exit 0) | (exit 0); echo $?'
964
965	atf_check -s exit:0 -o inline:'1\n' -e empty ${TEST_SH} -c \
966		'! (exit 1) | (exit 2) | (exit 0); echo $?'
967	atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
968		'set -o pipefail; ! (exit 1) | (exit 2) | (exit 0); echo $?'
969	atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
970		'set -o pipefail; ! (exit 1) | (exit 0) | (exit 0); echo $?'
971	atf_check -s exit:0 -o inline:'1\n' -e empty ${TEST_SH} -c \
972		'set -o pipefail; ! (exit 0) | (exit 0) | (exit 0); echo $?'
973}
974
975atf_test_case xx_bogus
976xx_bogus_head() {
977	atf_set "descr" "Tests that attempting to set a nonsense option fails."
978}
979xx_bogus_body() {
980	# Biggest problem here is picking a "nonsense option" that is
981	# not implemented by any shell, anywhere.  Hopefully this will do.
982
983	# 'set' is a special builtin, so a conforming shell should exit
984	# on an arg error, and the ERR should not be printed.
985	atf_check -s not-exit:0 -o empty -e not-empty \
986		${TEST_SH} -c 'set -% ; echo ERR'
987}
988
989atf_test_case Option_switching
990Option_switching_head() {
991	atf_set "descr" "options can be enabled and disabled"
992}
993Option_switching_body() {
994
995	# Cannot test -m, setting it causes test shell to fail...
996	# (test shell gets SIGKILL!)  Wonder why ... something related to atf
997	# That is, it works if just run as "sh -c 'echo $-; set -m; echo $-'"
998
999	# Don't bother testing toggling -n, once on, it stays on...
1000	# (and because the test fn refuses to allow us to try)
1001
1002	# Cannot test -o or -c here, or the extension -s
1003	# they can only be used, not switched
1004
1005	# these are the posix options, that all shells should implement
1006	test_option_on_off a b C e f h u v x      # m
1007
1008	# and these are extensions that might not exist (non-fatal to test)
1009	# -i and -s (and -c) are posix options, but are not required to
1010	# be accessible via the "set" command, just the command line.
1011	# We allow for -i to work with set, as that makes some sense,
1012	# -c and -s do not.
1013	test_optional_on_off E i I p q V X || true
1014
1015	# Also test (some) option combinations ...
1016	# only testing posix options here, because it is easier...
1017	test_option_on_off aeu vx Ca aCefux
1018}
1019
1020atf_init_test_cases() {
1021	# tests are run in order sort of names produces, so choose names wisely
1022
1023	# this one tests turning on/off all the mandatory. and extra flags
1024	atf_add_test_case Option_switching
1025	# and this tests the NetBSD "local -" functionality in functions.
1026	atf_add_test_case restore_local_opts
1027
1028	# no tests for	-m (no idea how to do that one)
1029	#		-I (no easy way to generate the EOF it ignores)
1030	#		-i (not sure how to test that one at the minute)
1031	#		-p (because we aren't going to run tests setuid)
1032	#		-V/-E (too much effort, and a real test would be huge)
1033	#		-c (because almost all the other tests test it anyway)
1034	#		-q (because, for now, I am lazy)
1035	#		-s (coming soon, hopefully)
1036	#		-o (really +o: again, hopefully soon)
1037	#		-o longname (again, just laziness, don't wait...)
1038	# 		-h/-b (because NetBSD doesn't implement them)
1039	atf_add_test_case set_a
1040	atf_add_test_case set_C
1041	atf_add_test_case set_e
1042	atf_add_test_case set_f
1043	atf_add_test_case set_n
1044	atf_add_test_case set_u
1045	atf_add_test_case set_v
1046	atf_add_test_case set_x
1047	atf_add_test_case set_X
1048
1049	atf_add_test_case vi_emacs_VE_toggle
1050
1051	atf_add_test_case pipefail
1052
1053	atf_add_test_case xx_bogus
1054}
1055