serval-dna/testframework.sh
Andrew Bettison 824763f5f0 Improve test framework
error() returns 254 if it does not exit (ie, during teardown)
2012-06-04 18:28:38 +09:30

879 lines
23 KiB
Bash

#!/bin/bash
#
# Serval Project testing framework for Bash shell
# Copyright 2012 Paul Gardner-Stephen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# This file is sourced by all testing scripts. A typical test script looks
# like this:
#
# #!/bin/bash
# source testframework.sh
# setup() {
# export BLAH_CONFIG=$TFWTMP/blah.conf
# echo "username=$LOGNAME" >$BLAH_CONFIG
# }
# teardown() {
# # $TFWTMP is always removed after every test, so no need to
# # remove blah.conf ourselves.
# }
# doc_feature1='Feature one works'
# test_feature1() {
# execute programUnderTest --feature1 arg1 arg2
# assertExitStatus '==' 0
# assertRealTime --message='ran in under half a second' '<=' 0.5
# assertStdoutIs ""
# assertStderrIs ""
# }
# doc_feature2='Feature two fails with status 1'
# setup_feature2() {
# # Overrides setup(), so we have to call it ourselves explicitly
# # here if we still want it.
# setup
# echo "option=specialValue" >>$BLAH_CONFIG
# }
# test_feature2() {
# execute programUnderTest --feature2 arg1 arg2
# assertExitStatus '==' 1
# assertStdoutIs -e "Response:\tok\n"
# assertStderrGrep "^ERROR: missing arg3$"
# }
# runTests "$@"
usage() {
echo "Usage: ${0##*/} [-t|--trace] [-v|--verbose] [--filter=prefix] [--]"
}
runTests() {
_tfw_stdout=1
_tfw_stderr=2
_tfw_checkBashVersion
_tfw_trace=false
_tfw_verbose=false
_tfw_invoking_script=$(abspath "${BASH_SOURCE[1]}")
_tfw_suite_name="${_tfw_invoking_script##*/}"
_tfw_cwd=$(abspath "$PWD")
_tfw_logfile="$_tfw_cwd/test.$_tfw_suite_name.log"
local allargs="$*"
local filter=
while [ $# -ne 0 ]; do
case "$1" in
--help) usage; exit 0;;
-t|--trace) _tfw_trace=true;;
-v|--verbose) _tfw_verbose=true;;
--filter=*) filter="${1#*=}";;
--) shift; break;;
--*) _tfw_fatal "unsupported option: $1";;
*) _tfw_fatal "spurious argument: $1";;
esac
shift
# Kick off the log file.
done
{
date
echo "$0 $allargs"
} >$_tfw_logfile
# Iterate through all test cases.
local testcount=0
local passcount=0
local testName
for testName in `_tfw_find_tests`
do
_tfw_test_name="$testName"
if [ -z "$filter" -o "${_tfw_test_name#$filter}" != "$_tfw_test_name" ]; then
let testcount=testcount+1
(
local docvar="doc_$_tfw_test_name"
_tfw_echo -n "$testcount. ${!docvar:-$_tfw_test_name}..."
trap '_tfw_status=$?; _tfw_teardown; exit $_tfw_status' 0 1 2 15
_tfw_result=ERROR
_tfw_setup
_tfw_result=FAIL
_tfw_phase=testcase
echo "# call test_$_tfw_test_name()"
$_tfw_trace && set -x
test_$_tfw_test_name
_tfw_result=PASS
exit 0
)
local stat=$?
case $stat in
255) exit 255;; # _tfw_fatal was called
254) _tfw_echo " ERROR";; # _tfw_failexit was called in setup or teardown or _tfw_error was called anywhere
0) _tfw_echo " PASS"; let passcount=passcount+1;;
*) _tfw_echo " FAIL";;
esac
fi
done
s=$([ $testcount -eq 1 ] || echo s)
_tfw_echo "$testcount test$s, $passcount passed"
[ $passcount -eq $testcount ]
}
# The following functions can be overridden by a test script to provide a
# default fixture for all test cases.
setup() {
:
}
teardown() {
:
}
# The following functions are provided to facilitate writing test cases and
# fixtures.
# Echo the absolute path (containing symlinks if given) of the given
# file/directory, which does not have to exist or even be accessible.
abspath() {
_tfw_abspath -L "$1"
}
# Echo the absolute path (resolving all symlinks) of the given file/directory,
# which does not have to exist or even be accessible.
realpath() {
_tfw_abspath -P "$1"
}
execute() {
echo -n "# execute "; _tfw_shellarg "$@"
_tfw_getopts execute "$@"
shift $_tfw_getopts_shift
_tfw_execute "$@"
}
executeOk() {
echo -n "# executeOk "; _tfw_shellarg "$@"
_tfw_getopts executeok "$@"
_tfw_opt_exit_status=0
_tfw_dump_on_fail --stderr
shift $_tfw_getopts_shift
_tfw_execute "$@"
}
assert() {
_tfw_getopts assert "$@"
shift $_tfw_getopts_shift
_tfw_assert "$@" || _tfw_failexit
echo -n "# assert "; _tfw_shellarg "$@"
return 0
}
assertExpr() {
_tfw_getopts assertexpr "$@"
shift $_tfw_getopts_shift
local awkexpr=$(_tfw_expr_to_awkexpr "$@")
_tfw_message="${_tfw_message+$_tfw_message }($awkexpr)"
_tfw_assert _tfw_eval_awkexpr "$awkexpr" || _tfw_failexit
echo -n "# assertExpr "; _tfw_shellarg "$awkexpr"
return 0
}
fail() {
_tfw_getopts fail "$@"
shift $_tfw_getopts_shift
[ $# -ne 0 ] && _tfw_failmsg "$1"
_tfw_backtrace
_tfw_failexit
}
error() {
_tfw_getopts error "$@"
shift $_tfw_getopts_shift
[ $# -ne 0 ] && _tfw_errormsg "$1"
_tfw_backtrace
_tfw_errorexit
}
fatal() {
[ $# -eq 0 ] && set -- "no reason given"
_tfw_fatalmsg "$@"
_tfw_backtrace
_tfw_fatalexit
}
tfw_cat() {
local header=
local show_nonprinting=
for file; do
case $file in
--stdout)
echo "#--- ${header:-stdout of ${_tfw_execute_argv0##*/}} ---"
cat $show_nonprinting $_tfw_tmp/stdout
echo "#---"
header=
show_nonprinting=
;;
--stderr)
echo "#--- ${header:-stderr of ${_tfw_execute_argv0##*/}} ---"
cat $show_nonprinting $_tfw_tmp/stderr
echo "#---"
header=
show_nonprinting=
;;
--header=*) header="${1#*=}";;
-v|--show-nonprinting) show_nonprinting=-v;;
*)
echo "#--- ${header:-$file} ---"
cat $show_nonprinting "$file"
echo "#---"
header=
show_nonprinting=
;;
esac
done
}
assertExitStatus() {
_tfw_getopts assertexitstatus "$@"
shift $_tfw_getopts_shift
[ -z "$_tfw_message" ] && _tfw_message="exit status of ${_tfw_execute_argv0##*/} ($_tfw_exitStatus) $*"
_tfw_assertExpr "$_tfw_exitStatus" "$@" || _tfw_failexit
echo "# assert $_tfw_message"
return 0
}
assertRealTime() {
_tfw_getopts assertrealtime "$@"
shift $_tfw_getopts_shift
[ -z "$_tfw_message" ] && _tfw_message="real execution time of ${_tfw_execute_argv0##*/} ($realtime) $*"
_tfw_assertExpr "$realtime" "$@" || _tfw_failexit
echo "# assert $_tfw_message"
return 0
}
replayStdout() {
cat $_tfw_tmp/stdout
}
replayStderr() {
cat $_tfw_tmp/stderr
}
assertStdoutIs() {
_tfw_assert_stdxxx_is stdout "$@" || _tfw_failexit
}
assertStderrIs() {
_tfw_assert_stdxxx_is stderr "$@" || _tfw_failexit
}
assertStdoutLineCount() {
_tfw_assert_stdxxx_linecount stdout "$@" || _tfw_failexit
}
assertStderrLineCount() {
_tfw_assert_stdxxx_linecount stderr "$@" || _tfw_failexit
}
assertStdoutGrep() {
_tfw_assert_stdxxx_grep stdout "$@" || _tfw_failexit
}
assertStderrGrep() {
_tfw_assert_stdxxx_grep stderr "$@" || _tfw_failexit
}
assertGrep() {
_tfw_getopts assertgrep "$@"
shift $_tfw_getopts_shift
if [ $# -ne 2 ]; then
_tfw_error "incorrect arguments"
return 254
fi
_tfw_dump_on_fail "$1"
_tfw_assert_grep "$1" "$1" "$2" || _tfw_failexit
}
# Internal (private) functions that are not to be invoked directly from test
# scripts.
# Utility for setting shopt variables and restoring their original value:
# _tfw_shopt -s extglob -u extdebug
# ...
# _tfw_shopt_restore
_tfw_shopt() {
if [ -n "$_tfw_shopt_orig" ]; then
_tfw_fatal "unrestored shopt settings: $_tfw_shopt_orig"
fi
_tfw_shopt_orig=
local op=s
while [ $# -ne 0 ]
do
case "$1" in
-s) op=s;;
-u) op=u;;
*)
local opt="$1"
_tfw_shopt_orig="${restore:+$restore; }shopt -$(shopt -q $opt && echo s || echo u) $opt"
shopt -$op $opt
;;
esac
shift
done
}
_tfw_shopt_restore() {
if [ -n "$_tfw_shopt_orig" ]; then
eval "$_tfw_shopt_orig"
_tfw_shopt_orig=
fi
}
# The rest of this file is parsed for extended glob patterns.
_tfw_shopt -s extglob
# Add shell quotation to the given arguments, so that when expanded using
# 'eval', the exact same argument results. This makes argument handling fully
# immune to spaces and shell metacharacters.
_tfw_shellarg() {
local arg
local -a shellarg=()
_tfw_shopt -s extglob
for arg; do
case "$arg" in
+([A-Za-z_0-9.,:=+\/-])) shellarg+=("$arg");;
*) shellarg+=("'${arg//'/'\\''}'");;
esac
done
_tfw_shopt_restore
echo "${shellarg[@]}"
}
# Echo the absolute path of the given path, using only Bash builtins.
_tfw_abspath() {
cdopt=-L
if [ $# -gt 1 -a "${1:0:1}" = - ]; then
cdopt="$1"
shift
fi
case "$1" in
*/)
builtin echo $(_tfw_abspath $cdopt "${1%/}")/
;;
/*/*)
if [ -d "$1" ]; then
(CDPATH= builtin cd $cdopt "$1" && builtin echo "$PWD")
else
builtin echo $(_tfw_abspath $cdopt "${1%/*}")/"${1##*/}"
fi
;;
/*)
echo "$1"
;;
*/*)
if [ -d "$1" ]; then
(CDPATH= builtin cd $cdopt "$1" && builtin echo "$PWD")
else
builtin echo $(_tfw_abspath $cdopt "${1%/*}")/"${1##*/}"
fi
;;
. | ..)
(CDPATH= builtin cd $cdopt "$1" && builtin echo "$PWD")
;;
*)
(CDPATH= builtin cd $cdopt . && builtin echo "$PWD/$1")
;;
esac
}
_tfw_setup() {
_tfw_phase=setup
_tfw_tmp=/tmp/_tfw-$$
mkdir $_tfw_tmp
exec <&- 5>&1 5>&2 >$_tfw_tmp/log.stdout 2>$_tfw_tmp/log.stderr 6>$_tfw_tmp/log.xtrace
BASH_XTRACEFD=6
_tfw_stdout=5
_tfw_stderr=5
if $_tfw_verbose; then
# These tail processes will die when the test case's subshell exits.
tail --pid=$$ --follow $_tfw_tmp/log.stdout >&$_tfw_stdout 2>/dev/null &
tail --pid=$$ --follow $_tfw_tmp/log.stderr >&$_tfw_stderr 2>/dev/null &
fi
export TFWTMP=$_tfw_tmp/tmp
mkdir $TFWTMP
cd $TFWTMP
echo '# SETUP'
case `type -t setup_$_tfw_test_name` in
function)
echo "# call setup_$_tfw_test_name()"
$_tfw_trace && set -x
setup_$_tfw_test_name $_tfw_test_name
set +x
;;
*)
echo "# call setup($_tfw_test_name)"
$_tfw_trace && set -x
setup $_tfw_test_name
set +x
;;
esac
echo '# END SETUP'
}
_tfw_teardown() {
_tfw_phase=teardown
echo '# TEARDOWN'
case `type -t teardown_$_tfw_test_name` in
function)
echo "# call teardown_$_tfw_test_name()"
$_tfw_trace && set -x
teardown_$_tfw_test_name
set +x
;;
*)
echo "# call teardown($_tfw_test_name)"
$_tfw_trace && set -x
teardown $_tfw_test_name
set +x
;;
esac
echo '# END TEARDOWN'
{
local banner="==================== $_tfw_test_name ===================="
echo "$banner"
echo "TEST RESULT: $_tfw_result"
echo '++++++++++ log.stdout ++++++++++'
cat $_tfw_tmp/log.stdout
echo '++++++++++'
echo '++++++++++ log.stderr ++++++++++'
cat $_tfw_tmp/log.stderr
echo '++++++++++'
if $_tfw_trace; then
echo '++++++++++ log.xtrace ++++++++++'
cat $_tfw_tmp/log.xtrace
echo '++++++++++'
fi
echo "${banner//[^=]/=}"
} >>$_tfw_logfile
rm -rf $_tfw_tmp
}
_tfw_execute() {
_tfw_execute_argv0="$1"
{ time -p "$@" >$_tfw_tmp/stdout 2>$_tfw_tmp/stderr ; } 2>$_tfw_tmp/times
_tfw_exitStatus=$?
# Deal with exit status.
if [ -n "$_tfw_opt_exit_status" ]; then
_tfw_message="exit status of ${_tfw_execute_argv0##*/} ($_tfw_exitStatus) is $_tfw_opt_exit_status"
_tfw_dump_stderr_on_fail=true
_tfw_assert [ "$_tfw_exitStatus" -eq "$_tfw_opt_exit_status" ] || _tfw_failexit
echo "# assert $_tfw_message"
else
echo "# exit status of ${_tfw_execute_argv0##*/} = $_tfw_exitStatus"
fi
# Parse execution time report.
if ! _tfw_parse_times_to_milliseconds real realtime_ms ||
! _tfw_parse_times_to_milliseconds user usertime_ms ||
! _tfw_parse_times_to_milliseconds sys systime_ms
then
echo '# malformed output from time:'
tfw_cat --header=times -v $_tfw_tmp/times
fi
return 0
}
_tfw_parse_times_to_milliseconds() {
local label="$1"
local var="$2"
local milliseconds=$(awk '$1 == "'"$label"'" {
value = $2
minutes = 0
if (match(value, "[0-9]+m")) {
minutes = substr(value, RSTART, RLENGTH - 1)
value = substr(value, 1, RSTART - 1) substr(value, RSTART + RLENGTH)
}
if (substr(value, length(value)) == "s") {
value = substr(value, 1, length(value) - 1)
}
if (match(value, "^[0-9]+(\.[0-9]+)?$")) {
seconds = value + 0
print (minutes * 60 + seconds) * 1000
}
}' $_tfw_tmp/times)
[ -z "$milliseconds" ] && return 1
[ -n "$var" ] && eval $var=$milliseconds
return 0
}
_tfw_assert() {
if ! "$@"; then
_tfw_failmsg "assertion failed: ${_tfw_message:-$*}"
_tfw_backtrace
return 1
fi
return 0
}
declare -a _tfw_opt_dump_on_fail
_tfw_dump_on_fail() {
for arg; do
local _found=false
local _f
for _f in "${_tfw_opt_dump_on_fail[@]}"; do
if [ "$_f" = "$arg" ]; then
_found=true
break
fi
done
$_found || _tfw_opt_dump_on_fail+=("$arg")
done
}
_tfw_getopts() {
local context="$1"
shift
_tfw_message=
_tfw_opt_dump_on_fail=()
_tfw_opt_error_on_fail=false
_tfw_opt_exit_status=
_tfw_opt_matches=
_tfw_opt_line=
_tfw_getopts_shift=0
while [ $# -ne 0 ]; do
case "$context:$1" in
*:--stdout) _tfw_dump_on_fail --stdout;;
*:--stderr) _tfw_dump_on_fail --stderr;;
assert*:--dump-on-fail=*) _tfw_dump_on_fail "${1#*=}";;
execute:--exit-status=*) _tfw_opt_exit_status="${1#*=}";;
assert*:--error-on-fail) _tfw_opt_error_on_fail=true;;
assert*:--message=*) _tfw_message="${1#*=}";;
assertgrep:--matches=*) _tfw_opt_matches="${1#*=}";;
assertfilecontent:--line=*) _tfw_opt_line="${1#*=}";;
*:--) let _tfw_getopts_shift=_tfw_getopts_shift+1; shift; break;;
*:--*) _tfw_error "unsupported option: $1";;
*) break;;
esac
let _tfw_getopts_shift=_tfw_getopts_shift+1
shift
done
}
_tfw_expr_to_awkexpr() {
local awkexpr=
for arg; do
if [ -z "${arg//[0-9]}" ]; then
awkexpr="${awkexpr:+$awkexpr }$arg"
else
case $arg in
'==' | '!=' | '<' | '<=' | '>' | '>=' | \
'~' | '!~' | '&&' | '||' | '!' )
awkexpr="${awkexpr:+$awkexpr }$arg"
;;
*)
arg=${arg//\\/\\\\} #} restore Vim syntax highlighting
arg=${arg//"/\\"}
awkexpr="${awkexpr:+$awkexpr }\"$arg\""
;;
esac
fi
done
echo $awkexpr
}
_tfw_eval_awkexpr() {
local awkerrs # on separate line so we don't lose exit status
awkerrs=$(awk "BEGIN { exit(($*) ? 0 : 1) }" </dev/null 2>&1)
local stat=$?
if [ -n "$awkerrs" ]; then
_tfw_error "invalid expression: $*"
stat=254
fi
return $stat
}
_tfw_assertExpr() {
local awkexpr=$(_tfw_expr_to_awkexpr "$@")
_tfw_assert _tfw_eval_awkexpr "$awkexpr" || _tfw_failexit
}
_tfw_assert_stdxxx_is() {
local qual="$1"
shift
_tfw_getopts assertfilecontent --$qual "$@"
shift $((_tfw_getopts_shift - 1))
if [ $# -lt 1 ]; then
_tfw_error "incorrect arguments"
return 254
fi
_tfw_shopt -s extglob
case "$_tfw_opt_line" in
+([0-9]))
sed -n -e "${_tfw_opt_line}p" "$_tfw_tmp/$qual" >"$_tfw_tmp/content"
;;
'')
ln -f "$_tfw_tmp/$qual" "$_tfw_tmp/content"
;;
*)
_tfw_error "unsupported value for --line=$_tfw_opt_line"
_tfw_backtrace
_tfw_shopt_restore
return 254
;;
esac
_tfw_shopt_restore
local message="${_tfw_message:-${_tfw_opt_line:+line $_tfw_opt_line of }$qual of ${_tfw_execute_argv0##*/} is $*}"
echo -n "$@" >$_tfw_tmp/stdxxx_is.tmp
if ! cmp --quiet $_tfw_tmp/stdxxx_is.tmp "$_tfw_tmp/content"; then
_tfw_failmsg "assertion failed: $message"
_tfw_backtrace
return 1
fi
echo "# assert $message"
return 0
}
_tfw_assert_stdxxx_linecount() {
local qual="$1"
shift
_tfw_getopts assertfilecontent --$qual "$@"
shift $((_tfw_getopts_shift - 1))
if [ $# -lt 1 ]; then
_tfw_error "incorrect arguments"
return 254
fi
local lineCount=$(( $(cat $_tfw_tmp/$qual | wc -l) + 0 ))
[ -z "$_tfw_message" ] && _tfw_message="$qual line count ($lineCount) $*"
_tfw_assertExpr "$lineCount" "$@" || _tfw_failexit
echo "# assert $_tfw_message"
return 0
}
_tfw_assert_stdxxx_grep() {
local qual="$1"
shift
_tfw_getopts assertgrep --$qual "$@"
shift $((_tfw_getopts_shift - 1))
if [ $# -ne 1 ]; then
_tfw_error "incorrect arguments"
return 254
fi
_tfw_assert_grep "$qual of ${_tfw_execute_argv0##*/}" $_tfw_tmp/$qual "$@"
}
_tfw_assert_grep() {
local label="$1"
local file="$2"
local pattern="$3"
local message=
local matches=$(( $(grep --regexp="$pattern" "$file" | wc -l) + 0 ))
local done=false
local ret=0
_tfw_shopt -s extglob
case "$_tfw_opt_matches" in
'')
done=true
message="${_tfw_message:-$label contains a line matching \"$pattern\"}"
if [ $matches -ne 0 ]; then
echo "# assert $message"
else
_tfw_failmsg "assertion failed: $message"
ret=1
fi
;;
esac
case "$_tfw_opt_matches" in
+([0-9]))
done=true
local s=$([ $_tfw_opt_matches -ne 1 ] && echo s)
message="${_tfw_message:-$label contains exactly $_tfw_opt_matches line$s matching \"$pattern\"}"
if [ $matches -eq $_tfw_opt_matches ]; then
echo "# assert $message"
else
_tfw_failmsg "assertion failed: $message"
ret=1
fi
;;
esac
case "$_tfw_opt_matches" in
+([0-9])-*([0-9]))
done=true
local bound=${_tfw_opt_matches%-*}
local s=$([ $bound -ne 1 ] && echo s)
message="${_tfw_message:-$label contains at least $bound line$s matching \"$pattern\"}"
if [ $matches -ge $bound ]; then
echo "# assert $message"
else
_tfw_failmsg "assertion failed: $message"
ret=1
fi
;;
esac
case "$_tfw_opt_matches" in
*([0-9])-+([0-9]))
done=true
local bound=${_tfw_opt_matches#*-}
local s=$([ $bound -ne 1 ] && echo s)
message="${_tfw_message:-$label contains at most $bound line$s matching \"$pattern\"}"
if [ $matches -le $bound ]; then
echo "# assert $message"
else
_tfw_failmsg "assertion failed: $message"
ret=1
fi
;;
esac
if ! $done; then
_tfw_error "unsupported value for --matches=$_tfw_opt_matches"
ret=254
fi
_tfw_shopt_restore
if [ $ret -ne 0 ]; then
_tfw_backtrace
fi
return $ret
}
# Write to the real stdout of the test script.
_tfw_echo() {
echo "$@" >&$_tfw_stdout
}
# Write a message to the real stderr of the test script, so the user sees it
# immediately. Also write the message to the test log, so it can be recovered
# later.
_tfw_echoerr() {
echo "$@" >&$_tfw_stderr
if [ $_tfw_stderr -ne 2 ]; then
echo "$@" >&2
fi
}
_tfw_checkBashVersion() {
[ -z "$BASH_VERSION" ] && _tfw_fatal "not running in Bash (/bin/bash) shell"
if [ -n "${BASH_VERSINFO[*]}" ]; then
[ ${BASH_VERSINFO[0]} -gt 3 ] && return 0
if [ ${BASH_VERSINFO[0]} -eq 3 ]; then
[ ${BASH_VERSINFO[1]} -gt 2 ] && return 0
if [ ${BASH_VERSINFO[1]} -eq 2 ]; then
[ ${BASH_VERSINFO[2]} -ge 48 ] && return 0
fi
fi
fi
_tfw_fatal "unsupported Bash version: $BASH_VERSION"
}
# Return a list of test names in the order that the test_TestName functions were
# defined.
_tfw_find_tests() {
_tfw_shopt -s extdebug
builtin declare -F |
sed -n -e '/^declare -f test_./s/^declare -f test_//p' |
while read name; do builtin declare -F "test_$name"; done |
sort --key 2,2n --key 3,3 |
sed -e 's/^test_//' -e 's/[ ].*//'
_tfw_shopt_restore
}
# A "fail" event occurs when any assertion fails, and indicates that the test
# has not passed. Other tests may still proceed. A "fail" event during setup
# or teardown is treated as an error, not a failure.
_tfw_failmsg() {
# A failure during setup or teardown is treated as an error.
case $_tfw_phase in
testcase)
if ! $_tfw_opt_error_on_fail; then
echo "FAIL: $*"
return 0;
fi
;;
esac
echo "ERROR: $*"
}
_tfw_backtrace() {
echo '#--- backtrace ---'
local -i up=1
while [ "${BASH_SOURCE[$up]}" == "${BASH_SOURCE[0]}" ]; do
let up=up+1
done
local -i i=0
while [ $up -lt ${#FUNCNAME[*]} -a "${BASH_SOURCE[$up]}" != "${BASH_SOURCE[0]}" ]; do
echo "[$i] ${FUNCNAME[$(($up-1))]}() called from ${FUNCNAME[$up]}() at line ${BASH_LINENO[$(($up-1))]} of ${BASH_SOURCE[$up]}"
let up=up+1
let i=i+1
done
echo '#---'
}
_tfw_failexit() {
# When exiting a test case due to a failure, log any diagnostic output that
# has been requested.
tfw_cat "${_tfw_opt_dump_on_fail[@]}"
# A failure during setup or teardown is treated as an error.
case $_tfw_phase in
testcase)
if ! $_tfw_opt_error_on_fail; then
exit 1
fi
;;
esac
_tfw_errorexit
}
# An "error" event prevents a test from running, so it neither passes nor fails.
# Other tests may still proceed.
_tfw_errormsg() {
[ $# -eq 0 ] && set -- "(no message)"
local -i up=1
while true; do
case ${FUNCNAME[$up]} in
_tfw_*) let up=up+1;;
*) break;;
esac
done
echo "ERROR in ${FUNCNAME[$up]}: $*"
}
_tfw_error() {
_tfw_errormsg "ERROR: $*"
_tfw_backtrace
_tfw_errorexit
}
_tfw_errorexit() {
# Do not exit process during teardown
_tfw_result=ERROR
case $_tfw_phase in
teardown) [ $_tfw_status -lt 254 ] && _tfw_status=254;;
*) exit 254;;
esac
return 254
}
# A "fatal" event stops the entire test run, and generally indicates an
# insurmountable problem in the test script or in the test framework itself.
_tfw_fatalmsg() {
_tfw_echoerr "${BASH_SOURCE[1]}: FATAL: $*"
}
_tfw_fatal() {
[ $# -eq 0 ] && set -- exiting
_tfw_echoerr "${BASH_SOURCE[1]}: FATAL: $*"
_tfw_fatalexit
}
_tfw_fatalexit() {
exit 255
}
# Restore the caller's shopt preferences before returning.
_tfw_shopt_restore