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