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