2023-05-10 22:12:01 +03:00
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck shell=bash
if [ [ " ${ BASH_SOURCE [0] } " -ef " $0 " ] ] ; then
echo >& 2 "This file should not be executed directly"
exit 1
fi
2023-05-22 14:24:12 +03:00
declare -i _CHILD_PID = 0
_PASSED_TESTS = ( )
_FAILED_TESTS = ( )
2023-05-10 22:12:01 +03:00
# Like trap, but passes the signal name as the first argument
2023-05-22 14:24:12 +03:00
_trap_with_sig( ) {
2023-05-10 22:12:01 +03:00
local fun = " ${ 1 : ? } "
local sig
shift
for sig in " $@ " ; do
# shellcheck disable=SC2064
trap " $fun $sig " " $sig "
done
}
# Propagate the caught signal to the current child process
2023-05-22 14:24:12 +03:00
_handle_signal( ) {
2023-05-10 22:12:01 +03:00
local sig = " ${ 1 : ? } "
2023-05-22 14:24:12 +03:00
if [ [ $_CHILD_PID -gt 0 ] ] ; then
echo " Propagating signal $sig to child process $_CHILD_PID "
kill -s " $sig " " $_CHILD_PID "
2023-05-10 22:12:01 +03:00
fi
}
2023-05-22 14:24:12 +03:00
# In order to make the _handle_signal() stuff above work, we have to execute
2023-05-10 22:12:01 +03:00
# each script asynchronously, since bash won't execute traps until the currently
# executed command finishes. This, however, introduces another issue regarding
# how bash's wait works. Quoting:
#
# When bash is waiting for an asynchronous command via the wait builtin,
# the reception of a signal for which a trap has been set will cause the wait
# builtin to return immediately with an exit status greater than 128,
# immediately after which the trap is executed.
#
# In other words - every time we propagate a signal, wait returns with
# 128+signal, so we have to wait again - repeat until the process dies.
2023-05-22 14:24:12 +03:00
_wait_harder( ) {
2023-05-10 22:12:01 +03:00
local pid = " ${ 1 : ? } "
while kill -0 " $pid " & >/dev/null; do
wait " $pid " || :
done
wait " $pid "
}
2023-05-22 14:24:12 +03:00
_show_summary( ) { (
set +x
if [ [ ${# _PASSED_TESTS [@] } -eq 0 && ${# _FAILED_TESTS [@] } -eq 0 ] ] ; then
echo >& 2 "No tests were executed, this is most likely an error"
exit 1
fi
printf "PASSED TESTS: %3d:\n" " ${# _PASSED_TESTS [@] } "
echo "------------------"
for t in " ${ _PASSED_TESTS [@] } " ; do
echo " $t "
done
if [ [ " ${# _FAILED_TESTS [@] } " -ne 0 ] ] ; then
printf "FAILED TESTS: %3d:\n" " ${# _FAILED_TESTS [@] } "
echo "------------------"
for t in " ${ _FAILED_TESTS [@] } " ; do
echo " $t "
done
fi
[ [ " ${# _FAILED_TESTS [@] } " -eq 0 ] ]
) }
2023-05-10 22:12:01 +03:00
# Like run_subtests, but propagate specified signals to the subtest script
run_subtests_with_signals( ) {
local subtests = ( " ${ 0 %.sh } " .*.sh)
local subtest
if [ [ " ${# subtests [@] } " -eq 0 ] ] ; then
echo >& 2 " No subtests found for file $0 "
exit 1
fi
if [ [ " $# " -eq 0 ] ] ; then
echo >& 2 "No signals to propagate were specified"
exit 1
fi
2023-05-22 14:24:12 +03:00
_trap_with_sig _handle_signal " $@ "
2023-05-10 22:12:01 +03:00
for subtest in " ${ subtests [@] } " ; do
: " --- $subtest BEGIN --- "
" ./ $subtest " &
2023-05-22 14:24:12 +03:00
_CHILD_PID = $!
_wait_harder " $_CHILD_PID " && _PASSED_TESTS += ( " $subtest " ) || _FAILED_TESTS += ( " $subtest " )
2023-05-10 22:12:01 +03:00
: " --- $subtest END --- "
done
2023-05-22 14:24:12 +03:00
_show_summary
2023-05-10 22:12:01 +03:00
}
2023-05-22 13:39:25 +03:00
# Run all subtests (i.e. files named as testsuite-<testid>.<subtest_name>.sh)
2023-05-10 22:12:01 +03:00
run_subtests( ) {
local subtests = ( " ${ 0 %.sh } " .*.sh)
local subtest
if [ [ " ${# subtests [@] } " -eq 0 ] ] ; then
echo >& 2 " No subtests found for file $0 "
exit 1
fi
for subtest in " ${ subtests [@] } " ; do
: " --- $subtest BEGIN --- "
2023-05-22 14:24:12 +03:00
" ./ $subtest " && _PASSED_TESTS += ( " $subtest " ) || _FAILED_TESTS += ( " $subtest " )
2023-05-10 22:12:01 +03:00
: " --- $subtest END --- "
done
2023-05-22 14:24:12 +03:00
_show_summary
2023-05-10 22:12:01 +03:00
}
2023-05-22 13:39:25 +03:00
# Run all test cases (i.e. functions prefixed with testcase_ in the current namespace)
run_testcases( ) {
local testcase testcases
# Create a list of all functions prefixed with testcase_
mapfile -t testcases < <( declare -F | awk '$3 ~ /^testcase_/ {print $3;}' )
if [ [ " ${# testcases [@] } " -eq 0 ] ] ; then
echo >& 2 "No test cases found, this is most likely an error"
exit 1
fi
for testcase in " ${ testcases [@] } " ; do
: " +++ $testcase BEGIN +++ "
# Note: the subshell here is used purposefully, otherwise we might
# unexpectedly inherit a RETURN trap handler from the called
# function and call it for the second time once we return,
# causing a "double-free"
( " $testcase " )
: " +++ $testcase END +++ "
done
}