From: Chaosheng Qu <[email protected]>
Subject: [PATCH] rtla: test that SIGINT unwinds cleanly
To: Steven Rostedt <[email protected]>
Cc: Tomas Glozar <[email protected]>,
    [email protected],
    [email protected]

rtla's tests only ever run the tool to completion.  Nothing exercises the
path where the tool is interrupted, which is where the trace instance
teardown can go wrong: a run that leaves an instance behind makes the next
run fail for reasons that have nothing to do with the tool, and a run that
never returns is not detected at all.

Add check_with_action(), which runs the tool like check() does but performs
an action while it is running, and always reports LEFTOVER_INSTANCES=n so
that a test can assert the instance was removed.  The tool is started with
"eval exec" so that $! is the tool itself: the action signals $RTLA_PID
directly, and timeout(1) stays the direct parent so it forwards the signal.

Then use it for osnoise and timerlat: SIGINT must make the tool unwind and
remove its instance.  check-sigint.sh looks at the process state rather
than kill -0, because a tool that has exited but has not been reaped still
answers kill -0.

Signed-off-by: Chaosheng Qu <[email protected]>
---
diff --git a/tools/tracing/rtla/tests/engine.sh 
b/tools/tracing/rtla/tests/engine.sh
index 4287cd6..3327e16 100644
--- a/tools/tracing/rtla/tests/engine.sh
+++ b/tools/tracing/rtla/tests/engine.sh
@@ -41,6 +41,86 @@ reset_osnoise() {
        popd >/dev/null
 }
 
+leftover_instances() {
+       # Count the trace instances rtla should have removed on exit.
+       local d count=0
+       for d in osnoise_top osnoise_hist timerlat_top timerlat_hist 
osnoise_trace
+       do
+               [ -d "/sys/kernel/tracing/instances/$d" ] && count=$((count + 
1))
+       done
+       echo $count
+}
+
+check_with_action() {
+       # Same as "check", but run ACTION while rtla is running, so that a test
+       # can interrupt the tool mid-run.
+       # Arguments: test_name rtla_args action exit_code [expected 
[unexpected]]
+       # The output of ACTION is appended to rtla's output, and the number of
+       # leftover trace instances is always reported as LEFTOVER_INSTANCES=n.
+       # ACTION can use $RTLA_PID to signal the tool.
+       local test_name=$1 tested_command=$2 action=$3
+       local expected_exitcode=${4:-0}
+       local expected_output=$5 unexpected_output=$6
+       local result exitcode fail failbuf pid
+
+       ctr=$(($ctr + 1))
+       [ -z "$TEST_COUNT" ] && return
+
+       [ "$NO_RESET_OSNOISE" == 1 ] || reset_osnoise
+
+       tmpdir=$(mktemp -d)
+       pushd $tmpdir >/dev/null
+
+       # eval so that the test can use shell syntax in its arguments, and exec
+       # so that $! is the tool itself and not an intermediate shell: tests
+       # signal $RTLA_PID directly.  timeout forwards the signal to its child.
+       # The arguments are re-parsed by the same eval as in check(), so quoting
+       # behaves identically; only exec and the quoted $RTLA are added.
+       ( eval "exec stdbuf -oL $TIMEOUT \"$RTLA\" $tested_command" ) > 
rtla.txt 2>&1 &
+       pid=$!
+       # The ACTION runs as a separate process, so RTLA_PID has to be exported
+       # or the helper scripts see it as unset.
+       export RTLA_PID=$pid
+
+       sleep "${ACTION_DELAY:-3}"
+       eval "$action" >> rtla.txt 2>&1
+
+       wait $pid
+       exitcode=$?
+       echo "LEFTOVER_INSTANCES=$(leftover_instances)" >> rtla.txt
+
+       result=$(cat rtla.txt)
+       failbuf=''
+       fail=0
+
+       if [ -n "$expected_output" ] && ! grep -qE "$expected_output" <<< 
"$result"
+       then
+               fail=1
+               failbuf+=$(printf "# Output match failed: \"%s\"" 
"$expected_output")
+               failbuf+=$'\n'
+       fi
+
+       if [ -n "$unexpected_output" ] && grep -qE "$unexpected_output" <<< 
"$result"
+       then
+               fail=1
+               failbuf+=$(printf "# Output non-match failed: \"%s\"" 
"$unexpected_output")
+               failbuf+=$'\n'
+       fi
+
+       if [ $exitcode -eq $expected_exitcode ] && [ $fail -eq 0 ]
+       then
+               echo "ok $ctr - $test_name"
+       else
+               echo "not ok $ctr - $test_name"
+               echo -n "$failbuf"
+               echo "$result" | col -b | while read line; do echo "# $line"; 
done
+               printf "#\n# exit code %s\n" $exitcode
+       fi
+
+       popd >/dev/null
+       rm -r $tmpdir
+}
+
 check() {
        test_name=$0
        tested_command=$1
diff --git a/tools/tracing/rtla/tests/osnoise.t 
b/tools/tracing/rtla/tests/osnoise.t
index 214fd40..df260e9 100644
--- a/tools/tracing/rtla/tests/osnoise.t
+++ b/tools/tracing/rtla/tests/osnoise.t
@@ -99,4 +99,11 @@ check_top_q_hist "with conditional continue" \
 check_top_hist "with trace output at end" \
        "osnoise TOOL -d 1s --on-end trace" 0 "^  Saving trace to 
osnoise_trace.txt$"
 
+# Cleanup test: the tool must unwind on SIGINT without leaving its trace
+# instance behind.
+check_with_action "top exits on SIGINT" \
+       "osnoise top -q" \
+       "$testdir/scripts/check-sigint.sh" \
+       0 "SIGINT handled OK" "LEFTOVER_INSTANCES=[1-9]"
+
 test_end
diff --git a/tools/tracing/rtla/tests/scripts/check-sigint.sh 
b/tools/tracing/rtla/tests/scripts/check-sigint.sh
new file mode 100755
index 0000000..7f63424
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-sigint.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Interrupt the tool with SIGINT and check that it unwinds instead of
+# hanging.  The caller reports LEFTOVER_INSTANCES separately.
+# Usage: check-sigint.sh
+# Output: "SIGINT handled OK" on success, an error otherwise.
+#
+# The tool is a child of the test shell, so once it has exited it stays
+# visible as a zombie until it is reaped -- and kill -0 still succeeds on a
+# zombie.  Check the process state instead, or a clean exit looks like a hang.
+
+pid=$RTLA_PID
+[ -n "$pid" ] || { echo "RTLA_PID not set"; exit 1; }
+
+kill -INT "$pid" 2>/dev/null || { echo "Failed to signal rtla"; exit 1; }
+
+# rtla's SIGINT handler stops tracing and lets the main loop return.  Give it
+# a few seconds before declaring it stuck.
+for _ in $(seq 30)
+do
+       [ -r "/proc/$pid/stat" ] || break
+       [ "$(awk '{print $3}' "/proc/$pid/stat" 2>/dev/null)" = "Z" ] && break
+       sleep 0.2
+done
+
+if [ -r "/proc/$pid/stat" ] && [ "$(awk '{print $3}' "/proc/$pid/stat")" != 
"Z" ]
+then
+       echo "rtla still running 6s after SIGINT"
+       exit 1
+fi
+
+echo "SIGINT handled OK"
diff --git a/tools/tracing/rtla/tests/timerlat.t 
b/tools/tracing/rtla/tests/timerlat.t
index 5116857..fe9c9da 100644
--- a/tools/tracing/rtla/tests/timerlat.t
+++ b/tools/tracing/rtla/tests/timerlat.t
@@ -153,4 +153,11 @@ else
 fi
 done
 
+# Cleanup test: the tool must unwind on SIGINT without leaving its trace
+# instance behind.
+check_with_action "top exits on SIGINT" \
+       "timerlat top -q" \
+       "$testdir/scripts/check-sigint.sh" \
+       0 "SIGINT handled OK" "LEFTOVER_INSTANCES=[1-9]"
+
 test_end


Reply via email to