Home | History | Annotate | Line # | Download | only in sh
      1 # $NetBSD: t_builtins.sh,v 1.6 2021/05/18 21:37:56 kre Exp $
      2 #
      3 # Copyright (c) 2018 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 #
     31 # This file tests the various sh builtin utilities.
     32 #
     33 # Those utilities that are really external programs, which are builtin in
     34 # for (mostly) performance (printf, kill, test, ...), are tested elsewhere.
     35 # We do test the builtin "echo" here as (in NetBSD) it is different than
     36 # the external one.
     37 #
     38 # The (mostly special) builtins which appear to be more syntax than command
     39 # are tested in other test programs, rather than here (break, continue...)
     40 #
     41 # And finally, those which are fundamental to the operation of the shell,
     42 # like wait, set, shift, ... are also tested in other test programs where
     43 # all their operations can be more thoroughly verified.
     44 #
     45 # This leaves those which need to be built in (cd, umask, ...) but whose
     46 # purpose is mostly to alter the environment in which the shell operates
     47 # of that of the commands it runs.   These tests act in co-operation with
     48 # other tests exist here (where thy do) by not duplicating tests run
     49 # elsewhere (ulimit is one example) but just adding to those.
     50 # One day these might be unified.
     51 #
     52 # We do test both standard use of the builtins (where they are standard)
     53 # and NetBSD sh extensions (when run on a shell with no support, such tests
     54 # should be skipped.)
     55 #
     56 
     57 # Utility function able to test whether most of the builtins exist in
     58 # the shell being tested.
     59 have_builtin()
     60 {
     61 	${TEST_SH} -c "( $3 $1 $4 ) >/dev/null 2>&1" 	&&
     62 	LC_ALL=C ${TEST_SH} -c \
     63 	    'case "$( (type '"$1"') 2>&1)" in
     64 		(*built*)	exit 0 ;;
     65 		(*reserved*)	exit 0 ;;   # zsh!! (reserved words are builtin)
     66 	     esac
     67 	     exit 1'					||
     68 	{
     69 		test -z "$2" && atf_skip "${TEST_SH} has no '$1$5' built-in"
     70 		return 1;
     71 	}
     72 
     73 	return 0
     74 }
     75 
     76 # And another to test if the shell being tested is the NetBSD shell,
     77 # as we use these tests both to test standards conformance (correctness)
     78 # which should be possible for all shells, and to test NetBSD
     79 # extensions (which we mostly do by testing if the extension exists)
     80 # and NetBSD sh behaviour for what is unspecified by the standard
     81 # (so we will be notified via test failure should that unspecified
     82 # behaviour alter) for which we have to discover if that shell is the
     83 # one being tested.
     84 
     85 is_netbsd_sh()
     86 {
     87 	unset NETBSD_SHELL 2>/dev/null
     88 	test -n "$( ${TEST_SH} -c 'printf %s "${NETBSD_SHELL}"')"
     89 }
     90 
     91 ### Helper functions
     92 
     93 nl='
     94 '
     95 reset()
     96 {
     97 	TEST_NUM=0
     98 	TEST_FAILURES=''
     99 	TEST_FAIL_COUNT=0
    100 	TEST_ID="$1"
    101 
    102 	# These are used in check()
    103 	atf_require_prog tr
    104 	atf_require_prog printf
    105 	atf_require_prog mktemp
    106 }
    107 
    108 # Test run & validate.
    109 #
    110 #	$1 is the command to run (via sh -c)
    111 #	$2 is the expected output
    112 #	$3 is the expected exit status from sh
    113 #	$4 is optional extra data for the error msg (if there is one)
    114 #
    115 # Stderr is exxpected to be empty, unless the expected exit code ($3) is != 0
    116 # in which case some message there is expected (and nothing is a failure).
    117 # When non-zero exit is expected, we note a different (non-zero) value
    118 # observed, but do not fail the test because of that.
    119 
    120 check()
    121 {
    122 	fail=false
    123 	TEMP_FILE=$( mktemp OUT.XXXXXX )
    124 	TEST_NUM=$(( $TEST_NUM + 1 ))
    125 	MSG=
    126 
    127 	# our local shell (ATF_SHELL) better do quoting correctly...
    128 	# some of the tests expect us to expand $nl internally...
    129 	CMD="$1"
    130 
    131 	# determine what the test generates, preserving trailing \n's
    132 	result="$( ${TEST_SH} -c "${CMD}" 2>"${TEMP_FILE}" && printf X )"
    133 	STATUS=$?
    134 	result="${result%X}"
    135 
    136 
    137 	if [ "${STATUS}" -ne "$3" ]; then
    138 		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
    139 		MSG="${MSG} expected exit code $3, got ${STATUS}"
    140 
    141 		# don't actually fail just because of wrong exit code
    142 		# unless we either expected, or received "good"
    143 		# or something else is detected as incorrect as well.
    144 		case "$3/${STATUS}" in
    145 		(*/0|0/*) fail=true;;
    146 		esac
    147 	fi
    148 
    149 	if [ "$3" -eq 0 ]; then
    150 		if [ -s "${TEMP_FILE}" ]; then
    151 			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
    152 			MSG="${MSG} Messages produced on stderr unexpected..."
    153 			MSG="${MSG}${nl}$( cat "${TEMP_FILE}" )"
    154 			fail=true
    155 		fi
    156 	else
    157 		if ! [ -s "${TEMP_FILE}" ]; then
    158 			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
    159 			MSG="${MSG} Expected messages on stderr,"
    160 			MSG="${MSG} nothing produced"
    161 			fail=true
    162 		fi
    163 	fi
    164 	rm -f "${TEMP_FILE}"
    165 
    166 	if [ "$2" != "${result}" ]
    167 	then
    168 		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
    169 		MSG="${MSG} Expected: <<$2>>, received: <<$result>>"
    170 		fail=true
    171 	fi
    172 
    173 	if $fail
    174 	then
    175 		if [ -n "$4" ]; then
    176 			MSG="${MSG}${MSG:+${nl}}[$TEST_NUM] Note: ${4}"
    177 		fi
    178 		MSG="${MSG}${MSG:+${nl}}[$TEST_NUM]"
    179  		MSG="${MSG} Full command: <<${CMD}>>"
    180 	fi
    181 
    182 	$fail && test -n "$TEST_ID" && {
    183 		TEST_FAILURES="${TEST_FAILURES}${TEST_FAILURES:+${nl}}"
    184 		TEST_FAILURES="${TEST_FAILURES}${TEST_ID}[$TEST_NUM]:"
    185 		TEST_FAILURES="${TEST_FAILURES} Test of <<$1>> failed.";
    186 		TEST_FAILURES="${TEST_FAILURES}${nl}${MSG}"
    187 		TEST_FAIL_COUNT=$(( $TEST_FAIL_COUNT + 1 ))
    188 		return 0
    189 	}
    190 	$fail && atf_fail "Test[$TEST_NUM] failed: $(
    191 	    # ATF does not like newlines in messages, so change them...
    192 		    printf '%s' "${MSG}" | tr '\n' ';'
    193 	    )"
    194 	return 0
    195 }
    196 
    197 results()
    198 {
    199 	test -n "$1" && atf_expect_fail "$1"
    200 
    201 	test -z "${TEST_ID}" && return 0
    202 	test -z "${TEST_FAILURES}" && return 0
    203 
    204 	echo >&2 "=========================================="
    205 	echo >&2 "While testing '${TEST_ID}'"
    206 	echo >&2 " - - - - - - - - - - - - - - - - -"
    207 	echo >&2 "${TEST_FAILURES}"
    208 
    209 	atf_fail \
    210  "Test ${TEST_ID}: $TEST_FAIL_COUNT (of $TEST_NUM) subtests failed - see stderr"
    211 }
    212 
    213 ####### End helpers
    214 
    215 atf_test_case colon
    216 colon_head() {
    217 	atf_set "descr" "Tests the shell special builtin ':' command"
    218 }
    219 colon_body() {
    220 	have_builtin : || return 0
    221 
    222 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c ":"
    223 
    224 	# ':' is a special builtin, so we should exit on redirect error
    225 	# and variable assignments should persist (stupid, but it is the rule)
    226 
    227 	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
    228 		": >/foo/bar; printf %s No-exit-BUG"
    229 	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
    230 		'X=BUG; X=OK : ; printf %s "${X}"'
    231 }
    232 
    233 atf_test_case echo
    234 echo_head() {
    235 	atf_set "descr" "Tests the shell builtin version of echo"
    236 }
    237 echo_body() {
    238 	have_builtin echo || return 0
    239 
    240 	if ! is_netbsd_sh; then
    241 		atf_skip \
    242 	   "${TEST_SH%% *} is not the NetBSD shell, this test is for it alone"
    243 		return 0
    244 	fi
    245 
    246 	reset echo
    247 
    248 	check 'echo "hello world"' "hello world${nl}" 0
    249 	check 'echo hello world' "hello world${nl}" 0
    250 	check 'echo -n "hello world"' "hello world" 0
    251 	check 'IFS=:; echo hello world' "hello world${nl}" 0
    252 	check 'IFS=; echo hello world' "hello world${nl}" 0
    253 
    254 	check 'echo -e "hello world"' "hello world${nl}" 0
    255 	check 'echo -e hello world' "hello world${nl}" 0
    256 	check 'IFS=:; echo -e hello world' "hello world${nl}" 0
    257 
    258 	# only one of the options is used
    259 	check 'echo -e -n "hello world"' "-n hello world${nl}" 0
    260 	check 'echo -n -e "hello world"' "-e hello world" 0
    261 	# and only when it is alone
    262 	check 'echo -en "hello world"' "-en hello world${nl}" 0
    263 	check 'echo -ne "hello world"' "-ne hello world${nl}" 0
    264 
    265 	# echo is specifically required to *not* support --
    266 	check 'echo -- "hello world"' "-- hello world${nl}" 0
    267 
    268 	# similarly any other unknown option is simply part of the output
    269 	for OPT in a b c v E N Q V 0 1 2 @ , \? \[ \] \( \; . \* -help -version
    270 	do
    271 		check "echo '-${OPT}' foo" "-${OPT} foo${nl}" 0
    272 	done
    273 
    274 	# Now test the \\ expansions, with and without -e
    275 
    276 	# We rely upon printf %b (tested elsewhere, not only a sh test)
    277 	# to verify the output when the \\ is supposed to be expanded.
    278 
    279 	for E in '' -e
    280 	do
    281 		for B in a b c e f n r t v \\ 04 010 012 0177
    282 		do
    283 			S="test string with \\${B} in it"
    284 			if [ -z "${E}" ]; then
    285 				R="${S}${nl}"
    286 			else
    287 				R="$(printf '%b\nX' "${S}")"
    288 				R=${R%X}
    289 			fi
    290 			check "echo $E '${S}'" "${R}" 0
    291 		done
    292 	done
    293 
    294 	check 'echo foo >&-' "" 1
    295 	check 'echo foo >&- 2>&-; echo $?; echo $?' "1${nl}0${nl}" 0
    296 
    297 	results
    298 }
    299 
    300 atf_test_case eval
    301 eval_head() {
    302 	atf_set "descr" "Tests the shell special builtin 'eval'"
    303 }
    304 eval_body() {
    305 	have_builtin eval || return 0
    306 
    307 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval "exit 0"'
    308 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval "exit 1"'
    309 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval exit 0'
    310 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval exit 1'
    311 
    312 	atf_check -s exit:0 -e empty -o inline:0 ${TEST_SH} -c \
    313 		'eval true; printf %d $?'
    314 	atf_check -s exit:0 -e empty -o inline:1 ${TEST_SH} -c \
    315 		'eval false; printf %d $?'
    316 
    317 	atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
    318 		'X=a Y=b Z=c; for V in X Y Z; do eval "printf %s \$$V"; done'
    319 	atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
    320 		'X=a Y=b Z=c; for V in X Y Z; do eval printf %s \$$V; done'
    321 	atf_check -s exit:0 -e empty -o inline:XYZ ${TEST_SH} -c \
    322 		'for V in X Y Z; do eval "${V}=${V}"; done; printf %s "$X$Y$Z"'
    323 
    324 	# make sure eval'd strings affect the shell environment
    325 
    326 	atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
    327 		'X=a; eval "X=b"; printf /%s/ "${X-unset}"'
    328 	atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
    329 		'X=a; Y=X; Z=b; eval "$Y=$Z"; printf /%s/ "${X-unset}"'
    330 	atf_check -s exit:0 -e empty -o inline:/unset/ ${TEST_SH} -c \
    331 		'X=a; eval "unset X"; printf /%s/ "${X-unset}"'
    332 	atf_check -s exit:0 -e empty -o inline:// ${TEST_SH} -c \
    333 		'unset X; eval "X="; printf /%s/ "${X-unset}"'
    334 	atf_check -s exit:0 -e empty -o inline:'2 y Z ' ${TEST_SH} -c \
    335 		'set -- X y Z; eval shift; printf "%s " "$#" "$@"'
    336 
    337 	# ensure an error in an eval'd string causes the shell to exit
    338 	# unless 'eval' is preceded by 'command' (in which case the
    339 	# string is not eval'd but execution continues)
    340 
    341 	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
    342 		'eval "if done"; printf %s status=$?'
    343 
    344 	atf_check -s exit:0 -e not-empty -o 'match:status=[1-9]' \
    345 	    ${TEST_SH} -c \
    346 		'command eval "if done"; printf %s status=$?'
    347 
    348 	atf_check -s not-exit:0 -e not-empty \
    349 	    -o 'match:status=[1-9]' -o 'not-match:[XY]' ${TEST_SH} -c \
    350 		 'command eval "printf X; if done; printf Y"
    351 		  S=$?; printf %s status=$S; exit $S'
    352 
    353 	# whether 'X' is output here is (or should be) unspecified.
    354 	atf_check -s not-exit:0 -e not-empty \
    355 	    -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
    356 		 'command eval "printf X
    357 		 		if done
    358 				printf Y"
    359 		  S=$?; printf %s status=$S; exit $S'
    360 
    361 	if is_netbsd_sh
    362 	then
    363 		# but on NetBSD we expect that X to appear...
    364 		atf_check -s not-exit:0 -e not-empty  -o 'match:X' \
    365 		    -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
    366 			 'command eval "printf X
    367 					if done
    368 					printf Y"
    369 			  S=$?; printf %s status=$S; exit $S'
    370 	fi
    371 }
    372 
    373 atf_test_case exec
    374 exec_head() {
    375 	atf_set "descr" "Tests the shell special builtin 'exec'"
    376 }
    377 exec_body() {
    378 	have_builtin exec || return 0
    379 
    380 	atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
    381 		'exec printf OK; printf BROKEN; exit 3'
    382 	atf_check -s exit:3 -e empty -o inline:OKOK ${TEST_SH} -c \
    383 		'(exec printf OK); printf OK; exit 3'
    384 }
    385 
    386 atf_test_case export
    387 export_head() {
    388 	atf_set "descr" "Tests the shell builtin 'export'"
    389 }
    390 export_body() {
    391 	have_builtin export || return 0
    392 
    393 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR'
    394 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR=abc'
    395 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export V A R'
    396 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
    397 		'export V A=1 R=2'
    398 
    399 	atf_require_prog printenv
    400 
    401 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
    402 		'unset VAR || exit 7; export VAR; printenv VAR'
    403 	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
    404 		'unset VAR || exit 7; export VAR=; printenv VAR'
    405 	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
    406 		'unset VAR || exit 7; VAR=; export VAR; printenv VAR'
    407 	atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
    408 		'unset VAR || exit 7; export VAR; VAR=; printenv VAR'
    409 	atf_check -s exit:0 -e empty -o inline:XYZ\\n ${TEST_SH} -c \
    410 		'unset VAR || exit 7; export VAR=XYZ; printenv VAR'
    411 	atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
    412 		'VAR=ABC; export VAR; printenv VAR'
    413 	atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
    414 		'unset VAR || exit 7; export VAR; VAR=ABC; printenv VAR'
    415 	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
    416 		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR'
    417 	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
    418 		'unset VAR || exit 7; export VAR;
    419 		 VAR=ABC; printenv VAR; VAR=XYZ; printenv VAR'
    420 
    421 	# don't check VAR=value, some shells provide meaningless quoting...
    422 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    423 		${TEST_SH} -c \
    424 			'VAR=foobar ; export VAR ; export -p'
    425 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    426 		${TEST_SH} -c \
    427 			'export VAR=foobar ; export -p'
    428 	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
    429 			'unset VAR ; export VAR ; export -p'
    430 	atf_check -s exit:0 -e empty -o not-match:VAR ${TEST_SH} -c \
    431 			'export VAR ; unset VAR ; export -p'
    432 	atf_check -s exit:0 -e empty -o not-match:VAR -o not-match:foobar \
    433 		${TEST_SH} -c \
    434 			'VAR=foobar; export VAR ; unset VAR ; export -p'
    435 
    436 	atf_check -s exit:0 -e empty -o match:VAR= -o match:FOUND=foobar \
    437 		${TEST_SH} -c \
    438 			'export VAR=foobar; V=$(export -p);
    439 			 unset VAR; eval "$V"; export -p;
    440 			 printf %s\\n FOUND=${VAR-unset}'
    441 	atf_check -s exit:0 -e empty -o match:VAR -o match:FOUND=unset \
    442 		${TEST_SH} -c \
    443 			'export VAR; V=$(export -p);
    444 			 unset VAR; eval "$V"; export -p;
    445 			 printf %s\\n FOUND=${VAR-unset}'
    446 
    447 	atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
    448 		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
    449 		unset VAR; printenv VAR; VAR=PQR; printenv VAR'
    450 	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=unset\\nMNO\\n \
    451 	    ${TEST_SH} -c \
    452 		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
    453 		 unset VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
    454 		 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
    455 }
    456 
    457 atf_test_case export_nbsd
    458 export_nbsd_head() {
    459 	atf_set "descr" "Tests NetBSD extensions to the shell builtin 'export'"
    460 }
    461 export_nbsd_body() {
    462 	have_builtin "export" "" "" "-n foo" ' -n' || return 0
    463 
    464 	atf_require_prog printenv
    465 
    466 	atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
    467 		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
    468 		export -n VAR; printenv VAR; VAR=PQR; printenv VAR'
    469 
    470 	atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=XYZ\\nMNO\\n \
    471 	    ${TEST_SH} -c \
    472 		'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
    473 		 export -n VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
    474 		 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
    475 
    476 	have_builtin "export" "" "" -x ' -x' || return 0
    477 
    478 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
    479 		'export VAR=exported; export -x VAR; printenv VAR'
    480 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
    481 		'export VAR=exported; export -x VAR; VAR=local; printenv VAR'
    482 	atf_check -s exit:0 -e empty -o inline:once\\nx\\n ${TEST_SH} -c \
    483 		'export VAR=exported
    484 		 export -x VAR
    485 		 VAR=once printenv VAR
    486 		 printenv VAR || printf %s\\n x'
    487 
    488 	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
    489 		'export VAR=exported; export -x VAR; export VAR=FOO'
    490 
    491 	have_builtin export '' 'export VAR;' '-q VAR' ' -q'  || return 0
    492 
    493 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    494 		'unset VAR; VAR=set; export -q VAR'
    495 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    496 		'export VAR=set; export -q VAR'
    497 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    498 		'VAR=set; RW=set; export -q VAR RW'
    499 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    500 		'VAR=set; export RO=set; export -q VAR RO'
    501 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    502 		'export VAR=set RO=set; export -q VAR RO'
    503 
    504 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    505 		'unset VAR; export -q VAR'
    506 	# next one is the same as the have_builtin test, so "cannot" fail...
    507 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    508 		'unset VAR; export VAR; export -q VAR'
    509 
    510 	# if we have -q we should also have -p var...
    511 	# What's more, we are testing NetBSD sh, so we know output format.
    512 
    513 	atf_check -s exit:0 -e empty -o match:VAR=foobar \
    514 		${TEST_SH} -c \
    515 			'VAR=foobar ; export VAR ; export -p VAR'
    516 	atf_check -s exit:0 -e empty -o inline:1 \
    517 		${TEST_SH} -c \
    518 			'VAR=foobar ; export VAR ;
    519 			printf %d $(export -p VAR | wc -l)'
    520 	atf_check -s exit:0 -e empty \
    521 		-o inline:'export VAR=foobar\nexport OTHER\n' \
    522 		${TEST_SH} -c \
    523 			'VAR=foobar; export VAR OTHER; export -p VAR OTHER'
    524 	atf_check -s exit:0 -e empty \
    525 		-o inline:'export A=aaa\nexport B\nexport D='"''"'\n' \
    526 		${TEST_SH} -c \
    527 			'A=aaa D= C=foo; unset B; export A B D;
    528 			 export -p A B C D'
    529 }
    530 
    531 atf_test_case getopts
    532 getopts_head() {
    533 	atf_set "descr" "Tests the shell builtin 'getopts'"
    534 }
    535 getopts_body() {
    536 	have_builtin getopts "" "f() {" "a x; }; f -a" || return 0
    537 }
    538 
    539 atf_test_case jobs
    540 jobs_head() {
    541 	atf_set "descr" "Tests the shell builting 'jobs' command"
    542 }
    543 jobs_body() {
    544 	have_builtin jobs || return 0
    545 
    546 	atf_require_prog sleep
    547 
    548 	# note that POSIX requires that we reference $! otherwise
    549 	# the shell is not required to remember the process...
    550 
    551 	atf_check -s exit:0 -e empty -o match:sleep -o match:Running \
    552 		${TEST_SH} -c 'sleep 1 & P=$!; jobs; wait'
    553 	atf_check -s exit:0 -e empty -o match:sleep -o match:Done \
    554 		${TEST_SH} -c 'sleep 1 & P=$!; sleep 2; jobs; wait'
    555 }
    556 
    557 atf_test_case read
    558 read_head() {
    559 	atf_set "descr" "Tests the shell builtin read command"
    560 }
    561 read_body() {
    562 	have_builtin read "" "echo x|" "var" || return 0
    563 }
    564 
    565 atf_test_case readonly
    566 readonly_head() {
    567 	atf_set "descr" "Tests the shell builtin 'readonly'"
    568 }
    569 readonly_body() {
    570 	have_builtin readonly || return 0
    571 
    572 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR'
    573 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR=abc'
    574 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A R'
    575 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A=1 R=2'
    576 
    577 	atf_check -s exit:0 -e empty -o inline:unset ${TEST_SH} -c \
    578 		'unset VAR; readonly VAR; printf %s ${VAR-unset}'
    579 	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
    580 		'unset VAR; readonly VAR=set; printf %s ${VAR-unset}'
    581 	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
    582 		'VAR=initial; readonly VAR=set; printf %s ${VAR-unset}'
    583 	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
    584 		'readonly VAR=initial; VAR=new; printf %s "${VAR}"'
    585 
    586 	# don't check VAR=value, some shells provide meaningless quoting...
    587 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    588 		${TEST_SH} -c \
    589 			'VAR=foobar ; readonly VAR ; readonly -p'
    590 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    591 		${TEST_SH} -c \
    592 			'readonly VAR=foobar ; readonly -p'
    593 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    594 		-o not-match:badvalue ${TEST_SH} -c \
    595 			'VAR=badvalue; readonly VAR=foobar ; readonly -p'
    596 	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
    597 			'unset VAR ; readonly VAR ; readonly -p'
    598 
    599 	# checking that readonly -p works (to reset stuff) is a pain...
    600 	# particularly since not all shells say "readonly..." by default
    601 	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=foobar \
    602 		${TEST_SH} -c \
    603 			'V=$(readonly MYVAR=foobar; readonly -p | grep " MYVAR")
    604 			 unset MYVAR; eval "$V"; readonly -p;
    605 			 printf %s\\n FOUND=${MYVAR-unset}'
    606 	atf_check -s exit:0 -e empty -o match:MYVAR\$ -o match:FOUND=unset \
    607 		${TEST_SH} -c \
    608 			'V=$(readonly MYVAR; readonly -p | grep " MYVAR")
    609 			 unset MYVAR; eval "$V"; readonly -p;
    610 			 printf %s\\n "FOUND=${MYVAR-unset}"'
    611 	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=empty \
    612 		${TEST_SH} -c \
    613 			'V=$(readonly MYVAR=; readonly -p | grep " MYVAR")
    614 			 unset VAR; eval "$V"; readonly -p;
    615 			 printf %s\\n "FOUND=${MYVAR-unset&}${MYVAR:-empty}"'
    616 
    617 	# don't test stderr, some shells inist on generating a message for an
    618 	# unset of a readonly var (rather than simply having unset make $?=1)
    619 
    620 	atf_check -s not-exit:0 -e empty -o empty ${TEST_SH} -c \
    621 		'unset VAR; readonly VAR=set;
    622 		 unset VAR 2>/dev/null && printf %s ${VAR:-XX}'
    623 	atf_check -s not-exit:0 -e ignore -o empty ${TEST_SH} -c \
    624 		'unset VAR; readonly VAR=set; unset VAR && printf %s ${VAR:-XX}'
    625 	atf_check -s exit:0 -e ignore -o inline:set ${TEST_SH} -c \
    626 		'unset VAR; readonly VAR=set; unset VAR; printf %s ${VAR-unset}'
    627 }
    628 
    629 atf_test_case readonly_nbsd
    630 readonly_nbsd_head() {
    631 	atf_set "descr" "Tests NetBSD extensions to 'readonly'"
    632 }
    633 readonly_nbsd_body() {
    634 	have_builtin readonly '' 'readonly VAR;' '-q VAR' ' -q'  || return 0
    635 
    636 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    637 		'VAR=set; readonly -q VAR'
    638 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    639 		'readonly VAR=set; readonly -q VAR'
    640 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    641 		'VAR=set; RW=set; readonly -q VAR RW'
    642 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    643 		'VAR=set; readonly RO=set; readonly -q VAR RO'
    644 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    645 		'readonly VAR=set RO=set; readonly -q VAR RO'
    646 
    647 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    648 		'unset VAR; readonly -q VAR'
    649 	# next one is the same as the have_builtin test, so "cannot" fail...
    650 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    651 		'unset VAR; readonly VAR; readonly -q VAR'
    652 
    653 	# if we have -q we should also have -p var...
    654 	# What's more, we are testing NetBSD sh, so we know output format.
    655 
    656 	atf_check -s exit:0 -e empty -o match:VAR=foobar \
    657 		${TEST_SH} -c \
    658 			'VAR=foobar ; readonly VAR ; readonly -p VAR'
    659 	atf_check -s exit:0 -e empty -o inline:1 \
    660 		${TEST_SH} -c \
    661 			'VAR=foobar ; readonly VAR ;
    662 			printf %d $(readonly -p VAR | wc -l)'
    663 	atf_check -s exit:0 -e empty \
    664 		-o inline:'readonly VAR=foobar\nreadonly OTHER\n' \
    665 		${TEST_SH} -c \
    666 			'VAR=foobar; readonly VAR OTHER; readonly -p VAR OTHER'
    667 	atf_check -s exit:0 -e empty \
    668 		-o inline:'readonly A=aaa\nreadonly B\nreadonly D='"''"'\n' \
    669 		${TEST_SH} -c \
    670 			'A=aaa D= C=foo; unset B; readonly A B D;
    671 			 readonly -p A B C D'
    672 }
    673 
    674 atf_test_case cd_pwd
    675 cd_pwd_head() {
    676 	atf_set "descr" "Tests the shell builtins 'cd' & 'pwd'"
    677 }
    678 cd_pwd_body() {
    679 	have_builtin cd "" "HOME=/;" || return 0
    680 	have_builtin pwd || return 0
    681 }
    682 
    683 atf_test_case true_false
    684 true_false_head() {
    685 	atf_set "descr" "Tests the 'true' and 'false' shell builtin commands"
    686 }
    687 true_false_body() {
    688 	have_builtin true || return 0
    689 
    690 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c true
    691 
    692 	# true is not a special builtin, so errors do not cause exit
    693 	# but we should still get an error from the broken redirect
    694 	# and the exit status of true should be false...
    695 
    696 	atf_check -s exit:0 -e not-empty -o inline:OK ${TEST_SH} -c \
    697 		"true >/foo/bar && printf %s NOT-; printf %s OK"
    698 
    699 	# and var-assigns should not affect the current sh env
    700 
    701 	atf_check -s exit:0 -e empty -o inline:IS-OK ${TEST_SH} -c \
    702 		'X=OK; X=BROKEN true && printf %s IS-; printf %s "${X}"'
    703 
    704 	have_builtin false "" ! || return 0
    705 
    706 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c false
    707 }
    708 
    709 atf_test_case type
    710 type_head() {
    711 	atf_set "descr" "Tests the sh builtin 'type' command"
    712 }
    713 type_body() {
    714 	have_builtin type "" "" type || return 0
    715 }
    716 
    717 # This currently has its own t_ulimit - either merge that here,
    718 # or delete this one and keep that...  ulimit -n is also tested in
    719 # the t_redir tests, as that affects the shell's use of file descriptors
    720 atf_test_case ulimit
    721 ulimit_head() {
    722 	atf_set "descr" "Tests the sh builtin 'ulimit'"
    723 }
    724 ulimit_body() {
    725 	have_builtin ulimit || return 0
    726 }
    727 
    728 atf_test_case umask
    729 umask_head() {
    730 	atf_set "descr" "Tests the sh builtin 'umask'"
    731 }
    732 umask_body() {
    733 	have_builtin umask || return 0
    734 
    735 	atf_require_prog touch
    736 	atf_require_prog stat
    737 	atf_require_prog rm
    738 	atf_require_prog chmod
    739 
    740 	reset umask
    741 
    742 	# 8 octal digits
    743 	for M in 0 1 2 3 4 5 6 7
    744 	do
    745 	    # Test numbers start: 1 25 49 73 97 121 145 169
    746 
    747 	    # 8 combinations of each to test (64 inner loops)
    748 	    # 3 tests in each loop, hence 192 subtests in all
    749 
    750 		# Test numbers from loop above, plus (below) and the next 2
    751 		#+     1        4        7         10	     13
    752 	    for T in "0${M}" "00${M}" "0${M}0" "0${M}00" "0${M}${M}" \
    753 		"0${M}${M}0" "0${M}${M}${M}"  "0${M}0${M}"
    754 		#+    16          19		  22
    755 	    do
    756 		# umask turns bits off, calculate which bits will be on...
    757 
    758 		D=$(( 0777 & ~ T ))		# for directories
    759 		F=$(( $D & ~ 0111 ))		# and files with no 'x' bits
    760 
    761 		# Note: $(( )) always produces decimal, so we test that format
    762 		# (see '%d' in printf of stat result)
    763 
    764 		# need chmod or we might have no perm to rmdir TD
    765 		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
    766 
    767 		# check that the umask applies to files created by the shell
    768 		check \
    769 		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
    770 				  "$F" 0 "$F is $(printf %#o $F)" # 1 4 7 10 ...
    771 
    772 		# and to files created by commands that the shell runs
    773 		check \
    774 		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
    775 				  "$F" 0 "$F is $(printf %#o $F)" # 2 5 8 11 ...
    776 
    777 		# and to directories created b ... (directories keep 'x')
    778 		check \
    779 		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
    780 				  "$D" 0 "$D is $(printf %#o $D)" # 3 6 9 12 ...
    781 	    done
    782 	done
    783 
    784 	# Now add a few more tests with less regular u/g/m masks
    785 	# In here, include tests where umask value has no leading '0'
    786 
    787 	# 10 loops, the same 3 tests in each loop, 30 more subtests
    788 	# from 193 .. 222
    789 
    790 	#        193 196 199  202 205 208 211  214  217 220
    791 	for T in 013 047 722 0772 027 123 421 0124 0513 067
    792 	do
    793 		D=$(( 0777 & ~ 0$T ))
    794 		F=$(( $D & ~ 0111 ))
    795 
    796 		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
    797 
    798 		check \
    799 		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
    800 				  "$F" 0 "$F is $(printf %#o $F)"	# +0
    801 
    802 		check \
    803 		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
    804 				  "$F" 0 "$F is $(printf %#o $F)"	# +1
    805 
    806 		check \
    807 		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
    808 				  "$D" 0 "$D is $(printf %#o $D)"	# +2
    809 	done
    810 
    811 	results
    812 }
    813 
    814 atf_test_case unset
    815 unset_head() {
    816 	atf_set "descr" "Tests the sh builtin 'unset'"
    817 }
    818 unset_body() {
    819 	have_builtin unset || return 0
    820 }
    821 
    822 atf_test_case hash
    823 hash_head() {
    824 	atf_set "descr" "Tests the sh builtin 'hash' (ash extension)"
    825 }
    826 hash_body() {
    827 	have_builtin hash || return 0
    828 }
    829 
    830 atf_test_case jobid
    831 jobid_head() {
    832 	atf_set "descr" "Tests sh builtin 'jobid' (NetBSD extension)"
    833 }
    834 jobid_body() {
    835 
    836 	# have_builtin jobid || return 0	No simple jobid command test
    837 	$TEST_SH -c '(exit 0)& jobid $!' >/dev/null 2>&1  || {
    838 		atf_skip "${TEST_SH} has no 'jobid' built-in"
    839 		return 0
    840 	}
    841 }
    842 
    843 atf_test_case let
    844 let_head() {
    845 	atf_set "descr" "Tests the sh builtin 'let' (common extension from ksh)"
    846 }
    847 let_body() {
    848 	have_builtin let "" "" 1 || return 0
    849 }
    850 
    851 atf_test_case local
    852 local_head() {
    853 	atf_set "descr" "Tests the shell builtin 'local' (common extension)"
    854 }
    855 local_body() {
    856 	have_builtin local "" "f () {" "X; }; f" || return 0
    857 }
    858 
    859 atf_test_case setvar
    860 setvar_head() {
    861 	atf_set "descr" "Tests the shell builtin 'setvar' (BSD extension)"
    862 }
    863 setvar_body() {
    864 	have_builtin setvar || return 0
    865 
    866 	atf_check -s exit:0 -e empty -o inline:foo ${TEST_SH} -c \
    867 		'unset PQ && setvar PQ foo; printf %s "${PQ-not set}"'
    868 	atf_check -s exit:0 -e empty -o inline:abcd ${TEST_SH} -c \
    869 		'for x in a b c d; do setvar "$x" "$x"; done;
    870 		 printf %s "$a$b$c$d"'
    871 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
    872 		'a=1; b=2; c=3; d=4
    873 		 for x in a b c d; do setvar "$x" ""; done;
    874 		 printf %s "$a$b$c$d"'
    875 }
    876 
    877 atf_test_case fdflags
    878 fdflags_head() {
    879 	atf_set "descr" \
    880 	   "Tests basic operation of sh builtin 'fdflags' (NetBSD extension)"
    881 }
    882 fdflags_body() {
    883 	have_builtin fdflags || return 0
    884 }
    885 
    886 atf_test_case fdflags__s
    887 fdflags__s_head() {
    888 	atf_set "descr" "Checks setting/clearing flags on file descriptors"
    889 }
    890 fdflags__s_body() {
    891 	have_builtin fdflags || return 0
    892 }
    893 
    894 atf_test_case fdflags__v
    895 fdflags__v_head() {
    896 	atf_set "descr" "Checks verbose operation of fdflags"
    897 }
    898 fdflags__v_body() {
    899 	have_builtin fdflags || return 0
    900 }
    901 
    902 atf_test_case fdflags__v_s
    903 fdflags__v_s_head() {
    904 	atf_set "descr" "tests verbose operation of fdflags -s"
    905 }
    906 fdflags__v_s_body() {
    907 	have_builtin fdflags || return 0
    908 }
    909 
    910 atf_test_case fdflags_multiple_fd
    911 fdflags_multiple_fd_head() {
    912 	atf_set "descr" "Checks operation of fdflags with more than one fd"
    913 }
    914 fdflags_multiple_fd_body() {
    915 	have_builtin fdflags || return 0
    916 }
    917 
    918 atf_test_case fdflags_one_flag_at_a_time
    919 fdflags_one_flag_at_a_time_head() {
    920 	atf_set "descr" "Tests all possible fdflags flags, and combinations"
    921 }
    922 fdflags_one_flag_at_a_time_body() {
    923 	have_builtin fdflags || return 0
    924 }
    925 
    926 atf_test_case fdflags_save_restore
    927 fdflags_save_restore_head() {
    928 	atf_set "descr" 'Verify that fd flags can be saved and restored'
    929 }
    930 fdflags_save_restore_body() {
    931 	have_builtin fdflags || return 0
    932 }
    933 
    934 atf_test_case fdflags_names_abbreviated
    935 fdflags_names_abbreviated_head() {
    936 	atf_set "descr" 'Tests using abbreviated names for fdflags'
    937 }
    938 fdflags_names_abbreviated_body() {
    939 	have_builtin fdflags || return 0
    940 }
    941 
    942 atf_test_case fdflags_xx_errors
    943 fdflags_xx_errors_head() {
    944 	atf_set "descr" 'Check various erroneous fdflags uses'
    945 }
    946 fdflags_xx_errors_body() {
    947 	have_builtin fdflags || return 0
    948 }
    949 
    950 
    951 atf_init_test_cases() {
    952 
    953 	# "standard" builtin commands in sh
    954 
    955 	# no tests of the "very special" (almost syntax) builtins
    956 	#  (break/continue/return) - they're tested enough elsewhere
    957 
    958 	atf_add_test_case cd_pwd
    959 	atf_add_test_case colon
    960 	atf_add_test_case echo
    961 	atf_add_test_case eval
    962 	atf_add_test_case exec
    963 	atf_add_test_case export
    964 	atf_add_test_case getopts
    965 	atf_add_test_case jobs
    966 	atf_add_test_case read
    967 	atf_add_test_case readonly
    968 	atf_add_test_case true_false
    969 	atf_add_test_case type
    970 	atf_add_test_case ulimit
    971 	atf_add_test_case umask
    972 	atf_add_test_case unset
    973 
    974 	# exit/wait/set/shift/trap/alias/unalias/. should have their own tests
    975 	# fc/times/fg/bg/%    are too messy to contemplate for now
    976 	# command ??  (probably should have some tests)
    977 
    978 	# Note that builtin versions of, printf, kill, ... are tested separately
    979 	# (these are all "optional" builtins)
    980 	# (echo is tested here because NetBSD sh builtin echo and /bin/echo
    981 	#  are different)
    982 
    983 	atf_add_test_case export_nbsd
    984 	atf_add_test_case hash
    985 	atf_add_test_case jobid
    986 	atf_add_test_case let
    987 	atf_add_test_case local
    988 	atf_add_test_case readonly_nbsd
    989 	atf_add_test_case setvar
    990 	# inputrc should probably be tested in libedit tests (somehow)
    991 
    992 	# fdflags has a bunch of test cases
    993 
    994 	# Always run one test, so we get at least "skipped" result
    995 	atf_add_test_case fdflags
    996 
    997 	# but no need to say "skipped" lots more times...
    998 	have_builtin fdflags available && {
    999 		atf_add_test_case fdflags__s
   1000 		atf_add_test_case fdflags__v
   1001 		atf_add_test_case fdflags__v_s
   1002 		atf_add_test_case fdflags_multiple_fd
   1003 		atf_add_test_case fdflags_names_abbreviated
   1004 		atf_add_test_case fdflags_one_flag_at_a_time
   1005 		atf_add_test_case fdflags_save_restore
   1006 		atf_add_test_case fdflags_xx_errors
   1007 	}
   1008 	return 0
   1009 }
   1010