Home | History | Annotate | Line # | Download | only in sh
      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 
     35 test_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 
     97 test_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 
    113 atf_test_case set_a
    114 set_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 }
    118 set_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 
    133 atf_test_case set_C
    134 set_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 }
    138 set_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 
    201 atf_test_case set_e
    202 set_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 }
    206 set_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 
    226 atf_test_case set_f
    227 set_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 }
    231 set_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 
    259 atf_test_case set_n
    260 set_n_head() {
    261 	atf_set "descr" "Tests that 'set -n' suppresses command execution " \
    262 	                "and that it behaves as defined by the standard"
    263 }
    264 set_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 
    351 atf_test_case set_u
    352 set_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 }
    356 set_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 
    404 atf_test_case set_v
    405 set_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 }
    409 set_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 
    461 atf_test_case set_x
    462 set_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 }
    466 set_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 
    502 atf_test_case set_X
    503 set_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 }
    507 set_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 
    825 opt_test_setup()
    826 {
    827 	test -n "$1" || { echo >&2 "Internal error"; exit 1; }
    828 
    829 	cat > "$1" << 'END_OF_FUNCTIONS'
    830 local_opt_check()
    831 {
    832 	local -
    833 }
    834 
    835 instr()
    836 {
    837 	expr "$2" : "\(.*$1\)" >/dev/null
    838 }
    839 
    840 save_opts()
    841 {
    842 	local -
    843 
    844 	set -e
    845 	set -u
    846 
    847 	instr e "$-" && instr u "$-" && return 0
    848 	echo ERR
    849 }
    850 
    851 fiddle_opts()
    852 {
    853 	set -e
    854 	set -u
    855 
    856 	instr e "$-" && instr u "$-" && return 0
    857 	echo ERR
    858 }
    859 
    860 local_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 }
    872 END_OF_FUNCTIONS
    873 }
    874 
    875 atf_test_case restore_local_opts
    876 restore_local_opts_head() {
    877 	atf_set "descr" "Tests that 'local -' saves and restores options.  " \
    878 			"Note that "local" is a local shell addition"
    879 }
    880 restore_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 
    894 atf_test_case vi_emacs_VE_toggle
    895 vi_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 }
    899 vi_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 
    925 atf_test_case pipefail
    926 pipefail_head() {
    927 	atf_set "descr" "Basic tests of the pipefail option"
    928 }
    929 pipefail_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 
    975 atf_test_case xx_bogus
    976 xx_bogus_head() {
    977 	atf_set "descr" "Tests that attempting to set a nonsense option fails."
    978 }
    979 xx_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 
    989 atf_test_case Option_switching
    990 Option_switching_head() {
    991 	atf_set "descr" "options can be enabled and disabled"
    992 }
    993 Option_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 
   1020 atf_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