From: Song Liu <song@kernel.org>
To: live-patching@vger.kernel.org
Cc: jpoimboe@kernel.org, peterz@infradead.org, jikos@kernel.org,
mbenes@suse.cz, pmladek@suse.com, joe.lawrence@redhat.com,
puranjay@kernel.org, kernel-team@meta.com,
Song Liu <song@kernel.org>
Subject: [PATCH 04/58] objtool/klp: Classify klp test outcomes
Date: Fri, 11 Sep 2026 11:42:11 -0700 [thread overview]
Message-ID: <20260911184305.1457308-5-song@kernel.org> (raw)
In-Reply-To: <20260911184305.1457308-1-song@kernel.org>
A test could pass, fail, or skip, and the runner reported only the exit
status of the last one to fail. Skips are the interesting case: they are
how a suite quietly stops testing anything.
Three kinds, and the runner counts them separately:
declared the test said in advance it does not apply here, e.g. gcc_only
on a clang run. Expected indefinitely.
probe the construct did not turn up in the built object this time.
Weaker: one which becomes permanent is a fixture that stopped
testing anything.
undeclared counted as a failure. A test which gives up for a reason it
never declared is a hole, not an outcome.
xfail and xpass come with them, so a known failure is reported rather than
commented out and forgotten, and one which starts passing says so instead
of going quietly green.
The result line is what gets classified, not the whole of a test's output:
objtool warns on stderr and the runner captures it, and a stray line ahead
of the result would otherwise leave the exit status to decide -- counting
an expected failure, which exits 0, as a pass.
Every test owes the plan exactly one result line, so a substituted verdict
replaces the test's own rather than being printed beside it, and a test
which dies before reporting gets a result of its own instead of being
inferred from its exit status.
Also: a TAP version line, LC_ALL=C so the order tests run in and the
behaviour of grep's character ranges inside them do not depend on the
invoking shell's locale, and fail() on stdout with every other verdict.
Co-developed-by: Puranjay Mohan <puranjay@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Assisted-by: Claude:claude-opus-5
Signed-off-by: Song Liu <song@kernel.org>
---
tools/objtool/tests/lib.sh | 67 ++++++++++++--
tools/objtool/tests/run-tests.sh | 148 ++++++++++++++++++++++++++-----
2 files changed, 186 insertions(+), 29 deletions(-)
diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh
index 7eb6860cfcad..7185198253bb 100644
--- a/tools/objtool/tests/lib.sh
+++ b/tools/objtool/tests/lib.sh
@@ -133,8 +133,38 @@ test_name="$(basename "$0" .sh)"
workdir=
pass() { echo "ok - $test_name${1:+: $1}"; exit 0; }
-fail() { echo "not ok - $test_name: $1" >&2; exit 1; }
-skip() { echo "ok - $test_name # SKIP $1"; exit 0; }
+fail() { echo "not ok - $test_name: $1"; exit 1; }
+
+# Two kinds of skip, and the runner tells them apart.
+#
+# declared_skip the test said in advance it does not apply here, e.g.
+# gcc_only on a clang run. Expected indefinitely.
+# probe_skip the construct did not turn up in the built object this
+# time. Weaker: it may appear on another compiler version,
+# and one which becomes permanent is a fixture that quietly
+# stopped testing anything.
+#
+# A bare skip() is neither, and the runner counts it as a failure: a test which
+# gives up for a reason it never declared is a hole, not an outcome.
+declared_skip() { echo "ok - $test_name # SKIP (declared) $1"; exit 0; }
+probe_skip() { echo "ok - $test_name # SKIP (probe) $1"; exit 0; }
+skip() { echo "ok - $test_name # SKIP $1"; exit 0; }
+
+# TAP directives. A test which is known to fail reports it rather than being
+# commented out and forgotten, and one which starts passing again says so
+# instead of quietly going green: the expectation has to be removed by hand,
+# which is the point.
+xfail()
+{
+ echo "not ok - $test_name${1:+: $1} # TODO known failure"
+ exit 0
+}
+
+xpass()
+{
+ echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed"
+ exit 1
+}
cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; }
@@ -164,6 +194,23 @@ export_syms()
done
}
+# gcc_only / clang_only <reason>
+gcc_only()
+{
+ case "$($CC --version 2>/dev/null | head -1)" in
+ *[Gg][Cc][Cc]*) return 0 ;;
+ esac
+ declared_skip "gcc only${1:+: $1}"
+}
+
+clang_only()
+{
+ case "$($CC --version 2>/dev/null | head -1)" in
+ *clang*) return 0 ;;
+ esac
+ declared_skip "clang only${1:+: $1}"
+}
+
# build_pair <fixture.c> [cflags...]
build_pair()
{
@@ -172,9 +219,9 @@ build_pair()
[ -f "$fixture" ] || fail "missing fixture $fixture"
$CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" ||
- skip "fixture does not build here: $(tail -1 "$workdir/cc.log")"
+ probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")"
$CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" ||
- skip "fixture does not build here: $(tail -1 "$workdir/cc.log")"
+ probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")"
}
# run_diff [expected exit status]
@@ -220,16 +267,20 @@ partial_link()
# find_thinlto_toolchain
#
-# Set $THIN_CC and $THIN_LD to a clang and lld from the same LLVM release. A
+# Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC). A
# mismatched pair fails with "Invalid summary version", which reads like a
# broken test rather than a broken environment.
+#
+# ThinLTO is clang-only; callers must use clang_only before calling this.
+# Only $CC (or an explicit THIN_CC override) is consulted -- the harness does
+# not search for a second compiler beside a gcc $CC.
find_thinlto_toolchain()
{
- local cc ld ver
+ local cc ver ld
- for cc in "${THIN_CC:-}" "$CC" clang; do
+ for cc in "${THIN_CC:-}" "$CC"; do
[ -n "$cc" ] || continue
- command -v "${cc%% *}" >/dev/null 2>&1 || continue
+ command -v "${cc%% *}" >/dev/null 2>&1 || return 1
ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1)
diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh
index 48728a98d995..6b937fc7f5bd 100755
--- a/tools/objtool/tests/run-tests.sh
+++ b/tools/objtool/tests/run-tests.sh
@@ -8,13 +8,51 @@
# 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") [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.
+
+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)
+
+A test which needs something of its own says so in its skip message.
+EOF
+ exit "${1:-0}"
+}
+
cd "$(dirname "$0")" || exit 1
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help) usage ;;
+ --) shift; break ;;
+ -*) echo "unknown option: $1" >&2; usage 1 ;;
+ *) break ;;
+ esac
+done
+
+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.
@@ -23,31 +61,99 @@ cd "$(dirname "$0")" || exit 1
dirs=( generic )
[ -d "$KLP_TEST_ARCH" ] && dirs+=( "$KLP_TEST_ARCH" )
-tests=()
-for d in "${dirs[@]}"; do
- for t in "$d"/test-*.sh; do
- [ -f "$t" ] && tests+=( "$t" )
+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
-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
+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
echo "1..${#tests[@]}"
-rc=0
+pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0
+
for t in "${tests[@]}"; do
- ./"$t" || rc=1
+ out="$(./"$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)) ;;
+ "not ok"*"# TODO"*) xfail=$((xfail + 1)) ;;
+ "ok"*"# TODO"*) xpass=$((xpass + 1)) ;;
+ "not ok"*) fail=$((fail + 1)) ;;
+ "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)) ;;
+ esac
+
+ echo "$result"
+ [ -n "$rest" ] && printf '%s\n' "$rest" | sed 's/^[^#]/# &/'
+
done
-exit $rc
+echo "# pass:$pass fail:$fail static-skip:$static_skip" \
+ "probe-skip:$probe_skip xfail:$xfail xpass:$xpass"
+
+[ "$fail" = 0 ] && [ "$xpass" = 0 ]
--
2.53.0-Meta
next prev parent reply other threads:[~2026-09-11 18:43 UTC|newest]
Thread overview: 77+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-11 18:42 [PATCH 00/58] Unit test framework for klp-build toolchain Song Liu
2026-09-11 18:42 ` [PATCH 01/58] objtool: Add test harness for the klp subcommands Song Liu
2026-09-11 18:42 ` [PATCH 02/58] objtool/klp: Check the klp test environment once, before any test Song Liu
2026-09-11 19:02 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 03/58] objtool/klp: Group the klp tests by architecture Song Liu
2026-09-11 18:42 ` Song Liu [this message]
2026-09-11 18:42 ` [PATCH 05/58] objtool/klp: Build klp test fixtures through the harness Song Liu
2026-09-11 18:42 ` [PATCH 06/58] objtool/klp: Grow the klp test harness vocabulary Song Liu
2026-09-11 19:03 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 07/58] objtool/klp: Give each run one working directory, one per test inside it Song Liu
2026-09-11 18:42 ` [PATCH 08/58] objtool/klp: Run the klp tests under set -u Song Liu
2026-09-11 18:42 ` [PATCH 09/58] objtool/klp: Document the klp test harness Song Liu
2026-09-11 19:00 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 10/58] objtool: Keep failing test workdirs by default Song Liu
2026-09-11 19:07 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 11/58] objtool: Forward toolchain variables to the klp test runner Song Liu
2026-09-11 18:42 ` [PATCH 12/58] objtool/klp: Add test for rejecting changed data Song Liu
2026-09-11 18:42 ` [PATCH 13/58] objtool/klp: Add test for newly introduced data Song Liu
2026-09-11 19:07 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 14/58] objtool/klp: Add test for newly introduced functions Song Liu
2026-09-11 18:42 ` [PATCH 15/58] objtool/klp: Add test for static local correlation Song Liu
2026-09-11 18:42 ` [PATCH 16/58] objtool/klp: Add test for cold function halves Song Liu
2026-09-11 19:07 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 17/58] objtool/klp: Add test for special section extraction Song Liu
2026-09-11 18:42 ` [PATCH 18/58] objtool/klp: Add test for selective " Song Liu
2026-09-11 18:42 ` [PATCH 19/58] objtool/klp: Add test for jump table key relocations Song Liu
2026-09-11 18:42 ` [PATCH 20/58] objtool/klp: Add test for rejecting module-owned static branch keys Song Liu
2026-09-11 18:42 ` [PATCH 21/58] objtool/klp: Add test for rejecting module-owned static call keys Song Liu
2026-09-11 18:42 ` [PATCH 22/58] objtool/klp: Add test for symids in discarded sections Song Liu
2026-09-11 18:42 ` [PATCH 23/58] objtool/klp: Add test for rejecting references to init code/data Song Liu
2026-09-11 19:18 ` sashiko-bot
2026-09-11 18:42 ` [PATCH 24/58] objtool/klp: Add test for correlation across ThinLTO name mangling Song Liu
2026-09-11 18:42 ` [PATCH 25/58] objtool/klp: Add test for objects without .modinfo Song Liu
2026-09-11 18:49 ` [PATCH 26/58] objtool/klp: Add test for unchecksummed input Song Liu
2026-09-11 18:50 ` [PATCH 27/58] objtool/klp: Add klp diff and post-link regression tests Song Liu
2026-09-11 18:50 ` [PATCH 28/58] objtool/klp: Add test for klp reloc section naming in module objects Song Liu
2026-09-11 18:50 ` [PATCH 29/58] objtool/klp: Add test for vmlinux relocs in a patched module Song Liu
2026-09-11 18:50 ` [PATCH 30/58] objtool/klp: Add test for Module.symvers path normalization Song Liu
2026-09-11 18:50 ` [PATCH 31/58] objtool/klp: Add test for the contents of the klp_funcs list Song Liu
2026-09-11 18:50 ` [PATCH 32/58] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references Song Liu
2026-09-11 18:50 ` [PATCH 33/58] objtool/klp: Add test for new references to exported symbols Song Liu
2026-09-11 18:50 ` [PATCH 34/58] objtool/klp: Add test for empty x86 alternative replacements Song Liu
2026-09-11 18:50 ` [PATCH 35/58] objtool/klp: Add test for recorded checksum values Song Liu
2026-09-11 18:50 ` [PATCH 36/58] objtool/klp: Add test for position-independent checksums Song Liu
2026-09-11 19:16 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 37/58] objtool/klp: Add test for sympos in module objects Song Liu
2026-09-11 19:21 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 38/58] objtool/klp: Add test for sympos resolved against a linked vmlinux Song Liu
2026-09-11 18:50 ` [PATCH 39/58] objtool/klp: Add test for static locals which must not be correlated Song Liu
2026-09-11 18:50 ` [PATCH 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Song Liu
2026-09-11 19:20 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 41/58] objtool/klp: Add test for kCFI prefix symbols and traps Song Liu
2026-09-11 19:21 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 42/58] objtool/klp: Add test for symbols whose linkage the patch changes Song Liu
2026-09-11 18:50 ` [PATCH 43/58] objtool/klp: Test rejection of a file-local static branch key Song Liu
2026-09-11 18:50 ` [PATCH 44/58] objtool/klp: Test a hand-built livepatch module's static call keys Song Liu
2026-09-11 18:50 ` [PATCH 45/58] objtool/klp: Test text annotations on alternative replacements Song Liu
2026-09-11 18:50 ` [PATCH 46/58] objtool/klp: Add test for data object checksums Song Liu
2026-09-11 18:50 ` [PATCH 47/58] objtool/klp: Add test for symbols with no checksum entry of their own Song Liu
2026-09-11 19:23 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 48/58] objtool/klp: Add test for a static branch introduced by the patch Song Liu
2026-09-11 18:50 ` [PATCH 49/58] objtool/klp: Add test for tracepoint and pr_debug static branch keys Song Liu
2026-09-11 18:50 ` [PATCH 50/58] objtool/klp: Add test for a static call introduced by the patch Song Liu
2026-09-11 19:25 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 51/58] objtool/klp: Add test for instruction operand checksums Song Liu
2026-09-11 18:50 ` [PATCH 52/58] objtool/klp: Add test for alternative replacement code in checksums Song Liu
2026-09-11 19:30 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 53/58] objtool/klp: Add test for the alignment of cloned data sections Song Liu
2026-09-11 18:50 ` [PATCH 54/58] objtool/klp: Add test for a patch which strips a data annotation Song Liu
2026-09-11 19:24 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 55/58] objtool/klp: Add test for absolute and __ADDRESSABLE symbols Song Liu
2026-09-11 18:50 ` [PATCH 56/58] objtool/klp: Add test for UBSAN metadata in an unchanged function Song Liu
2026-09-11 18:50 ` [PATCH 57/58] objtool/klp: Add test for Clang switch jump tables Song Liu
2026-09-11 19:27 ` sashiko-bot
2026-09-11 18:50 ` [PATCH 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name Song Liu
2026-09-11 19:28 ` sashiko-bot
2026-09-13 1:53 ` [PATCH 00/58] Unit test framework for klp-build toolchain Josh Poimboeuf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260911184305.1457308-5-song@kernel.org \
--to=song@kernel.org \
--cc=jikos@kernel.org \
--cc=joe.lawrence@redhat.com \
--cc=jpoimboe@kernel.org \
--cc=kernel-team@meta.com \
--cc=live-patching@vger.kernel.org \
--cc=mbenes@suse.cz \
--cc=peterz@infradead.org \
--cc=pmladek@suse.com \
--cc=puranjay@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.