blob: c0762f532d2a3c174500803a42324b457d1ff61b [file]
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Run the objtool klp tests. Each test-*.sh prints one TAP result line.
#
# Tests live in generic/ and in a directory per architecture. A run executes
# generic/ plus the one matching this architecture, so a test which cannot
# apply here is not run rather than reporting a skip; what was left out is
# reported once, as a comment, so differing coverage is still visible.
#
# A run covers one compiler and one architecture; CI runs the combinations.
# The harness checks the environment once up front and fails the run if the
# suite cannot execute, rather than letting every test skip and exit 0.
set -u
# Determinism: the order test-*.sh expands in, and how grep's character ranges
# and sort's collation behave inside the tests, are all locale-dependent. A
# suite whose results depend on the invoking shell's locale is a suite whose
# failures cannot be reproduced.
export LC_ALL=C
usage()
{
cat <<EOF
usage: $(basename "$0") [-k|--keep] [test...]
Run the objtool klp tests for this architecture: everything in generic/, plus
everything in the directory named for it. With no arguments, runs all of them.
A test may be named with or without its "test-" prefix and ".sh" suffix, and is
looked for in both directories.
Options:
-k, --keep same as KEEP=all (see below)
Environment:
OBJTOOL objtool binary to test (default ../objtool)
CC compiler used to build fixtures (default gcc)
ARCH architecture the tests are for (default: uname -m)
KEEP failed keep only failing tests (default)
all keep every test's working directory
none remove all working directories
A test which needs something of its own says so in its skip message.
EOF
exit "${1:-0}"
}
cd "$(dirname "$0")" || exit 1
keep_from_args=
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage ;;
-k|--keep) keep_from_args=all; shift ;;
--) shift; break ;;
-*) echo "unknown option: $1" >&2; usage 1 ;;
*) break ;;
esac
done
KLP_TEST_KEEP="${KEEP:-failed}"
[ -n "$keep_from_args" ] && KLP_TEST_KEEP="$keep_from_args"
case "$KLP_TEST_KEEP" in
all|none|failed) ;;
*)
echo "invalid KEEP=$KLP_TEST_KEEP (want failed, all, or none)" >&2
exit 1
;;
esac
export KLP_TEST_KEEP
echo "TAP version 13"
# Sourcing the harness runs its preflight, which decides which architecture
# this run is for -- so the test list cannot be built before it has, and the
# tests inherit the answers rather than working them out again.
. ./lib.sh
dirs=( generic )
[ -d "$KLP_TEST_ARCH" ] && dirs+=( "$KLP_TEST_ARCH" )
if [ $# -gt 0 ]; then
tests=()
for arg in "$@"; do
name="test-${arg#test-}"; name="${name%.sh}.sh"
found=
for d in "${dirs[@]}"; do
[ -f "$d/$name" ] || continue
[ -x "$d/$name" ] ||
{ echo "not executable: $d/$name" >&2; exit 1; }
tests+=( "$d/$name" ); found=y
done
[ -n "$found" ] ||
{ echo "no such test for $KLP_TEST_ARCH: $arg" >&2; exit 1; }
done
else
tests=()
for d in "${dirs[@]}"; do
for t in "$d"/test-*.sh; do
[ -f "$t" ] && tests+=( "$t" )
done
done
[ "${#tests[@]}" -gt 0 ] ||
{ echo "1..0 # SKIP no tests found"; exit 0; }
# Tests for another architecture are absent from this run entirely. Say
# how many, so a run which covers less than the tree holds does not look
# like one that covers all of it.
for d in */; do
d="${d%/}"
case "$d" in generic|"$KLP_TEST_ARCH") continue ;; esac
n=$(ls "$d"/test-*.sh 2>/dev/null | wc -l)
[ "$n" -gt 0 ] || continue
echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \
"(this run is $KLP_TEST_ARCH)"
done
fi
# One directory for the whole run, one per test inside it, mirroring the
# source layout. A run then leaves a single thing behind instead of 39
# scattered among everything else using mktemp.
rundir="$(mktemp -d "${TMPDIR:-/tmp}/klp-tests.XXXXXXXX")" ||
{ echo "Bail out! cannot create a working directory" >&2; exit 1; }
# The run's own two levels: each test's directory, and the one per source
# directory holding them. Take them away if the tests left them empty, and
# say so if they did not. Either rmdir may fail -- the glob stays unexpanded
# when nothing was created -- so ask the directory itself rather than trusting
# the status. Never rm -rf: what to keep is the tests' decision, made in
# cleanup() as each one exits, and this must not overrule it.
reap_rundir()
{
rmdir "$rundir"/*/ 2>/dev/null
rmdir "$rundir" 2>/dev/null
[ -d "$rundir" ]
}
# An interrupted run has the same directory to answer for, and the tests it
# never reached will not clean up on their way out. The one it was running
# has, and under the default it kept what it had built, so say where.
interrupted()
{
reap_rundir && echo "# interrupted; what was built is in $rundir"
exit 130
}
trap interrupted INT TERM HUP
echo "1..${#tests[@]}"
pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0
failed_dirs=()
for t in "${tests[@]}"; do
out="$(KLP_TEST_WORKDIR="$rundir/${t%.sh}" ./"$t" 2>&1)"
rc=$?
# A test prints one result line, but it is not necessarily the only
# thing it prints: objtool warns on stderr, and the runner captures
# that. Classify the result line itself rather than the whole of the
# output, or a stray line ahead of it makes every pattern below miss and
# the exit status decide -- which would count an expected failure, which
# exits 0, as a pass.
result="$(printf '%s\n' "$out" | grep -E '^(ok|not ok)' | tail -1)"
rest="$(printf '%s\n' "$out" | grep -Ev '^(ok|not ok)')"
# Classify from the result line, not the exit status: a skip and a pass
# both exit 0, and telling them apart is the point of counting.
#
# The two skip kinds differ in what they promise. A static skip was
# declared before the test ran ("clang does not do this"), so it is
# expected indefinitely. A probe skip means the construct did not turn
# up this time, which is weaker and worth watching: one that becomes
# permanent is a fixture that quietly stopped testing anything.
case "$result" in
*"# SKIP (declared)"*) static_skip=$((static_skip + 1)) ;;
*"# SKIP (probe)"*) probe_skip=$((probe_skip + 1)) ;;
*"# SKIP"*)
# An undeclared skip: the test gave up for a reason it never
# said it might. That is a hole, not an expected outcome.
#
# Replace the line rather than adding one. Every test owes the
# plan exactly one result, and a consumer counting them is
# entitled to say so when the totals disagree.
rest="$rest${rest:+$'\n'}was: $result"
result="not ok - $(basename "$t" .sh): undeclared skip"
result="$result (use gcc_only/clang_only or require_input_*)"
fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
"not ok"*"# TODO"*) xfail=$((xfail + 1)) ;;
"ok"*"# TODO"*) xpass=$((xpass + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
"not ok"*) fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
"ok"*) pass=$((pass + 1)) ;;
*)
# No result line at all: the test died before reporting.
rest="$rest${rest:+$'\n'}exited $rc without a result line"
result="not ok - $(basename "$t" .sh): no TAP result"
fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
esac
echo "$result"
[ -n "$rest" ] && printf '%s\n' "$rest" | sed 's/^[^#]/# &/'
done
echo "# pass:$pass fail:$fail static-skip:$static_skip" \
"probe-skip:$probe_skip xfail:$xfail xpass:$xpass"
case "$KLP_TEST_KEEP" in
all)
echo "# keep=all: workdirs kept in $rundir"
echo "# inspect: diff.log, readelf -S out.o under each test-* subdirectory"
echo "# cleanup: rm -rf $rundir"
;;
failed)
if [ "${#failed_dirs[@]}" -gt 0 ]; then
echo "# keep=failed: ${#failed_dirs[@]} failing test(s) kept under $rundir:"
for d in "${failed_dirs[@]}"; do
echo "# ${d#"$rundir"/}/"
done
echo "# inspect: diff.log readelf -S out.o"
echo "# one test: $PWD/run-tests.sh <name>"
echo "# cleanup: rm -rf $rundir"
elif reap_rundir; then
echo "# $rundir was not empty;" \
"a test did not clean up after itself"
fi
;;
none)
if reap_rundir; then
echo "# $rundir was not empty;" \
"a test did not clean up after itself"
elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then
echo "# keep=none: artifacts were removed" \
"(re-run with KEEP=failed or KEEP=all)"
fi
;;
esac
[ "$fail" = 0 ] && [ "$xpass" = 0 ]