The integration tests are started with the absolute path of the test unit as $0, so run_subtests() derives absolute paths for the subtests and the summary prints them in full. Show just the script name, the directory is the same for every entry and only adds noise. Signed-off-by: Paul Meyer <katexochen0@gmail.com>
236 lines
6.7 KiB
Bash
236 lines
6.7 KiB
Bash
# 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
|
|
|
|
declare -i _CHILD_PID=0
|
|
_PASSED_TESTS=()
|
|
_SKIPPED_TESTS=()
|
|
|
|
# A subtest may exit with this code to report that it skipped itself,
|
|
# matching the skip code used by the integration test harness.
|
|
_SUBTEST_SKIP_RC=77
|
|
|
|
# Like trap, but passes the signal name as the first argument
|
|
_trap_with_sig() {
|
|
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
|
|
_handle_signal() {
|
|
local sig="${1:?}"
|
|
|
|
if [[ $_CHILD_PID -gt 0 ]]; then
|
|
echo "Propagating signal $sig to child process $_CHILD_PID"
|
|
kill -s "$sig" "$_CHILD_PID"
|
|
fi
|
|
}
|
|
|
|
# In order to make the _handle_signal() stuff above work, we have to execute
|
|
# 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.
|
|
_wait_harder() {
|
|
local pid="${1:?}"
|
|
|
|
while kill -0 "$pid" &>/dev/null; do
|
|
wait "$pid" || :
|
|
done
|
|
|
|
wait "$pid"
|
|
}
|
|
|
|
_show_summary() {(
|
|
set +x
|
|
|
|
if [[ ${#_PASSED_TESTS[@]} -eq 0 && ${#_SKIPPED_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 [[ ${#_SKIPPED_TESTS[@]} -gt 0 ]]; then
|
|
printf "SKIPPED TESTS: %3d:\n" "${#_SKIPPED_TESTS[@]}"
|
|
echo "-------------------"
|
|
for t in "${_SKIPPED_TESTS[@]}"; do
|
|
echo "$t"
|
|
done
|
|
fi
|
|
)}
|
|
|
|
_record_subtest_rc() {
|
|
local subtest="${1:?}" rc="${2:?}"
|
|
|
|
subtest="${subtest##*/}"
|
|
|
|
if [[ $rc -eq $_SUBTEST_SKIP_RC ]]; then
|
|
echo "Subtest $subtest skipped"
|
|
_SKIPPED_TESTS+=("$subtest")
|
|
elif [[ $rc -ne 0 ]]; then
|
|
echo "Subtest $subtest failed"
|
|
return 1
|
|
else
|
|
_PASSED_TESTS+=("$subtest")
|
|
fi
|
|
}
|
|
|
|
# Like run_subtests, but propagate specified signals to the subtest script
|
|
run_subtests_with_signals() {
|
|
local subtests=("${0%.sh}".*.sh)
|
|
local subtest rc
|
|
|
|
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
|
|
|
|
_trap_with_sig _handle_signal "$@"
|
|
|
|
for subtest in "${subtests[@]}"; do
|
|
if [[ -n "${TEST_MATCH_SUBTEST:-}" ]] && ! [[ "$subtest" =~ $TEST_MATCH_SUBTEST ]]; then
|
|
echo "Skipping $subtest (not matching '$TEST_MATCH_SUBTEST')"
|
|
continue
|
|
fi
|
|
|
|
for skip in ${TEST_SKIP_SUBTESTS:-}; do
|
|
if [[ "$subtest" =~ $skip ]]; then
|
|
echo "Skipping $subtest (matching '$skip')"
|
|
continue 2
|
|
fi
|
|
done
|
|
|
|
: "--- $subtest BEGIN ---"
|
|
SECONDS=0
|
|
rc=0
|
|
"./$subtest" &
|
|
_CHILD_PID=$!
|
|
_wait_harder "$_CHILD_PID" || rc=$?
|
|
_record_subtest_rc "$subtest" "$rc" || return 1
|
|
: "--- $subtest END (${SECONDS}s) ---"
|
|
done
|
|
|
|
_show_summary
|
|
}
|
|
|
|
# Run all subtests (i.e. files named as $TESTNAME.<subtest_name>.sh)
|
|
run_subtests() {
|
|
local subtests=("${0%.sh}".*.sh)
|
|
local subtest rc
|
|
|
|
if [[ "${#subtests[@]}" -eq 0 ]]; then
|
|
echo >&2 "No subtests found for file $0"
|
|
exit 1
|
|
fi
|
|
|
|
for subtest in "${subtests[@]}"; do
|
|
if [[ -n "${TEST_MATCH_SUBTEST:-}" ]] && ! [[ "$subtest" =~ $TEST_MATCH_SUBTEST ]]; then
|
|
echo "Skipping $subtest (not matching '$TEST_MATCH_SUBTEST')"
|
|
continue
|
|
fi
|
|
|
|
for skip in ${TEST_SKIP_SUBTESTS:-}; do
|
|
if [[ "$subtest" =~ $skip ]]; then
|
|
echo "Skipping $subtest (matching '$skip')"
|
|
continue 2
|
|
fi
|
|
done
|
|
|
|
: "--- $subtest BEGIN ---"
|
|
SECONDS=0
|
|
rc=0
|
|
"./$subtest" || rc=$?
|
|
_record_subtest_rc "$subtest" "$rc" || return 1
|
|
: "--- $subtest END (${SECONDS}s) ---"
|
|
done
|
|
|
|
_show_summary
|
|
}
|
|
|
|
_finalize_subtests() {
|
|
if [[ ${#_PASSED_TESTS[@]} -eq 0 && ${#_SKIPPED_TESTS[@]} -gt 0 ]]; then
|
|
echo "All subtests skipped" | tee --append /skipped
|
|
exit "$_SUBTEST_SKIP_RC"
|
|
fi
|
|
|
|
touch /testok
|
|
exit 0
|
|
}
|
|
|
|
# Run all subtests and finalize the test in one shot: exit 77 (skipped) if every subtest skipped,
|
|
# otherwise mark success (/testok) and exit. Use this ONLY for tests whose body is just subtests.
|
|
# Do NOT use it if the parent script has meaningful test content of its own.
|
|
run_subtests_and_exit() {
|
|
run_subtests
|
|
_finalize_subtests
|
|
}
|
|
|
|
# Like run_subtests_and_exit, but propagates the given signals to the subtests (see
|
|
# run_subtests_with_signals).
|
|
run_subtests_with_signals_and_exit() {
|
|
run_subtests_with_signals "$@"
|
|
_finalize_subtests
|
|
}
|
|
|
|
# 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
|
|
if [[ -n "${TEST_MATCH_TESTCASE:-}" ]] && ! [[ "$testcase" =~ $TEST_MATCH_TESTCASE ]]; then
|
|
echo "Skipping $testcase (not matching '$TEST_MATCH_TESTCASE')"
|
|
continue
|
|
fi
|
|
|
|
for skip in ${TEST_SKIP_TESTCASES:-}; do
|
|
if [[ "$testcase" =~ $skip ]]; then
|
|
echo "Skipping $testcase (matching '$skip')"
|
|
continue 2
|
|
fi
|
|
done
|
|
|
|
: "+++ $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
|
|
}
|