t_option.sh revision 1.8 1 # $NetBSD: t_option.sh,v 1.8 2021/11/22 05:07:15 kre Exp $
2 #
3 # Copyright (c) 2016 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 # The standard
31 # http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
32 # says:
33 # ...[lots]
34
35 test_option_on_off()
36 {
37 atf_require_prog tr
38
39 for opt
40 do
41 # t is needed, as inside $()` $- appears to lose
42 # the 'e' option if it happened to already be
43 # set. Must check if that is what should
44 # happen, but that is a different issue.
45
46 test -z "${opt}" && continue
47
48 # if we are playing with more that one option at a
49 # time, the code below requires that we start with no
50 # options set, or it will mis-diagnose the situation
51 CLEAR=''
52 test "${#opt}" -gt 1 &&
53 CLEAR='xx="$-" && xx=$(echo "$xx" | tr -d cs) && test -n "$xx" && set +"$xx";'
54
55 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
56 "opt=${opt}"'
57 x() {
58 echo "ERROR: Unable to $1 option $2" >&2
59 exit 1
60 }
61 s() {
62 set -"$1"
63 t="$-"
64 x=$(echo "$t" | tr -d "$1")
65 test "$t" = "$x" && x set "$1"
66 return 0
67 }
68 c() {
69 set +"$1"
70 t="$-"
71 x=$(echo "$t" | tr -d "$1")
72 test "$t" != "$x" && x clear "$1"
73 return 0
74 }
75 '"${CLEAR}"'
76
77 # if we do not do this, -x tracing splatters stderr
78 # for some shells, -v does as well (is that correct?)
79 case "${opt}" in
80 (*[xXv]*) exec 2>/dev/null;;
81 esac
82
83 o="$-"
84 x=$(echo "$o" | tr -d "$opt")
85
86 if [ "$o" = "$x" ]; then # option was off
87 s "${opt}"
88 c "${opt}"
89 else
90 c "${opt}"
91 s "${opt}"
92 fi
93 '
94 done
95 }
96
97 test_optional_on_off()
98 {
99 RET=0
100 OPTS=
101 for opt
102 do
103 test "${opt}" = n && continue
104 ${TEST_SH} -c "set -${opt}" 2>/dev/null &&
105 OPTS="${OPTS} ${opt}" || RET=1
106 done
107
108 test -n "${OPTS}" && test_option_on_off ${OPTS}
109
110 return "${RET}"
111 }
112
113 atf_test_case set_a
114 set_a_head() {
115 atf_set "descr" "Tests that 'set -a' turns on all var export " \
116 "and that it behaves as defined by the standard"
117 }
118 set_a_body() {
119 atf_require_prog env
120 atf_require_prog grep
121
122 test_option_on_off a
123
124 # without -a, new variables should not be exported (so grep "fails")
125 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -ce \
126 'unset VAR; set +a; VAR=value; env | grep "^VAR="'
127
128 # with -a, they should be
129 atf_check -s exit:0 -o match:VAR=value -e empty ${TEST_SH} -ce \
130 'unset VAR; set -a; VAR=value; env | grep "^VAR="'
131 }
132
133 atf_test_case set_C
134 set_C_head() {
135 atf_set "descr" "Tests that 'set -C' turns on no clobber mode " \
136 "and that it behaves as defined by the standard"
137 }
138 set_C_body() {
139 atf_require_prog ls
140
141 test_option_on_off C
142
143 # Check that the environment to use for the tests is sane ...
144 # we assume current dir is a new tempory directory & is empty
145
146 test -z "$(ls)" || atf_skip "Test execution directory not clean"
147 test -c "/dev/null" || atf_skip "Problem with /dev/null"
148
149 echo Dummy_Content > Junk_File
150 echo Precious_Content > Important_File
151
152 # Check that we can redirect onto file when -C is not set
153 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
154 '
155 D=$(ls -l Junk_File) || exit 1
156 set +C
157 echo "Overwrite it now" > Junk_File
158 A=$(ls -l Junk_File) || exit 1
159 test "${A}" != "${D}"
160 '
161
162 # Check that we cannot redirect onto file when -C is set
163 atf_check -s exit:0 -o empty -e not-empty ${TEST_SH} -c \
164 '
165 D=$(ls -l Important_File) || exit 1
166 set -C
167 echo "Fail to Overwrite it now" > Important_File
168 A=$(ls -l Important_File) || exit 1
169 test "${A}" = "${D}"
170 '
171
172 # Check that we can append to file, even when -C is set
173 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
174 '
175 D=$(ls -l Junk_File) || exit 1
176 set -C
177 echo "Append to it now" >> Junk_File
178 A=$(ls -l Junk_File) || exit 1
179 test "${A}" != "${D}"
180 '
181
182 # Check that we abort on attempt to redirect onto file when -Ce is set
183 atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \
184 '
185 set -Ce
186 echo "Fail to Overwrite it now" > Important_File
187 echo "Should not reach this point"
188 '
189
190 # Last check that we can override -C for when we really need to
191 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
192 '
193 D=$(ls -l Junk_File) || exit 1
194 set -C
195 echo "Change the poor bugger again" >| Junk_File
196 A=$(ls -l Junk_File) || exit 1
197 test "${A}" != "${D}"
198 '
199 }
200
201 atf_test_case set_e
202 set_e_head() {
203 atf_set "descr" "Tests that 'set -e' turns on error detection " \
204 "and that a simple case behaves as defined by the standard"
205 }
206 set_e_body() {
207 test_option_on_off e
208
209 # Check that -e does nothing if no commands fail
210 atf_check -s exit:0 -o match:I_am_OK -e empty \
211 ${TEST_SH} -c \
212 'false; printf "%s" I_am; set -e; true; printf "%s\n" _OK'
213
214 # and that it (silently, but with exit status) aborts if cmd fails
215 atf_check -s not-exit:0 -o match:I_am -o not-match:Broken -e empty \
216 ${TEST_SH} -c \
217 'false; printf "%s" I_am; set -e; false; printf "%s\n" _Broken'
218
219 # same, except -e this time is on from the beginning
220 atf_check -s not-exit:0 -o match:I_am -o not-match:Broken -e empty \
221 ${TEST_SH} -ec 'printf "%s" I_am; false; printf "%s\n" _Broken'
222
223 # More checking of -e in other places, there is lots to deal with.
224 }
225
226 atf_test_case set_f
227 set_f_head() {
228 atf_set "descr" "Tests that 'set -f' turns off pathname expansion " \
229 "and that it behaves as defined by the standard"
230 }
231 set_f_body() {
232 atf_require_prog ls
233
234 test_option_on_off f
235
236 # Check that the environment to use for the tests is sane ...
237 # we assume current dir is a new tempory directory & is empty
238
239 test -z "$(ls)" || atf_skip "Test execution directory not clean"
240
241 # we will assume that atf will clean up this junk directory
242 # when we are done. But for testing pathname expansion
243 # we need files
244
245 for f in a b c d e f aa ab ac ad ae aaa aab aac aad aba abc bbb ccc
246 do
247 echo "$f" > "$f"
248 done
249
250 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -ec \
251 'X=$(echo b*); Y=$(echo b*); test "${X}" != "a*";
252 test "${X}" = "${Y}"'
253
254 # now test expansion is different when -f is set
255 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -ec \
256 'X=$(echo b*); Y=$(set -f; echo b*); test "${X}" != "${Y}"'
257 }
258
259 atf_test_case set_n
260 set_n_head() {
261 atf_set "descr" "Tests that 'set -n' suppresses command execution " \
262 "and that it behaves as defined by the standard"
263 }
264 set_n_body() {
265 # pointless to test this, if it turns on, it stays on...
266 # test_option_on_off n
267 # so just allow the tests below to verify it can be turned on
268
269 # nothing should be executed, hence no output...
270 atf_check -s exit:0 -o empty -e empty \
271 ${TEST_SH} -enc 'echo ABANDON HOPE; echo ALL YE; echo ...'
272
273 # this is true even when the "commands" do not exist
274 atf_check -s exit:0 -o empty -e empty \
275 ${TEST_SH} -enc 'ERR; FAIL; ABANDON HOPE'
276
277 # but if there is a syntax error, it should be detected (w or w/o -e)
278 atf_check -s not-exit:0 -o empty -e not-empty \
279 ${TEST_SH} -enc 'echo JUMP; for frogs swim; echo in puddles'
280 atf_check -s not-exit:0 -o empty -e not-empty \
281 ${TEST_SH} -nc 'echo ABANDON HOPE; echo "ALL YE; echo ...'
282 atf_check -s not-exit:0 -o empty -e not-empty \
283 ${TEST_SH} -enc 'echo ABANDON HOPE;; echo ALL YE; echo ...'
284 atf_check -s not-exit:0 -o empty -e not-empty \
285 ${TEST_SH} -nc 'do YOU ABANDON HOPE; for all eternity?'
286
287 # now test enabling -n in the middle of a script
288 # note that once turned on, it cannot be turned off again.
289 #
290 # omit more complex cases, as those can send some shells
291 # into infinite loops, and believe it or not, that might be OK!
292
293 atf_check -s exit:0 -o match:first -o not-match:second -e empty \
294 ${TEST_SH} -c 'echo first; set -n; echo second'
295 atf_check -s exit:0 -o match:first -o not-match:third -e empty \
296 ${TEST_SH} -c 'echo first; set -n; echo second; set +n; echo third'
297 atf_check -s exit:0 -o inline:'a\nb\n' -e empty \
298 ${TEST_SH} -c 'for x in a b c d
299 do
300 case "$x" in
301 a);; b);; c) set -n;; d);;
302 esac
303 printf "%s\n" "$x"
304 done'
305
306 # This last one is a bit more complex to explain, so I will not try
307
308 # First, we need to know what signal number is used for SIGUSR1 on
309 # the local (testing) system (signal number is $(( $XIT - 128 )) )
310
311 # this will take slightly over 1 second elapsed time (the sleep 1)
312 # The "10" for the first sleep just needs to be something big enough
313 # that the rest of the commands have time to complete, even on
314 # very slow testing systems. 10 should be enough. Otherwise irrelevant
315
316 # The shell will usually blather to stderr about the sleep 10 being
317 # killed, but it affects nothing, so just allow it to cry.
318
319 (sleep 10 & sleep 1; kill -USR1 $!; wait $!)
320 XIT="$?"
321
322 # The exit value should be an integer > 128 and < 256 (often 158)
323 # If it is not just skip the test
324
325 # If we do run the test, it should take (slightly over) either 1 or 2
326 # seconds to complete, depending upon the shell being tested.
327
328 case "${XIT}" in
329 ( 129 | 1[3-9][0-9] | 2[0-4][0-9] | 25[0-5] )
330
331 # The script below should exit with the same code - no output
332
333 # Or that is the result that seems best explanable.
334 # "set -n" in uses like this is not exactly well defined...
335
336 # This script comes from a member of the austin group
337 # (they author changes to the posix shell spec - and more.)
338 # The author is also an (occasional?) NetBSD user.
339 atf_check -s exit:${XIT} -o empty -e empty ${TEST_SH} -c '
340 trap "set -n" USR1
341 { sleep 1; kill -USR1 $$; sleep 1; } &
342 false
343 wait && echo t || echo f
344 wait
345 echo foo
346 '
347 ;;
348 esac
349 }
350
351 atf_test_case set_u
352 set_u_head() {
353 atf_set "descr" "Tests that 'set -u' turns on unset var detection " \
354 "and that it behaves as defined by the standard"
355 }
356 set_u_body() {
357 test_option_on_off u
358
359 unset ENV # make sure there is nothing there to cause problems
360
361 # first make sure it is OK to unset an unset variable
362 atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
363 'unset _UNSET_VARIABLE_; echo OK'
364 # even if -u is set
365 atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -cue \
366 'unset _UNSET_VARIABLE_; echo OK'
367
368 # and that without -u accessing an unset variable is harmless
369 atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
370 'unset X; echo ${X}; echo OK'
371 # and that the unset variable test expansion works properly
372 atf_check -s exit:0 -o match:OKOK -e empty ${TEST_SH} -ce \
373 'unset X; printf "%s" ${X-OK}; echo OK'
374
375 # Next test that with -u set, the shell aborts on access to unset var
376 # do not use -e, want to make sure it is -u that causes abort
377 atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
378 'unset X; set -u; echo ${X}; echo ERR'
379 # quoting should make no difference...
380 atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
381 'unset X; set -u; echo "${X}"; echo ERR'
382
383 # Now a bunch of accesses to unset vars, with -u, in ways that are OK
384 atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
385 'unset X; set -u; echo ${X-GOOD}; echo OK'
386 atf_check -s exit:0 -o match:OK -e empty ${TEST_SH} -ce \
387 'unset X; set -u; echo ${X-OK}'
388 atf_check -s exit:0 -o not-match:ERR -o match:OK -e empty \
389 ${TEST_SH} -ce 'unset X; set -u; echo ${X+ERR}; echo OK'
390
391 # and some more ways that are not OK
392 atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
393 'unset X; set -u; echo ${X#foo}; echo ERR'
394 atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
395 'unset X; set -u; echo ${X%%bar}; echo ERR'
396
397 # lastly, just while we are checking unset vars, test aborts w/o -u
398 atf_check -s not-exit:0 -o not-match:ERR -e not-empty ${TEST_SH} -c \
399 'unset X; echo ${X?}; echo ERR'
400 atf_check -s not-exit:0 -o not-match:ERR -e match:X_NOT_SET \
401 ${TEST_SH} -c 'unset X; echo ${X?X_NOT_SET}; echo ERR'
402 }
403
404 atf_test_case set_v
405 set_v_head() {
406 atf_set "descr" "Tests that 'set -v' turns on input read echoing " \
407 "and that it behaves as defined by the standard"
408 }
409 set_v_body() {
410 test_option_on_off v
411
412 # check that -v does nothing if no later input line is read
413 atf_check -s exit:0 \
414 -o match:OKOK -o not-match:echo -o not-match:printf \
415 -e empty \
416 ${TEST_SH} -ec 'printf "%s" OK; set -v; echo OK; exit 0'
417
418 # but that it does when there are multiple lines
419 cat <<- 'EOF' |
420 set -v
421 printf %s OK
422 echo OK
423 exit 0
424 EOF
425 atf_check -s exit:0 \
426 -o match:OKOK -o not-match:echo -o not-match:printf \
427 -e match:printf -e match:OK -e match:echo \
428 -e not-match:set ${TEST_SH}
429
430 # and that it can be disabled again
431 cat <<- 'EOF' |
432 set -v
433 printf %s OK
434 set +v
435 echo OK
436 exit 0
437 EOF
438 atf_check -s exit:0 \
439 -o match:OKOK -o not-match:echo -o not-match:printf \
440 -e match:printf -e match:OK -e not-match:echo \
441 ${TEST_SH}
442
443 # and lastly, that shell keywords do get output when "read"
444 cat <<- 'EOF' |
445 set -v
446 for i in 111 222 333
447 do
448 printf %s $i
449 done
450 exit 0
451 EOF
452 atf_check -s exit:0 \
453 -o match:111222333 -o not-match:printf \
454 -o not-match:for -o not-match:do -o not-match:done \
455 -e match:printf -e match:111 -e not-match:111222 \
456 -e match:for -e match:do -e match:done \
457 ${TEST_SH} ||
458 atf_fail '111 222 333 test failure'
459 }
460
461 atf_test_case set_x
462 set_x_head() {
463 atf_set "descr" "Tests that 'set -x' turns on command exec logging " \
464 "and that it behaves as defined by the standard"
465 }
466 set_x_body() {
467 test_option_on_off x
468
469 # check that cmd output appears after -x is enabled
470 atf_check -s exit:0 \
471 -o match:OKOK -o not-match:echo -o not-match:printf \
472 -e not-match:printf -e match:OK -e match:echo \
473 ${TEST_SH} -ec 'printf "%s" OK; set -x; echo OK; exit 0'
474
475 # and that it stops again afer -x is disabled
476 atf_check -s exit:0 \
477 -o match:OKOK -o not-match:echo -o not-match:printf \
478 -e match:printf -e match:OK -e not-match:echo \
479 ${TEST_SH} -ec 'set -x; printf "%s" OK; set +x; echo OK; exit 0'
480
481 # also check that PS4 is output correctly
482 atf_check -s exit:0 \
483 -o match:OK -o not-match:echo \
484 -e match:OK -e match:Run:echo \
485 ${TEST_SH} -ec 'PS4=Run:; set -x; echo OK; exit 0'
486
487 return 0
488
489 # This one seems controversial... I suspect it is NetBSD's sh
490 # that is wrong to not output "for" "while" "if" ... etc
491
492 # and lastly, that shell keywords do not get output when "executed"
493 atf_check -s exit:0 \
494 -o match:111222333 -o not-match:printf \
495 -o not-match:for \
496 -e match:printf -e match:111 -e not-match:111222 \
497 -e not-match:for -e not-match:do -e not-match:done \
498 ${TEST_SH} -ec \
499 'set -x; for i in 111 222 333; do printf "%s" $i; done; echo; exit 0'
500 }
501
502 atf_test_case set_X
503 set_X_head() {
504 atf_set "descr" "Tests that 'set -X' turns on command exec logging " \
505 "and that it enables set -x and retains a single fd"
506 }
507 set_X_body() {
508
509 # First we need to verify that $TEST_SH supports -X
510 test_optional_on_off X ||
511 atf_skip "$TEST_SH does not support -X"
512
513 # and that the -X it implements is the -X we expect
514 $TEST_SH -c 'exec 2>/dev/null;
515 set +x; set -X;
516 case "$-" in (*x*) exit 0;; esac;
517 exit 1' ||
518 atf_skip "$TEST_SH supports -X but not 'the' -X"
519
520 # Above has already tested that set -X => set -x
521 # Now test that set +X => set +x
522 # and that set -x and set +x do not affect -X
523
524 atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
525 'set -x; set +X; case "$-" in (*x*) echo FAIL; exit 1;; esac'
526
527 atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
528 'set -X; set +x;
529 case "$-" in (*x*) echo FAIL; exit 1;; esac
530 case "$-" in (*X*) exit 0;; esac; echo ERROR; exit 2'
531
532 atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
533 'set -X; set +x; set -x;
534 case "$-" in (*x*X*|*X*x*) exit 0;; esac; echo ERROR; exit 2'
535
536 atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
537 'set +X; set -x;
538 case "$-" in (*X*) echo FAIL; exit 1;; esac
539 case "$-" in (*x*) exit 0;; esac; echo ERROR; exit 2'
540
541 atf_check -s exit:0 -o empty -e ignore ${TEST_SH} -c \
542 'set +X; set -x; set +x;
543 case "$-" in (*[xX]*) echo FAULT; exit 3;; esac'
544
545 # The following just verify regular tracing using -X instead of -x
546 # These are the same tests as the -x test (set_x) performs.
547
548 # check that cmd output appears after -X is enabled
549 atf_check -s exit:0 \
550 -o match:OKOK -o not-match:echo -o not-match:printf \
551 -e not-match:printf -e match:OK -e match:echo \
552 ${TEST_SH} -ec 'printf "%s" OK; set -X; echo OK; exit 0'
553
554 # and that it stops again afer -X is disabled
555 atf_check -s exit:0 \
556 -o match:OKOK -o not-match:echo -o not-match:printf \
557 -e match:printf -e match:OK -e not-match:echo \
558 ${TEST_SH} -ec 'set -X; printf "%s" OK; set +X; echo OK; exit 0'
559
560 # also check that PS4 is output correctly
561 atf_check -s exit:0 \
562 -o match:OK -o not-match:echo \
563 -e match:OK -e match:Run:echo \
564 ${TEST_SH} -ec 'PS4=Run:; set -X; echo OK; exit 0'
565
566 # end copies of -x tests ...
567
568 # now check that we can move stderr around without affecting -X output
569
570 atf_check -s exit:0 \
571 -o match:OKOK -o not-match:echo -o not-match:printf \
572 -e match:printf -e match:OK -e match:echo \
573 ${TEST_SH} -ecX 'printf "%s" OK; exec 2>/dev/null; echo OK'
574 atf_check -s exit:0 \
575 -o match:OKOK -o not-match:echo -o not-match:printf \
576 -e match:printf -e match:OK -e match:echo \
577 ${TEST_SH} -ecX 'printf "%s" OK; exec 2>&1; echo OK'
578 atf_check -s exit:0 \
579 -o match:OKOK -o not-match:echo -o not-match:printf \
580 -e match:printf -e match:OK -e match:echo \
581 ${TEST_SH} -ecX 'printf "%s" OK; exec 2>&-; echo OK'
582
583 # and that we can put tracing on an external file, leaving stderr alone
584
585 atf_require_prog grep
586
587 rm -f X-trace
588 atf_check -s exit:0 \
589 -o match:OKOK -o not-match:echo -o not-match:printf \
590 -e empty \
591 ${TEST_SH} -ec 'PS4=; set -X 2>X-trace; printf "%s" OK; echo OK'
592 test -s X-trace || atf_fail "T1: Failed to create trace output file"
593 grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
594 atf_fail "T1: -X tracing missing printf"
595 grep >/dev/null 2>&1 'echo.*OK' X-trace ||
596 atf_fail "T1: -X tracing missing echo"
597
598 rm -f X-trace
599 atf_check -s exit:0 \
600 -o match:OKOK -o not-match:echo -o not-match:printf \
601 -e empty \
602 ${TEST_SH} -ec \
603 'PS4=; set -X 2>X-trace;
604 printf "%s" OK;
605 exec 2>/dev/null;
606 echo OK'
607 test -s X-trace || atf_fail "T2: Failed to create trace output file"
608 grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
609 atf_fail "T2: -X tracing missing printf"
610 grep >/dev/null 2>&1 'exec' X-trace ||
611 atf_fail "T2: -X tracing missing exec"
612 grep >/dev/null 2>&1 'echo.*OK' X-trace ||
613 atf_fail "T2: -X tracing missing echo after stderr redirect"
614
615 rm -f X-trace
616 atf_check -s exit:0 \
617 -o match:OKOK -o not-match:echo -o not-match:printf \
618 -e empty \
619 ${TEST_SH} -ec \
620 'PS4=; set -X 2>X-trace;
621 printf "%s" OK;
622 set -X 2>/dev/null;
623 echo OK'
624 test -s X-trace || atf_fail "T3: Failed to create trace output file"
625 grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
626 atf_fail "T3: -X tracing missing printf"
627 grep >/dev/null 2>&1 'set.*-X' X-trace ||
628 atf_fail "T3: -X tracing missing set -X"
629 grep >/dev/null 2>&1 'echo.*OK' X-trace &&
630 atf_fail "T3: -X tracing included echo after set -X redirect"
631
632 rm -f X-trace
633 atf_check -s exit:0 \
634 -o match:OKOK -o not-match:echo -o not-match:printf \
635 -e match:echo -e match:OK -e not-match:printf \
636 ${TEST_SH} -ec \
637 'PS4=; set -X 2>X-trace;
638 printf "%s" OK;
639 set -X;
640 echo OK'
641 test -s X-trace || atf_fail "T4: Failed to create trace output file"
642 grep >/dev/null 2>&1 'printf.*%s.*OK' X-trace ||
643 atf_fail "T4: -X tracing missing printf"
644 grep >/dev/null 2>&1 'set.*-X' X-trace ||
645 atf_fail "T4: -X tracing missing set -X"
646 grep >/dev/null 2>&1 'echo.*OK' X-trace &&
647 atf_fail "T4: -X tracing included echo after set -X redirect"
648
649 # Now check that -X and the tracing files work properly wrt functions
650
651 # a shell that supports -X should support "local -" ... but verify
652
653 ( ${TEST_SH} -c 'fn() { local - || exit 2; set -f; }; set +f; fn;
654 case "$-" in ("*f*") exit 1;; esac; exit 0' ) 2>/dev/null ||
655 atf_skip "-X function test: 'local -' unsupported"
656
657 rm -f X-trace X-trace-fn
658 atf_check -s exit:0 \
659 -o match:OKhelloGOOD \
660 -e empty \
661 ${TEST_SH} -c '
662 say() {
663 printf "%s" "$*"
664 }
665 funct() {
666 local -
667
668 set -X 2>X-trace-fn
669 say hello
670 }
671
672 set -X 2>X-trace
673
674 printf OK
675 funct
676 echo GOOD
677 '
678 test -s X-trace || atf_fail "T5: Failed to create trace output file"
679 test -s X-trace-fn || atf_fail "T5: Failed to create fn trace output"
680 grep >/dev/null 2>&1 'printf.*OK' X-trace ||
681 atf_fail "T5: -X tracing missing printf"
682 grep >/dev/null 2>&1 funct X-trace ||
683 atf_fail "T5: -X tracing missing funct"
684 grep >/dev/null 2>&1 'set.*-X' X-trace ||
685 atf_fail "T5: -X tracing missing set -X from in funct"
686 grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
687 atf_fail "T5: -X tracing missing echo after funct redirect"
688 grep >/dev/null 2>&1 'say.*hello' X-trace &&
689 atf_fail "T5: -X tracing included 'say' after funct redirect"
690 grep >/dev/null 2>&1 'say.*hello' X-trace-fn ||
691 atf_fail "T5: -X funct tracing missed 'say'"
692
693 rm -f X-trace X-trace-fn
694
695 atf_check -s exit:0 \
696 -o match:OKhelloGOOD \
697 -e empty \
698 ${TEST_SH} -c '
699 say() {
700 printf "%s" "$*"
701 }
702 funct() {
703 local -
704
705 set +X
706 say hello
707 }
708
709 set -X 2>X-trace
710
711 printf OK
712 funct
713 echo GOOD
714 '
715 test -s X-trace || atf_fail "T6: Failed to create trace output file"
716 grep >/dev/null 2>&1 'printf.*OK' X-trace ||
717 atf_fail "T6: -X tracing missing printf"
718 grep >/dev/null 2>&1 funct X-trace ||
719 atf_fail "T6: -X tracing missing funct"
720 grep >/dev/null 2>&1 'set.*+X' X-trace ||
721 atf_fail "T6: -X tracing missing set +X from in funct"
722 grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
723 atf_fail "T6: -X tracing missing echo after funct redirect"
724 grep >/dev/null 2>&1 'say.*hello' X-trace &&
725 atf_fail "T6: -X tracing included 'say' after funct redirect"
726
727 rm -f X-trace
728
729 atf_check -s exit:0 \
730 -o match:OKtracednotraceGOOD \
731 -e match:say -e match:traced -e not-match:notrace \
732 ${TEST_SH} -c '
733 say() {
734 printf "%s" "$*"
735 }
736 funct() {
737 local -
738
739 set +X -x
740
741 say traced
742 exec 2>/dev/null
743 say notrace
744
745 }
746
747 set -X 2>X-trace
748
749 printf OK
750 funct
751 echo GOOD
752 '
753 test -s X-trace || atf_fail "T7: Failed to create trace output file"
754 grep >/dev/null 2>&1 'printf.*OK' X-trace ||
755 atf_fail "T7: -X tracing missing printf"
756 grep >/dev/null 2>&1 funct X-trace ||
757 atf_fail "T7: -X tracing missing funct"
758 grep >/dev/null 2>&1 'set.*+X.*-x' X-trace ||
759 atf_fail "T7: -X tracing missing set +X -x from in funct"
760 grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
761 atf_fail "T7: -X tracing missing echo after funct +X"
762 grep >/dev/null 2>&1 'say.*hello' X-trace &&
763 atf_fail "T7: -X tracing included 'say' after funct +X"
764
765 rm -f X-trace X-trace-fn
766 atf_check -s exit:0 \
767 -o "match:OKg'daybye-bye.*hello.*GOOD" \
768 -e empty \
769 ${TEST_SH} -c '
770 say() {
771 printf "%s" "$*"
772 }
773 fn1() {
774 local -
775
776 set -X 2>>X-trace-fn
777 say "g'\''day"
778 "$@"
779 say bye-bye
780 }
781 fn2() {
782 set +X
783 say hello
784 "$@"
785 say goodbye
786 }
787
788 set -X 2>X-trace
789
790 printf OK
791 fn1
792 fn1 fn2
793 fn1 fn1 fn2
794 fn1 fn2 fn1 fn2 fn1
795 fn1 fn1 fn2 fn2 fn1
796 echo GOOD
797 '
798
799 # That test generally succeeds if the earlier ones did
800 # and if it did not dump core!
801
802 # But we can check a few things...
803
804 test -s X-trace || atf_fail "T8: Failed to create trace output file"
805 test -s X-trace-fn || atf_fail "T8: Failed to create trace output file"
806 grep >/dev/null 2>&1 'printf.*OK' X-trace ||
807 atf_fail "T8: -X tracing missing printf"
808 grep >/dev/null 2>&1 fn1 X-trace ||
809 atf_fail "T8: -X tracing missing fn1"
810 grep >/dev/null 2>&1 'set.*-X' X-trace ||
811 atf_fail "T8: -X tracing missing set -X from in fn1"
812 grep >/dev/null 2>&1 'echo.*GOOD' X-trace ||
813 atf_fail "T8: -X tracing missing echo after fn1 redirect"
814 grep >/dev/null 2>&1 'say.*hello' X-trace &&
815 atf_fail "T8: -X tracing included 'say' after fn2 +X"
816 grep >/dev/null 2>&1 'say.*hello' X-trace-fn &&
817 atf_fail "T8: -X fn tracing included 'say' after fn2 +X"
818
819
820 rm -f X-trace
821
822 return 0
823 }
824
825 opt_test_setup()
826 {
827 test -n "$1" || { echo >&2 "Internal error"; exit 1; }
828
829 cat > "$1" << 'END_OF_FUNCTIONS'
830 local_opt_check()
831 {
832 local -
833 }
834
835 instr()
836 {
837 expr "$2" : "\(.*$1\)" >/dev/null
838 }
839
840 save_opts()
841 {
842 local -
843
844 set -e
845 set -u
846
847 instr e "$-" && instr u "$-" && return 0
848 echo ERR
849 }
850
851 fiddle_opts()
852 {
853 set -e
854 set -u
855
856 instr e "$-" && instr u "$-" && return 0
857 echo ERR
858 }
859
860 local_test()
861 {
862 set +eu
863
864 save_opts
865 instr '[eu]' "$-" || printf %s "OK"
866
867 fiddle_opts
868 instr e "$-" && instr u "$-" && printf %s "OK"
869
870 set +eu
871 }
872 END_OF_FUNCTIONS
873 }
874
875 atf_test_case restore_local_opts
876 restore_local_opts_head() {
877 atf_set "descr" "Tests that 'local -' saves and restores options. " \
878 "Note that "local" is a local shell addition"
879 }
880 restore_local_opts_body() {
881 atf_require_prog cat
882 atf_require_prog expr
883
884 FN="test-funcs.$$"
885 opt_test_setup "${FN}" || atf_skip "Cannot setup test environment"
886
887 ${TEST_SH} -ec ". './${FN}'; local_opt_check" 2>/dev/null ||
888 atf_skip "sh extension 'local -' not supported by ${TEST_SH}"
889
890 atf_check -s exit:0 -o match:OKOK -o not-match:ERR -e empty \
891 ${TEST_SH} -ec ". './${FN}'; local_test"
892 }
893
894 atf_test_case vi_emacs_VE_toggle
895 vi_emacs_VE_toggle_head() {
896 atf_set "descr" "Tests enabling vi disables emacs (and v.v - but why?)"\
897 " Note that -V and -E are local shell additions"
898 }
899 vi_emacs_VE_toggle_body() {
900
901 test_optional_on_off V E ||
902 atf_skip "One or both V & E opts unsupported by ${TEST_SH}"
903
904 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '
905 q() {
906 eval "case \"$-\" in
907 (*${2}*) return 1;;
908 (*${1}*) return 0;;
909 esac"
910 return 1
911 }
912 x() {
913 echo >&2 "Option set or toggle failure:" \
914 " on=$1 off=$2 set=$-"
915 exit 1
916 }
917 set -V; q V E || x V E
918 set -E; q E V || x E V
919 set -V; q V E || x V E
920 set +EV; q "" "[VE]" || x "" VE
921 exit 0
922 '
923 }
924
925 atf_test_case pipefail
926 pipefail_head() {
927 atf_set "descr" "Basic tests of the pipefail option"
928 }
929 pipefail_body() {
930 ${TEST_SH} -c 'set -o pipefail' 2>/dev/null ||
931 atf_skip "pipefail option not supported by ${TEST_SH}"
932
933 atf_check -s exit:0 -o match:'pipefail.*off' -e empty ${TEST_SH} -c \
934 'set -o | grep pipefail'
935 atf_check -s exit:0 -o match:'pipefail.*on' -e empty ${TEST_SH} -c \
936 'set -o pipefail; set -o | grep pipefail'
937
938 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
939 '(exit 1) | (exit 2) | (exit 0)'
940 atf_check -s exit:2 -o empty -e empty ${TEST_SH} -c \
941 'set -o pipefail; (exit 1) | (exit 2) | (exit 0)'
942 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
943 'set -o pipefail; (exit 1) | (exit 0) | (exit 0)'
944 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
945 'set -o pipefail; (exit 0) | (exit 0) | (exit 0)'
946
947 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
948 '! (exit 1) | (exit 2) | (exit 0)'
949 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
950 'set -o pipefail; ! (exit 1) | (exit 2) | (exit 0)'
951 atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \
952 'set -o pipefail; ! (exit 1) | (exit 0) | (exit 0)'
953 atf_check -s exit:1 -o empty -e empty ${TEST_SH} -c \
954 'set -o pipefail; ! (exit 0) | (exit 0) | (exit 0)'
955
956 atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
957 '(exit 1) | (exit 2) | (exit 0); echo $?'
958 atf_check -s exit:0 -o inline:'2\n' -e empty ${TEST_SH} -c \
959 'set -o pipefail; (exit 1) | (exit 2) | (exit 0); echo $?'
960 atf_check -s exit:0 -o inline:'1\n' -e empty ${TEST_SH} -c \
961 'set -o pipefail; (exit 1) | (exit 0) | (exit 0); echo $?'
962 atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
963 'set -o pipefail; (exit 0) | (exit 0) | (exit 0); echo $?'
964
965 atf_check -s exit:0 -o inline:'1\n' -e empty ${TEST_SH} -c \
966 '! (exit 1) | (exit 2) | (exit 0); echo $?'
967 atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
968 'set -o pipefail; ! (exit 1) | (exit 2) | (exit 0); echo $?'
969 atf_check -s exit:0 -o inline:'0\n' -e empty ${TEST_SH} -c \
970 'set -o pipefail; ! (exit 1) | (exit 0) | (exit 0); echo $?'
971 atf_check -s exit:0 -o inline:'1\n' -e empty ${TEST_SH} -c \
972 'set -o pipefail; ! (exit 0) | (exit 0) | (exit 0); echo $?'
973 }
974
975 atf_test_case xx_bogus
976 xx_bogus_head() {
977 atf_set "descr" "Tests that attempting to set a nonsense option fails."
978 }
979 xx_bogus_body() {
980 # Biggest problem here is picking a "nonsense option" that is
981 # not implemented by any shell, anywhere. Hopefully this will do.
982
983 # 'set' is a special builtin, so a conforming shell should exit
984 # on an arg error, and the ERR should not be printed.
985 atf_check -s not-exit:0 -o empty -e not-empty \
986 ${TEST_SH} -c 'set -% ; echo ERR'
987 }
988
989 atf_test_case Option_switching
990 Option_switching_head() {
991 atf_set "descr" "options can be enabled and disabled"
992 }
993 Option_switching_body() {
994
995 # Cannot test -m, setting it causes test shell to fail...
996 # (test shell gets SIGKILL!) Wonder why ... something related to atf
997 # That is, it works if just run as "sh -c 'echo $-; set -m; echo $-'"
998
999 # Don't bother testing toggling -n, once on, it stays on...
1000 # (and because the test fn refuses to allow us to try)
1001
1002 # Cannot test -o or -c here, or the extension -s
1003 # they can only be used, not switched
1004
1005 # these are the posix options, that all shells should implement
1006 test_option_on_off a b C e f h u v x # m
1007
1008 # and these are extensions that might not exist (non-fatal to test)
1009 # -i and -s (and -c) are posix options, but are not required to
1010 # be accessable via the "set" command, just the command line.
1011 # We allow for -i to work with set, as that makes some sense,
1012 # -c and -s do not.
1013 test_optional_on_off E i I p q V X || true
1014
1015 # Also test (some) option combinations ...
1016 # only testing posix options here, because it is easier...
1017 test_option_on_off aeu vx Ca aCefux
1018 }
1019
1020 atf_init_test_cases() {
1021 # tests are run in order sort of names produces, so choose names wisely
1022
1023 # this one tests turning on/off all the mandatory. and extra flags
1024 atf_add_test_case Option_switching
1025 # and this tests the NetBSD "local -" functionality in functions.
1026 atf_add_test_case restore_local_opts
1027
1028 # no tests for -m (no idea how to do that one)
1029 # -I (no easy way to generate the EOF it ignores)
1030 # -i (not sure how to test that one at the minute)
1031 # -p (because we aren't going to run tests setuid)
1032 # -V/-E (too much effort, and a real test would be huge)
1033 # -c (because almost all the other tests test it anyway)
1034 # -q (because, for now, I am lazy)
1035 # -s (coming soon, hopefully)
1036 # -o (really +o: again, hopefully soon)
1037 # -o longname (again, just laziness, don't wait...)
1038 # -h/-b (because NetBSD doesn't implement them)
1039 atf_add_test_case set_a
1040 atf_add_test_case set_C
1041 atf_add_test_case set_e
1042 atf_add_test_case set_f
1043 atf_add_test_case set_n
1044 atf_add_test_case set_u
1045 atf_add_test_case set_v
1046 atf_add_test_case set_x
1047 atf_add_test_case set_X
1048
1049 atf_add_test_case vi_emacs_VE_toggle
1050
1051 atf_add_test_case pipefail
1052
1053 atf_add_test_case xx_bogus
1054 }
1055