Home | History | Annotate | Line # | Download | only in sh
t_builtins.sh revision 1.4
      1 # $NetBSD: t_builtins.sh,v 1.4 2018/12/12 11:52:05 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 	have_builtin export '' 'export VAR;' '-q VAR' ' -q'  || return 0
    421 
    422 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    423 		'unset VAR; VAR=set; export -q VAR'
    424 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    425 		'export VAR=set; export -q VAR'
    426 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    427 		'VAR=set; RW=set; export -q VAR RW'
    428 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    429 		'VAR=set; export RO=set; export -q VAR RO'
    430 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    431 		'export VAR=set RO=set; export -q VAR RO'
    432 
    433 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    434 		'unset VAR; export -q VAR'
    435 	# next one is the same as the have_builtin test, so "cannot" fail...
    436 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    437 		'unset VAR; export VAR; export -q VAR'
    438 
    439 	# if we have -q we should also have -p var...
    440 	# What's more, we are testing NetBSD sh, so we know output format.
    441 
    442 	atf_check -s exit:0 -e empty -o match:VAR=foobar \
    443 		${TEST_SH} -c \
    444 			'VAR=foobar ; export VAR ; export -p VAR'
    445 	atf_check -s exit:0 -e empty -o inline:1 \
    446 		${TEST_SH} -c \
    447 			'VAR=foobar ; export VAR ;
    448 			printf %d $(export -p VAR | wc -l)'
    449 	atf_check -s exit:0 -e empty \
    450 		-o inline:'export VAR=foobar\nexport OTHER\n' \
    451 		${TEST_SH} -c \
    452 			'VAR=foobar; export VAR OTHER; export -p VAR OTHER'
    453 	atf_check -s exit:0 -e empty \
    454 		-o inline:'export A=aaa\nexport B\nexport D='"''"'\n' \
    455 		${TEST_SH} -c \
    456 			'A=aaa D= C=foo; unset B; export A B D;
    457 			 export -p A B C D'
    458 }
    459 
    460 atf_test_case getopts
    461 getopts_head() {
    462 	atf_set "descr" "Tests the shell builtin 'getopts'"
    463 }
    464 getopts_body() {
    465 	have_builtin getopts "" "f() {" "a x; }; f -a" || return 0
    466 }
    467 
    468 atf_test_case jobs
    469 jobs_head() {
    470 	atf_set "descr" "Tests the shell builting 'jobs' command"
    471 }
    472 jobs_body() {
    473 	have_builtin jobs || return 0
    474 
    475 	atf_require_prog sleep
    476 
    477 	# note that POSIX requires that we reference $! otherwise
    478 	# the shell is not required to remember the process...
    479 
    480 	atf_check -s exit:0 -e empty -o match:sleep -o match:Running \
    481 		${TEST_SH} -c 'sleep 1 & P=$!; jobs; wait'
    482 	atf_check -s exit:0 -e empty -o match:sleep -o match:Done \
    483 		${TEST_SH} -c 'sleep 1 & P=$!; sleep 2; jobs; wait'
    484 }
    485 
    486 atf_test_case read
    487 read_head() {
    488 	atf_set "descr" "Tests the shell builtin read command"
    489 }
    490 read_body() {
    491 	have_builtin read "" "echo x|" "var" || return 0
    492 }
    493 
    494 atf_test_case readonly
    495 readonly_head() {
    496 	atf_set "descr" "Tests the shell builtin 'readonly'"
    497 }
    498 readonly_body() {
    499 	have_builtin readonly || return 0
    500 
    501 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR'
    502 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR=abc'
    503 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A R'
    504 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A=1 R=2'
    505 
    506 	atf_check -s exit:0 -e empty -o inline:unset ${TEST_SH} -c \
    507 		'unset VAR; readonly VAR; printf %s ${VAR-unset}'
    508 	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
    509 		'unset VAR; readonly VAR=set; printf %s ${VAR-unset}'
    510 	atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
    511 		'VAR=initial; readonly VAR=set; printf %s ${VAR-unset}'
    512 	atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
    513 		'readonly VAR=initial; VAR=new; printf %s "${VAR}"'
    514 
    515 	# don't check VAR=value, some shells provide meaningless quoting...
    516 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    517 		${TEST_SH} -c \
    518 			'VAR=foobar ; readonly VAR ; readonly -p'
    519 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    520 		${TEST_SH} -c \
    521 			'readonly VAR=foobar ; readonly -p'
    522 	atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
    523 		-o not-match:badvalue ${TEST_SH} -c \
    524 			'VAR=badvalue; readonly VAR=foobar ; readonly -p'
    525 	atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
    526 			'unset VAR ; readonly VAR ; readonly -p'
    527 
    528 	# checking that readonly -p works (to reset stuff) is a pain...
    529 	# particularly since not all shells say "readonly..." by default
    530 	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=foobar \
    531 		${TEST_SH} -c \
    532 			'V=$(readonly MYVAR=foobar; readonly -p | grep " MYVAR")
    533 			 unset MYVAR; eval "$V"; readonly -p;
    534 			 printf %s\\n FOUND=${MYVAR-unset}'
    535 	atf_check -s exit:0 -e empty -o match:MYVAR\$ -o match:FOUND=unset \
    536 		${TEST_SH} -c \
    537 			'V=$(readonly MYVAR; readonly -p | grep " MYVAR")
    538 			 unset MYVAR; eval "$V"; readonly -p;
    539 			 printf %s\\n "FOUND=${MYVAR-unset}"'
    540 	atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=empty \
    541 		${TEST_SH} -c \
    542 			'V=$(readonly MYVAR=; readonly -p | grep " MYVAR")
    543 			 unset VAR; eval "$V"; readonly -p;
    544 			 printf %s\\n "FOUND=${MYVAR-unset&}${MYVAR:-empty}"'
    545 
    546 	# don't test stderr, some shells inist on generating a message for an
    547 	# unset of a readonly var (rather than simply having unset make $?=1)
    548 
    549 	atf_check -s not-exit:0 -e empty -o empty ${TEST_SH} -c \
    550 		'unset VAR; readonly VAR=set;
    551 		 unset VAR 2>/dev/null && printf %s ${VAR:-XX}'
    552 	atf_check -s not-exit:0 -e ignore -o empty ${TEST_SH} -c \
    553 		'unset VAR; readonly VAR=set; unset VAR && printf %s ${VAR:-XX}'
    554 	atf_check -s exit:0 -e ignore -o inline:set ${TEST_SH} -c \
    555 		'unset VAR; readonly VAR=set; unset VAR; printf %s ${VAR-unset}'
    556 }
    557 
    558 atf_test_case readonly_nbsd
    559 readonly_nbsd_head() {
    560 	atf_set "descr" "Tests NetBSD extensions to 'readonly'"
    561 }
    562 readonly_nbsd_body() {
    563 	have_builtin readonly '' 'readonly VAR;' '-q VAR' ' -q'  || return 0
    564 
    565 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    566 		'VAR=set; readonly -q VAR'
    567 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    568 		'readonly VAR=set; readonly -q VAR'
    569 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    570 		'VAR=set; RW=set; readonly -q VAR RW'
    571 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    572 		'VAR=set; readonly RO=set; readonly -q VAR RO'
    573 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    574 		'readonly VAR=set RO=set; readonly -q VAR RO'
    575 
    576 	atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
    577 		'unset VAR; readonly -q VAR'
    578 	# next one is the same as the have_builtin test, so "cannot" fail...
    579 	atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
    580 		'unset VAR; readonly VAR; readonly -q VAR'
    581 
    582 	# if we have -q we should also have -p var...
    583 	# What's more, we are testing NetBSD sh, so we know output format.
    584 
    585 	atf_check -s exit:0 -e empty -o match:VAR=foobar \
    586 		${TEST_SH} -c \
    587 			'VAR=foobar ; readonly VAR ; readonly -p VAR'
    588 	atf_check -s exit:0 -e empty -o inline:1 \
    589 		${TEST_SH} -c \
    590 			'VAR=foobar ; readonly VAR ;
    591 			printf %d $(readonly -p VAR | wc -l)'
    592 	atf_check -s exit:0 -e empty \
    593 		-o inline:'readonly VAR=foobar\nreadonly OTHER\n' \
    594 		${TEST_SH} -c \
    595 			'VAR=foobar; readonly VAR OTHER; readonly -p VAR OTHER'
    596 	atf_check -s exit:0 -e empty \
    597 		-o inline:'readonly A=aaa\nreadonly B\nreadonly D='"''"'\n' \
    598 		${TEST_SH} -c \
    599 			'A=aaa D= C=foo; unset B; readonly A B D;
    600 			 readonly -p A B C D'
    601 }
    602 
    603 atf_test_case cd_pwd
    604 cd_pwd_head() {
    605 	atf_set "descr" "Tests the shell builtins 'cd' & 'pwd'"
    606 }
    607 cd_pwd_body() {
    608 	have_builtin cd "" "HOME=/;" || return 0
    609 	have_builtin pwd || return 0
    610 }
    611 
    612 atf_test_case true_false
    613 true_false_head() {
    614 	atf_set "descr" "Tests the 'true' and 'false' shell builtin commands"
    615 }
    616 true_false_body() {
    617 	have_builtin true || return 0
    618 
    619 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c true
    620 
    621 	# true is not a special builtin, so errors do not cause exit
    622 	# but we should still get an error from the broken redirect
    623 	# and the exit status of true should be false...
    624 
    625 	atf_check -s exit:0 -e not-empty -o inline:OK ${TEST_SH} -c \
    626 		"true >/foo/bar && printf %s NOT-; printf %s OK"
    627 
    628 	# and var-assigns should not affect the current sh env
    629 
    630 	atf_check -s exit:0 -e empty -o inline:IS-OK ${TEST_SH} -c \
    631 		'X=OK; X=BROKEN true && printf %s IS-; printf %s "${X}"'
    632 
    633 	have_builtin false "" ! || return 0
    634 
    635 	atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c false
    636 }
    637 
    638 atf_test_case type
    639 type_head() {
    640 	atf_set "descr" "Tests the sh builtin 'type' command"
    641 }
    642 type_body() {
    643 	have_builtin type "" "" type || return 0
    644 }
    645 
    646 # This currently has its own t_ulimit - either merge that here,
    647 # or delete this one and keep that...  ulimit -n is also tested in
    648 # the t_redir tests, as that affects the shell's use of file descriptors
    649 atf_test_case ulimit
    650 ulimit_head() {
    651 	atf_set "descr" "Tests the sh builtin 'ulimit'"
    652 }
    653 ulimit_body() {
    654 	have_builtin ulimit || return 0
    655 }
    656 
    657 atf_test_case umask
    658 umask_head() {
    659 	atf_set "descr" "Tests the sh builtin 'umask'"
    660 }
    661 umask_body() {
    662 	have_builtin umask || return 0
    663 
    664 	atf_require_prog touch
    665 	atf_require_prog stat
    666 	atf_require_prog rm
    667 	atf_require_prog chmod
    668 
    669 	reset umask
    670 
    671 	# 8 octal digits
    672 	for M in 0 1 2 3 4 5 6 7
    673 	do
    674 	    # Test numbers start: 1 25 49 73 97 121 145 169
    675 
    676 	    # 8 combinations of each to test (64 inner loops)
    677 	    # 3 tests in each loop, hence 192 subtests in all
    678 
    679 		# Test numbers from loop above, plus (below) and the next 2
    680 		#+     1        4        7         10	     13
    681 	    for T in "0${M}" "00${M}" "0${M}0" "0${M}00" "0${M}${M}" \
    682 		"0${M}${M}0" "0${M}${M}${M}"  "0${M}0${M}"
    683 		#+    16          19		  22
    684 	    do
    685 		# umask turns bits off, calculate which bits will be on...
    686 
    687 		D=$(( 0777 & ~ T ))		# for directories
    688 		F=$(( $D & ~ 0111 ))		# and files with no 'x' bits
    689 
    690 		# Note: $(( )) always produces decimal, so we test that format
    691 		# (see '%d' in printf of stat result)
    692 
    693 		# need chmod or we might have no perm to rmdir TD
    694 		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
    695 
    696 		# check that the umask applies to files created by the shell
    697 		check \
    698 		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
    699 				  "$F" 0 "$F is $(printf %#o $F)" # 1 4 7 10 ...
    700 
    701 		# and to files created by commands that the shell runs
    702 		check \
    703 		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
    704 				  "$F" 0 "$F is $(printf %#o $F)" # 2 5 8 11 ...
    705 
    706 		# and to directories created b ... (directories keep 'x')
    707 		check \
    708 		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
    709 				  "$D" 0 "$D is $(printf %#o $D)" # 3 6 9 12 ...
    710 	    done
    711 	done
    712 
    713 	# Now add a few more tests with less regular u/g/m masks
    714 	# In here, include tests where umask value has no leading '0'
    715 
    716 	# 10 loops, the same 3 tests in each loop, 30 more subtests
    717 	# from 193 .. 222
    718 
    719 	#        193 196 199  202 205 208 211  214  217 220
    720 	for T in 013 047 722 0772 027 123 421 0124 0513 067
    721 	do
    722 		D=$(( 0777 & ~ 0$T ))
    723 		F=$(( $D & ~ 0111 ))
    724 
    725 		{ chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
    726 
    727 		check \
    728 		  "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
    729 				  "$F" 0 "$F is $(printf %#o $F)"	# +0
    730 
    731 		check \
    732 		  "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
    733 				  "$F" 0 "$F is $(printf %#o $F)"	# +1
    734 
    735 		check \
    736 		  "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
    737 				  "$D" 0 "$D is $(printf %#o $D)"	# +2
    738 	done
    739 
    740 	results
    741 }
    742 
    743 atf_test_case unset
    744 unset_head() {
    745 	atf_set "descr" "Tests the sh builtin 'unset'"
    746 }
    747 unset_body() {
    748 	have_builtin unset || return 0
    749 }
    750 
    751 atf_test_case hash
    752 hash_head() {
    753 	atf_set "descr" "Tests the sh builtin 'hash' (ash extension)"
    754 }
    755 hash_body() {
    756 	have_builtin hash || return 0
    757 }
    758 
    759 atf_test_case jobid
    760 jobid_head() {
    761 	atf_set "descr" "Tests sh builtin 'jobid' (NetBSD extension)"
    762 }
    763 jobid_body() {
    764 
    765 	# have_builtin jobid || return 0	No simple jobid command test
    766 	$TEST_SH -c '(exit 0)& jobid $!' >/dev/null 2>&1  || {
    767 		atf_skip "${TEST_SH} has no 'jobid' built-in"
    768 		return 0
    769 	}
    770 }
    771 
    772 atf_test_case let
    773 let_head() {
    774 	atf_set "descr" "Tests the sh builtin 'let' (common extension from ksh)"
    775 }
    776 let_body() {
    777 	have_builtin let "" "" 1 || return 0
    778 }
    779 
    780 atf_test_case local
    781 local_head() {
    782 	atf_set "descr" "Tests the shell builtin 'local' (common extension)"
    783 }
    784 local_body() {
    785 	have_builtin local "" "f () {" "X; }; f" || return 0
    786 }
    787 
    788 atf_test_case setvar
    789 setvar_head() {
    790 	atf_set "descr" "Tests the shell builtin 'setvar' (BSD extension)"
    791 }
    792 setvar_body() {
    793 	have_builtin setvar || return 0
    794 
    795 	atf_check -s exit:0 -e empty -o inline:foo ${TEST_SH} -c \
    796 		'unset PQ && setvar PQ foo; printf %s "${PQ-not set}"'
    797 	atf_check -s exit:0 -e empty -o inline:abcd ${TEST_SH} -c \
    798 		'for x in a b c d; do setvar "$x" "$x"; done;
    799 		 printf %s "$a$b$c$d"'
    800 	atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
    801 		'a=1; b=2; c=3; d=4
    802 		 for x in a b c d; do setvar "$x" ""; done;
    803 		 printf %s "$a$b$c$d"'
    804 }
    805 
    806 atf_test_case fdflags
    807 fdflags_head() {
    808 	atf_set "descr" \
    809 	   "Tests basic operation of sh builtin 'fdflags' (NetBSD extension)"
    810 }
    811 fdflags_body() {
    812 	have_builtin fdflags || return 0
    813 }
    814 
    815 atf_test_case fdflags__s
    816 fdflags__s_head() {
    817 	atf_set "descr" "Checks setting/clearing flags on file descriptors"
    818 }
    819 fdflags__s_body() {
    820 	have_builtin fdflags || return 0
    821 }
    822 
    823 atf_test_case fdflags__v
    824 fdflags__v_head() {
    825 	atf_set "descr" "Checks verbose operation of fdflags"
    826 }
    827 fdflags__v_body() {
    828 	have_builtin fdflags || return 0
    829 }
    830 
    831 atf_test_case fdflags__v_s
    832 fdflags__v_s_head() {
    833 	atf_set "descr" "tests verbose operation of fdflags -s"
    834 }
    835 fdflags__v_s_body() {
    836 	have_builtin fdflags || return 0
    837 }
    838 
    839 atf_test_case fdflags_multiple_fd
    840 fdflags_multiple_fd_head() {
    841 	atf_set "descr" "Checks operation of fdflags with more than one fd"
    842 }
    843 fdflags_multiple_fd_body() {
    844 	have_builtin fdflags || return 0
    845 }
    846 
    847 atf_test_case fdflags_one_flag_at_a_time
    848 fdflags_one_flag_at_a_time_head() {
    849 	atf_set "descr" "Tests all possible fdflags flags, and combinations"
    850 }
    851 fdflags_one_flag_at_a_time_body() {
    852 	have_builtin fdflags || return 0
    853 }
    854 
    855 atf_test_case fdflags_save_restore
    856 fdflags_save_restore_head() {
    857 	atf_set "descr" 'Verify that fd flags can be saved and restored'
    858 }
    859 fdflags_save_restore_body() {
    860 	have_builtin fdflags || return 0
    861 }
    862 
    863 atf_test_case fdflags_names_abbreviated
    864 fdflags_names_abbreviated_head() {
    865 	atf_set "descr" 'Tests using abbreviated names for fdflags'
    866 }
    867 fdflags_names_abbreviated_body() {
    868 	have_builtin fdflags || return 0
    869 }
    870 
    871 atf_test_case fdflags_xx_errors
    872 fdflags_xx_errors_head() {
    873 	atf_set "descr" 'Check various erroneous fdflags uses'
    874 }
    875 fdflags_xx_errors_body() {
    876 	have_builtin fdflags || return 0
    877 }
    878 
    879 
    880 atf_init_test_cases() {
    881 
    882 	# "standard" builtin commands in sh
    883 
    884 	# no tests of the "very special" (almost syntax) builtins
    885 	#  (break/continue/return) - they're tested enough elsewhere
    886 
    887 	atf_add_test_case cd_pwd
    888 	atf_add_test_case colon
    889 	atf_add_test_case echo
    890 	atf_add_test_case eval
    891 	atf_add_test_case exec
    892 	atf_add_test_case export
    893 	atf_add_test_case getopts
    894 	atf_add_test_case jobs
    895 	atf_add_test_case read
    896 	atf_add_test_case readonly
    897 	atf_add_test_case true_false
    898 	atf_add_test_case type
    899 	atf_add_test_case ulimit
    900 	atf_add_test_case umask
    901 	atf_add_test_case unset
    902 
    903 	# exit/wait/set/shift/trap/alias/unalias/. should have their own tests
    904 	# fc/times/fg/bg/%    are too messy to contemplate for now
    905 	# command ??  (probably should have some tests)
    906 
    907 	# Note that builtin versions of, printf, kill, ... are tested separately
    908 	# (these are all "optional" builtins)
    909 	# (echo is tested here because NetBSD sh builtin echo and /bin/echo
    910 	#  are different)
    911 
    912 	atf_add_test_case export_nbsd
    913 	atf_add_test_case hash
    914 	atf_add_test_case jobid
    915 	atf_add_test_case let
    916 	atf_add_test_case local
    917 	atf_add_test_case readonly_nbsd
    918 	atf_add_test_case setvar
    919 	# inputrc should probably be tested in libedit tests (somehow)
    920 
    921 	# fdflags has a bunch of test cases
    922 
    923 	# Always run one test, so we get at least "skipped" result
    924 	atf_add_test_case fdflags
    925 
    926 	# but no need to say "skipped" lots more times...
    927 	have_builtin fdflags available && {
    928 		atf_add_test_case fdflags__s
    929 		atf_add_test_case fdflags__v
    930 		atf_add_test_case fdflags__v_s
    931 		atf_add_test_case fdflags_multiple_fd
    932 		atf_add_test_case fdflags_names_abbreviated
    933 		atf_add_test_case fdflags_one_flag_at_a_time
    934 		atf_add_test_case fdflags_save_restore
    935 		atf_add_test_case fdflags_xx_errors
    936 	}
    937 	return 0
    938 }
    939