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