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