1313498Sngie# $NetBSD: t_set_e.sh,v 1.4 2016/03/31 16:22:27 christos Exp $
2272343Sngie#
3272343Sngie# Copyright (c) 2007 The NetBSD Foundation, Inc.
4272343Sngie# All rights reserved.
5272343Sngie#
6272343Sngie# Redistribution and use in source and binary forms, with or without
7272343Sngie# modification, are permitted provided that the following conditions
8272343Sngie# are met:
9272343Sngie# 1. Redistributions of source code must retain the above copyright
10272343Sngie#    notice, this list of conditions and the following disclaimer.
11272343Sngie# 2. Redistributions in binary form must reproduce the above copyright
12272343Sngie#    notice, this list of conditions and the following disclaimer in the
13272343Sngie#    documentation and/or other materials provided with the distribution.
14272343Sngie#
15272343Sngie# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
16272343Sngie# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17272343Sngie# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18272343Sngie# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
19272343Sngie# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20272343Sngie# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21272343Sngie# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22272343Sngie# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23272343Sngie# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24272343Sngie# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25272343Sngie# POSSIBILITY OF SUCH DAMAGE.
26272343Sngie#
27272343Sngie
28272343Sngie# references:
29272343Sngie#   http://www.opengroup.org/onlinepubs/009695399/utilities/set.html
30272343Sngie#   http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
31272343Sngie
32272343Sngie# the implementation of "sh" to test
33313498Sngie: ${TEST_SH:="/bin/sh"}
34272343Sngie
35272343Sngiefailwith()
36272343Sngie{
37272343Sngie	case "$SH_FAILS" in
38272343Sngie		"") SH_FAILS=`echo "$1"`;;
39272343Sngie		*) SH_FAILS="$SH_FAILS"`echo; echo "$1"`;;
40272343Sngie	esac
41272343Sngie}
42272343Sngie
43272343Sngiecheck1()
44272343Sngie{
45272343Sngie	#echo "$TEST_SH -c $1"
46272343Sngie	result=`$TEST_SH -c "$1" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//'`
47272343Sngie	if [ "$result" != "$2" ]; then
48272343Sngie		MSG=`printf "%-56s %-8s  %s" "$3" "$result" "$2"`
49272343Sngie		failwith "$MSG"
50272343Sngie		failcount=`expr $failcount + 1`
51272343Sngie	fi
52272343Sngie	count=`expr $count + 1`
53272343Sngie}
54272343Sngie
55272343Sngie# direct check: try the given expression.
56272343Sngiedcheck()
57272343Sngie{
58272343Sngie	check1 "$1" "$2" "$1"
59272343Sngie}
60272343Sngie
61272343Sngie# eval check: indirect through eval.
62272343Sngie# as of this writing, this changes the behavior pretty drastically and
63272343Sngie# is thus important to test. (PR bin/29861)
64272343Sngieecheck()
65272343Sngie{
66313498Sngie	check1 'eval '"'( $1 )'" "$2" "eval '($1)'"
67272343Sngie}
68272343Sngie
69272343Sngieatf_test_case all
70272343Sngieall_head() {
71272343Sngie	atf_set "descr" "Tests that 'set -e' works correctly"
72272343Sngie}
73272343Sngieall_body() {
74272343Sngie	count=0
75272343Sngie	failcount=0
76272343Sngie
77272343Sngie	# make sure exiting from a subshell behaves as expected
78272343Sngie	dcheck '(set -e; exit 1; echo ERR$?); echo OK$?' 'OK1'
79272343Sngie	echeck '(set -e; exit 1; echo ERR$?); echo OK$?' 'OK1'
80272343Sngie
81272343Sngie	# first, check basic functioning.
82272343Sngie	# The ERR shouldn't print; the result of the () should be 1.
83272343Sngie	# Henceforth we'll assume that we don't need to check $?.
84313498Sngie	dcheck '(set -e; false; echo ERR$?); echo OK$?' 'OK1'
85313498Sngie	echeck '(set -e; false; echo ERR$?); echo OK$?' 'OK1'
86272343Sngie
87272343Sngie	# these cases should be equivalent to the preceding.
88272343Sngie	dcheck '(set -e; /nonexistent; echo ERR); echo OK' 'OK'
89272343Sngie	echeck '(set -e; /nonexistent; echo ERR); echo OK' 'OK'
90272343Sngie	dcheck '(set -e; nonexistent-program-on-path; echo ERR); echo OK' 'OK'
91272343Sngie	echeck '(set -e; nonexistent-program-on-path; echo ERR); echo OK' 'OK'
92272343Sngie	dcheck 'f() { false; }; (set -e; f; echo ERR); echo OK' 'OK'
93272343Sngie	echeck 'f() { false; }; (set -e; f; echo ERR); echo OK' 'OK'
94272343Sngie	dcheck 'f() { return 1; }; (set -e; f; echo ERR); echo OK' 'OK'
95272343Sngie	echeck 'f() { return 1; }; (set -e; f; echo ERR); echo OK' 'OK'
96272343Sngie
97272343Sngie	# but! with set -e, the false should cause an *immediate* exit.
98272343Sngie	# The return form should not, as such, but there's no way to
99272343Sngie	# distinguish it.
100272343Sngie	dcheck 'f() { false; echo ERR; }; (set -e; f); echo OK' 'OK'
101272343Sngie	echeck 'f() { false; echo ERR; }; (set -e; f); echo OK' 'OK'
102272343Sngie
103272343Sngie	# set is not scoped, so these should not exit at all.
104272343Sngie	dcheck 'f() { set +e; false; echo OK; }; (set -e; f); echo OK' 'OK OK'
105272343Sngie	echeck 'f() { set +e; false; echo OK; }; (set -e; f); echo OK' 'OK OK'
106272343Sngie
107272343Sngie	# according to the standard, only failing *simple* commands
108272343Sngie	# cause an exit under -e. () is not a simple command.
109272343Sngie	#   Correct (per POSIX):
110272343Sngie	#dcheck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK OK'
111272343Sngie	#echeck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK OK'
112272343Sngie	#   Wrong current behavior:
113272343Sngie	dcheck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK'
114272343Sngie	echeck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK'
115272343Sngie
116272343Sngie	# make sure an inner nested shell does exit though.
117272343Sngie	dcheck '(set -e; (false; echo ERR)); echo OK' 'OK'
118272343Sngie
119272343Sngie	# The left hand side of an || or && is explicitly tested and
120272343Sngie	# thus should not cause an exit. Furthermore, because a || or
121272343Sngie	# && expression is not a simple command, there should be no
122272343Sngie	# exit even if the overall result is false.
123272343Sngie	dcheck '(set -e; false || true; echo OK); echo OK' 'OK OK'
124272343Sngie	echeck '(set -e; false || true; echo OK); echo OK' 'OK OK'
125272343Sngie	dcheck '(set -e; false && true; echo OK); echo OK' 'OK OK'
126272343Sngie	echeck '(set -e; false && true; echo OK); echo OK' 'OK OK'
127272343Sngie
128272343Sngie	# However, the right hand side is not tested, so a failure
129272343Sngie	# there *should* cause an exit, regardless of whether it
130272343Sngie	# appears inside a non-simple command.
131272343Sngie	#
132272343Sngie	# Note that in at least one place the standard does not
133272343Sngie	# distinguish between the left and right hand sides of
134272343Sngie	# logical operators. It is possible that for strict
135272343Sngie	# compliance these need to not exit; however, if so that
136272343Sngie	# should probably be limited to when some strict-posix setting
137272343Sngie	# is in effect and tested accordingly.
138272343Sngie	#
139272343Sngie	dcheck '(set -e; false || false; echo ERR); echo OK' 'OK'
140272343Sngie	dcheck '(set -e; true && false; echo ERR); echo OK' 'OK'
141272343Sngie	echeck '(set -e; false || false; echo ERR); echo OK' 'OK'
142272343Sngie	echeck '(set -e; true && false; echo ERR); echo OK' 'OK'
143272343Sngie
144272343Sngie	# correct:
145272343Sngie	#dcheck '(set -e; false && false; echo ERR); echo OK' 'OK'
146272343Sngie	#echeck '(set -e; false && false; echo ERR); echo OK' 'OK'
147272343Sngie
148272343Sngie	# wrong current behavior:
149272343Sngie	dcheck '(set -e; false && false; echo ERR); echo OK' 'ERR OK'
150272343Sngie	echeck '(set -e; false && false; echo ERR); echo OK' 'ERR OK'
151272343Sngie
152272343Sngie	# A failure that is not reached because of short-circuit
153272343Sngie	# evaluation should not cause an exit, however.
154272343Sngie	dcheck '(set -e; true || false; echo OK); echo OK' 'OK OK'
155272343Sngie	echeck '(set -e; true || false; echo OK); echo OK' 'OK OK'
156272343Sngie
157272343Sngie	# For completeness, test the other two combinations.
158272343Sngie	dcheck '(set -e; true || true; echo OK); echo OK' 'OK OK'
159272343Sngie	dcheck '(set -e; true && true; echo OK); echo OK' 'OK OK'
160272343Sngie	echeck '(set -e; true || true; echo OK); echo OK' 'OK OK'
161272343Sngie	echeck '(set -e; true && true; echo OK); echo OK' 'OK OK'
162272343Sngie
163272343Sngie	# likewise, none of these should exit.
164272343Sngie	dcheck '(set -e; while false; do :; done; echo OK); echo OK' 'OK OK'
165272343Sngie	dcheck '(set -e; if false; then :; fi; echo OK); echo OK' 'OK OK'
166272343Sngie	# problematic :-)
167272343Sngie	#dcheck '(set -e; until false; do :; done; echo OK); echo OK' 'OK OK'
168272343Sngie	dcheck '(set -e; until [ "$t" = 1 ]; do t=1; done; echo OK); echo OK' \
169272343Sngie	  'OK OK'
170272343Sngie	echeck '(set -e; while false; do :; done; echo OK); echo OK' 'OK OK'
171272343Sngie	echeck '(set -e; if false; then :; fi; echo OK); echo OK' 'OK OK'
172272343Sngie	echeck '(set -e; until [ "$t" = 1 ]; do t=1; done; echo OK); echo OK' \
173272343Sngie	  'OK OK'
174272343Sngie
175272343Sngie	# the bang operator tests its argument and thus the argument
176272343Sngie	# should not cause an exit. it is also not a simple command (I
177272343Sngie	# believe) so it also shouldn't exit even if it yields a false
178272343Sngie	# result.
179272343Sngie	dcheck '(set -e; ! false; echo OK); echo OK' 'OK OK'
180272343Sngie	dcheck '(set -e; ! true; echo OK); echo OK' 'OK OK'
181272343Sngie	echeck '(set -e; ! false; echo OK); echo OK' 'OK OK'
182272343Sngie	echeck '(set -e; ! true; echo OK); echo OK' 'OK OK'
183272343Sngie
184272343Sngie	# combined case with () and &&; the inner expression is false
185272343Sngie	# but does not itself exit, and the () should not cause an 
186272343Sngie	# exit even when failing.
187272343Sngie	# correct:
188272343Sngie	#dcheck '(set -e; (false && true); echo OK); echo OK' 'OK OK'
189272343Sngie	#echeck '(set -e; (false && true); echo OK); echo OK' 'OK OK'
190272343Sngie	# wrong current behavior:
191272343Sngie	dcheck '(set -e; (false && true); echo OK); echo OK' 'OK'
192272343Sngie	echeck '(set -e; (false && true); echo OK); echo OK' 'OK'
193272343Sngie
194272343Sngie	# pipelines. only the right-hand end is significant.
195272343Sngie	dcheck '(set -e; false | true; echo OK); echo OK' 'OK OK'
196272343Sngie	echeck '(set -e; false | true; echo OK); echo OK' 'OK OK'
197272343Sngie	dcheck '(set -e; true | false; echo ERR); echo OK' 'OK'
198272343Sngie	echeck '(set -e; true | false; echo ERR); echo OK' 'OK'
199272343Sngie
200272343Sngie	dcheck '(set -e; while true | false; do :; done; echo OK); echo OK' \
201272343Sngie	    'OK OK'
202272343Sngie	dcheck '(set -e; if true | false; then :; fi; echo OK); echo OK' \
203272343Sngie	    'OK OK'
204272343Sngie
205272343Sngie
206272343Sngie	# According to dsl@ in PR bin/32282, () is not defined as a
207272343Sngie	# subshell, only as a grouping operator [and a scope, I guess]
208313498Sngie
209313498Sngie	#		(This is incorrect.   () is definitely a sub-shell)
210313498Sngie
211272343Sngie	# so the nested false ought to cause the whole shell to exit,
212272343Sngie	# not just the subshell. dholland@ would like to see C&V,
213272343Sngie	# because that seems like a bad idea. (Among other things, it
214272343Sngie	# would break all the above test logic, which relies on being
215272343Sngie	# able to isolate set -e behavior inside ().) However, I'm
216272343Sngie	# going to put these tests here to make sure the issue gets
217272343Sngie	# dealt with sometime.
218272343Sngie	#
219272343Sngie	# XXX: the second set has been disabled in the name of making
220272343Sngie	# all tests "pass".
221313498Sngie	#
222313498Sngie	# As they should be, they are utter nonsense.
223272343Sngie
224313498Sngie	# 1. error if the whole shell exits (current correct behavior)
225272343Sngie	dcheck 'echo OK; (set -e; false); echo OK' 'OK OK'
226272343Sngie	echeck 'echo OK; (set -e; false); echo OK' 'OK OK'
227272343Sngie	# 2. error if the whole shell does not exit (dsl's suggested behavior)
228272343Sngie	#dcheck 'echo OK; (set -e; false); echo ERR' 'OK'
229272343Sngie	#echeck 'echo OK; (set -e; false); echo ERR' 'OK'
230272343Sngie
231272343Sngie	# The current behavior of the shell is that it exits out as
232272343Sngie	# far as -e is set and then stops. This is probably a
233272343Sngie	# consequence of it handling () wrong, but it's a somewhat
234272343Sngie	# curious compromise position between 1. and 2. above.
235272343Sngie	dcheck '(set -e; (false; echo ERR); echo ERR); echo OK' 'OK'
236272343Sngie	echeck '(set -e; (false; echo ERR); echo ERR); echo OK' 'OK'
237272343Sngie
238272343Sngie	# backquote expansion (PR bin/17514)
239272343Sngie
240313498Sngie	# (in-)correct
241272343Sngie	#dcheck '(set -e; echo ERR `false`; echo ERR); echo OK' 'OK'
242272343Sngie	#dcheck '(set -e; echo ERR $(false); echo ERR); echo OK' 'OK'
243272343Sngie	#dcheck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'OK'
244272343Sngie	#dcheck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'OK'
245313498Sngie	# Not-wrong current behavior
246313498Sngie	# the exit status of ommand substitution is ignored in most cases
247313498Sngie	# None of these should be causing the shell to exit.
248272343Sngie	dcheck '(set -e; echo ERR `false`; echo ERR); echo OK' 'ERR ERR OK'
249272343Sngie	dcheck '(set -e; echo ERR $(false); echo ERR); echo OK' 'ERR ERR OK'
250272343Sngie	dcheck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'ERR ERR OK'
251272343Sngie	dcheck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'ERR ERR OK'
252272343Sngie
253313498Sngie	# This is testing one case (the case?) where the exit status is used
254272343Sngie	dcheck '(set -e; x=`false`; echo ERR); echo OK' 'OK'
255272343Sngie	dcheck '(set -e; x=$(false); echo ERR); echo OK' 'OK'
256272343Sngie	dcheck '(set -e; x=`exit 3`; echo ERR); echo OK' 'OK'
257272343Sngie	dcheck '(set -e; x=$(exit 3); echo ERR); echo OK' 'OK'
258272343Sngie
259313498Sngie	# correct (really just commented out incorrect nonsense)
260272343Sngie	#echeck '(set -e; echo ERR `false`; echo ERR); echo OK' 'OK'
261272343Sngie	#echeck '(set -e; echo ERR $(false); echo ERR); echo OK' 'OK'
262272343Sngie	#echeck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'OK'
263272343Sngie	#echeck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'OK'
264272343Sngie
265313498Sngie	# not-wrong current behavior (as above)
266272343Sngie	echeck '(set -e; echo ERR `false`; echo ERR); echo OK' 'ERR ERR OK'
267272343Sngie	echeck '(set -e; echo ERR $(false); echo ERR); echo OK' 'ERR ERR OK'
268272343Sngie	echeck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'ERR ERR OK'
269272343Sngie	echeck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'ERR ERR OK'
270272343Sngie
271272343Sngie	echeck '(set -e; x=`false`; echo ERR); echo OK' 'OK'
272272343Sngie	echeck '(set -e; x=$(false); echo ERR); echo OK' 'OK'
273272343Sngie	echeck '(set -e; x=`exit 3`; echo ERR); echo OK' 'OK'
274272343Sngie	echeck '(set -e; x=$(exit 3); echo ERR); echo OK' 'OK'
275272343Sngie
276272343Sngie	# shift (PR bin/37493)
277272343Sngie	# correct
278313498Sngie	# Actually, both ways are correct, both are permitted
279272343Sngie	#dcheck '(set -e; shift || true; echo OK); echo OK' 'OK OK'
280272343Sngie	#echeck '(set -e; shift || true; echo OK); echo OK' 'OK OK'
281313498Sngie	# (not-) wrong current behavior
282313498Sngie	#dcheck '(set -e; shift || true; echo OK); echo OK' 'OK'
283313498Sngie	#echeck '(set -e; shift || true; echo OK); echo OK' 'OK'
284272343Sngie
285313498Sngie	# what is wrong is this test assuming one behaviour or the other
286313498Sngie	# (and incidentally this has nothing whatever to do with "-e",
287313498Sngie	# the test should really be moved elsewhere...)
288313498Sngie	# But for now, leave it here, and correct it:
289313498Sngie	dcheck '(set -e; shift && echo OK); echo OK' 'OK'
290313498Sngie	echeck '(set -e; shift && echo OK); echo OK' 'OK'
291313498Sngie
292272343Sngie	# Done.
293272343Sngie
294272343Sngie	if [ "x$SH_FAILS" != x ]; then
295272343Sngie	    printf '%-56s %-8s  %s\n' "Expression" "Result" "Should be"
296272343Sngie	    echo "$SH_FAILS"
297272343Sngie	    atf_fail "$failcount of $count failed cases"
298272343Sngie	else
299272343Sngie	    atf_pass
300272343Sngie	fi
301272343Sngie}
302272343Sngie
303272343Sngieatf_init_test_cases() {
304272343Sngie	atf_add_test_case all
305272343Sngie}
306