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