t_builtins.sh revision 1.5 1 # $NetBSD: t_builtins.sh,v 1.5 2019/01/09 10:51:23 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 results
295 }
296
297 atf_test_case eval
298 eval_head() {
299 atf_set "descr" "Tests the shell special builtin 'eval'"
300 }
301 eval_body() {
302 have_builtin eval || return 0
303
304 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval "exit 0"'
305 atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval "exit 1"'
306 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'eval exit 0'
307 atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c 'eval exit 1'
308
309 atf_check -s exit:0 -e empty -o inline:0 ${TEST_SH} -c \
310 'eval true; printf %d $?'
311 atf_check -s exit:0 -e empty -o inline:1 ${TEST_SH} -c \
312 'eval false; printf %d $?'
313
314 atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
315 'X=a Y=b Z=c; for V in X Y Z; do eval "printf %s \$$V"; done'
316 atf_check -s exit:0 -e empty -o inline:abc ${TEST_SH} -c \
317 'X=a Y=b Z=c; for V in X Y Z; do eval printf %s \$$V; done'
318 atf_check -s exit:0 -e empty -o inline:XYZ ${TEST_SH} -c \
319 'for V in X Y Z; do eval "${V}=${V}"; done; printf %s "$X$Y$Z"'
320
321 # make sure eval'd strings affect the shell environment
322
323 atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
324 'X=a; eval "X=b"; printf /%s/ "${X-unset}"'
325 atf_check -s exit:0 -e empty -o inline:/b/ ${TEST_SH} -c \
326 'X=a; Y=X; Z=b; eval "$Y=$Z"; printf /%s/ "${X-unset}"'
327 atf_check -s exit:0 -e empty -o inline:/unset/ ${TEST_SH} -c \
328 'X=a; eval "unset X"; printf /%s/ "${X-unset}"'
329 atf_check -s exit:0 -e empty -o inline:// ${TEST_SH} -c \
330 'unset X; eval "X="; printf /%s/ "${X-unset}"'
331 atf_check -s exit:0 -e empty -o inline:'2 y Z ' ${TEST_SH} -c \
332 'set -- X y Z; eval shift; printf "%s " "$#" "$@"'
333
334 # ensure an error in an eval'd string causes the shell to exit
335 # unless 'eval' is preceded by 'command' (in which case the
336 # string is not eval'd but execution continues)
337
338 atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
339 'eval "if done"; printf %s status=$?'
340
341 atf_check -s exit:0 -e not-empty -o 'match:status=[1-9]' \
342 ${TEST_SH} -c \
343 'command eval "if done"; printf %s status=$?'
344
345 atf_check -s not-exit:0 -e not-empty \
346 -o 'match:status=[1-9]' -o 'not-match:[XY]' ${TEST_SH} -c \
347 'command eval "printf X; if done; printf Y"
348 S=$?; printf %s status=$S; exit $S'
349
350 # whether 'X' is output here is (or should be) unspecified.
351 atf_check -s not-exit:0 -e not-empty \
352 -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
353 'command eval "printf X
354 if done
355 printf Y"
356 S=$?; printf %s status=$S; exit $S'
357
358 if is_netbsd_sh
359 then
360 # but on NetBSD we expect that X to appear...
361 atf_check -s not-exit:0 -e not-empty -o 'match:X' \
362 -o 'match:status=[1-9]' -o 'not-match:Y' ${TEST_SH} -c \
363 'command eval "printf X
364 if done
365 printf Y"
366 S=$?; printf %s status=$S; exit $S'
367 fi
368 }
369
370 atf_test_case exec
371 exec_head() {
372 atf_set "descr" "Tests the shell special builtin 'exec'"
373 }
374 exec_body() {
375 have_builtin exec || return 0
376
377 atf_check -s exit:0 -e empty -o inline:OK ${TEST_SH} -c \
378 'exec printf OK; printf BROKEN; exit 3'
379 atf_check -s exit:3 -e empty -o inline:OKOK ${TEST_SH} -c \
380 '(exec printf OK); printf OK; exit 3'
381 }
382
383 atf_test_case export
384 export_head() {
385 atf_set "descr" "Tests the shell builtin 'export'"
386 }
387 export_body() {
388 have_builtin export || return 0
389
390 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR'
391 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export VAR=abc'
392 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'export V A R'
393 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
394 'export V A=1 R=2'
395
396 atf_require_prog printenv
397
398 atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
399 'unset VAR || exit 7; export VAR; printenv VAR'
400 atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
401 'unset VAR || exit 7; export VAR=; printenv VAR'
402 atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
403 'unset VAR || exit 7; VAR=; export VAR; printenv VAR'
404 atf_check -s exit:0 -e empty -o inline:\\n ${TEST_SH} -c \
405 'unset VAR || exit 7; export VAR; VAR=; printenv VAR'
406 atf_check -s exit:0 -e empty -o inline:XYZ\\n ${TEST_SH} -c \
407 'unset VAR || exit 7; export VAR=XYZ; printenv VAR'
408 atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
409 'VAR=ABC; export VAR; printenv VAR'
410 atf_check -s exit:0 -e empty -o inline:ABC\\n ${TEST_SH} -c \
411 'unset VAR || exit 7; export VAR; VAR=ABC; printenv VAR'
412 atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
413 'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR'
414 atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
415 'unset VAR || exit 7; export VAR;
416 VAR=ABC; printenv VAR; VAR=XYZ; printenv VAR'
417
418 # don't check VAR=value, some shells provide meaningless quoting...
419 atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
420 ${TEST_SH} -c \
421 'VAR=foobar ; export VAR ; export -p'
422 atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
423 ${TEST_SH} -c \
424 'export VAR=foobar ; export -p'
425 atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
426 'unset VAR ; export VAR ; export -p'
427 atf_check -s exit:0 -e empty -o not-match:VAR ${TEST_SH} -c \
428 'export VAR ; unset VAR ; export -p'
429 atf_check -s exit:0 -e empty -o not-match:VAR -o not-match:foobar \
430 ${TEST_SH} -c \
431 'VAR=foobar; export VAR ; unset VAR ; export -p'
432
433 atf_check -s exit:0 -e empty -o match:VAR= -o match:FOUND=foobar \
434 ${TEST_SH} -c \
435 'export VAR=foobar; V=$(export -p);
436 unset VAR; eval "$V"; export -p;
437 printf %s\\n FOUND=${VAR-unset}'
438 atf_check -s exit:0 -e empty -o match:VAR -o match:FOUND=unset \
439 ${TEST_SH} -c \
440 'export VAR; V=$(export -p);
441 unset VAR; eval "$V"; export -p;
442 printf %s\\n FOUND=${VAR-unset}'
443
444 atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
445 'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
446 unset VAR; printenv VAR; VAR=PQR; printenv VAR'
447 atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=unset\\nMNO\\n \
448 ${TEST_SH} -c \
449 'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
450 unset VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
451 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
452 }
453
454 atf_test_case export_nbsd
455 export_nbsd_head() {
456 atf_set "descr" "Tests NetBSD extensions to the shell builtin 'export'"
457 }
458 export_nbsd_body() {
459 have_builtin "export" "" "" "-n foo" ' -n' || return 0
460
461 atf_require_prog printenv
462
463 atf_check -s exit:1 -e empty -o inline:ABC\\nXYZ\\n ${TEST_SH} -c \
464 'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
465 export -n VAR; printenv VAR; VAR=PQR; printenv VAR'
466
467 atf_check -s exit:0 -e empty -o inline:ABC\\nXYZ\\nVAR=XYZ\\nMNO\\n \
468 ${TEST_SH} -c \
469 'VAR=ABC; export VAR; printenv VAR; VAR=XYZ; printenv VAR;
470 export -n VAR; printf %s\\n "VAR=${VAR-unset}"; printenv VAR;
471 VAR=PQR; printenv VAR; VAR=MNO; export VAR; printenv VAR'
472
473 have_builtin "export" "" "" -x ' -x' || return 0
474
475 atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
476 'export VAR=exported; export -x VAR; printenv VAR'
477 atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c \
478 'export VAR=exported; export -x VAR; VAR=local; printenv VAR'
479 atf_check -s exit:0 -e empty -o inline:once\\nx\\n ${TEST_SH} -c \
480 'export VAR=exported
481 export -x VAR
482 VAR=once printenv VAR
483 printenv VAR || printf %s\\n x'
484
485 atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
486 'export VAR=exported; export -x VAR; export VAR=FOO'
487
488 have_builtin export '' 'export VAR;' '-q VAR' ' -q' || return 0
489
490 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
491 'unset VAR; VAR=set; export -q VAR'
492 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
493 'export VAR=set; export -q VAR'
494 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
495 'VAR=set; RW=set; export -q VAR RW'
496 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
497 'VAR=set; export RO=set; export -q VAR RO'
498 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
499 'export VAR=set RO=set; export -q VAR RO'
500
501 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
502 'unset VAR; export -q VAR'
503 # next one is the same as the have_builtin test, so "cannot" fail...
504 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
505 'unset VAR; export VAR; export -q VAR'
506
507 # if we have -q we should also have -p var...
508 # What's more, we are testing NetBSD sh, so we know output format.
509
510 atf_check -s exit:0 -e empty -o match:VAR=foobar \
511 ${TEST_SH} -c \
512 'VAR=foobar ; export VAR ; export -p VAR'
513 atf_check -s exit:0 -e empty -o inline:1 \
514 ${TEST_SH} -c \
515 'VAR=foobar ; export VAR ;
516 printf %d $(export -p VAR | wc -l)'
517 atf_check -s exit:0 -e empty \
518 -o inline:'export VAR=foobar\nexport OTHER\n' \
519 ${TEST_SH} -c \
520 'VAR=foobar; export VAR OTHER; export -p VAR OTHER'
521 atf_check -s exit:0 -e empty \
522 -o inline:'export A=aaa\nexport B\nexport D='"''"'\n' \
523 ${TEST_SH} -c \
524 'A=aaa D= C=foo; unset B; export A B D;
525 export -p A B C D'
526 }
527
528 atf_test_case getopts
529 getopts_head() {
530 atf_set "descr" "Tests the shell builtin 'getopts'"
531 }
532 getopts_body() {
533 have_builtin getopts "" "f() {" "a x; }; f -a" || return 0
534 }
535
536 atf_test_case jobs
537 jobs_head() {
538 atf_set "descr" "Tests the shell builting 'jobs' command"
539 }
540 jobs_body() {
541 have_builtin jobs || return 0
542
543 atf_require_prog sleep
544
545 # note that POSIX requires that we reference $! otherwise
546 # the shell is not required to remember the process...
547
548 atf_check -s exit:0 -e empty -o match:sleep -o match:Running \
549 ${TEST_SH} -c 'sleep 1 & P=$!; jobs; wait'
550 atf_check -s exit:0 -e empty -o match:sleep -o match:Done \
551 ${TEST_SH} -c 'sleep 1 & P=$!; sleep 2; jobs; wait'
552 }
553
554 atf_test_case read
555 read_head() {
556 atf_set "descr" "Tests the shell builtin read command"
557 }
558 read_body() {
559 have_builtin read "" "echo x|" "var" || return 0
560 }
561
562 atf_test_case readonly
563 readonly_head() {
564 atf_set "descr" "Tests the shell builtin 'readonly'"
565 }
566 readonly_body() {
567 have_builtin readonly || return 0
568
569 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR'
570 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly VAR=abc'
571 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A R'
572 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c 'readonly V A=1 R=2'
573
574 atf_check -s exit:0 -e empty -o inline:unset ${TEST_SH} -c \
575 'unset VAR; readonly VAR; printf %s ${VAR-unset}'
576 atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
577 'unset VAR; readonly VAR=set; printf %s ${VAR-unset}'
578 atf_check -s exit:0 -e empty -o inline:set ${TEST_SH} -c \
579 'VAR=initial; readonly VAR=set; printf %s ${VAR-unset}'
580 atf_check -s not-exit:0 -e not-empty -o empty ${TEST_SH} -c \
581 'readonly VAR=initial; VAR=new; printf %s "${VAR}"'
582
583 # don't check VAR=value, some shells provide meaningless quoting...
584 atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
585 ${TEST_SH} -c \
586 'VAR=foobar ; readonly VAR ; readonly -p'
587 atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
588 ${TEST_SH} -c \
589 'readonly VAR=foobar ; readonly -p'
590 atf_check -s exit:0 -e empty -o match:VAR= -o match:foobar \
591 -o not-match:badvalue ${TEST_SH} -c \
592 'VAR=badvalue; readonly VAR=foobar ; readonly -p'
593 atf_check -s exit:0 -e empty -o match:VAR\$ ${TEST_SH} -c \
594 'unset VAR ; readonly VAR ; readonly -p'
595
596 # checking that readonly -p works (to reset stuff) is a pain...
597 # particularly since not all shells say "readonly..." by default
598 atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=foobar \
599 ${TEST_SH} -c \
600 'V=$(readonly MYVAR=foobar; readonly -p | grep " MYVAR")
601 unset MYVAR; eval "$V"; readonly -p;
602 printf %s\\n FOUND=${MYVAR-unset}'
603 atf_check -s exit:0 -e empty -o match:MYVAR\$ -o match:FOUND=unset \
604 ${TEST_SH} -c \
605 'V=$(readonly MYVAR; readonly -p | grep " MYVAR")
606 unset MYVAR; eval "$V"; readonly -p;
607 printf %s\\n "FOUND=${MYVAR-unset}"'
608 atf_check -s exit:0 -e empty -o match:MYVAR= -o match:FOUND=empty \
609 ${TEST_SH} -c \
610 'V=$(readonly MYVAR=; readonly -p | grep " MYVAR")
611 unset VAR; eval "$V"; readonly -p;
612 printf %s\\n "FOUND=${MYVAR-unset&}${MYVAR:-empty}"'
613
614 # don't test stderr, some shells inist on generating a message for an
615 # unset of a readonly var (rather than simply having unset make $?=1)
616
617 atf_check -s not-exit:0 -e empty -o empty ${TEST_SH} -c \
618 'unset VAR; readonly VAR=set;
619 unset VAR 2>/dev/null && printf %s ${VAR:-XX}'
620 atf_check -s not-exit:0 -e ignore -o empty ${TEST_SH} -c \
621 'unset VAR; readonly VAR=set; unset VAR && printf %s ${VAR:-XX}'
622 atf_check -s exit:0 -e ignore -o inline:set ${TEST_SH} -c \
623 'unset VAR; readonly VAR=set; unset VAR; printf %s ${VAR-unset}'
624 }
625
626 atf_test_case readonly_nbsd
627 readonly_nbsd_head() {
628 atf_set "descr" "Tests NetBSD extensions to 'readonly'"
629 }
630 readonly_nbsd_body() {
631 have_builtin readonly '' 'readonly VAR;' '-q VAR' ' -q' || return 0
632
633 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
634 'VAR=set; readonly -q VAR'
635 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
636 'readonly VAR=set; readonly -q VAR'
637 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
638 'VAR=set; RW=set; readonly -q VAR RW'
639 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
640 'VAR=set; readonly RO=set; readonly -q VAR RO'
641 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
642 'readonly VAR=set RO=set; readonly -q VAR RO'
643
644 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
645 'unset VAR; readonly -q VAR'
646 # next one is the same as the have_builtin test, so "cannot" fail...
647 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
648 'unset VAR; readonly VAR; readonly -q VAR'
649
650 # if we have -q we should also have -p var...
651 # What's more, we are testing NetBSD sh, so we know output format.
652
653 atf_check -s exit:0 -e empty -o match:VAR=foobar \
654 ${TEST_SH} -c \
655 'VAR=foobar ; readonly VAR ; readonly -p VAR'
656 atf_check -s exit:0 -e empty -o inline:1 \
657 ${TEST_SH} -c \
658 'VAR=foobar ; readonly VAR ;
659 printf %d $(readonly -p VAR | wc -l)'
660 atf_check -s exit:0 -e empty \
661 -o inline:'readonly VAR=foobar\nreadonly OTHER\n' \
662 ${TEST_SH} -c \
663 'VAR=foobar; readonly VAR OTHER; readonly -p VAR OTHER'
664 atf_check -s exit:0 -e empty \
665 -o inline:'readonly A=aaa\nreadonly B\nreadonly D='"''"'\n' \
666 ${TEST_SH} -c \
667 'A=aaa D= C=foo; unset B; readonly A B D;
668 readonly -p A B C D'
669 }
670
671 atf_test_case cd_pwd
672 cd_pwd_head() {
673 atf_set "descr" "Tests the shell builtins 'cd' & 'pwd'"
674 }
675 cd_pwd_body() {
676 have_builtin cd "" "HOME=/;" || return 0
677 have_builtin pwd || return 0
678 }
679
680 atf_test_case true_false
681 true_false_head() {
682 atf_set "descr" "Tests the 'true' and 'false' shell builtin commands"
683 }
684 true_false_body() {
685 have_builtin true || return 0
686
687 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c true
688
689 # true is not a special builtin, so errors do not cause exit
690 # but we should still get an error from the broken redirect
691 # and the exit status of true should be false...
692
693 atf_check -s exit:0 -e not-empty -o inline:OK ${TEST_SH} -c \
694 "true >/foo/bar && printf %s NOT-; printf %s OK"
695
696 # and var-assigns should not affect the current sh env
697
698 atf_check -s exit:0 -e empty -o inline:IS-OK ${TEST_SH} -c \
699 'X=OK; X=BROKEN true && printf %s IS-; printf %s "${X}"'
700
701 have_builtin false "" ! || return 0
702
703 atf_check -s exit:1 -e empty -o empty ${TEST_SH} -c false
704 }
705
706 atf_test_case type
707 type_head() {
708 atf_set "descr" "Tests the sh builtin 'type' command"
709 }
710 type_body() {
711 have_builtin type "" "" type || return 0
712 }
713
714 # This currently has its own t_ulimit - either merge that here,
715 # or delete this one and keep that... ulimit -n is also tested in
716 # the t_redir tests, as that affects the shell's use of file descriptors
717 atf_test_case ulimit
718 ulimit_head() {
719 atf_set "descr" "Tests the sh builtin 'ulimit'"
720 }
721 ulimit_body() {
722 have_builtin ulimit || return 0
723 }
724
725 atf_test_case umask
726 umask_head() {
727 atf_set "descr" "Tests the sh builtin 'umask'"
728 }
729 umask_body() {
730 have_builtin umask || return 0
731
732 atf_require_prog touch
733 atf_require_prog stat
734 atf_require_prog rm
735 atf_require_prog chmod
736
737 reset umask
738
739 # 8 octal digits
740 for M in 0 1 2 3 4 5 6 7
741 do
742 # Test numbers start: 1 25 49 73 97 121 145 169
743
744 # 8 combinations of each to test (64 inner loops)
745 # 3 tests in each loop, hence 192 subtests in all
746
747 # Test numbers from loop above, plus (below) and the next 2
748 #+ 1 4 7 10 13
749 for T in "0${M}" "00${M}" "0${M}0" "0${M}00" "0${M}${M}" \
750 "0${M}${M}0" "0${M}${M}${M}" "0${M}0${M}"
751 #+ 16 19 22
752 do
753 # umask turns bits off, calculate which bits will be on...
754
755 D=$(( 0777 & ~ T )) # for directories
756 F=$(( $D & ~ 0111 )) # and files with no 'x' bits
757
758 # Note: $(( )) always produces decimal, so we test that format
759 # (see '%d' in printf of stat result)
760
761 # need chmod or we might have no perm to rmdir TD
762 { chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
763
764 # check that the umask applies to files created by the shell
765 check \
766 "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
767 "$F" 0 "$F is $(printf %#o $F)" # 1 4 7 10 ...
768
769 # and to files created by commands that the shell runs
770 check \
771 "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
772 "$F" 0 "$F is $(printf %#o $F)" # 2 5 8 11 ...
773
774 # and to directories created b ... (directories keep 'x')
775 check \
776 "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
777 "$D" 0 "$D is $(printf %#o $D)" # 3 6 9 12 ...
778 done
779 done
780
781 # Now add a few more tests with less regular u/g/m masks
782 # In here, include tests where umask value has no leading '0'
783
784 # 10 loops, the same 3 tests in each loop, 30 more subtests
785 # from 193 .. 222
786
787 # 193 196 199 202 205 208 211 214 217 220
788 for T in 013 047 722 0772 027 123 421 0124 0513 067
789 do
790 D=$(( 0777 & ~ 0$T ))
791 F=$(( $D & ~ 0111 ))
792
793 { chmod +rwx TF TFT TD; rm -fr TF TFT TD; } 2>/dev/null || :
794
795 check \
796 "umask $T; > TF; printf %d \$(stat -nf %#Lp TF)" \
797 "$F" 0 "$F is $(printf %#o $F)" # +0
798
799 check \
800 "umask $T; touch TFT; printf %d \$(stat -nf %#Lp TFT)" \
801 "$F" 0 "$F is $(printf %#o $F)" # +1
802
803 check \
804 "umask $T; mkdir TD; printf %d \$(stat -nf %#Lp TD)" \
805 "$D" 0 "$D is $(printf %#o $D)" # +2
806 done
807
808 results
809 }
810
811 atf_test_case unset
812 unset_head() {
813 atf_set "descr" "Tests the sh builtin 'unset'"
814 }
815 unset_body() {
816 have_builtin unset || return 0
817 }
818
819 atf_test_case hash
820 hash_head() {
821 atf_set "descr" "Tests the sh builtin 'hash' (ash extension)"
822 }
823 hash_body() {
824 have_builtin hash || return 0
825 }
826
827 atf_test_case jobid
828 jobid_head() {
829 atf_set "descr" "Tests sh builtin 'jobid' (NetBSD extension)"
830 }
831 jobid_body() {
832
833 # have_builtin jobid || return 0 No simple jobid command test
834 $TEST_SH -c '(exit 0)& jobid $!' >/dev/null 2>&1 || {
835 atf_skip "${TEST_SH} has no 'jobid' built-in"
836 return 0
837 }
838 }
839
840 atf_test_case let
841 let_head() {
842 atf_set "descr" "Tests the sh builtin 'let' (common extension from ksh)"
843 }
844 let_body() {
845 have_builtin let "" "" 1 || return 0
846 }
847
848 atf_test_case local
849 local_head() {
850 atf_set "descr" "Tests the shell builtin 'local' (common extension)"
851 }
852 local_body() {
853 have_builtin local "" "f () {" "X; }; f" || return 0
854 }
855
856 atf_test_case setvar
857 setvar_head() {
858 atf_set "descr" "Tests the shell builtin 'setvar' (BSD extension)"
859 }
860 setvar_body() {
861 have_builtin setvar || return 0
862
863 atf_check -s exit:0 -e empty -o inline:foo ${TEST_SH} -c \
864 'unset PQ && setvar PQ foo; printf %s "${PQ-not set}"'
865 atf_check -s exit:0 -e empty -o inline:abcd ${TEST_SH} -c \
866 'for x in a b c d; do setvar "$x" "$x"; done;
867 printf %s "$a$b$c$d"'
868 atf_check -s exit:0 -e empty -o empty ${TEST_SH} -c \
869 'a=1; b=2; c=3; d=4
870 for x in a b c d; do setvar "$x" ""; done;
871 printf %s "$a$b$c$d"'
872 }
873
874 atf_test_case fdflags
875 fdflags_head() {
876 atf_set "descr" \
877 "Tests basic operation of sh builtin 'fdflags' (NetBSD extension)"
878 }
879 fdflags_body() {
880 have_builtin fdflags || return 0
881 }
882
883 atf_test_case fdflags__s
884 fdflags__s_head() {
885 atf_set "descr" "Checks setting/clearing flags on file descriptors"
886 }
887 fdflags__s_body() {
888 have_builtin fdflags || return 0
889 }
890
891 atf_test_case fdflags__v
892 fdflags__v_head() {
893 atf_set "descr" "Checks verbose operation of fdflags"
894 }
895 fdflags__v_body() {
896 have_builtin fdflags || return 0
897 }
898
899 atf_test_case fdflags__v_s
900 fdflags__v_s_head() {
901 atf_set "descr" "tests verbose operation of fdflags -s"
902 }
903 fdflags__v_s_body() {
904 have_builtin fdflags || return 0
905 }
906
907 atf_test_case fdflags_multiple_fd
908 fdflags_multiple_fd_head() {
909 atf_set "descr" "Checks operation of fdflags with more than one fd"
910 }
911 fdflags_multiple_fd_body() {
912 have_builtin fdflags || return 0
913 }
914
915 atf_test_case fdflags_one_flag_at_a_time
916 fdflags_one_flag_at_a_time_head() {
917 atf_set "descr" "Tests all possible fdflags flags, and combinations"
918 }
919 fdflags_one_flag_at_a_time_body() {
920 have_builtin fdflags || return 0
921 }
922
923 atf_test_case fdflags_save_restore
924 fdflags_save_restore_head() {
925 atf_set "descr" 'Verify that fd flags can be saved and restored'
926 }
927 fdflags_save_restore_body() {
928 have_builtin fdflags || return 0
929 }
930
931 atf_test_case fdflags_names_abbreviated
932 fdflags_names_abbreviated_head() {
933 atf_set "descr" 'Tests using abbreviated names for fdflags'
934 }
935 fdflags_names_abbreviated_body() {
936 have_builtin fdflags || return 0
937 }
938
939 atf_test_case fdflags_xx_errors
940 fdflags_xx_errors_head() {
941 atf_set "descr" 'Check various erroneous fdflags uses'
942 }
943 fdflags_xx_errors_body() {
944 have_builtin fdflags || return 0
945 }
946
947
948 atf_init_test_cases() {
949
950 # "standard" builtin commands in sh
951
952 # no tests of the "very special" (almost syntax) builtins
953 # (break/continue/return) - they're tested enough elsewhere
954
955 atf_add_test_case cd_pwd
956 atf_add_test_case colon
957 atf_add_test_case echo
958 atf_add_test_case eval
959 atf_add_test_case exec
960 atf_add_test_case export
961 atf_add_test_case getopts
962 atf_add_test_case jobs
963 atf_add_test_case read
964 atf_add_test_case readonly
965 atf_add_test_case true_false
966 atf_add_test_case type
967 atf_add_test_case ulimit
968 atf_add_test_case umask
969 atf_add_test_case unset
970
971 # exit/wait/set/shift/trap/alias/unalias/. should have their own tests
972 # fc/times/fg/bg/% are too messy to contemplate for now
973 # command ?? (probably should have some tests)
974
975 # Note that builtin versions of, printf, kill, ... are tested separately
976 # (these are all "optional" builtins)
977 # (echo is tested here because NetBSD sh builtin echo and /bin/echo
978 # are different)
979
980 atf_add_test_case export_nbsd
981 atf_add_test_case hash
982 atf_add_test_case jobid
983 atf_add_test_case let
984 atf_add_test_case local
985 atf_add_test_case readonly_nbsd
986 atf_add_test_case setvar
987 # inputrc should probably be tested in libedit tests (somehow)
988
989 # fdflags has a bunch of test cases
990
991 # Always run one test, so we get at least "skipped" result
992 atf_add_test_case fdflags
993
994 # but no need to say "skipped" lots more times...
995 have_builtin fdflags available && {
996 atf_add_test_case fdflags__s
997 atf_add_test_case fdflags__v
998 atf_add_test_case fdflags__v_s
999 atf_add_test_case fdflags_multiple_fd
1000 atf_add_test_case fdflags_names_abbreviated
1001 atf_add_test_case fdflags_one_flag_at_a_time
1002 atf_add_test_case fdflags_save_restore
1003 atf_add_test_case fdflags_xx_errors
1004 }
1005 return 0
1006 }
1007