NetBSD/tests/bin/sh/t_set_e.sh

306 lines
12 KiB
Bash

# $NetBSD: t_set_e.sh,v 1.4 2016/03/31 16:22:27 christos Exp $
#
# Copyright (c) 2007 The NetBSD Foundation, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# references:
# http://www.opengroup.org/onlinepubs/009695399/utilities/set.html
# http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
# the implementation of "sh" to test
: ${TEST_SH:="/bin/sh"}
failwith()
{
case "$SH_FAILS" in
"") SH_FAILS=`echo "$1"`;;
*) SH_FAILS="$SH_FAILS"`echo; echo "$1"`;;
esac
}
check1()
{
#echo "$TEST_SH -c $1"
result=`$TEST_SH -c "$1" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//'`
if [ "$result" != "$2" ]; then
MSG=`printf "%-56s %-8s %s" "$3" "$result" "$2"`
failwith "$MSG"
failcount=`expr $failcount + 1`
fi
count=`expr $count + 1`
}
# direct check: try the given expression.
dcheck()
{
check1 "$1" "$2" "$1"
}
# eval check: indirect through eval.
# as of this writing, this changes the behavior pretty drastically and
# is thus important to test. (PR bin/29861)
echeck()
{
check1 'eval '"'( $1 )'" "$2" "eval '($1)'"
}
atf_test_case all
all_head() {
atf_set "descr" "Tests that 'set -e' works correctly"
}
all_body() {
count=0
failcount=0
# make sure exiting from a subshell behaves as expected
dcheck '(set -e; exit 1; echo ERR$?); echo OK$?' 'OK1'
echeck '(set -e; exit 1; echo ERR$?); echo OK$?' 'OK1'
# first, check basic functioning.
# The ERR shouldn't print; the result of the () should be 1.
# Henceforth we'll assume that we don't need to check $?.
dcheck '(set -e; false; echo ERR$?); echo OK$?' 'OK1'
echeck '(set -e; false; echo ERR$?); echo OK$?' 'OK1'
# these cases should be equivalent to the preceding.
dcheck '(set -e; /nonexistent; echo ERR); echo OK' 'OK'
echeck '(set -e; /nonexistent; echo ERR); echo OK' 'OK'
dcheck '(set -e; nonexistent-program-on-path; echo ERR); echo OK' 'OK'
echeck '(set -e; nonexistent-program-on-path; echo ERR); echo OK' 'OK'
dcheck 'f() { false; }; (set -e; f; echo ERR); echo OK' 'OK'
echeck 'f() { false; }; (set -e; f; echo ERR); echo OK' 'OK'
dcheck 'f() { return 1; }; (set -e; f; echo ERR); echo OK' 'OK'
echeck 'f() { return 1; }; (set -e; f; echo ERR); echo OK' 'OK'
# but! with set -e, the false should cause an *immediate* exit.
# The return form should not, as such, but there's no way to
# distinguish it.
dcheck 'f() { false; echo ERR; }; (set -e; f); echo OK' 'OK'
echeck 'f() { false; echo ERR; }; (set -e; f); echo OK' 'OK'
# set is not scoped, so these should not exit at all.
dcheck 'f() { set +e; false; echo OK; }; (set -e; f); echo OK' 'OK OK'
echeck 'f() { set +e; false; echo OK; }; (set -e; f); echo OK' 'OK OK'
# according to the standard, only failing *simple* commands
# cause an exit under -e. () is not a simple command.
# Correct (per POSIX):
#dcheck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK OK'
#echeck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK OK'
# Wrong current behavior:
dcheck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK'
echeck '(set -e; (set +e; false; echo OK; false); echo OK)' 'OK'
# make sure an inner nested shell does exit though.
dcheck '(set -e; (false; echo ERR)); echo OK' 'OK'
# The left hand side of an || or && is explicitly tested and
# thus should not cause an exit. Furthermore, because a || or
# && expression is not a simple command, there should be no
# exit even if the overall result is false.
dcheck '(set -e; false || true; echo OK); echo OK' 'OK OK'
echeck '(set -e; false || true; echo OK); echo OK' 'OK OK'
dcheck '(set -e; false && true; echo OK); echo OK' 'OK OK'
echeck '(set -e; false && true; echo OK); echo OK' 'OK OK'
# However, the right hand side is not tested, so a failure
# there *should* cause an exit, regardless of whether it
# appears inside a non-simple command.
#
# Note that in at least one place the standard does not
# distinguish between the left and right hand sides of
# logical operators. It is possible that for strict
# compliance these need to not exit; however, if so that
# should probably be limited to when some strict-posix setting
# is in effect and tested accordingly.
#
dcheck '(set -e; false || false; echo ERR); echo OK' 'OK'
dcheck '(set -e; true && false; echo ERR); echo OK' 'OK'
echeck '(set -e; false || false; echo ERR); echo OK' 'OK'
echeck '(set -e; true && false; echo ERR); echo OK' 'OK'
# correct:
#dcheck '(set -e; false && false; echo ERR); echo OK' 'OK'
#echeck '(set -e; false && false; echo ERR); echo OK' 'OK'
# wrong current behavior:
dcheck '(set -e; false && false; echo ERR); echo OK' 'ERR OK'
echeck '(set -e; false && false; echo ERR); echo OK' 'ERR OK'
# A failure that is not reached because of short-circuit
# evaluation should not cause an exit, however.
dcheck '(set -e; true || false; echo OK); echo OK' 'OK OK'
echeck '(set -e; true || false; echo OK); echo OK' 'OK OK'
# For completeness, test the other two combinations.
dcheck '(set -e; true || true; echo OK); echo OK' 'OK OK'
dcheck '(set -e; true && true; echo OK); echo OK' 'OK OK'
echeck '(set -e; true || true; echo OK); echo OK' 'OK OK'
echeck '(set -e; true && true; echo OK); echo OK' 'OK OK'
# likewise, none of these should exit.
dcheck '(set -e; while false; do :; done; echo OK); echo OK' 'OK OK'
dcheck '(set -e; if false; then :; fi; echo OK); echo OK' 'OK OK'
# problematic :-)
#dcheck '(set -e; until false; do :; done; echo OK); echo OK' 'OK OK'
dcheck '(set -e; until [ "$t" = 1 ]; do t=1; done; echo OK); echo OK' \
'OK OK'
echeck '(set -e; while false; do :; done; echo OK); echo OK' 'OK OK'
echeck '(set -e; if false; then :; fi; echo OK); echo OK' 'OK OK'
echeck '(set -e; until [ "$t" = 1 ]; do t=1; done; echo OK); echo OK' \
'OK OK'
# the bang operator tests its argument and thus the argument
# should not cause an exit. it is also not a simple command (I
# believe) so it also shouldn't exit even if it yields a false
# result.
dcheck '(set -e; ! false; echo OK); echo OK' 'OK OK'
dcheck '(set -e; ! true; echo OK); echo OK' 'OK OK'
echeck '(set -e; ! false; echo OK); echo OK' 'OK OK'
echeck '(set -e; ! true; echo OK); echo OK' 'OK OK'
# combined case with () and &&; the inner expression is false
# but does not itself exit, and the () should not cause an
# exit even when failing.
# correct:
#dcheck '(set -e; (false && true); echo OK); echo OK' 'OK OK'
#echeck '(set -e; (false && true); echo OK); echo OK' 'OK OK'
# wrong current behavior:
dcheck '(set -e; (false && true); echo OK); echo OK' 'OK'
echeck '(set -e; (false && true); echo OK); echo OK' 'OK'
# pipelines. only the right-hand end is significant.
dcheck '(set -e; false | true; echo OK); echo OK' 'OK OK'
echeck '(set -e; false | true; echo OK); echo OK' 'OK OK'
dcheck '(set -e; true | false; echo ERR); echo OK' 'OK'
echeck '(set -e; true | false; echo ERR); echo OK' 'OK'
dcheck '(set -e; while true | false; do :; done; echo OK); echo OK' \
'OK OK'
dcheck '(set -e; if true | false; then :; fi; echo OK); echo OK' \
'OK OK'
# According to dsl@ in PR bin/32282, () is not defined as a
# subshell, only as a grouping operator [and a scope, I guess]
# (This is incorrect. () is definitely a sub-shell)
# so the nested false ought to cause the whole shell to exit,
# not just the subshell. dholland@ would like to see C&V,
# because that seems like a bad idea. (Among other things, it
# would break all the above test logic, which relies on being
# able to isolate set -e behavior inside ().) However, I'm
# going to put these tests here to make sure the issue gets
# dealt with sometime.
#
# XXX: the second set has been disabled in the name of making
# all tests "pass".
#
# As they should be, they are utter nonsense.
# 1. error if the whole shell exits (current correct behavior)
dcheck 'echo OK; (set -e; false); echo OK' 'OK OK'
echeck 'echo OK; (set -e; false); echo OK' 'OK OK'
# 2. error if the whole shell does not exit (dsl's suggested behavior)
#dcheck 'echo OK; (set -e; false); echo ERR' 'OK'
#echeck 'echo OK; (set -e; false); echo ERR' 'OK'
# The current behavior of the shell is that it exits out as
# far as -e is set and then stops. This is probably a
# consequence of it handling () wrong, but it's a somewhat
# curious compromise position between 1. and 2. above.
dcheck '(set -e; (false; echo ERR); echo ERR); echo OK' 'OK'
echeck '(set -e; (false; echo ERR); echo ERR); echo OK' 'OK'
# backquote expansion (PR bin/17514)
# (in-)correct
#dcheck '(set -e; echo ERR `false`; echo ERR); echo OK' 'OK'
#dcheck '(set -e; echo ERR $(false); echo ERR); echo OK' 'OK'
#dcheck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'OK'
#dcheck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'OK'
# Not-wrong current behavior
# the exit status of ommand substitution is ignored in most cases
# None of these should be causing the shell to exit.
dcheck '(set -e; echo ERR `false`; echo ERR); echo OK' 'ERR ERR OK'
dcheck '(set -e; echo ERR $(false); echo ERR); echo OK' 'ERR ERR OK'
dcheck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'ERR ERR OK'
dcheck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'ERR ERR OK'
# This is testing one case (the case?) where the exit status is used
dcheck '(set -e; x=`false`; echo ERR); echo OK' 'OK'
dcheck '(set -e; x=$(false); echo ERR); echo OK' 'OK'
dcheck '(set -e; x=`exit 3`; echo ERR); echo OK' 'OK'
dcheck '(set -e; x=$(exit 3); echo ERR); echo OK' 'OK'
# correct (really just commented out incorrect nonsense)
#echeck '(set -e; echo ERR `false`; echo ERR); echo OK' 'OK'
#echeck '(set -e; echo ERR $(false); echo ERR); echo OK' 'OK'
#echeck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'OK'
#echeck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'OK'
# not-wrong current behavior (as above)
echeck '(set -e; echo ERR `false`; echo ERR); echo OK' 'ERR ERR OK'
echeck '(set -e; echo ERR $(false); echo ERR); echo OK' 'ERR ERR OK'
echeck '(set -e; echo ERR `exit 3`; echo ERR); echo OK' 'ERR ERR OK'
echeck '(set -e; echo ERR $(exit 3); echo ERR); echo OK' 'ERR ERR OK'
echeck '(set -e; x=`false`; echo ERR); echo OK' 'OK'
echeck '(set -e; x=$(false); echo ERR); echo OK' 'OK'
echeck '(set -e; x=`exit 3`; echo ERR); echo OK' 'OK'
echeck '(set -e; x=$(exit 3); echo ERR); echo OK' 'OK'
# shift (PR bin/37493)
# correct
# Actually, both ways are correct, both are permitted
#dcheck '(set -e; shift || true; echo OK); echo OK' 'OK OK'
#echeck '(set -e; shift || true; echo OK); echo OK' 'OK OK'
# (not-) wrong current behavior
#dcheck '(set -e; shift || true; echo OK); echo OK' 'OK'
#echeck '(set -e; shift || true; echo OK); echo OK' 'OK'
# what is wrong is this test assuming one behaviour or the other
# (and incidentally this has nothing whatever to do with "-e",
# the test should really be moved elsewhere...)
# But for now, leave it here, and correct it:
dcheck '(set -e; shift && echo OK); echo OK' 'OK'
echeck '(set -e; shift && echo OK); echo OK' 'OK'
# Done.
if [ "x$SH_FAILS" != x ]; then
printf '%-56s %-8s %s\n' "Expression" "Result" "Should be"
echo "$SH_FAILS"
atf_fail "$failcount of $count failed cases"
else
atf_pass
fi
}
atf_init_test_cases() {
atf_add_test_case all
}