mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-01-18 18:56:25 +00:00
Improve test framework: orderly shutdown on signal
Uses bash job control internally instead of PIDs, because job control allows all processes in a job to be killed. Changes the way _tfw_shopt/_tfw_shopt_restore work, to avoid "unrestored shopt" errors on interrupt.
This commit is contained in:
parent
e1fb64e9d4
commit
3c736b1f6c
110
testframework.sh
110
testframework.sh
@ -75,15 +75,15 @@ Options:
|
|||||||
|
|
||||||
# Internal utility for setting shopt variables and restoring their original
|
# Internal utility for setting shopt variables and restoring their original
|
||||||
# value:
|
# value:
|
||||||
# _tfw_shopt -s extglob -u extdebug
|
# local oo
|
||||||
|
# _tfw_shopt oo -s extglob -u extdebug
|
||||||
# ...
|
# ...
|
||||||
# _tfw_shopt_restore
|
# _tfw_shopt_restore oo
|
||||||
_tfw_shopt() {
|
_tfw_shopt() {
|
||||||
if [ -n "$_tfw_shopt_orig" ]; then
|
local _var="$1"
|
||||||
_tfw_fatal "unrestored shopt settings: $_tfw_shopt_orig"
|
shift
|
||||||
fi
|
|
||||||
_tfw_shopt_orig=
|
|
||||||
local op=s
|
local op=s
|
||||||
|
local restore=
|
||||||
while [ $# -ne 0 ]
|
while [ $# -ne 0 ]
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@ -91,24 +91,23 @@ _tfw_shopt() {
|
|||||||
-u) op=u;;
|
-u) op=u;;
|
||||||
*)
|
*)
|
||||||
local opt="$1"
|
local opt="$1"
|
||||||
_tfw_shopt_orig="${restore:+$restore; }shopt -$(shopt -q $opt && echo s || echo u) $opt"
|
restore="${restore:+$restore; }shopt -$(shopt -q $opt && echo s || echo u) $opt"
|
||||||
shopt -$op $opt
|
shopt -$op $opt
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
eval $_var='"$restore"'
|
||||||
}
|
}
|
||||||
_tfw_shopt_restore() {
|
_tfw_shopt_restore() {
|
||||||
if [ -n "$_tfw_shopt_orig" ]; then
|
local _var="$1"
|
||||||
eval "$_tfw_shopt_orig"
|
[ -n "${!_var}" ] && eval "${!_var}"
|
||||||
_tfw_shopt_orig=
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
_tfw_shopt_orig=
|
|
||||||
declare -a _tfw_running_pids
|
declare -a _tfw_running_jobs
|
||||||
|
|
||||||
# The rest of this file is parsed for extended glob patterns.
|
# The rest of this file is parsed for extended glob patterns.
|
||||||
_tfw_shopt -s extglob
|
_tfw_shopt _tfw_orig_shopt -s extglob
|
||||||
|
|
||||||
runTests() {
|
runTests() {
|
||||||
_tfw_stdout=1
|
_tfw_stdout=1
|
||||||
@ -119,7 +118,7 @@ runTests() {
|
|||||||
_tfw_suite_name="${_tfw_invoking_script##*/}"
|
_tfw_suite_name="${_tfw_invoking_script##*/}"
|
||||||
_tfw_cwd=$(abspath "$PWD")
|
_tfw_cwd=$(abspath "$PWD")
|
||||||
_tfw_tmpdir="${TFW_TMPDIR:-${TMPDIR:-/tmp}}/_tfw-$$"
|
_tfw_tmpdir="${TFW_TMPDIR:-${TMPDIR:-/tmp}}/_tfw-$$"
|
||||||
trap '_tfw_status=$?; rm -rf "$_tfw_tmpdir"; exit $_tfw_status' EXIT SIGHUP SIGINT SIGTERM
|
trap '_tfw_status=$?; _tfw_killtests; rm -rf "$_tfw_tmpdir"; exit $_tfw_status' EXIT SIGHUP SIGINT SIGTERM
|
||||||
rm -rf "$_tfw_tmpdir"
|
rm -rf "$_tfw_tmpdir"
|
||||||
mkdir -p "$_tfw_tmpdir" || return $?
|
mkdir -p "$_tfw_tmpdir" || return $?
|
||||||
_tfw_logdir="${TFW_LOGDIR:-$_tfw_cwd/testlog}/$_tfw_suite_name"
|
_tfw_logdir="${TFW_LOGDIR:-$_tfw_cwd/testlog}/$_tfw_suite_name"
|
||||||
@ -131,7 +130,8 @@ runTests() {
|
|||||||
local allargs="$*"
|
local allargs="$*"
|
||||||
local -a filters=()
|
local -a filters=()
|
||||||
local njobs=1
|
local njobs=1
|
||||||
_tfw_shopt -s extglob
|
local oo
|
||||||
|
_tfw_shopt oo -s extglob
|
||||||
while [ $# -ne 0 ]; do
|
while [ $# -ne 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--help) usage; exit 0;;
|
--help) usage; exit 0;;
|
||||||
@ -149,7 +149,7 @@ runTests() {
|
|||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
_tfw_shopt_restore
|
_tfw_shopt_restore oo
|
||||||
if $_tfw_verbose && [ $njobs -ne 1 ]; then
|
if $_tfw_verbose && [ $njobs -ne 1 ]; then
|
||||||
_tfw_fatal "--verbose is incompatible with --jobs=$njobs"
|
_tfw_fatal "--verbose is incompatible with --jobs=$njobs"
|
||||||
fi
|
fi
|
||||||
@ -161,6 +161,8 @@ runTests() {
|
|||||||
rm -f "$_tfw_logdir"/*
|
rm -f "$_tfw_logdir"/*
|
||||||
# Enumerate all the test cases.
|
# Enumerate all the test cases.
|
||||||
_tfw_find_tests "${filters[@]}"
|
_tfw_find_tests "${filters[@]}"
|
||||||
|
# Enable job control.
|
||||||
|
set -m
|
||||||
# Iterate through all test cases, starting a new test whenever the number of
|
# Iterate through all test cases, starting a new test whenever the number of
|
||||||
# running tests is less than the job limit.
|
# running tests is less than the job limit.
|
||||||
_tfw_testcount=0
|
_tfw_testcount=0
|
||||||
@ -168,7 +170,7 @@ runTests() {
|
|||||||
_tfw_failcount=0
|
_tfw_failcount=0
|
||||||
_tfw_errorcount=0
|
_tfw_errorcount=0
|
||||||
_tfw_fatalcount=0
|
_tfw_fatalcount=0
|
||||||
_tfw_running_pids=()
|
_tfw_running_jobs=()
|
||||||
_tfw_test_number_watermark=0
|
_tfw_test_number_watermark=0
|
||||||
local testNumber
|
local testNumber
|
||||||
local testPosition=0
|
local testPosition=0
|
||||||
@ -178,7 +180,7 @@ runTests() {
|
|||||||
let ++testPosition
|
let ++testPosition
|
||||||
let ++_tfw_testcount
|
let ++_tfw_testcount
|
||||||
# Wait for any existing child process to finish.
|
# Wait for any existing child process to finish.
|
||||||
while [ $njobs -ne 0 -a ${#_tfw_running_pids[*]} -ge $njobs ]; do
|
while [ $njobs -ne 0 -a ${#_tfw_running_jobs[*]} -ge $njobs ]; do
|
||||||
_tfw_harvest_processes
|
_tfw_harvest_processes
|
||||||
done
|
done
|
||||||
[ $_tfw_fatalcount -ne 0 ] && break
|
[ $_tfw_fatalcount -ne 0 ] && break
|
||||||
@ -251,12 +253,13 @@ runTests() {
|
|||||||
fi
|
fi
|
||||||
} >"$_tfw_logdir/$testNumber.$testName.$result"
|
} >"$_tfw_logdir/$testNumber.$testName.$result"
|
||||||
exit 0
|
exit 0
|
||||||
) &
|
) </dev/null &
|
||||||
_tfw_running_pids+=($!)
|
local job=$(jobs %% | sed -n -e '1s/^\[\([0-9]\+\)\].*/\1/p')
|
||||||
ln -s "$_tfw_results_dir/$testName" "$_tfw_results_dir/pid-$!"
|
_tfw_running_jobs+=($job)
|
||||||
|
ln -f -s "$_tfw_results_dir/$testName" "$_tfw_results_dir/job-$job"
|
||||||
done
|
done
|
||||||
# Wait for all child processes to finish.
|
# Wait for all child processes to finish.
|
||||||
while [ ${#_tfw_running_pids[*]} -ne 0 ]; do
|
while [ ${#_tfw_running_jobs[*]} -ne 0 ]; do
|
||||||
_tfw_harvest_processes
|
_tfw_harvest_processes
|
||||||
done
|
done
|
||||||
# Clean up working directory.
|
# Clean up working directory.
|
||||||
@ -268,6 +271,22 @@ runTests() {
|
|||||||
[ $_tfw_fatalcount -eq 0 -a $_tfw_failcount -eq 0 -a $_tfw_errorcount -eq 0 ]
|
[ $_tfw_fatalcount -eq 0 -a $_tfw_failcount -eq 0 -a $_tfw_errorcount -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_tfw_killtests() {
|
||||||
|
if [ $njobs -eq 1 ]; then
|
||||||
|
echo -n " killing..."
|
||||||
|
else
|
||||||
|
echo -n -e "\r\rKilling tests...\r"
|
||||||
|
fi
|
||||||
|
trap '' SIGHUP SIGINT SIGTERM
|
||||||
|
local job
|
||||||
|
for job in ${_tfw_running_jobs[*]}; do
|
||||||
|
kill %$job 2>/dev/null
|
||||||
|
done
|
||||||
|
while [ ${#_tfw_running_jobs[*]} -ne 0 ]; do
|
||||||
|
_tfw_harvest_processes
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
_tfw_echo_intro() {
|
_tfw_echo_intro() {
|
||||||
local docvar="doc_$3"
|
local docvar="doc_$3"
|
||||||
echo -n "$2. ${!docvar:-$3}..."
|
echo -n "$2. ${!docvar:-$3}..."
|
||||||
@ -282,16 +301,16 @@ _tfw_harvest_processes() {
|
|||||||
sleep 1 &
|
sleep 1 &
|
||||||
spid=$!
|
spid=$!
|
||||||
set -m
|
set -m
|
||||||
wait $spid 2>/dev/null
|
wait $spid >/dev/null 2>/dev/null
|
||||||
trap - SIGCHLD
|
trap - SIGCHLD
|
||||||
# </incantation>
|
# </incantation>
|
||||||
local -a surviving_pids=()
|
local -a surviving_jobs=()
|
||||||
local pid
|
local job
|
||||||
for pid in ${_tfw_running_pids[*]}; do
|
for job in ${_tfw_running_jobs[*]}; do
|
||||||
if kill -0 $pid 2>/dev/null; then
|
if jobs %$job >/dev/null 2>/dev/null; then
|
||||||
surviving_pids+=($pid)
|
surviving_jobs+=($job)
|
||||||
elif [ -s "$_tfw_results_dir/pid-$pid" ]; then
|
elif [ -s "$_tfw_results_dir/job-$job" ]; then
|
||||||
set -- $(<"$_tfw_results_dir/pid-$pid")
|
set -- $(<"$_tfw_results_dir/job-$job")
|
||||||
local testPosition="$1"
|
local testPosition="$1"
|
||||||
local testNumber="$2"
|
local testNumber="$2"
|
||||||
local testName="$3"
|
local testName="$3"
|
||||||
@ -331,11 +350,13 @@ _tfw_harvest_processes() {
|
|||||||
_tfw_echo_result "$result"
|
_tfw_echo_result "$result"
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
rm -f "$_tfw_results_dir/job-$job"
|
||||||
else
|
else
|
||||||
_tfw_echoerr "${BASH_SOURCE[1]}: child process $pid terminated without result"
|
_tfw_echoerr "${BASH_SOURCE[1]}: job %$job terminated without result"
|
||||||
|
rm -f "$_tfw_results_dir/job-$job"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
_tfw_running_pids=(${surviving_pids[*]})
|
_tfw_running_jobs=(${surviving_jobs[*]})
|
||||||
}
|
}
|
||||||
|
|
||||||
_tfw_echo_result() {
|
_tfw_echo_result() {
|
||||||
@ -649,14 +670,12 @@ assertGrep() {
|
|||||||
_tfw_shellarg() {
|
_tfw_shellarg() {
|
||||||
local arg
|
local arg
|
||||||
_tfw_args=()
|
_tfw_args=()
|
||||||
_tfw_shopt -s extglob
|
|
||||||
for arg; do
|
for arg; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
+([A-Za-z_0-9.,:=+\/-])) _tfw_args+=("$arg");;
|
'' | *[^A-Za-z_0-9.,:=+\/-]* ) _tfw_args+=("'${arg//'/'\\''}'");;
|
||||||
*) _tfw_args+=("'${arg//'/'\\''}'");;
|
*) _tfw_args+=("$arg");;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
_tfw_shopt_restore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Echo the absolute path of the given path, using only Bash builtins.
|
# Echo the absolute path of the given path, using only Bash builtins.
|
||||||
@ -855,7 +874,8 @@ _tfw_getopts() {
|
|||||||
_tfw_opt_matches=
|
_tfw_opt_matches=
|
||||||
_tfw_opt_line=
|
_tfw_opt_line=
|
||||||
_tfw_getopts_shift=0
|
_tfw_getopts_shift=0
|
||||||
_tfw_shopt -s extglob
|
local oo
|
||||||
|
_tfw_shopt oo -s extglob
|
||||||
while [ $# -ne 0 ]; do
|
while [ $# -ne 0 ]; do
|
||||||
case "$context:$1" in
|
case "$context:$1" in
|
||||||
*:--stdout) _tfw_dump_on_fail --stdout;;
|
*:--stdout) _tfw_dump_on_fail --stdout;;
|
||||||
@ -893,7 +913,7 @@ _tfw_getopts() {
|
|||||||
[ -z "$_tfw_executable" ] && _tfw_error "missing executable argument"
|
[ -z "$_tfw_executable" ] && _tfw_error "missing executable argument"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
_tfw_shopt_restore
|
_tfw_shopt_restore oo
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1019,7 +1039,8 @@ _tfw_assert_grep() {
|
|||||||
local matches=$(( $(grep --regexp="$pattern" "$file" | wc -l) + 0 ))
|
local matches=$(( $(grep --regexp="$pattern" "$file" | wc -l) + 0 ))
|
||||||
local done=false
|
local done=false
|
||||||
local ret=0
|
local ret=0
|
||||||
_tfw_shopt -s extglob
|
local oo
|
||||||
|
_tfw_shopt oo -s extglob
|
||||||
case "$_tfw_opt_matches" in
|
case "$_tfw_opt_matches" in
|
||||||
'')
|
'')
|
||||||
done=true
|
done=true
|
||||||
@ -1077,7 +1098,7 @@ _tfw_assert_grep() {
|
|||||||
_tfw_error "unsupported value for --matches=$_tfw_opt_matches"
|
_tfw_error "unsupported value for --matches=$_tfw_opt_matches"
|
||||||
ret=$?
|
ret=$?
|
||||||
fi
|
fi
|
||||||
_tfw_shopt_restore
|
_tfw_shopt_restore oo
|
||||||
fi
|
fi
|
||||||
if [ $ret -ne 0 ]; then
|
if [ $ret -ne 0 ]; then
|
||||||
_tfw_backtrace
|
_tfw_backtrace
|
||||||
@ -1121,7 +1142,8 @@ _tfw_checkTerminfo() {
|
|||||||
# an alphabetic character (not numeric or '_').
|
# an alphabetic character (not numeric or '_').
|
||||||
_tfw_find_tests() {
|
_tfw_find_tests() {
|
||||||
_tfw_tests=()
|
_tfw_tests=()
|
||||||
_tfw_shopt -s extdebug
|
local oo
|
||||||
|
_tfw_shopt oo -s extdebug
|
||||||
local name
|
local name
|
||||||
for name in $(builtin declare -F |
|
for name in $(builtin declare -F |
|
||||||
sed -n -e '/^declare -f test_[A-Za-z]/s/^declare -f test_//p' |
|
sed -n -e '/^declare -f test_[A-Za-z]/s/^declare -f test_//p' |
|
||||||
@ -1188,7 +1210,7 @@ _tfw_find_tests() {
|
|||||||
fi
|
fi
|
||||||
_tfw_tests+=("$testName")
|
_tfw_tests+=("$testName")
|
||||||
done
|
done
|
||||||
_tfw_shopt_restore
|
_tfw_shopt_restore oo
|
||||||
}
|
}
|
||||||
|
|
||||||
# A "fail" event occurs when any assertion fails, and indicates that the test
|
# A "fail" event occurs when any assertion fails, and indicates that the test
|
||||||
@ -1286,4 +1308,4 @@ _tfw_fatalexit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Restore the caller's shopt preferences before returning.
|
# Restore the caller's shopt preferences before returning.
|
||||||
_tfw_shopt_restore
|
_tfw_shopt_restore _tfw_orig_shopt
|
||||||
|
Loading…
Reference in New Issue
Block a user