public inbox for linux-rt-users@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers
@ 2026-03-30 19:43 Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 01/36] tests: Add pre-test and post-test cleanup of stalld processes Wander Lairson Costa
                   ` (35 more replies)
  0 siblings, 36 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The stalld test suite relies heavily on hardcoded sleep calls for
synchronization: waiting for stalld to initialize, for starvation
to be detected, and for boosting to occur. These fixed delays make
the tests both slow (sleeping the full duration even when the event
happens instantly) and unreliable (too short on slow systems, too
long on fast ones).

This series replaces approximately 120 sleep calls across 20 test
files with event-driven helpers that return as soon as the expected
event occurs. The core mechanism is wait_for_log_message(), which
uses process substitution with tail -f to detect log patterns
instantly rather than polling. On top of this, domain-specific
helpers like wait_for_starvation_detected(), wait_for_boost_detected(),
start_starvation_gen(), and start_stalld_with_log() encapsulate
common synchronization patterns into reusable functions with proper
readiness detection and cleanup.

The series also introduces pass() and fail() helpers to standardize
test result reporting, replaces duplicated stalld invocation
boilerplate with shared helpers, and fixes several pre-existing test
bugs exposed by the tighter synchronization: invalid argument tests
that never actually failed, a multi-CPU detection grep that was too
restrictive, and FIFO fallback behavior that was incorrectly treated
as an error. A stalld source fix is included to die on invalid CPU
affinity, which was silently ignored.

Clark Williams (1):
  tests: Add pre-test and post-test cleanup of stalld processes

Wander Lairson Costa (35):
  tests/helpers: Fix stalld daemon detection in start_stalld()
  tests/helpers: Remove duplicate log() function
  tests: Add per-test runtime measurement to run_tests.sh
  tests/functional: Fix and refactor test_backend_selection.sh
  tests/functional: Fix test_logging_destinations.sh path and backend
  tests/helpers: Replace sleep with poll in start_stalld_with_log()
  tests/helpers: Fix stop_stalld() timeout and shutdown logic
  tests/helpers: Fix relative path in backend detection functions
  tests/functional: Remove redundant post-stop_stalld sleeps
  tests/functional: Fix false positive log matching in
    test_logging_destinations
  tests/helpers: Rewrite wait_for_log_message() with process
    substitution
  tests/helpers: Add wait_for_stalld_ready() and use in
    start_stalld_with_log()
  tests/helpers: Fix fractional sleep timeout bugs
  tests/helpers: Flush stdout after starvation_gen startup messages
  tests/helpers: Add start_starvation_gen() helper function
  tests/helpers: Add wait_for_starvation_detected() and
    wait_for_boost_detected()
  tests/functional: Use start_starvation_gen() helper
  tests/functional: Replace detection sleeps with event-driven helpers
  tests/functional: Remove duplicated -a flag in
    test_fifo_priority_starvation
  tests/functional: Add missing -a flag in test_starvation_detection
  tests/functional: Use start_stalld_with_log() in test_log_only
  tests/functional: Use start_stalld_with_log() in
    test_logging_destinations
  tests/functional: Use start_stalld_with_log() in test_cpu_selection
  tests/functional: Use wait_for_stalld_ready() in
    test_backend_selection
  tests/functional: Use timeout for error path in test_force_fifo
  tests/functional: Use timeout for invalid argument tests
  tests: Add pass() helper and replace assert_equals hack
  tests/functional: Use pass() for all test pass reporting
  tests: Add fail() helper and use for all test failures
  tests/helpers: Use pass()/fail() in assert functions
  tests/functional: Fix multi-CPU detection in test_starvation_detection
  tests/functional: Accept FIFO fallback in test_fifo_boosting
  tests/functional: Fix readiness detection and FIFO fallback in
    test_force_fifo
  tests/functional: Fix invalid pidfile test in test_pidfile
  stalld: die on invalid CPU affinity

 src/stalld.c                                  |   6 +-
 tests/functional/test_affinity.sh             |  43 +--
 tests/functional/test_backend_selection.sh    | 193 +++-------
 tests/functional/test_boost_duration.sh       | 118 ++----
 tests/functional/test_boost_period.sh         | 116 ++----
 tests/functional/test_boost_restoration.sh    |  79 ++--
 tests/functional/test_boost_runtime.sh        | 132 ++-----
 tests/functional/test_cpu_selection.sh        |  79 +---
 tests/functional/test_deadline_boosting.sh    | 116 +++---
 tests/functional/test_fifo_boosting.sh        | 133 +++----
 .../test_fifo_priority_starvation.sh          | 106 ++----
 tests/functional/test_force_fifo.sh           | 101 ++---
 tests/functional/test_foreground.sh           |  18 +-
 tests/functional/test_idle_detection.sh       |  40 +-
 tests/functional/test_log_only.sh             |  46 +--
 tests/functional/test_logging_destinations.sh |  51 ++-
 tests/functional/test_pidfile.sh              |  75 ++--
 tests/functional/test_runqueue_parsing.sh     | 118 +++---
 tests/functional/test_starvation_detection.sh | 136 +++----
 tests/functional/test_starvation_threshold.sh |  98 ++---
 tests/functional/test_task_merging.sh         |  69 ++--
 tests/helpers/starvation_gen.c                |   1 +
 tests/helpers/test_helpers.sh                 | 351 ++++++++++++------
 tests/run_tests.sh                            |  61 ++-
 24 files changed, 905 insertions(+), 1381 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH stalld 01/36] tests: Add pre-test and post-test cleanup of stalld processes
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 02/36] tests/helpers: Fix stalld daemon detection in start_stalld() Wander Lairson Costa
                   ` (34 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Claude, Wander Lairson Costa

From: Clark Williams <williams@redhat.com>

Add kill_existing_stalld() function to test_helpers.sh and
kill_existing_stalld_processes() function to run_tests.sh to ensure
no stale stalld processes from previous test runs interfere with new
tests. This prevents orphaned processes from interrupted test runs
from causing false negatives or unexpected behavior.

Changes:
- test_helpers.sh: Add kill_existing_stalld() with graceful then
  forced shutdown, export the function, and call from
  setup_test_environment()
- run_tests.sh: Add kill_existing_stalld_processes() with logging,
  call from init_tests() before tests start, and call from
  cleanup_runner() on exit

This ensures clean test environment initialization and proper cleanup
even when test runs are interrupted.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Assisted-By: git-scm-master (claude-sonnet-4 / 36f1d1f06f2e)
Signed-off-by: Clark Williams <williams@redhat.com>
Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 35 ++++++++++++++++++++++++++++-
 tests/run_tests.sh            | 42 +++++++++++++++++++++++++++++++++++
 2 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 630601a..c42dab3 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -407,6 +407,36 @@ stop_stalld() {
 	fi
 }
 
+# Kill any existing stalld processes (cleanup from previous runs)
+# This ensures a clean slate before starting tests
+kill_existing_stalld() {
+	local pids=$(pgrep -x stalld 2>/dev/null)
+	if [ -n "${pids}" ]; then
+		echo "Killing existing stalld processes: ${pids}"
+		for pid in ${pids}; do
+			# Try graceful shutdown first
+			kill ${pid} 2>/dev/null || true
+		done
+		sleep 0.5
+		# Force kill any remaining
+		pids=$(pgrep -x stalld 2>/dev/null)
+		if [ -n "${pids}" ]; then
+			for pid in ${pids}; do
+				kill -9 ${pid} 2>/dev/null || true
+			done
+			sleep 0.2
+		fi
+		# Verify all killed
+		pids=$(pgrep -x stalld 2>/dev/null)
+		if [ -n "${pids}" ]; then
+			echo -e "${YELLOW}WARNING: Could not kill all stalld processes: ${pids}${NC}"
+			return 1
+		fi
+		echo "All existing stalld processes killed"
+	fi
+	return 0
+}
+
 # Cleanup function (call in trap)
 cleanup() {
 	local exit_code=$?
@@ -712,6 +742,9 @@ disable_dl_server() {
 setup_test_environment() {
 	echo "Setting up test environment..."
 
+	# Kill any existing stalld processes from previous runs
+	kill_existing_stalld
+
 	# Save and disable RT throttling
 	save_rt_throttling
 	disable_rt_throttling
@@ -1022,7 +1055,7 @@ export -f start_test end_test
 export -f assert_equals assert_contains assert_not_contains
 export -f assert_file_exists assert_file_not_exists
 export -f assert_process_running assert_process_not_running
-export -f start_stalld stop_stalld cleanup
+export -f start_stalld stop_stalld kill_existing_stalld cleanup
 export -f wait_for_log_message
 export -f get_thread_policy get_thread_priority
 export -f create_cpu_load
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index c6adc49..16b6750 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -220,6 +220,15 @@ cleanup_runner() {
 	fi
 
 	if [ $EUID -eq 0 ]; then
+		# Kill any remaining stalld processes
+		local pids=$(pgrep -x stalld 2>/dev/null)
+		if [ -n "${pids}" ]; then
+			echo -e "${BLUE}Cleaning up remaining stalld processes: ${pids}${NC}"
+			for pid in ${pids}; do
+				kill -9 ${pid} 2>/dev/null || true
+			done
+		fi
+
 		restore_dl_server_state
 		restore_rt_throttling_state
 	fi
@@ -236,6 +245,34 @@ handle_interrupt() {
 trap cleanup_runner EXIT
 trap handle_interrupt INT TERM
 
+# Kill any existing stalld processes before tests
+kill_existing_stalld_processes() {
+	local pids=$(pgrep -x stalld 2>/dev/null)
+	if [ -n "${pids}" ]; then
+		echo -e "${BLUE}Killing existing stalld processes: ${pids}${NC}" | tee -a "${LOG_FILE}"
+		for pid in ${pids}; do
+			kill ${pid} 2>/dev/null || true
+		done
+		sleep 0.5
+		# Force kill any remaining
+		pids=$(pgrep -x stalld 2>/dev/null)
+		if [ -n "${pids}" ]; then
+			for pid in ${pids}; do
+				kill -9 ${pid} 2>/dev/null || true
+			done
+			sleep 0.2
+		fi
+		# Verify
+		pids=$(pgrep -x stalld 2>/dev/null)
+		if [ -n "${pids}" ]; then
+			echo -e "${YELLOW}WARNING: Could not kill all stalld processes: ${pids}${NC}" | tee -a "${LOG_FILE}"
+		else
+			echo -e "${GREEN}All existing stalld processes killed${NC}" | tee -a "${LOG_FILE}"
+		fi
+		echo "" | tee -a "${LOG_FILE}"
+	fi
+}
+
 # Initialize
 init_tests() {
 	mkdir -p "${RESULTS_DIR}"
@@ -243,6 +280,11 @@ init_tests() {
 	print_banner | tee "${LOG_FILE}"
 	echo "" | tee -a "${LOG_FILE}"
 
+	# Kill any existing stalld processes from previous runs
+	if [ $EUID -eq 0 ]; then
+		kill_existing_stalld_processes
+	fi
+
 	# Save and disable RT throttling if running as root
 	if [ $EUID -eq 0 ]; then
 		save_and_disable_rt_throttling
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 02/36] tests/helpers: Fix stalld daemon detection in start_stalld()
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 01/36] tests: Add pre-test and post-test cleanup of stalld processes Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 03/36] tests/helpers: Remove duplicate log() function Wander Lairson Costa
                   ` (33 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The start_stalld() test helper function incorrectly assumed that
daemonized processes would always have a parent PID (PPID) of 1 (init)
or 2 (kthreadd). This assumption is no longer valid on modern Linux
systems utilizing systemd, where orphaned processes are commonly
re-parented to the user's systemd session, not directly to the global
init process.

This led to failures in the test_foreground test on both sched_debug
and queue_track backends due to the helper failing to correctly
identify the daemonized stalld process.

To address this, the daemon detection logic has been refactored.
Instead of relying on PPID checks, the helper now first waits for the
initial shell background process (shell_pid) that started stalld to
exit. This exit signals that stalld has successfully performed its
double-fork and daemonized. Once the parent shell process has exited,
the helper then uses pgrep -n -x stalld to reliably find the newest
running stalld instance, which corresponds to the daemonized process.
This approach provides a more robust and platform-agnostic method for
detecting stalld when it runs in daemon mode.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 45 ++++++++++-------------------------
 1 file changed, 13 insertions(+), 32 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index c42dab3..5a384a7 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -321,48 +321,29 @@ start_stalld() {
 				fi
 			fi
 		else
-			# Daemon mode: parent forks, child becomes daemon (ppid=1), parent exits
-			# We need to wait for and find the DAEMON child, not the exiting parent
+			# Daemon mode: stalld double-forks, so the shell_pid will exit
+			# and the daemon will be re-parented. Wait for the shell process
+			# to exit, then find the newest stalld process.
 			while [ $attempt -lt $max_attempts ]; do
 				sleep 0.5
 
-				# Get all stalld processes
-				local pids=$(pgrep -x stalld 2>/dev/null)
-
-				for pid in $pids; do
-					if kill -0 $pid 2>/dev/null; then
-						# Check parent PID - daemon should have ppid=1 (init) or ppid=2 (kthreadd)
-						local ppid=$(ps -o ppid= -p $pid 2>/dev/null | tr -d ' ')
-
-						# For daemonized processes, wait for ppid=1 or 2
-						if [ "$ppid" = "1" ] || [ "$ppid" = "2" ]; then
-							STALLD_PID=$pid
-							# Wait a bit to ensure daemon is stable
-							sleep 0.5
-							# Verify it's still running
-							if kill -0 $pid 2>/dev/null; then
-								break 2  # Break out of both loops
-							else
-								# Daemon died, keep looking
-								STALLD_PID=""
-							fi
-						fi
+				# Check if shell_pid has exited (daemonization complete)
+				if ! kill -0 ${shell_pid} 2>/dev/null; then
+					# Shell process exited, daemon should be running
+					# Use pgrep -n to find the newest stalld process
+					STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
+					if [ -n "${STALLD_PID}" ] && kill -0 ${STALLD_PID} 2>/dev/null; then
+						break
 					fi
-				done
-
-				# If we found a daemonized process, we're done
-				if [ -n "${STALLD_PID}" ]; then
-					break
 				fi
 
 				attempt=$((attempt + 1))
 			done
 
-			# Last resort: use the shell PID if nothing else worked
+			# If we still don't have a PID, try one more time
 			if [ -z "${STALLD_PID}" ]; then
-				if kill -0 ${shell_pid} 2>/dev/null; then
-					STALLD_PID=${shell_pid}
-				fi
+				sleep 1
+				STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
 			fi
 		fi
 	fi
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 03/36] tests/helpers: Remove duplicate log() function
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 01/36] tests: Add pre-test and post-test cleanup of stalld processes Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 02/36] tests/helpers: Fix stalld daemon detection in start_stalld() Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 04/36] tests: Add per-test runtime measurement to run_tests.sh Wander Lairson Costa
                   ` (32 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

test_helpers.sh defines log() twice: the full version at line 43
with journal correlation via logger, and a simplified version at
line 895 that only echoes to stdout. In bash, the second definition
shadows the first, so all tests were silently losing journal
logging that enables correlating test activities with stalld
behavior in journalctl.

Remove the duplicate to restore journal logging for all tests.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 5a384a7..3fa8d07 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -891,11 +891,6 @@ start_stalld_with_backend() {
 # Consolidated helper functions (previously duplicated across tests)
 #
 
-# Logging with timestamp (used by many functional tests)
-log() {
-	echo "[$(date +'%H:%M:%S')] $*"
-}
-
 # Get scheduling policy (0=OTHER, 1=FIFO, 2=RR, 6=DEADLINE)
 get_sched_policy() {
 	local pid=$1
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 04/36] tests: Add per-test runtime measurement to run_tests.sh
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (2 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 03/36] tests/helpers: Remove duplicate log() function Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 05/36] tests/functional: Fix and refactor test_backend_selection.sh Wander Lairson Costa
                   ` (31 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The test runner provides no visibility into how long individual
tests take or the total suite runtime. This makes it difficult to
identify slow tests and to measure the impact of future
optimizations to test determinism.

Add elapsed time tracking to run_unit_test() and run_shell_test()
using bash's built-in SECONDS variable. Each test result now
includes the elapsed time in its output, changing the format from
"PASS" to "PASS (12s)" for all outcomes (PASS, FAIL, SKIP). The
total suite runtime is also displayed in print_summary().

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/run_tests.sh | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index 16b6750..bbb3b16 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -464,8 +464,10 @@ run_unit_test() {
 		test_log="${RESULTS_DIR}/${backend_mode//:/_}_${test_name}.log"
 	fi
 
+	local start_time=$SECONDS
 	if "${test_path}" > "${test_log}" 2>&1; then
-		echo -e "${GREEN}PASS${NC}" | tee -a "${LOG_FILE}"
+		local elapsed=$((SECONDS - start_time))
+		echo -e "${GREEN}PASS${NC} (${elapsed}s)" | tee -a "${LOG_FILE}"
 		PASSED_TESTS=$((PASSED_TESTS + 1))
 		if [ -n "${backend}" ]; then
 			BACKEND_PASSED["${backend}"]=$((BACKEND_PASSED["${backend}"] + 1))
@@ -474,7 +476,8 @@ run_unit_test() {
 			MODE_PASSED["${mode}"]=$((MODE_PASSED["${mode}"] + 1))
 		fi
 	else
-		echo -e "${RED}FAIL${NC}" | tee -a "${LOG_FILE}"
+		local elapsed=$((SECONDS - start_time))
+		echo -e "${RED}FAIL${NC} (${elapsed}s)" | tee -a "${LOG_FILE}"
 		echo "  See ${test_log} for details" | tee -a "${LOG_FILE}"
 		FAILED_TESTS=$((FAILED_TESTS + 1))
 		if [ -n "${backend}" ]; then
@@ -601,8 +604,10 @@ run_shell_test() {
 		test_log="${RESULTS_DIR}/${backend_mode//:/_}_${test_name}.log"
 	fi
 
+	local start_time=$SECONDS
 	if bash "${test_path}" > "${test_log}" 2>&1; then
-		echo -e "${GREEN}PASS${NC}" | tee -a "${LOG_FILE}"
+		local elapsed=$((SECONDS - start_time))
+		echo -e "${GREEN}PASS${NC} (${elapsed}s)" | tee -a "${LOG_FILE}"
 		PASSED_TESTS=$((PASSED_TESTS + 1))
 		if [ -n "${backend}" ]; then
 			BACKEND_PASSED["${backend}"]=$((BACKEND_PASSED["${backend}"] + 1))
@@ -612,9 +617,10 @@ run_shell_test() {
 		fi
 	else
 		local exit_code=$?
+		local elapsed=$((SECONDS - start_time))
 		if [ ${exit_code} -eq 77 ]; then
 			# Exit code 77 = SKIP (autotools convention)
-			echo -e "${YELLOW}SKIP${NC}" | tee -a "${LOG_FILE}"
+			echo -e "${YELLOW}SKIP${NC} (${elapsed}s)" | tee -a "${LOG_FILE}"
 			SKIPPED_TESTS=$((SKIPPED_TESTS + 1))
 			if [ -n "${backend}" ]; then
 				BACKEND_SKIPPED["${backend}"]=$((BACKEND_SKIPPED["${backend}"] + 1))
@@ -623,7 +629,7 @@ run_shell_test() {
 				MODE_SKIPPED["${mode}"]=$((MODE_SKIPPED["${mode}"] + 1))
 			fi
 		else
-			echo -e "${RED}FAIL${NC}" | tee -a "${LOG_FILE}"
+			echo -e "${RED}FAIL${NC} (${elapsed}s)" | tee -a "${LOG_FILE}"
 			echo "  See ${test_log} for details" | tee -a "${LOG_FILE}"
 			FAILED_TESTS=$((FAILED_TESTS + 1))
 			if [ -n "${backend}" ]; then
@@ -644,6 +650,7 @@ print_summary() {
 	echo -e "  ${GREEN}Passed:  ${PASSED_TESTS}${NC}" | tee -a "${LOG_FILE}"
 	echo -e "  ${RED}Failed:  ${FAILED_TESTS}${NC}" | tee -a "${LOG_FILE}"
 	echo -e "  ${YELLOW}Skipped: ${SKIPPED_TESTS}${NC}" | tee -a "${LOG_FILE}"
+	echo "  Time:    $((SECONDS - SUITE_START_TIME))s" | tee -a "${LOG_FILE}"
 
 	# Print per-backend statistics if matrix testing enabled
 	if [ ${BACKEND_MATRIX} -eq 1 ] && [ ${#BACKEND_TOTAL[@]} -gt 0 ]; then
@@ -866,6 +873,8 @@ run_specific_test() {
 
 # Main execution
 main() {
+	SUITE_START_TIME=$SECONDS
+
 	# Export BACKEND and THREADING_MODE for use by test scripts
 	export STALLD_TEST_BACKEND="${BACKEND}"
 	export STALLD_TEST_THREADING_MODE="${THREADING_MODE}"
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 05/36] tests/functional: Fix and refactor test_backend_selection.sh
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (3 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 04/36] tests: Add per-test runtime measurement to run_tests.sh Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 06/36] tests/functional: Fix test_logging_destinations.sh path and backend Wander Lairson Costa
                   ` (30 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

test_backend_selection.sh fails when invoked by run_tests.sh because
it uses a relative path (../stalld) that resolves incorrectly when the
working directory is not tests/functional/. It also lacks
parse_test_options for backend passthrough from the test runner, does
not track temporary log files for cleanup, and contains four
copy-pasted 30-line blocks that implement the same
start/verify/check/stop pattern.

Extract the repeated pattern into a test_backend_flag() helper
function that starts stalld with a given -b flag, verifies the
expected backend message appears in the log, and calls stop_stalld.
The helper uses $! instead of pgrep for PID capture since stalld
runs in foreground mode. Fix the stalld path to use ${TEST_ROOT},
add parse_test_options and CLEANUP_FILES tracking, and correct the
test numbering.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_backend_selection.sh | 193 +++++++--------------
 1 file changed, 61 insertions(+), 132 deletions(-)

diff --git a/tests/functional/test_backend_selection.sh b/tests/functional/test_backend_selection.sh
index 6227756..5d60072 100755
--- a/tests/functional/test_backend_selection.sh
+++ b/tests/functional/test_backend_selection.sh
@@ -10,6 +10,7 @@
 
 TEST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
 source "${TEST_ROOT}/helpers/test_helpers.sh"
+parse_test_options "$@" || exit $?
 
 start_test "Backend Selection"
 
@@ -18,155 +19,83 @@ setup_test_environment
 
 require_root
 
-# Test 1: Start stalld with sched_debug backend
-echo "Test 1: Starting stalld with sched_debug backend"
-STALLD_LOG="/tmp/stalld_backend_sched_debug_$$.log"
-# Call stalld directly to capture its actual stderr output
-# IMPORTANT: -v must come BEFORE -b so verbose mode is enabled when backend message is logged
-../stalld -v -f -l -b sched_debug -t 60 > "${STALLD_LOG}" 2>&1 &
-sleep 1
-# Get the actual stalld PID
-STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
-if [ -z "${STALLD_PID}" ]; then
-	TEST_FAILED=$((TEST_FAILED + 1))
-	echo -e "  ${RED}FAIL${NC}: stalld failed to start with sched_debug backend"
-else
+# Helper: start stalld with a specific backend flag, verify the expected
+# backend message appears in the log. This test intentionally bypasses
+# start_stalld_with_log() because it needs to control the -b flag
+# directly rather than inheriting it from STALLD_TEST_BACKEND.
+#
+# Usage: test_backend_flag <backend_flag> <expected_message> <description>
+test_backend_flag() {
+	local backend_flag=$1
+	local expected_msg=$2
+	local description=$3
+	local log_file="/tmp/stalld_backend_${backend_flag}_$$.log"
+
+	CLEANUP_FILES+=("${log_file}")
+
+	"${TEST_ROOT}/../stalld" -v -f -l -b "${backend_flag}" -t 60 \
+		> "${log_file}" 2>&1 &
+	STALLD_PID=$!
 	CLEANUP_PIDS+=("${STALLD_PID}")
 	sleep 1
-	if kill -0 ${STALLD_PID} 2>/dev/null; then
-		# Check if log contains backend message
-		if grep -q "using sched_debug backend" "${STALLD_LOG}"; then
-			assert_equals "0" "0" "sched_debug backend selected"
-		else
-			TEST_FAILED=$((TEST_FAILED + 1))
-			echo -e "  ${RED}FAIL${NC}: Backend message not found in log"
-			echo "  Log contents:"
-			cat "${STALLD_LOG}"
-		fi
-		stop_stalld
+
+	if ! kill -0 ${STALLD_PID} 2>/dev/null; then
+		TEST_FAILED=$((TEST_FAILED + 1))
+		echo -e "  ${RED}FAIL${NC}: stalld failed to start (${description})"
+		return 1
+	fi
+
+	if grep -q "${expected_msg}" "${log_file}"; then
+		assert_equals "0" "0" "${description}"
 	else
 		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: stalld failed to start with sched_debug backend"
+		echo -e "  ${RED}FAIL${NC}: Backend message not found (${description})"
+		echo "  Expected: ${expected_msg}"
+		echo "  Log contents:"
+		cat "${log_file}"
 	fi
-fi
-rm -f "${STALLD_LOG}"
 
+	stop_stalld
+}
+
+# Test 1: sched_debug backend (full name)
+echo "Test 1: Starting stalld with sched_debug backend"
+test_backend_flag "sched_debug" "using sched_debug backend" \
+	"sched_debug backend selected"
+
+# Test 2: queue_track backend (if available)
 echo ""
-echo "=== Test 4: Check queue_track (BPF) backend availability ==="
+echo "Test 2: Check queue_track (BPF) backend"
 if is_backend_available "queue_track"; then
-	echo "Test 2a: Starting stalld with queue_track backend"
-	STALLD_LOG="/tmp/stalld_backend_queue_track_$$.log"
-	# Call stalld directly to capture its actual stderr output
-	# IMPORTANT: -v must come BEFORE -b so verbose mode is enabled when backend message is logged
-	../stalld -v -f -l -b queue_track -t 60 > "${STALLD_LOG}" 2>&1 &
-	sleep 1
-	# Get the actual stalld PID
-	STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
-	if [ -z "${STALLD_PID}" ]; then
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: stalld failed to start with queue_track backend"
-	else
-		CLEANUP_PIDS+=("${STALLD_PID}")
-		sleep 1
-		if kill -0 ${STALLD_PID} 2>/dev/null; then
-			# Check if log contains backend message
-			if grep -q "using queue_track backend" "${STALLD_LOG}"; then
-				assert_equals "0" "0" "queue_track backend selected"
-			else
-				TEST_FAILED=$((TEST_FAILED + 1))
-				echo -e "  ${RED}FAIL${NC}: Backend message not found in log"
-				echo "  Log contents:"
-				cat "${STALLD_LOG}"
-			fi
-			stop_stalld
-		else
-			TEST_FAILED=$((TEST_FAILED + 1))
-			echo -e "  ${RED}FAIL${NC}: stalld failed to start with queue_track backend"
-		fi
-	fi
-	rm -f "${STALLD_LOG}"
+	test_backend_flag "queue_track" "using queue_track backend" \
+		"queue_track backend selected"
 else
-	echo "ℹ queue_track (BPF) backend not available"
-	echo "  (This is expected on i686, powerpc, ppc64le, or kernels ≤3.x)"
+	echo "  queue_track (BPF) backend not available"
+	echo "  (This is expected on i686, powerpc, ppc64le, or kernels <=3.x)"
 	TEST_PASSED=$((TEST_PASSED + 1))
 fi
 
-# Test 3: Test short names (S for sched_debug)
+# Test 3: Short name 'S' for sched_debug
+echo ""
 echo "Test 3: Testing short name 'S' for sched_debug"
-STALLD_LOG="/tmp/stalld_backend_short_S_$$.log"
-# Call stalld directly to capture its actual stderr output
-# IMPORTANT: -v must come BEFORE -b so verbose mode is enabled when backend message is logged
-../stalld -v -f -l -b S -t 60 > "${STALLD_LOG}" 2>&1 &
-sleep 1
-# Get the actual stalld PID
-STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
-if [ -z "${STALLD_PID}" ]; then
-	TEST_FAILED=$((TEST_FAILED + 1))
-	echo -e "  ${RED}FAIL${NC}: stalld failed to start with short name 'S'"
-else
-	CLEANUP_PIDS+=("${STALLD_PID}")
-	sleep 1
-	if kill -0 ${STALLD_PID} 2>/dev/null; then
-		# Check if log contains backend message
-		if grep -q "using sched_debug backend" "${STALLD_LOG}"; then
-			assert_equals "0" "0" "Short name 'S' works for sched_debug"
-		else
-			TEST_FAILED=$((TEST_FAILED + 1))
-			echo -e "  ${RED}FAIL${NC}: Backend message not found for short name"
-			echo "  Log contents:"
-			cat "${STALLD_LOG}"
-		fi
-		stop_stalld
-	else
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: stalld failed to start with short name 'S'"
-	fi
-fi
-rm -f "${STALLD_LOG}"
+test_backend_flag "S" "using sched_debug backend" \
+	"Short name 'S' works for sched_debug"
 
-# Test 4: Test STALLD_TEST_BACKEND environment variable
+# Test 4: STALLD_TEST_BACKEND environment variable
+echo ""
 if [ -n "${STALLD_TEST_BACKEND}" ]; then
 	echo "Test 4: Testing STALLD_TEST_BACKEND=${STALLD_TEST_BACKEND}"
-	STALLD_LOG="/tmp/stalld_backend_env_$$.log"
-	# Call stalld directly to capture its actual stderr output
-	# start_stalld adds -b based on STALLD_TEST_BACKEND, so we mimic that here
-	# IMPORTANT: -v must come BEFORE -b so verbose mode is enabled when backend message is logged
-	../stalld -v -f -l -b "${STALLD_TEST_BACKEND}" -t 60 > "${STALLD_LOG}" 2>&1 &
-	sleep 1
-	# Get the actual stalld PID
-	STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
-	if [ -z "${STALLD_PID}" ]; then
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: stalld failed to start with STALLD_TEST_BACKEND"
-	else
-		CLEANUP_PIDS+=("${STALLD_PID}")
-		sleep 1
-		if kill -0 ${STALLD_PID} 2>/dev/null; then
-			# Normalize backend name for comparison
-			BACKEND_NORMALIZED="${STALLD_TEST_BACKEND}"
-			case "${STALLD_TEST_BACKEND}" in
-				S) BACKEND_NORMALIZED="sched_debug" ;;
-				Q) BACKEND_NORMALIZED="queue_track" ;;
-			esac
-
-			# Check if log contains backend message
-			if grep -q "using ${BACKEND_NORMALIZED} backend" "${STALLD_LOG}"; then
-				assert_equals "0" "0" "STALLD_TEST_BACKEND environment variable respected"
-			else
-				TEST_FAILED=$((TEST_FAILED + 1))
-				echo -e "  ${RED}FAIL${NC}: Backend ${BACKEND_NORMALIZED} not used from environment"
-				echo "  Log contents:"
-				cat "${STALLD_LOG}"
-			fi
-			stop_stalld
-		else
-			echo "ℹ Could not verify backend in logs (may not be logged)"
-			TEST_PASSED=$((TEST_PASSED + 1))
-		fi
-	fi
-	rm -f "${STALLD_LOG}"
+	# Normalize short names for expected message
+	BACKEND_NORMALIZED="${STALLD_TEST_BACKEND}"
+	case "${STALLD_TEST_BACKEND}" in
+		S) BACKEND_NORMALIZED="sched_debug" ;;
+		Q) BACKEND_NORMALIZED="queue_track" ;;
+	esac
+	test_backend_flag "${STALLD_TEST_BACKEND}" \
+		"using ${BACKEND_NORMALIZED} backend" \
+		"STALLD_TEST_BACKEND environment variable respected"
 else
-	echo "ℹ Skipping queue_track test - backend not available on this system"
+	echo "Test 4: Skipping (STALLD_TEST_BACKEND not set)"
 	TEST_PASSED=$((TEST_PASSED + 1))
 fi
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 06/36] tests/functional: Fix test_logging_destinations.sh path and backend
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (4 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 05/36] tests/functional: Fix and refactor test_backend_selection.sh Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 07/36] tests/helpers: Replace sleep with poll in start_stalld_with_log() Wander Lairson Costa
                   ` (29 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Tests 1 and 4 invoke stalld directly to capture its stdout for
verification. They used a relative path (../stalld) that breaks
when run_tests.sh invokes the test from a different working
directory, and used pgrep to find the PID instead of capturing $!
from the background launch. They also lacked backend passthrough
for matrix testing.

Fix the stalld path to use ${TEST_ROOT}, capture the PID via $!,
add BACKEND_FLAG construction and passthrough for the direct
invocations, and remove a redundant rm -f already handled by
CLEANUP_FILES. Tests 2 and 3 use start_stalld and need no changes.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_logging_destinations.sh | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/tests/functional/test_logging_destinations.sh b/tests/functional/test_logging_destinations.sh
index 1d41464..6fa4907 100755
--- a/tests/functional/test_logging_destinations.sh
+++ b/tests/functional/test_logging_destinations.sh
@@ -28,12 +28,15 @@ LOG_FILE="/tmp/stalld_test_verbose_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
 
 # Start stalld directly (not using start_stalld helper) to capture output
-../stalld -f -v -l -t 5 > "${LOG_FILE}" 2>&1 &
-sleep 2
-STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
-if [ -n "${STALLD_PID}" ]; then
-	CLEANUP_PIDS+=("${STALLD_PID}")
+# Add backend flag if specified via test runner
+BACKEND_FLAG=""
+if [ -n "${STALLD_TEST_BACKEND}" ]; then
+	BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
 fi
+"${TEST_ROOT}/../stalld" -f -v ${BACKEND_FLAG} -l -t 5 > "${LOG_FILE}" 2>&1 &
+STALLD_PID=$!
+CLEANUP_PIDS+=("${STALLD_PID}")
+sleep 2
 
 if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 	# Check that output was written to our log file
@@ -51,7 +54,6 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 fi
 
 stop_stalld
-rm -f "${LOG_FILE}"
 
 # Test 2: Kernel message log (-k)
 echo ""
@@ -153,12 +155,10 @@ LOG_FILE="/tmp/stalld_test_combined_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
 
 # Start stalld directly (not using start_stalld helper) to capture output
-../stalld -f -v -k -s -l -t 5 > "${LOG_FILE}" 2>&1 &
+"${TEST_ROOT}/../stalld" -f -v -k -s ${BACKEND_FLAG} -l -t 5 > "${LOG_FILE}" 2>&1 &
+STALLD_PID=$!
+CLEANUP_PIDS+=("${STALLD_PID}")
 sleep 2
-STALLD_PID=$(pgrep -n -x stalld 2>/dev/null)
-if [ -n "${STALLD_PID}" ]; then
-	CLEANUP_PIDS+=("${STALLD_PID}")
-fi
 
 if assert_process_running "${STALLD_PID}" "stalld with combined logging should be running"; then
 	# Verify verbose output
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 07/36] tests/helpers: Replace sleep with poll in start_stalld_with_log()
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (5 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 06/36] tests/functional: Fix test_logging_destinations.sh path and backend Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 08/36] tests/helpers: Fix stop_stalld() timeout and shutdown logic Wander Lairson Costa
                   ` (28 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

start_stalld_with_log() uses a hardcoded sleep 1 after launching
stalld, which is both fragile and wasteful. On systems where BPF
backend initialization takes longer than one second, it is
insufficient. On fast systems, it delays every test unnecessarily.

Replace the fixed sleep with a polling loop that checks whether
stalld has started writing to the log file. A brief initial sleep
covers the fast path, then 1-second polling with a 15-second
timeout handles slow startup such as BPF initialization. On
timeout, the process is killed with SIGTERM and escalated to
SIGKILL if it does not exit within one second.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 30 ++++++++++++++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 3fa8d07..f6d6ce2 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -948,11 +948,37 @@ start_stalld_with_log() {
 		echo "Using backend: ${STALLD_TEST_BACKEND}"
 	fi
 
-	# Start stalld with output redirected
-	${TEST_ROOT}/../stalld ${stalld_args} > "${log_file}" 2>&1 &
+	# Start stalld with line-buffered output so tail -f can detect
+	# readiness immediately instead of waiting for the buffer to fill.
+	stdbuf -oL ${TEST_ROOT}/../stalld ${stalld_args} > "${log_file}" 2>&1 &
 	STALLD_PID=$!
 	CLEANUP_PIDS+=("${STALLD_PID}")
+
+	# Poll for stalld to start writing to the log file rather than
+	# using a fixed sleep. Brief initial sleep covers the fast path,
+	# then 1-second polling for slow systems (e.g. BPF init).
+	sleep 0.01
+	local timeout=15
+	local elapsed=0
+	while [ $elapsed -lt $timeout ]; do
+		if ! kill -0 ${STALLD_PID} 2>/dev/null; then
+			echo -e "${RED}ERROR: stalld exited during startup${NC}"
+			return 1
+		fi
+		if [ -s "${log_file}" ]; then
+			return 0
+		fi
+		sleep 1
+		elapsed=$((elapsed + 1))
+	done
+
+	echo -e "${RED}ERROR: stalld did not produce output within ${timeout}s${NC}"
+	kill ${STALLD_PID} 2>/dev/null
 	sleep 1
+	if kill -0 ${STALLD_PID} 2>/dev/null; then
+		kill -9 ${STALLD_PID} 2>/dev/null
+	fi
+	return 1
 }
 
 # Wait for scheduling policy to change to expected value
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 08/36] tests/helpers: Fix stop_stalld() timeout and shutdown logic
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (6 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 07/36] tests/helpers: Replace sleep with poll in start_stalld_with_log() Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 09/36] tests/helpers: Fix relative path in backend detection functions Wander Lairson Costa
                   ` (27 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

stop_stalld() has two bugs. The polling loop after SIGKILL uses
sleep 0.1 with an integer counter, so the nominal timeout of 10
actually gives only 1 second. Additionally, SIGTERM only gets
0.2 seconds before escalating to SIGKILL, which is insufficient
for stalld to perform graceful cleanup such as removing its
pidfile or flushing logs.

Restructure stop_stalld() into two clear phases. First send
SIGTERM and poll for graceful exit with 1-second intervals for
up to 5 seconds, giving stalld time to clean up. Then escalate
to SIGKILL only if the process is still alive, and poll again
for up to 5 seconds with correct timeout arithmetic. Document
the guarantee that the process is dead before the function
returns so callers do not need post-stop sleeps.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index f6d6ce2..1750423 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -365,24 +365,33 @@ start_stalld() {
 }
 
 # Stop stalld
+# Sends SIGTERM and polls for graceful exit, then escalates to
+# SIGKILL if needed. Guarantees the process is dead before
+# returning so callers do not need post-stop sleeps.
 stop_stalld() {
 	if [ -n "${STALLD_PID}" ]; then
 		if kill -0 ${STALLD_PID} 2>/dev/null; then
-			# Try graceful shutdown first
+			# Try graceful shutdown first (SIGTERM)
 			kill ${STALLD_PID} 2>/dev/null || true
-			# Give it a moment to exit gracefully
-			sleep 0.2
-			# Force kill if still running
-			if kill -0 ${STALLD_PID} 2>/dev/null; then
-				kill -9 ${STALLD_PID} 2>/dev/null || true
-			fi
-			# Poll for process termination (don't use wait - might not be a child)
-			local timeout=10
+
+			# Poll for graceful exit (up to 5 seconds)
+			local timeout=5
 			local elapsed=0
 			while kill -0 ${STALLD_PID} 2>/dev/null && [ ${elapsed} -lt ${timeout} ]; do
-				sleep 0.1
+				sleep 1
 				elapsed=$((elapsed + 1))
 			done
+
+			# Escalate to SIGKILL if still running
+			if kill -0 ${STALLD_PID} 2>/dev/null; then
+				kill -9 ${STALLD_PID} 2>/dev/null || true
+				# Poll for forced termination (up to 5 seconds)
+				elapsed=0
+				while kill -0 ${STALLD_PID} 2>/dev/null && [ ${elapsed} -lt ${timeout} ]; do
+					sleep 1
+					elapsed=$((elapsed + 1))
+				done
+			fi
 		fi
 		STALLD_PID=""
 	fi
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 09/36] tests/helpers: Fix relative path in backend detection functions
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (7 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 08/36] tests/helpers: Fix stop_stalld() timeout and shutdown logic Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 10/36] tests/functional: Remove redundant post-stop_stalld sleeps Wander Lairson Costa
                   ` (26 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

detect_default_backend() and is_backend_available() use a
hardcoded relative path "../stalld" to locate the binary for
backend detection via nm. This breaks when tests are invoked from
a different working directory, such as when run_tests.sh executes
them from the project root.

Use ${TEST_ROOT}/../stalld instead, consistent with the path
resolution used in start_stalld_with_log() and
start_starvation_gen().

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 1750423..8ff1b80 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -816,7 +816,7 @@ pick_test_cpu() {
 
 # Detect which backend stalld was compiled with (default)
 detect_default_backend() {
-	local stalld_bin="../stalld"
+	local stalld_bin="${TEST_ROOT}/../stalld"
 	if [ ! -x "${stalld_bin}" ]; then
 		echo "unknown"
 		return 1
@@ -839,7 +839,7 @@ detect_default_backend() {
 # Check if a specific backend is available
 is_backend_available() {
 	local backend=$1
-	local stalld_bin="../stalld"
+	local stalld_bin="${TEST_ROOT}/../stalld"
 
 	if [ ! -x "${stalld_bin}" ]; then
 		return 1
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 10/36] tests/functional: Remove redundant post-stop_stalld sleeps
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (8 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 09/36] tests/helpers: Fix relative path in backend detection functions Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 11/36] tests/functional: Fix false positive log matching in test_logging_destinations Wander Lairson Costa
                   ` (25 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

stop_stalld() now guarantees the process is dead before returning,
making the sleep 1 calls that follow it in test scripts redundant.
Remove all 30 occurrences across test_affinity.sh,
test_boost_duration.sh, test_boost_runtime.sh, test_force_fifo.sh,
test_foreground.sh, test_pidfile.sh, and
test_starvation_threshold.sh.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_affinity.sh             | 7 -------
 tests/functional/test_boost_duration.sh       | 4 ----
 tests/functional/test_boost_runtime.sh        | 4 ----
 tests/functional/test_force_fifo.sh           | 6 ------
 tests/functional/test_foreground.sh           | 1 -
 tests/functional/test_pidfile.sh              | 5 -----
 tests/functional/test_starvation_threshold.sh | 3 ---
 7 files changed, 30 deletions(-)

diff --git a/tests/functional/test_affinity.sh b/tests/functional/test_affinity.sh
index 90dd69f..c3888ba 100755
--- a/tests/functional/test_affinity.sh
+++ b/tests/functional/test_affinity.sh
@@ -89,7 +89,6 @@ else
 fi
 
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 2: Single CPU affinity
@@ -115,7 +114,6 @@ else
 fi
 
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 3: Multi-CPU affinity (CPU list)
@@ -143,7 +141,6 @@ if [ "$num_cpus" -ge 4 ]; then
     fi
 
     stop_stalld
-    sleep 1
 else
     log "⊘ SKIP: Test 3 requires at least 4 CPUs"
 fi
@@ -173,7 +170,6 @@ if [ "$num_cpus" -ge 4 ]; then
     fi
 
     stop_stalld
-    sleep 1
 else
     log "⊘ SKIP: Test 4 requires at least 4 CPUs"
 fi
@@ -210,7 +206,6 @@ if [ "$num_cpus" -ge 2 ]; then
     fi
 
     stop_stalld
-    sleep 1
 else
     log "⊘ SKIP: Test 5 requires at least 2 CPUs"
 fi
@@ -247,7 +242,6 @@ if [ "$num_cpus" -ge 2 ]; then
     fi
 
     stop_stalld
-    sleep 1
 else
     log "⊘ SKIP: Test 6 requires at least 2 CPUs"
 fi
@@ -318,7 +312,6 @@ else
 fi
 
 stop_stalld
-sleep 1
 
 log ""
 log "All affinity tests completed"
diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index e9f8928..4df5489 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -84,7 +84,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 2: Short duration (1 second)
@@ -123,7 +122,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 3: Long duration (10 seconds)
@@ -163,7 +161,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 4: Verify task policy is restored after boost duration
@@ -201,7 +198,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 5: Invalid duration values
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index 60ccdc6..0475da5 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -84,7 +84,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 2: Custom runtime (10,000 ns = 10 microseconds, less than default)
@@ -123,7 +122,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 3: Larger runtime (100,000 ns = 100 microseconds)
@@ -162,7 +160,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 4: Runtime < period (valid configuration)
@@ -203,7 +200,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null || true
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 5: Runtime > period (should error or be rejected)
diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index 704d605..cab8003 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -85,7 +85,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 2: Force FIFO mode (-F)
@@ -134,7 +133,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 3: Verify FIFO priority setting
@@ -175,7 +173,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 4: Verify FIFO emulation behavior (sleep runtime, restore, sleep remainder)
@@ -223,7 +220,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 5: Single-threaded mode with FIFO (should fail/exit)
@@ -285,7 +281,6 @@ log "ℹ INFO: SCHED_DEADLINE boosts: $deadline_boosts"
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 # Run with FIFO
 STALLD_LOG_FIFO="/tmp/stalld_test_force_fifo_comparison_$$.log"
@@ -304,7 +299,6 @@ log "ℹ INFO: SCHED_FIFO boosts: $fifo_boosts"
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 log "ℹ INFO: Comparison complete (DEADLINE: $deadline_boosts, FIFO: $fifo_boosts)"
 
diff --git a/tests/functional/test_foreground.sh b/tests/functional/test_foreground.sh
index 693d4b1..c5afbba 100755
--- a/tests/functional/test_foreground.sh
+++ b/tests/functional/test_foreground.sh
@@ -45,7 +45,6 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 fi
 
 stop_stalld
-sleep 1
 
 # Test 2: With -f flag, stalld should stay in foreground
 echo ""
diff --git a/tests/functional/test_pidfile.sh b/tests/functional/test_pidfile.sh
index 675b15e..155855e 100755
--- a/tests/functional/test_pidfile.sh
+++ b/tests/functional/test_pidfile.sh
@@ -68,7 +68,6 @@ if [ $default_found -eq 0 ]; then
 fi
 
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 2: Custom pidfile location
@@ -112,7 +111,6 @@ fi
 log ""
 log "Test 3: Verify pidfile removed on clean shutdown"
 stop_stalld
-sleep 1
 
 if [ ! -f "${custom_pidfile}" ]; then
     log "✓ PASS: Pidfile removed on clean shutdown"
@@ -156,7 +154,6 @@ else
 fi
 
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 5: Test with foreground mode
@@ -192,7 +189,6 @@ else
 fi
 
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 6: Invalid pidfile path (permission denied)
@@ -278,7 +274,6 @@ else
 fi
 
 stop_stalld
-sleep 1
 
 log ""
 log "All pidfile tests completed"
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index c2dda57..41a2cca 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -98,7 +98,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 2: Verify no detection before threshold
@@ -149,7 +148,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 3: Shorter threshold (3 seconds)
@@ -197,7 +195,6 @@ fi
 kill -TERM "${STARVE_PID}" 2>/dev/null
 wait "${STARVE_PID}" 2>/dev/null || true
 stop_stalld
-sleep 1
 
 #=============================================================================
 # Test 4: Invalid threshold values
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 11/36] tests/functional: Fix false positive log matching in test_logging_destinations
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (9 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 10/36] tests/functional: Remove redundant post-stop_stalld sleeps Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 12/36] tests/helpers: Rewrite wait_for_log_message() with process substitution Wander Lairson Costa
                   ` (24 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The test framework's log() function sends entries to the system
journal tagged with [stalld] via logger -t stalld. The logging
destination tests were using grep "stalld" to verify daemon
output in dmesg, syslog, and journalctl, which also matched
these framework entries and caused false passes.

Add a local has_stalld_log() filter function that matches
"stalld" while excluding lines containing "stalld". Use grep -F
for fixed-string matching throughout to avoid unnecessary regex
interpretation.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_logging_destinations.sh | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/tests/functional/test_logging_destinations.sh b/tests/functional/test_logging_destinations.sh
index 6fa4907..171be8b 100755
--- a/tests/functional/test_logging_destinations.sh
+++ b/tests/functional/test_logging_destinations.sh
@@ -21,6 +21,12 @@ setup_test_environment
 # Require root for this test
 require_root
 
+# Filter stdin for stalld daemon messages, excluding the test
+# framework's own log entries tagged with [TEST].
+has_stalld_log() {
+	grep -F "stalld" | grep -Fqv "[TEST]"
+}
+
 # Test 1: Verbose mode (-v) logs to stdout/stderr
 echo "Test 1: Verbose mode (-v) logs to stdout"
 
@@ -74,7 +80,7 @@ if command -v dmesg >/dev/null 2>&1; then
 		# Note: This might not work in all environments
 		if [ ${DMESG_AFTER} -gt ${DMESG_BEFORE} ]; then
 			# Check if recent dmesg contains stalld messages
-			if dmesg | tail -10 | grep -q "stalld"; then
+			if dmesg | tail -10 | has_stalld_log; then
 				assert_equals "1" "1" "stalld messages in kernel log"
 			else
 				echo -e "  ${YELLOW}SKIP${NC}: cannot verify kernel log messages"
@@ -113,7 +119,7 @@ if [ -n "${SYSLOG_FILE}" ]; then
 
 		if [ ${SYSLOG_AFTER} -gt ${SYSLOG_BEFORE} ]; then
 			# Check for stalld messages in recent syslog
-			if tail -20 "${SYSLOG_FILE}" | grep -q "stalld"; then
+			if tail -20 "${SYSLOG_FILE}" | has_stalld_log; then
 				assert_equals "1" "1" "stalld messages in syslog"
 			else
 				echo -e "  ${YELLOW}SKIP${NC}: no stalld messages found in syslog"
@@ -133,9 +139,9 @@ elif command -v journalctl >/dev/null 2>&1; then
 
 	if assert_process_running "${STALLD_PID}" "stalld with -s should be running"; then
 		# Check journalctl for stalld messages
-		if journalctl -u stalld --since "1 minute ago" 2>/dev/null | grep -q "stalld"; then
+		if journalctl -u stalld --since "1 minute ago" 2>/dev/null | has_stalld_log; then
 			assert_equals "1" "1" "stalld messages in journalctl"
-		elif journalctl --since "1 minute ago" 2>/dev/null | grep -q "stalld"; then
+		elif journalctl --since "1 minute ago" 2>/dev/null | has_stalld_log; then
 			assert_equals "1" "1" "stalld messages in system journal"
 		else
 			echo -e "  ${YELLOW}SKIP${NC}: no stalld messages in journal (may take time to appear)"
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 12/36] tests/helpers: Rewrite wait_for_log_message() with process substitution
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (10 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 11/36] tests/functional: Fix false positive log matching in test_logging_destinations Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 13/36] tests/helpers: Add wait_for_stalld_ready() and use in start_stalld_with_log() Wander Lairson Costa
                   ` (23 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

wait_for_log_message() uses a 1-second polling loop to check for
a pattern in a log file, adding up to 1 second of latency even
when the message appears instantly. It also has a journalctl
fallback that is unused by any caller and a misleading default
log file parameter (/var/log/syslog).

Rewrite the function to use process substitution instead of polling.
By feeding the output of a timeout-wrapped tail -f into grep -m1 -q
via process substitution, the function returns immediately when the
pattern appears. Process substitution is specifically required here
rather than a standard pipeline. In a pipeline, bash waits for both
processes to exit. Because tail -f blocks on inotify and does not
receive SIGPIPE, a pipeline would hang for the full timeout duration
even after grep finds a match.

Additionally, remove the unused journalctl fallback and make the log
file a required parameter since all callers pass one explicitly.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 41 +++++++++++++++--------------------
 1 file changed, 18 insertions(+), 23 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 8ff1b80..08838a2 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -500,36 +500,31 @@ handle_signal() {
 trap cleanup EXIT
 trap handle_signal INT TERM
 
-# Parse stalld log for specific message
+# Wait for a specific message to appear in a log file.
+# Uses tail -f piped through grep for instant detection -- returns
+# immediately when the pattern appears instead of sleeping between
+# polling intervals. Replays existing file content so messages
+# written before this function is called are also matched.
+#
+# Usage: wait_for_log_message <pattern> <timeout> <log_file>
 wait_for_log_message() {
 	local pattern=$1
 	local timeout=${2:-10}
-	local log_file=${3:-/var/log/syslog}
+	local log_file=$3
 
-	# If log_file doesn't exist, try journalctl
-	if [ ! -f "${log_file}" ]; then
-		# Using journalctl instead
-		local elapsed=0
-		while [ ${elapsed} -lt ${timeout} ]; do
-			if journalctl -u stalld --since "1 minute ago" 2>/dev/null | grep -q "${pattern}"; then
-				return 0
-			fi
-			sleep 1
-			elapsed=$((elapsed + 1))
-		done
+	if [ -z "${log_file}" ]; then
+		echo -e "${RED}ERROR: wait_for_log_message requires a log file${NC}"
 		return 1
 	fi
 
-	local elapsed=0
-	while [ ${elapsed} -lt ${timeout} ]; do
-		if grep -q "${pattern}" "${log_file}"; then
-			return 0
-		fi
-		sleep 1
-		elapsed=$((elapsed + 1))
-	done
-
-	return 1
+	# Process substitution runs tail in the background so bash
+	# only waits for grep to finish. A pipeline (tail | grep)
+	# would block until timeout kills tail even after grep has
+	# matched, because tail -f is blocked on inotify and never
+	# receives SIGPIPE.
+	grep -m1 -q "${pattern}" \
+		< <(timeout "${timeout}" tail -f -n +1 "${log_file}" 2>/dev/null)
+	return $?
 }
 
 # Get thread scheduling policy
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 13/36] tests/helpers: Add wait_for_stalld_ready() and use in start_stalld_with_log()
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (11 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 12/36] tests/helpers: Rewrite wait_for_log_message() with process substitution Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 14/36] tests/helpers: Fix fractional sleep timeout bugs Wander Lairson Costa
                   ` (22 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

start_stalld_with_log() checks for a non-empty log file to
determine if stalld has started, but this is a weak signal that
does not guarantee initialization is complete. Backend loading,
boost method detection, and thread setup may still be in progress.

Add wait_for_stalld_ready() that watches for a stalld log message,
which stalld prints at the end of its init sequence after all setup
is complete. Integrate it into start_stalld_with_log() to replace
the non-empty file check, and reuse stop_stalld() in the error path
instead of duplicating the kill logic.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 45 +++++++++++++----------------------
 1 file changed, 16 insertions(+), 29 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 08838a2..e4e9b23 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -501,10 +501,8 @@ trap cleanup EXIT
 trap handle_signal INT TERM
 
 # Wait for a specific message to appear in a log file.
-# Uses tail -f piped through grep for instant detection -- returns
-# immediately when the pattern appears instead of sleeping between
-# polling intervals. Replays existing file content so messages
-# written before this function is called are also matched.
+# Returns immediately when the pattern is found, or returns 1
+# after the timeout expires.
 #
 # Usage: wait_for_log_message <pattern> <timeout> <log_file>
 wait_for_log_message() {
@@ -527,6 +525,15 @@ wait_for_log_message() {
 	return $?
 }
 
+# Wait for stalld to complete initialization.
+#
+# Usage: wait_for_stalld_ready <log_file> [timeout]
+wait_for_stalld_ready() {
+	local log_file=$1
+	local timeout=${2:-15}
+	wait_for_log_message "checking cpu\|waiting tasks" "${timeout}" "${log_file}"
+}
+
 # Get thread scheduling policy
 get_thread_policy() {
 	local pid=$1
@@ -958,31 +965,11 @@ start_stalld_with_log() {
 	STALLD_PID=$!
 	CLEANUP_PIDS+=("${STALLD_PID}")
 
-	# Poll for stalld to start writing to the log file rather than
-	# using a fixed sleep. Brief initial sleep covers the fast path,
-	# then 1-second polling for slow systems (e.g. BPF init).
-	sleep 0.01
-	local timeout=15
-	local elapsed=0
-	while [ $elapsed -lt $timeout ]; do
-		if ! kill -0 ${STALLD_PID} 2>/dev/null; then
-			echo -e "${RED}ERROR: stalld exited during startup${NC}"
-			return 1
-		fi
-		if [ -s "${log_file}" ]; then
-			return 0
-		fi
-		sleep 1
-		elapsed=$((elapsed + 1))
-	done
-
-	echo -e "${RED}ERROR: stalld did not produce output within ${timeout}s${NC}"
-	kill ${STALLD_PID} 2>/dev/null
-	sleep 1
-	if kill -0 ${STALLD_PID} 2>/dev/null; then
-		kill -9 ${STALLD_PID} 2>/dev/null
+	if ! wait_for_stalld_ready "${log_file}" 15; then
+		echo -e "${RED}ERROR: stalld did not initialize within 15s${NC}"
+		stop_stalld
+		return 1
 	fi
-	return 1
 }
 
 # Wait for scheduling policy to change to expected value
@@ -1062,7 +1049,7 @@ export -f assert_equals assert_contains assert_not_contains
 export -f assert_file_exists assert_file_not_exists
 export -f assert_process_running assert_process_not_running
 export -f start_stalld stop_stalld kill_existing_stalld cleanup
-export -f wait_for_log_message
+export -f wait_for_log_message wait_for_stalld_ready
 export -f get_thread_policy get_thread_priority
 export -f create_cpu_load
 export -f detect_default_backend is_backend_available get_available_backends start_stalld_with_backend
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 14/36] tests/helpers: Fix fractional sleep timeout bugs
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (12 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 13/36] tests/helpers: Add wait_for_stalld_ready() and use in start_stalld_with_log() Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 15/36] tests/helpers: Flush stdout after starvation_gen startup messages Wander Lairson Costa
                   ` (21 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The cleanup() process termination loop and start_stalld() pidfile
wait both use fractional sleeps (0.1s and 0.5s) with integer
counters, causing the actual timeouts to be a fraction of the
nominal values. cleanup() with timeout=5 and sleep 0.1 gives only
0.5 seconds, and start_stalld() with timeout=15 and sleep 0.5
gives only 7.5 seconds.

Change both loops to use sleep 1, making the timeout arithmetic
correct. Restructure the cleanup loop to use the same two-phase
SIGTERM/SIGKILL pattern established in stop_stalld().

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index e4e9b23..06c0471 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -288,7 +288,7 @@ start_stalld() {
 		local timeout=15
 		local elapsed=0
 		while [ ! -f "$pidfile" ] && [ $elapsed -lt $timeout ]; do
-			sleep 0.5
+			sleep 1
 			elapsed=$((elapsed + 1))
 		done
 
@@ -452,21 +452,23 @@ cleanup() {
 		if [ -n "${pid}" ] && [ "${pid}" -gt 0 ] 2>/dev/null; then
 			# Check if process exists
 			if kill -0 ${pid} 2>/dev/null; then
-				# Try gentle kill first
 				kill ${pid} 2>/dev/null || true
-				# Give it a moment
-				sleep 0.1
-				# Force kill if still running
-				if kill -0 ${pid} 2>/dev/null; then
-					kill -9 ${pid} 2>/dev/null || true
-				fi
-				# Poll for termination (don't use wait - might not be a child)
+
 				local timeout=5
 				local elapsed=0
 				while kill -0 ${pid} 2>/dev/null && [ ${elapsed} -lt ${timeout} ]; do
-					sleep 0.1
+					sleep 1
 					elapsed=$((elapsed + 1))
 				done
+
+				if kill -0 ${pid} 2>/dev/null; then
+					kill -9 ${pid} 2>/dev/null || true
+					elapsed=0
+					while kill -0 ${pid} 2>/dev/null && [ ${elapsed} -lt ${timeout} ]; do
+						sleep 1
+						elapsed=$((elapsed + 1))
+					done
+				fi
 			fi
 		fi
 	done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 15/36] tests/helpers: Flush stdout after starvation_gen startup messages
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (13 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 14/36] tests/helpers: Fix fractional sleep timeout bugs Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 16/36] tests/helpers: Add start_starvation_gen() helper function Wander Lairson Costa
                   ` (20 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

starvation_gen prints its startup configuration (iteration target and
stop instructions) via printf, but does not flush stdout afterward.
When stdout is not connected to a terminal, libc switches to block
buffering, so these messages may not reach the test harness until the
buffer fills or the process exits.

Add an explicit fflush(stdout) after the startup printf calls to
ensure immediate delivery regardless of buffering mode.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/starvation_gen.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/helpers/starvation_gen.c b/tests/helpers/starvation_gen.c
index dc0e3de..81ee714 100644
--- a/tests/helpers/starvation_gen.c
+++ b/tests/helpers/starvation_gen.c
@@ -345,6 +345,7 @@ int main(int argc, char **argv) {
 	}
 	printf("\nBlockees will complete if boosted enough to finish %lu iterations\n", cfg.work_target);
 	printf("Press Ctrl+C to stop early\n");
+	fflush(stdout);
 
 	/*
 	 * Wait for specified duration OR until all blockees complete (whichever comes first).
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 16/36] tests/helpers: Add start_starvation_gen() helper function
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (14 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 15/36] tests/helpers: Flush stdout after starvation_gen startup messages Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 17/36] tests/helpers: Add wait_for_starvation_detected() and wait_for_boost_detected() Wander Lairson Costa
                   ` (19 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Tests currently launch starvation_gen in the background and sleep
1-2 seconds hoping all threads are created and pinned. This is
non-deterministic and wastes time.

Add start_starvation_gen() that redirects starvation_gen stdout to
a log file and polls for the "Press Ctrl+C to stop early\n" message
emitted after all threads pass the pthread barrier. A brief initial
sleep covers the fast path, then the function falls back to
1-second polling with a 10-second timeout. On timeout, the process
is killed with SIGTERM and escalated to SIGKILL if it does not exit
within one second. The function sets STARVE_PID and STARVE_LOG for
test use and tracks both in CLEANUP_PIDS and CLEANUP_FILES.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 58 ++++++++++++++++++++++++++++++++++-
 1 file changed, 57 insertions(+), 1 deletion(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 06c0471..276ffe8 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -1045,6 +1045,62 @@ init_functional_test() {
 	export TEST_CPU STALLD_CPU STARVE_GEN STALLD_LOG
 }
 
+# Start starvation_gen in background with readiness detection
+# Launches starvation_gen, redirects its stdout to a log file, and polls
+# for the "ready" message that starvation_gen prints after all threads
+# have passed the pthread barrier and are actively running/starving.
+#
+# Usage: start_starvation_gen [starvation_gen_args...]
+# Sets: STARVE_PID, STARVE_LOG
+# Example: start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d 15
+start_starvation_gen() {
+	local starve_bin="${TEST_ROOT}/helpers/starvation_gen"
+	if [ ! -x "${starve_bin}" ]; then
+		echo -e "${RED}ERROR: starvation_gen not found at ${starve_bin}${NC}"
+		return 1
+	fi
+
+	STARVE_LOG="/tmp/stalld_starvgen_$$.log"
+	CLEANUP_FILES+=("${STARVE_LOG}")
+
+	"${starve_bin}" "$@" > "${STARVE_LOG}" 2>&1 &
+	STARVE_PID=$!
+	CLEANUP_PIDS+=("${STARVE_PID}")
+
+	# Poll for "ready" message with timeout
+	# starvation_gen prints "ready" after all threads pass the barrier.
+	# Brief initial sleep covers the fast path, then 1-second polling
+	# for slow/loaded systems.
+	sleep 0.01
+
+	local timeout=10
+	local elapsed=0
+	while [ $elapsed -lt $timeout ]; do
+		if ! kill -0 ${STARVE_PID} 2>/dev/null; then
+			echo -e "${RED}ERROR: starvation_gen exited prematurely${NC}"
+			echo "  Log contents:"
+			cat "${STARVE_LOG}"
+			return 1
+		fi
+		if grep -q "Press Ctrl+C to stop early" "${STARVE_LOG}" 2>/dev/null; then
+			echo "starvation_gen ready (PID ${STARVE_PID})"
+			return 0
+		fi
+		sleep 1
+		elapsed=$((elapsed + 1))
+	done
+
+	echo -e "${RED}ERROR: starvation_gen did not become ready within ${timeout}s${NC}"
+	echo "  Log contents:"
+	cat "${STARVE_LOG}"
+	kill ${STARVE_PID} 2>/dev/null
+	sleep 1
+	if kill -0 ${STARVE_PID} 2>/dev/null; then
+		kill -9 ${STARVE_PID} 2>/dev/null
+	fi
+	return 1
+}
+
 # Export functions for use in tests
 export -f start_test end_test
 export -f assert_equals assert_contains assert_not_contains
@@ -1061,5 +1117,5 @@ export -f save_dl_server restore_dl_server disable_dl_server
 export -f setup_test_environment
 export -f get_num_cpus get_online_cpus pick_test_cpu
 export -f log get_sched_policy get_sched_priority get_nice_value get_ctxt_switches
-export -f start_stalld_with_log wait_for_policy_change
+export -f start_stalld_with_log start_starvation_gen wait_for_policy_change
 export -f calculate_detection_timeout init_functional_test
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 17/36] tests/helpers: Add wait_for_starvation_detected() and wait_for_boost_detected()
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (15 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 16/36] tests/helpers: Add start_starvation_gen() helper function Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 18/36] tests/functional: Use start_starvation_gen() helper Wander Lairson Costa
                   ` (18 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Tests that verify starvation detection and boosting currently use
hardcoded sleep calls to wait for stalld to act, making them
timing-dependent and flaky on slow systems. Subsequent commits will
replace ~48 such sleep calls across 10 test files.

Add two event-driven helpers that wrap wait_for_log_message() with
domain-specific patterns matching the log messages emitted by stalld
during starvation detection and task boosting. Both use a default
timeout of 30 seconds and return immediately when the event occurs.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 276ffe8..864036d 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -536,6 +536,24 @@ wait_for_stalld_ready() {
 	wait_for_log_message "checking cpu\|waiting tasks" "${timeout}" "${log_file}"
 }
 
+# Wait for stalld to detect a starving task.
+#
+# Usage: wait_for_starvation_detected <log_file> [timeout]
+wait_for_starvation_detected() {
+	local log_file=$1
+	local timeout=${2:-30}
+	wait_for_log_message "starved on CPU" "${timeout}" "${log_file}"
+}
+
+# Wait for stalld to boost a starving task.
+#
+# Usage: wait_for_boost_detected <log_file> [timeout]
+wait_for_boost_detected() {
+	local log_file=$1
+	local timeout=${2:-30}
+	wait_for_log_message "boosted pid" "${timeout}" "${log_file}"
+}
+
 # Get thread scheduling policy
 get_thread_policy() {
 	local pid=$1
@@ -1107,7 +1125,7 @@ export -f assert_equals assert_contains assert_not_contains
 export -f assert_file_exists assert_file_not_exists
 export -f assert_process_running assert_process_not_running
 export -f start_stalld stop_stalld kill_existing_stalld cleanup
-export -f wait_for_log_message wait_for_stalld_ready
+export -f wait_for_log_message wait_for_stalld_ready wait_for_starvation_detected wait_for_boost_detected
 export -f get_thread_policy get_thread_priority
 export -f create_cpu_load
 export -f detect_default_backend is_backend_available get_available_backends start_stalld_with_backend
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 18/36] tests/functional: Use start_starvation_gen() helper
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (16 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 17/36] tests/helpers: Add wait_for_starvation_detected() and wait_for_boost_detected() Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 19/36] tests/functional: Replace detection sleeps with event-driven helpers Wander Lairson Costa
                   ` (17 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Every functional test that creates starvation scenarios launches
starvation_gen manually: backgrounding the process, capturing the
PID, registering it for cleanup, and sleeping 1-3 seconds to wait
for startup. This duplicated boilerplate is fragile because the
sleep duration is an arbitrary guess that may be too short on slow
systems or too long on fast ones.

Replace all raw starvation_gen invocations across 14 test files
with the start_starvation_gen() helper introduced in Phase B. The
helper handles process management internally and uses event-driven
readiness detection instead of a fixed sleep, returning only after
starvation_gen confirms all threads are running. For tests that
need multiple simultaneous instances or use alternate PID variable
names, the global STARVE_PID is aliased immediately after the call.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_boost_duration.sh       | 16 ++------
 tests/functional/test_boost_period.sh         | 16 ++------
 tests/functional/test_boost_restoration.sh    | 24 +++---------
 tests/functional/test_boost_runtime.sh        | 16 ++------
 tests/functional/test_deadline_boosting.sh    | 26 ++++---------
 tests/functional/test_fifo_boosting.sh        | 35 +++---------------
 .../test_fifo_priority_starvation.sh          | 35 +++---------------
 tests/functional/test_force_fifo.sh           | 24 +++---------
 tests/functional/test_idle_detection.sh       |  8 +---
 tests/functional/test_log_only.sh             |  8 +---
 tests/functional/test_runqueue_parsing.sh     | 24 +++---------
 tests/functional/test_starvation_detection.sh | 37 ++++---------------
 tests/functional/test_starvation_threshold.sh | 21 ++---------
 tests/functional/test_task_merging.sh         | 22 ++++-------
 14 files changed, 70 insertions(+), 242 deletions(-)

diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index 4df5489..c835fd3 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -63,9 +63,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l > "${STA
 # Create starvation
 starvation_duration=15
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 wait_time=$((threshold + 2))
@@ -102,9 +100,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${short_
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection"
@@ -141,9 +137,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${long_d
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${long_starvation}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${long_starvation} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${long_starvation}
 
 # Wait for detection
 log "Waiting ${wait_time}s for detection"
@@ -179,9 +173,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${durati
 
 # Create starvation with a specific task we can track
 log "Creating starvation on CPU ${TEST_CPU} for 15s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 1 -d 15 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 1 -d 15
 
 # Wait for detection
 log "Waiting ${wait_time}s for detection"
diff --git a/tests/functional/test_boost_period.sh b/tests/functional/test_boost_period.sh
index 97e0427..aab4436 100755
--- a/tests/functional/test_boost_period.sh
+++ b/tests/functional/test_boost_period.sh
@@ -60,9 +60,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -N > "${STALL
 # Create starvation
 starvation_duration=$((threshold + 5))
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 wait_time=$((threshold + 2))
@@ -104,9 +102,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $custom_pe
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 sleep ${wait_time}
@@ -139,9 +135,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $short_per
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 sleep ${wait_time}
@@ -174,9 +168,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $long_peri
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 sleep ${wait_time}
diff --git a/tests/functional/test_boost_restoration.sh b/tests/functional/test_boost_restoration.sh
index 1b8f809..beadf47 100755
--- a/tests/functional/test_boost_restoration.sh
+++ b/tests/functional/test_boost_restoration.sh
@@ -64,12 +64,9 @@ start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_dura
 
 # Create starvation (starvation_gen creates SCHED_FIFO blocker prio 80, blockee prio 1)
 log "Creating starvation with SCHED_FIFO tasks (blocker prio 80, blockee prio 1)"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -b 1 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -b 1 -n 1 -d 20
 
 # Find the starved task
-sleep 2
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
 tracked_pid=""
 for child_pid in ${STARVE_CHILDREN}; do
@@ -201,11 +198,8 @@ chmod +x /tmp/fifo_task_$$.sh /tmp/fifo_task_running_$$.sh
 CLEANUP_FILES+=("/tmp/fifo_task_$$.sh" "/tmp/fifo_task_running_$$.sh")
 
 # Also create the blocker that will starve our FIFO task
-"${STARVE_GEN}" -c ${TEST_CPU} -p 90 -n 1 -d 20 &
-BLOCKER_PID=$!
-CLEANUP_PIDS+=("${BLOCKER_PID}")
-
-sleep 1
+start_starvation_gen -c ${TEST_CPU} -p 90 -n 1 -d 20
+BLOCKER_PID=${STARVE_PID}
 
 # Start our FIFO task on the same CPU (it will starve)
 bash /tmp/fifo_task_running_$$.sh &
@@ -294,9 +288,7 @@ start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_dura
 
 # Use -o flag to create SCHED_OTHER blockees
 log "Creating SCHED_OTHER starvation (RT blocker prio 80, SCHED_OTHER blockee)"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -o -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -o -n 1 -d 20
 
 # Wait for starvation_gen to complete
 log "Waiting for starvation test to complete..."
@@ -326,9 +318,7 @@ start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_dura
 
 # Create starvation
 log "Creating starvation"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
 # Wait for starvation detection
 sleep $((threshold + 1))
@@ -389,9 +379,7 @@ start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_dura
 # This ensures the task exits DURING the boost period
 short_duration=$((threshold - 2))
 log "Creating starvation that will exit after ${short_duration}s"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d ${short_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d ${short_duration}
 
 # Give stalld time to detect starvation and start boosting
 # Need: threshold (10s) + buffer for detection (2s) = 12s
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index 0475da5..b8dfd1f 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -63,9 +63,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l > "${STA
 # Create starvation
 starvation_duration=10
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 wait_time=$((threshold + 2))
@@ -102,9 +100,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${custom
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection and boosting"
@@ -140,9 +136,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${large_
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection and boosting"
@@ -180,9 +174,7 @@ start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${valid_
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection and boosting"
diff --git a/tests/functional/test_deadline_boosting.sh b/tests/functional/test_deadline_boosting.sh
index d715186..de72100 100755
--- a/tests/functional/test_deadline_boosting.sh
+++ b/tests/functional/test_deadline_boosting.sh
@@ -63,9 +63,7 @@ start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALL
 # Create starvation
 starvation_duration=$((threshold + 8))
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting (threshold + granularity + buffer)
 # With -g 1, stalld checks every 1 second. In worst case, it checks just before
@@ -131,9 +129,7 @@ start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} \
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 15 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
 
 # Wait for boosting (threshold + granularity + buffer)
 wait_time=$((threshold + 1 + 3))
@@ -192,9 +188,7 @@ start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
 # Wait for starvation detection (threshold + granularity + buffer)
 wait_time=$((threshold + 1 + 3))
@@ -267,9 +261,7 @@ start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
 # Find a starved task and verify initial policy
 sleep 2
@@ -362,15 +354,13 @@ else
 
     # Create starvation on CPU0
     log "Creating starvation on CPU ${CPU0}"
-    "${STARVE_GEN}" -c ${CPU0} -p 80 -n 1 -d 15 &
-    STARVE_PID0=$!
-    CLEANUP_PIDS+=("${STARVE_PID0}")
+    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 15
+    STARVE_PID0=${STARVE_PID}
 
     # Create starvation on CPU1
     log "Creating starvation on CPU ${CPU1}"
-    "${STARVE_GEN}" -c ${CPU1} -p 80 -n 1 -d 15 &
-    STARVE_PID1=$!
-    CLEANUP_PIDS+=("${STARVE_PID1}")
+    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 15
+    STARVE_PID1=${STARVE_PID}
 
     # Wait for detection and boosting (threshold + granularity + buffer)
     wait_time=$((threshold + 1 + 3))
diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index 7fbe297..a6168e9 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -56,12 +56,7 @@ threshold=5
 # Create starvation FIRST (before stalld starts)
 starvation_duration=$((threshold + 8))
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and create actual starvation
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with -F flag to force SCHED_FIFO boosting"
 # Note: -F requires non-single-threaded mode (aggressive mode)
@@ -109,12 +104,7 @@ rm -f "${STALLD_LOG}"
 
 # Create starvation FIRST
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 15 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and get child PIDs while they exist
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
 log "Starvation generator children PIDs: ${STARVE_CHILDREN}"
 
@@ -183,12 +173,7 @@ rm -f "${STALLD_LOG}"
 
 # Create starvation FIRST
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
 start_stalld -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} \
     -d ${boost_duration} -p ${boost_period} -r ${boost_runtime} \
@@ -233,12 +218,7 @@ STALLD_LOG_DEADLINE="/tmp/stalld_test_deadline_compare_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG_DEADLINE}")
 
 # Create starvation FIRST
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d 15 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Find a starved task and capture context switches immediately
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d 15
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
 deadline_tracked_pid=""
 for child_pid in ${STARVE_CHILDREN}; do
@@ -281,12 +261,7 @@ STALLD_LOG_FIFO="/tmp/stalld_test_fifo_compare_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG_FIFO}")
 
 # Create starvation FIRST
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d 15 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Find a starved task and capture context switches immediately
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d 15
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
 fifo_tracked_pid=""
 for child_pid in ${STARVE_CHILDREN}; do
diff --git a/tests/functional/test_fifo_priority_starvation.sh b/tests/functional/test_fifo_priority_starvation.sh
index 3459f3c..cf47971 100755
--- a/tests/functional/test_fifo_priority_starvation.sh
+++ b/tests/functional/test_fifo_priority_starvation.sh
@@ -64,12 +64,7 @@ starvation_duration=$((threshold + 5))
 log "Creating FIFO-on-FIFO starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 log "  Blocker: SCHED_FIFO priority 10"
 log "  Blockee: SCHED_FIFO priority 5"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 10 -b 5 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and pin to CPU
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
@@ -124,12 +119,7 @@ threshold=5
 boost_duration=3
 
 log "Creating FIFO-on-FIFO starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 10 -b 5 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 1 -d 20
 
 # Find the starved task (blockee) PID
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
@@ -198,12 +188,7 @@ threshold=3
 # Create long starvation to trigger multiple detection cycles
 starvation_duration=15
 log "Creating long FIFO-on-FIFO starvation for ${starvation_duration}s"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 10 -b 5 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 log "Will monitor for multiple detection cycles to verify timestamp preservation"
@@ -267,12 +252,7 @@ threshold=5
 log "Creating FIFO-on-FIFO starvation with close priorities"
 log "  Blocker: SCHED_FIFO priority 6"
 log "  Blockee: SCHED_FIFO priority 5"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 6 -b 5 -n 1 -d $((threshold + 5)) &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 6 -b 5 -n 1 -d $((threshold + 5))
 
 log "Starting stalld with ${threshold}s threshold"
 start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
@@ -309,12 +289,7 @@ rm -f "${STALLD_LOG}"
 threshold=5
 
 log "Creating FIFO-on-FIFO starvation"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 10 -b 5 -n 2 -d 20 -v &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and print PIDs
-sleep 3
+start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d 20 -v
 
 # Extract blocker and blockee PIDs from starvation_gen output
 # The output shows "Blocker TID: <pid>" and "Blockee N TID: <pid>"
diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index cab8003..4c43101 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -56,9 +56,7 @@ start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} > "${STALLD_LOG}" 2>&1
 # Create starvation
 starvation_duration=10
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 wait_time=$((threshold + 2))
@@ -104,9 +102,7 @@ start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A > "${STALLD_LOG2}" 2>&
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection and boosting"
@@ -150,9 +146,7 @@ start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A > "${STALLD_LOG3}" 2>&
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection and boosting"
@@ -192,9 +186,7 @@ start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A -d ${boost_duration} >
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${long_starvation}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${long_starvation} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${long_starvation}
 
 # Wait for detection and boosting
 log "Waiting ${wait_time}s for detection and boosting"
@@ -270,9 +262,7 @@ CLEANUP_FILES+=("${STALLD_LOG_DL}")
 
 log "Running comparison test with SCHED_DEADLINE"
 start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -d ${comparison_duration} > "${STALLD_LOG_DL}" 2>&1
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${comparison_starvation} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${comparison_starvation}
 sleep $((threshold + 3))
 
 deadline_boosts=$(grep -c "boost" "${STALLD_LOG_DL}" || echo 0)
@@ -288,9 +278,7 @@ CLEANUP_FILES+=("${STALLD_LOG_FIFO}")
 
 log "Running comparison test with SCHED_FIFO"
 start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A -d ${comparison_duration} > "${STALLD_LOG_FIFO}" 2>&1
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${comparison_starvation} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${comparison_starvation}
 sleep $((threshold + 3))
 
 fifo_boosts=$(grep -c "boost" "${STALLD_LOG_FIFO}" || echo 0)
diff --git a/tests/functional/test_idle_detection.sh b/tests/functional/test_idle_detection.sh
index c3aa565..755a4b5 100755
--- a/tests/functional/test_idle_detection.sh
+++ b/tests/functional/test_idle_detection.sh
@@ -160,9 +160,7 @@ sleep 3
 
 # Now create load to make CPU busy
 log "Creating load on CPU ${TEST_CPU} to make it busy"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d 12 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d 12
 
 # Wait for stalld to detect the busy CPU and starvation (threshold + granularity + buffer)
 log "Waiting for stalld to detect busy CPU and starvation..."
@@ -232,9 +230,7 @@ else
 
     # Create load only on CPU1, leave CPU0 idle
     log "Creating load on CPU ${CPU1} only"
-    "${STARVE_GEN}" -c ${CPU1} -p 80 -n 2 -d 12 &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${CPU1} -p 80 -n 2 -d 12
 
     # Wait for detection (threshold + granularity + buffer)
     wait_time=$((threshold + 1 + 3))
diff --git a/tests/functional/test_log_only.sh b/tests/functional/test_log_only.sh
index cfd1b93..93c71cb 100755
--- a/tests/functional/test_log_only.sh
+++ b/tests/functional/test_log_only.sh
@@ -47,12 +47,8 @@ CLEANUP_FILES+=("${LOG_FILE}")
 echo "Creating starvation on CPU ${TEST_CPU} (will run for 15 seconds)"
 
 # Start starvation generator BEFORE stalld to ensure CPU is busy from the start
-${TEST_ROOT}/helpers/starvation_gen -c ${TEST_CPU} -p 10 -n 1 -d 15 &
-STARVGEN_PID=$!
-CLEANUP_PIDS+=("${STARVGEN_PID}")
-
-# Give the starvation generator time to start and monopolize the CPU
-sleep 1
+start_starvation_gen -c ${TEST_CPU} -p 10 -n 1 -d 15
+STARVGEN_PID=${STARVE_PID}
 
 # Start stalld in log-only mode with verbose output to capture logs
 echo "Starting stalld in log-only mode with 5 second threshold"
diff --git a/tests/functional/test_runqueue_parsing.sh b/tests/functional/test_runqueue_parsing.sh
index 2f23910..7edf936 100755
--- a/tests/functional/test_runqueue_parsing.sh
+++ b/tests/functional/test_runqueue_parsing.sh
@@ -98,9 +98,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
     # Create starvation to generate task data
     starvation_duration=$((threshold + 5))
     log "Creating starvation for ${starvation_duration}s"
-    "${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
     # Wait for detection (threshold + granularity + buffer)
     wait_time=$((threshold + 1 + 3))
@@ -159,9 +157,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     # Create starvation
     starvation_duration=$((threshold + 5))
     log "Creating starvation for ${starvation_duration}s"
-    "${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
     # Wait for detection (threshold + granularity + buffer)
     wait_time=$((threshold + 1 + 3))
@@ -229,9 +225,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     rm -f "${STALLD_LOG_BPF}"
     start_stalld -f -v -g 1 -l -b queue_track -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_BPF}" 2>&1
 
-    "${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
     wait_time=$((threshold + 1 + 3))
     sleep ${wait_time}
@@ -251,9 +245,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     rm -f "${STALLD_LOG_SCHED}"
     start_stalld -f -v -g 1 -l -b sched_debug -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_SCHED}" 2>&1
 
-    "${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
     wait_time=$((threshold + 1 + 3))
     sleep ${wait_time}
@@ -320,9 +312,7 @@ if [ -n "$test_backend" ]; then
 
     # Create starvation with known parameters
     log "Creating starvation with known task name (starvation_gen)"
-    "${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 10 -v &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 10 -v
 
     # Wait for detection (threshold + granularity + buffer)
     wait_time=$((threshold + 1 + 3))
@@ -384,9 +374,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     start_stalld -f -v -g 1 -l -b sched_debug -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_SCHED}" 2>&1
 
     # Create brief starvation just to initialize the backend
-    "${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 8 &
-    STARVE_PID=$!
-    CLEANUP_PIDS+=("${STARVE_PID}")
+    start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 8
 
     wait_time=$((threshold + 1 + 3))
     sleep ${wait_time}
diff --git a/tests/functional/test_starvation_detection.sh b/tests/functional/test_starvation_detection.sh
index 89901d3..6a1821d 100755
--- a/tests/functional/test_starvation_detection.sh
+++ b/tests/functional/test_starvation_detection.sh
@@ -67,12 +67,7 @@ threshold=5
 # Create starvation BEFORE starting stalld to avoid idle detection race
 starvation_duration=$((threshold + 5))
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and pin to CPU
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
@@ -129,12 +124,7 @@ threshold=5
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 15 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
 
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
@@ -194,12 +184,7 @@ threshold=3
 # Create long starvation to trigger multiple detection cycles
 starvation_duration=15
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start
-sleep 2
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 log "Will monitor for multiple detection cycles to verify timestamp preservation"
@@ -292,18 +277,13 @@ else
 
     # Create starvation on CPU0
     log "Creating starvation on CPU ${CPU0}"
-    "${STARVE_GEN}" -c ${CPU0} -p 80 -n 1 -d 12 &
-    STARVE_PID0=$!
-    CLEANUP_PIDS+=("${STARVE_PID0}")
+    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 12
+    STARVE_PID0=${STARVE_PID}
 
     # Create starvation on CPU1
     log "Creating starvation on CPU ${CPU1}"
-    "${STARVE_GEN}" -c ${CPU1} -p 80 -n 1 -d 12 &
-    STARVE_PID1=$!
-    CLEANUP_PIDS+=("${STARVE_PID1}")
-
-    # Give starvation generators time to start
-    sleep 2
+    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 12
+    STARVE_PID1=${STARVE_PID}
 
     start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU_MULTI}
 
@@ -394,8 +374,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a $
 
 # Create short-lived starvation that exits before threshold
 log "Creating short-lived starvation (3s, less than ${threshold}s threshold)"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 3 &
-STARVE_PID=$!
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 3
 
 # Wait for task to exit
 sleep 5
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index 41a2cca..45a4679 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -65,12 +65,7 @@ threshold=5
 # Create starvation BEFORE starting stalld (avoid detecting kworker tasks)
 starvation_duration=10
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and create actual starvation
-sleep 2
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold"
 # Use -i to ignore kernel workers that may starve before our test tasks
@@ -115,12 +110,7 @@ CLEANUP_FILES+=("${STALLD_LOG2}")
 # Create starvation that will last 6 seconds (less than threshold)
 starvation_duration=6
 log "Creating short starvation (${starvation_duration}s) with threshold of ${threshold}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and create actual starvation
-sleep 2
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold"
 start_stalld -f -v -N -M -g 1 -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold} > "${STALLD_LOG2}" 2>&1
@@ -165,12 +155,7 @@ CLEANUP_FILES+=("${STALLD_LOG3}")
 # Create starvation for 8 seconds
 starvation_duration=8
 log "Creating starvation for ${starvation_duration}s with threshold of ${threshold}s"
-"${STARVE_GEN}" -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
-
-# Give starvation generator time to start and create actual starvation
-sleep 2
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold"
 # Use -i to ignore kernel workers that may starve before our test tasks
diff --git a/tests/functional/test_task_merging.sh b/tests/functional/test_task_merging.sh
index 053c648..1bea759 100755
--- a/tests/functional/test_task_merging.sh
+++ b/tests/functional/test_task_merging.sh
@@ -70,9 +70,7 @@ start_stalld -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${ST
 # Create long starvation to span multiple monitoring cycles
 starvation_duration=18
 log "Creating starvation for ${starvation_duration}s (multiple detection cycles)"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration} &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for first detection (threshold + granularity + buffer)
 log "Waiting for first detection cycle..."
@@ -150,9 +148,7 @@ start_stalld -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${ST
 
 # Create starvation
 log "Creating starvation"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
 # Wait for detection (threshold + granularity + buffer)
 wait_time=$((threshold + 1 + 3))
@@ -226,9 +222,7 @@ start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d 2 > "${
 
 # Create starvation that will get boosted (allowing progress)
 log "Creating starvation that will be boosted"
-"${STARVE_GEN}" -c ${TEST_CPU} -p 80 -n 1 -d 20 &
-STARVE_PID=$!
-CLEANUP_PIDS+=("${STARVE_PID}")
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
 # Wait for first detection and boost (threshold + granularity + buffer)
 wait_time=$((threshold + 1 + 3))
@@ -296,14 +290,12 @@ else
 
     # Create starvation on both CPUs
     log "Creating starvation on CPU ${CPU0}"
-    "${STARVE_GEN}" -c ${CPU0} -p 80 -n 1 -d 15 &
-    STARVE_PID0=$!
-    CLEANUP_PIDS+=("${STARVE_PID0}")
+    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 15
+    STARVE_PID0=${STARVE_PID}
 
     log "Creating starvation on CPU ${CPU1}"
-    "${STARVE_GEN}" -c ${CPU1} -p 80 -n 1 -d 15 &
-    STARVE_PID1=$!
-    CLEANUP_PIDS+=("${STARVE_PID1}")
+    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 15
+    STARVE_PID1=${STARVE_PID}
 
     # Wait for multiple detection cycles (threshold + granularity + buffer, then more)
     wait_time=$((threshold + 1 + 3))
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 19/36] tests/functional: Replace detection sleeps with event-driven helpers
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (17 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 18/36] tests/functional: Use start_starvation_gen() helper Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 20/36] tests/functional: Remove duplicated -a flag in test_fifo_priority_starvation Wander Lairson Costa
                   ` (16 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Functional tests wait for stalld to detect starvation or boost tasks
by sleeping a fixed duration then grepping the log. These hardcoded
sleeps add unnecessary latency on fast systems and can be too short
on slow ones, causing flaky results.

Replace the sleep-then-grep pattern with wait_for_starvation_detected()
and wait_for_boost_detected(), which use tail -f internally to return
as soon as the expected log message appears. For tests that combine
detection and boost-expiry waits in a single sleep, split them into
a helper call for detection followed by a duration sleep for expiry.
Sleeps for negative tests, boost expiry, and measurement windows are
intentionally preserved.

Additionally, migrate all start_stalld calls that redirect to a log
file to use start_stalld_with_log(), which provides event-driven
readiness detection and adds -g 1 for faster scan cycles. Without
this, stalld's default 5-second granularity caused detection timeouts
on systems where the starvation window was too short. Fix a pre-existing
bug in test_boost_duration where -d 10 exceeded -t 3, causing stalld
to reject the arguments. Fix a pre-existing bug in test_boost_period
where -p values of 100ms and 10s were rejected by stalld.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_boost_duration.sh       | 42 +++++----------
 tests/functional/test_boost_period.sh         | 46 ++++++----------
 tests/functional/test_boost_restoration.sh    | 28 ++++------
 tests/functional/test_boost_runtime.sh        | 33 +++---------
 tests/functional/test_deadline_boosting.sh    | 52 +++++++------------
 tests/functional/test_fifo_boosting.sh        | 50 +++++++++---------
 .../test_fifo_priority_starvation.sh          | 36 ++++++-------
 tests/functional/test_force_fifo.sh           | 37 +++++--------
 tests/functional/test_idle_detection.sh       | 17 +++---
 tests/functional/test_log_only.sh             |  5 +-
 tests/functional/test_runqueue_parsing.sh     | 31 ++++-------
 tests/functional/test_starvation_detection.sh | 39 +++++---------
 tests/functional/test_starvation_threshold.sh | 25 ++++-----
 tests/functional/test_task_merging.sh         | 28 +++++-----
 14 files changed, 176 insertions(+), 293 deletions(-)

diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index c835fd3..af657db 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -58,20 +58,15 @@ log "=========================================="
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default boost duration)"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l
 
 # Create starvation
 starvation_duration=15
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting
-wait_time=$((threshold + 2))
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if boosting occurred (in log-only mode we look for detection messages)
-if grep -qi "detect\|starv" "${STALLD_LOG}"; then
+# Wait for starvation detection
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: Starvation detection occurred with default duration"
 else
     log "✗ FAIL: No starvation detection"
@@ -96,18 +91,14 @@ STALLD_LOG2="/tmp/stalld_test_boost_duration_test2_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG2}")
 
 log "Starting stalld with ${threshold}s threshold and ${short_duration}s boost duration"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${short_duration} -l > "${STALLD_LOG2}" 2>&1
+start_stalld_with_log "${STALLD_LOG2}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${short_duration} -l
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting
-log "Waiting ${wait_time}s for detection"
-sleep ${wait_time}
-
-# Check if detection occurred
-if grep -qi "detect\|starv" "${STALLD_LOG2}"; then
+# Wait for starvation detection
+if wait_for_starvation_detected "${STALLD_LOG2}"; then
     log "✓ PASS: Starvation detection with ${short_duration}s duration"
 else
     log "✗ FAIL: No starvation detection with short duration"
@@ -129,22 +120,19 @@ log "=========================================="
 
 long_duration=10
 long_starvation=20
+threshold=10
 STALLD_LOG3="/tmp/stalld_test_boost_duration_test3_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG3}")
 
 log "Starting stalld with ${threshold}s threshold and ${long_duration}s boost duration"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${long_duration} -l > "${STALLD_LOG3}" 2>&1
+start_stalld_with_log "${STALLD_LOG3}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${long_duration} -l
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${long_starvation}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${long_starvation}
 
-# Wait for detection
-log "Waiting ${wait_time}s for detection"
-sleep ${wait_time}
-
-# Check if detection occurred
-if grep -qi "detect\|starv" "${STALLD_LOG3}"; then
+# Wait for starvation detection
+if wait_for_starvation_detected "${STALLD_LOG3}"; then
     log "✓ PASS: Starvation detection with ${long_duration}s duration"
 else
     log "✗ FAIL: No starvation detection with long duration"
@@ -164,22 +152,20 @@ log "=========================================="
 log "Test 4: Verify policy restoration after boost duration"
 log "=========================================="
 
+threshold=3
 duration=2
 STALLD_LOG4="/tmp/stalld_test_boost_duration_test4_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG4}")
 
 log "Starting stalld with ${threshold}s threshold and ${duration}s boost duration"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${duration} -l > "${STALLD_LOG4}" 2>&1
+start_stalld_with_log "${STALLD_LOG4}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${duration} -l
 
 # Create starvation with a specific task we can track
 log "Creating starvation on CPU ${TEST_CPU} for 15s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 1 -d 15
 
-# Wait for detection
-log "Waiting ${wait_time}s for detection"
-sleep ${wait_time}
-
-if grep -qi "detect\|starv" "${STALLD_LOG4}"; then
+# Wait for starvation detection
+if wait_for_starvation_detected "${STALLD_LOG4}"; then
     log "✓ PASS: Starvation detection with ${duration}s boost duration"
 else
     log "✗ FAIL: No starvation detection"
diff --git a/tests/functional/test_boost_period.sh b/tests/functional/test_boost_period.sh
index aab4436..4838672 100755
--- a/tests/functional/test_boost_period.sh
+++ b/tests/functional/test_boost_period.sh
@@ -55,20 +55,15 @@ log "=========================================="
 
 threshold=5
 log "Starting stalld with default period"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -N > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -N
 
 # Create starvation
 starvation_duration=$((threshold + 5))
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting
-wait_time=$((threshold + 2))
-log "Waiting ${wait_time}s for starvation detection and boosting..."
-sleep ${wait_time}
-
-# Check if boosting occurred
-if grep -q "boost" "${STALLD_LOG}"; then
+# Wait for starvation detection and boosting
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred with default period"
 
     # Try to find period value in logs
@@ -98,17 +93,14 @@ log "=========================================="
 custom_period=500000000
 rm -f "${STALLD_LOG}"
 log "Starting stalld with custom period ${custom_period} ns"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $custom_period -N > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $custom_period -N
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting
-sleep ${wait_time}
-
-# Check if boosting occurred
-if grep -q "boost" "${STALLD_LOG}"; then
+# Wait for starvation detection and boosting
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred with custom period ${custom_period} ns"
 else
     log "✗ FAIL: No boosting with custom period"
@@ -125,23 +117,20 @@ stop_stalld
 #=============================================================================
 log ""
 log "=========================================="
-log "Test 3: Very short period of 100,000,000 ns (100ms)"
+log "Test 3: Short period of 200,000,000 ns (200ms)"
 log "=========================================="
 
-short_period=100000000
+short_period=200000000
 rm -f "${STALLD_LOG}"
 log "Starting stalld with short period ${short_period} ns"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $short_period -N > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $short_period -N
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting
-sleep ${wait_time}
-
-# Check if boosting occurred
-if grep -q "boost" "${STALLD_LOG}"; then
+# Wait for starvation detection and boosting
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred with short period ${short_period} ns"
 else
     log "✗ FAIL: No boosting with short period"
@@ -158,23 +147,20 @@ stop_stalld
 #=============================================================================
 log ""
 log "=========================================="
-log "Test 4: Very long period of 10,000,000,000 ns (10s)"
+log "Test 4: Long period of 3,000,000,000 ns (3s)"
 log "=========================================="
 
-long_period=10000000000
+long_period=3000000000
 rm -f "${STALLD_LOG}"
 log "Starting stalld with long period ${long_period} ns"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $long_period -N > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t $threshold -p $long_period -N
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting
-sleep ${wait_time}
-
-# Check if boosting occurred
-if grep -q "boost" "${STALLD_LOG}"; then
+# Wait for starvation detection and boosting
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred with long period ${long_period} ns"
 else
     log "✗ FAIL: No boosting with long period"
diff --git a/tests/functional/test_boost_restoration.sh b/tests/functional/test_boost_restoration.sh
index beadf47..fc5a25a 100755
--- a/tests/functional/test_boost_restoration.sh
+++ b/tests/functional/test_boost_restoration.sh
@@ -60,7 +60,7 @@ boost_duration=3
 
 log "Starting stalld with ${boost_duration}s boost duration"
 # Use -i to ignore kworkers so stalld focuses on our test workload
-start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker" > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker"
 
 # Create starvation (starvation_gen creates SCHED_FIFO blocker prio 80, blockee prio 1)
 log "Creating starvation with SCHED_FIFO tasks (blocker prio 80, blockee prio 1)"
@@ -92,7 +92,7 @@ if [ -n "${tracked_pid}" ]; then
 
     # Wait for starvation detection and boosting
     log "Waiting for starvation detection and boost..."
-    sleep $((threshold + 1))
+    wait_for_boost_detected "${STALLD_LOG}"
 
     # Check policy during boost (should be DEADLINE=6)
     if [ -f "/proc/${tracked_pid}/sched" ]; then
@@ -170,7 +170,7 @@ boost_duration=3
 
 log "Starting stalld"
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker" > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker"
 
 # Create a SCHED_FIFO task that will starve
 # We'll create our own RT task instead of using starvation_gen
@@ -226,7 +226,7 @@ if chrt -f -p 10 ${FIFO_TASK_PID} 2>/dev/null; then
 
     # Wait for starvation detection
     log "Waiting for starvation detection and boost..."
-    sleep $((threshold + 1))
+    wait_for_boost_detected "${STALLD_LOG}"
 
     # Check if boosted
     if [ -f "/proc/${FIFO_TASK_PID}/sched" ]; then
@@ -284,7 +284,7 @@ boost_duration=3
 
 log "Starting stalld"
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker" > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker"
 
 # Use -o flag to create SCHED_OTHER blockees
 log "Creating SCHED_OTHER starvation (RT blocker prio 80, SCHED_OTHER blockee)"
@@ -314,17 +314,14 @@ boost_duration=4  # 4 second boost
 
 log "Starting stalld with ${boost_duration}s boost duration"
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker" > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker"
 
 # Create starvation
 log "Creating starvation"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
-# Wait for starvation detection
-sleep $((threshold + 1))
-
-# Check when boost occurred
-if grep -q "boosted" "${STALLD_LOG}"; then
+# Wait for starvation detection and boosting
+if wait_for_boost_detected "${STALLD_LOG}"; then
     boost_time=$(date +%s)
     log "Boost detected at timestamp: ${boost_time}"
 
@@ -373,7 +370,7 @@ boost_duration=5  # Task will exit during boost (after 8s, boost is 5s)
 
 log "Starting stalld with ${threshold}s threshold, ${boost_duration}s boost (task will exit during boost)"
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker" > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} -N -i "kworker"
 
 # Create starvation that exits after threshold - 2s (so 8s)
 # This ensures the task exits DURING the boost period
@@ -381,11 +378,8 @@ short_duration=$((threshold - 2))
 log "Creating starvation that will exit after ${short_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d ${short_duration}
 
-# Give stalld time to detect starvation and start boosting
-# Need: threshold (10s) + buffer for detection (2s) = 12s
-sleep $((threshold + 2))
-
-if grep -q "boosted" "${STALLD_LOG}"; then
+# Wait for starvation detection and boosting
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boost occurred"
 
     # At this point (12s), starvation_gen has exited (at 8s) during the boost
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index b8dfd1f..734a6f8 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -58,7 +58,7 @@ log "=========================================="
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default boost runtime)"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l
 
 # Create starvation
 starvation_duration=10
@@ -66,12 +66,7 @@ log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-wait_time=$((threshold + 2))
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if detection occurred
-if grep -qi "detect\|starv" "${STALLD_LOG}"; then
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: Starvation detection with default runtime"
 else
     log "✗ FAIL: No starvation detection"
@@ -96,18 +91,14 @@ STALLD_LOG2="/tmp/stalld_test_boost_runtime_test2_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG2}")
 
 log "Starting stalld with ${threshold}s threshold and ${custom_runtime}ns runtime"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${custom_runtime} -l > "${STALLD_LOG2}" 2>&1
+start_stalld_with_log "${STALLD_LOG2}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${custom_runtime} -l
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if detection occurred
-if grep -qi "detect\|starv" "${STALLD_LOG2}"; then
+if wait_for_starvation_detected "${STALLD_LOG2}"; then
     log "✓ PASS: Starvation detection with custom runtime ${custom_runtime}ns"
 else
     log "✗ FAIL: No starvation detection with custom runtime"
@@ -132,18 +123,14 @@ STALLD_LOG3="/tmp/stalld_test_boost_runtime_test3_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG3}")
 
 log "Starting stalld with ${threshold}s threshold and ${large_runtime}ns runtime"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${large_runtime} -l > "${STALLD_LOG3}" 2>&1
+start_stalld_with_log "${STALLD_LOG3}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${large_runtime} -l
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if detection occurred
-if grep -qi "detect\|starv" "${STALLD_LOG3}"; then
+if wait_for_starvation_detected "${STALLD_LOG3}"; then
     log "✓ PASS: Starvation detection with large runtime ${large_runtime}ns"
 else
     log "✗ FAIL: No starvation detection with large runtime"
@@ -170,18 +157,14 @@ STALLD_LOG4="/tmp/stalld_test_boost_runtime_test4_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG4}")
 
 log "Starting stalld with runtime ${valid_runtime}ns < period ${period}ns"
-start_stalld -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${valid_runtime} -p ${period} -l > "${STALLD_LOG4}" 2>&1
+start_stalld_with_log "${STALLD_LOG4}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${valid_runtime} -p ${period} -l
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if detection occurred
-if grep -qi "detect\|starv" "${STALLD_LOG4}"; then
+if wait_for_starvation_detected "${STALLD_LOG4}"; then
     log "✓ PASS: Starvation detection with runtime < period"
 else
     log "✗ FAIL: No starvation detection when runtime < period"
diff --git a/tests/functional/test_deadline_boosting.sh b/tests/functional/test_deadline_boosting.sh
index de72100..a5727da 100755
--- a/tests/functional/test_deadline_boosting.sh
+++ b/tests/functional/test_deadline_boosting.sh
@@ -58,22 +58,16 @@ log "=========================================="
 threshold=5
 log "Starting stalld with ${threshold}s threshold (default DEADLINE boosting)"
 # Use -g 1 for 1-second granularity to ensure timely detection
-start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Create starvation
 starvation_duration=$((threshold + 8))
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for detection and boosting (threshold + granularity + buffer)
-# With -g 1, stalld checks every 1 second. In worst case, it checks just before
-# threshold is reached, then waits another granularity period.
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for starvation detection and boosting (threshold: ${threshold}s, granularity: 1s)..."
-sleep ${wait_time}
-
-# Verify boosting occurred
-if grep -q "boosted" "${STALLD_LOG}"; then
+# Wait for boosting
+log "Waiting for boost detection..."
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred"
 
     # Verify SCHED_DEADLINE was used
@@ -123,18 +117,16 @@ log "  Runtime: ${boost_runtime}ns (50µs)"
 log "  Duration: ${boost_duration}s"
 
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} \
-    -p ${boost_period} -r ${boost_runtime} -d ${boost_duration} \
-    > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} \
+    -p ${boost_period} -r ${boost_runtime} -d ${boost_duration}
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
 
-# Wait for boosting (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for detection..."
-sleep ${wait_time}
+# Wait for boosting
+log "Waiting for boost detection..."
+wait_for_boost_detected "${STALLD_LOG}"
 
 # Try to find the boosted task PID
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
@@ -184,16 +176,15 @@ boost_duration=5
 
 log "Starting stalld with ${boost_duration}s boost duration"
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration}
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
-# Wait for starvation detection (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for detection..."
-sleep ${wait_time}
+# Wait for boosting
+log "Waiting for boost detection..."
+wait_for_boost_detected "${STALLD_LOG}"
 
 # Find a starved task
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
@@ -257,7 +248,7 @@ boost_duration=3
 
 log "Starting stalld with ${boost_duration}s boost duration"
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration}
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
@@ -285,10 +276,8 @@ if [ -n "${tracked_pid}" ]; then
         log "⚠ WARNING: Initial policy is not SCHED_OTHER (got ${initial_policy})"
     fi
 
-    # Wait for starvation detection and boosting (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    log "Waiting ${wait_time}s for detection..."
-    sleep ${wait_time}
+    # Wait for starvation detection and boosting
+    wait_for_boost_detected "${STALLD_LOG}"
 
     # Check if policy changed to DEADLINE during boost
     boosted_policy=$(get_sched_policy ${tracked_pid})
@@ -350,7 +339,7 @@ else
     log "Testing simultaneous boosts on CPU ${CPU0} and CPU ${CPU1}"
 
     rm -f "${STALLD_LOG}"
-    start_stalld -f -v -g 1 -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+    start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU}
 
     # Create starvation on CPU0
     log "Creating starvation on CPU ${CPU0}"
@@ -362,10 +351,9 @@ else
     start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 15
     STARVE_PID1=${STARVE_PID}
 
-    # Wait for detection and boosting (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    log "Waiting ${wait_time}s for detection..."
-    sleep ${wait_time}
+    # Wait for boosting on both CPUs
+    log "Waiting for boost detection..."
+    wait_for_boost_detected "${STALLD_LOG}"
 
     # Count boost messages
     boost_count=$(grep -c "boosted" "${STALLD_LOG}")
diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index a6168e9..3088141 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -61,15 +61,11 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 log "Starting stalld with -F flag to force SCHED_FIFO boosting"
 # Note: -F requires non-single-threaded mode (aggressive mode)
 # Use -g 1 for 1-second granularity to ensure timely detection
-start_stalld -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for detection and boosting (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for starvation detection and boosting (threshold: ${threshold}s, granularity: 1s)..."
-sleep ${wait_time}
-
-# Verify FIFO boosting occurred
-if grep -qiE "boosted.*(SCHED_FIFO|FIFO)|FIFO.*boost" "${STALLD_LOG}"; then
+# Wait for boosting
+log "Waiting for boost detection..."
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred with -F flag"
 
     # Verify SCHED_FIFO was used
@@ -109,12 +105,11 @@ STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
 log "Starvation generator children PIDs: ${STARVE_CHILDREN}"
 
 log "Starting stalld with -F flag (FIFO boosting)"
-start_stalld -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for boosting (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for detection..."
-sleep ${wait_time}
+# Wait for boosting
+log "Waiting for boost detection..."
+wait_for_boost_detected "${STALLD_LOG}"
 
 fifo_task_found=0
 for child_pid in ${STARVE_CHILDREN}; do
@@ -175,13 +170,16 @@ rm -f "${STALLD_LOG}"
 log "Creating starvation on CPU ${TEST_CPU}"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
-start_stalld -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} \
-    -d ${boost_duration} -p ${boost_period} -r ${boost_runtime} \
-    > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} \
+    -d ${boost_duration} -p ${boost_period} -r ${boost_runtime}
+
+# Wait for boosting to start
+log "Waiting for boost detection..."
+wait_for_boost_detected "${STALLD_LOG}"
 
-# Wait for boosting to complete (threshold + granularity + boost_duration + buffer)
+# Wait for FIFO emulation cycles to complete (boost_duration + buffer)
 log "Waiting for FIFO emulation cycles to complete..."
-sleep $((threshold + 1 + boost_duration + 2))
+sleep $((boost_duration + 2))
 
 # Count boost events (FIFO emulation creates multiple boosts)
 boost_count=$(grep -c "boosted.*SCHED_FIFO" "${STALLD_LOG}")
@@ -234,10 +232,12 @@ if [ -n "${deadline_tracked_pid}" ]; then
 fi
 
 # NOW start stalld
-start_stalld -f -v -g 1 -N -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} > "${STALLD_LOG_DEADLINE}" 2>&1
+start_stalld_with_log "${STALLD_LOG_DEADLINE}" -f -v -g 1 -N -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration}
 
-# Wait for detection, boost, and some progress (threshold + granularity + boost_duration)
-sleep $((threshold + 1 + boost_duration))
+# Wait for boost detection, then let boost run to completion
+log "Waiting for DEADLINE boost detection..."
+wait_for_boost_detected "${STALLD_LOG_DEADLINE}"
+sleep $((boost_duration + 1))
 
 ctxt_after_deadline=0
 if [ -n "${deadline_tracked_pid}" ] && [ -f "/proc/${deadline_tracked_pid}/status" ]; then
@@ -277,10 +277,12 @@ if [ -n "${fifo_tracked_pid}" ]; then
 fi
 
 # NOW start stalld
-start_stalld -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration} > "${STALLD_LOG_FIFO}" 2>&1
+start_stalld_with_log "${STALLD_LOG_FIFO}" -f -v -g 1 -N -F -A -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration}
 
-# Wait for detection, boost, and some progress (threshold + granularity + boost_duration)
-sleep $((threshold + 1 + boost_duration))
+# Wait for boost detection, then let boost run to completion
+log "Waiting for FIFO boost detection..."
+wait_for_boost_detected "${STALLD_LOG_FIFO}"
+sleep $((boost_duration + 1))
 
 ctxt_after_fifo=0
 if [ -n "${fifo_tracked_pid}" ] && [ -f "/proc/${fifo_tracked_pid}/status" ]; then
diff --git a/tests/functional/test_fifo_priority_starvation.sh b/tests/functional/test_fifo_priority_starvation.sh
index cf47971..adb7b44 100755
--- a/tests/functional/test_fifo_priority_starvation.sh
+++ b/tests/functional/test_fifo_priority_starvation.sh
@@ -69,13 +69,9 @@ start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d ${starvation_duration}
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for detection (threshold + small buffer)
-wait_time=$((threshold + 2))
-log "Waiting ${wait_time}s for starvation detection..."
-sleep ${wait_time}
-
-# Verify starvation was detected
-if grep -q "starved on CPU" "${STALLD_LOG}"; then
+# Wait for starvation detection
+log "Waiting for starvation detection..."
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: FIFO-on-FIFO starvation detected"
 
     # Verify correct CPU is logged
@@ -195,12 +191,14 @@ log "Will monitor for multiple detection cycles to verify timestamp preservation
 start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Wait for multiple detection cycles
-log "Waiting for multiple detection cycles..."
-sleep $((threshold + 2))
+log "Waiting for first detection cycle..."
+wait_for_starvation_detected "${STALLD_LOG}"
 log "First detection cycle should have occurred"
-sleep 3
+log "Waiting for second detection cycle..."
+wait_for_starvation_detected "${STALLD_LOG}"
 log "Second detection cycle should have occurred"
-sleep 3
+log "Waiting for third detection cycle..."
+wait_for_starvation_detected "${STALLD_LOG}"
 log "Third detection cycle should have occurred"
 
 # Check if we see accumulating starvation time in logs
@@ -257,11 +255,9 @@ start_starvation_gen -c ${TEST_CPU} -p 6 -b 5 -n 1 -d $((threshold + 5))
 log "Starting stalld with ${threshold}s threshold"
 start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for detection
-sleep $((threshold + 2))
-
-# Verify detection works even with close priorities
-if grep -q "starved on CPU" "${STALLD_LOG}"; then
+# Wait for starvation detection
+log "Waiting for starvation detection..."
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: Starvation detected even with close priority gap (6 vs 5)"
 else
     log "⚠ WARNING: Starvation not detected with close priority gap"
@@ -298,11 +294,9 @@ log "Starvation generator PID: ${STARVE_PID}"
 log "Starting stalld with boosting enabled"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for detection and boosting
-sleep $((threshold + 2))
-
-# Verify boosting occurred
-if grep -q "boosted" "${STALLD_LOG}"; then
+# Wait for boosting
+log "Waiting for boost detection..."
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "✓ PASS: Boosting occurred"
 
     # Try to verify the correct task was boosted
diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index 4c43101..17bc66e 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -51,7 +51,7 @@ log "=========================================="
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default, no -F)"
-start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -t ${threshold}
 
 # Create starvation
 starvation_duration=10
@@ -59,12 +59,7 @@ log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-wait_time=$((threshold + 2))
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if boosting occurred and look for DEADLINE mentions
-if grep -q "boost" "${STALLD_LOG}"; then
+if wait_for_boost_detected "${STALLD_LOG}"; then
     log "Boosting occurred"
 
     # Look for SCHED_DEADLINE indicators
@@ -98,18 +93,14 @@ STALLD_LOG2="/tmp/stalld_test_force_fifo_test2_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG2}")
 
 log "Starting stalld with -F flag and aggressive mode (-A)"
-start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A > "${STALLD_LOG2}" 2>&1
+start_stalld_with_log "${STALLD_LOG2}" -f -v -c "${TEST_CPU}" -t ${threshold} -F -A
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-# Check if boosting occurred and look for FIFO mentions
-if grep -q "boost" "${STALLD_LOG2}"; then
+if wait_for_boost_detected "${STALLD_LOG2}"; then
     log "Boosting occurred with -F flag"
 
     # Look for SCHED_FIFO indicators
@@ -142,15 +133,14 @@ STALLD_LOG3="/tmp/stalld_test_force_fifo_test3_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG3}")
 
 log "Starting stalld with -F and -A flags"
-start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A > "${STALLD_LOG3}" 2>&1
+start_stalld_with_log "${STALLD_LOG3}" -f -v -c "${TEST_CPU}" -t ${threshold} -F -A
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${starvation_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
+wait_for_boost_detected "${STALLD_LOG3}"
 
 # Check logs for priority information
 if grep -qi "priority\|prio" "${STALLD_LOG3}"; then
@@ -182,17 +172,14 @@ STALLD_LOG4="/tmp/stalld_test_force_fifo_test4_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG4}")
 
 log "Starting stalld with -F, -A, and ${boost_duration}s boost duration"
-start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A -d ${boost_duration} > "${STALLD_LOG4}" 2>&1
+start_stalld_with_log "${STALLD_LOG4}" -f -v -c "${TEST_CPU}" -t ${threshold} -F -A -d ${boost_duration}
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU} for ${long_starvation}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${long_starvation}
 
 # Wait for detection and boosting
-log "Waiting ${wait_time}s for detection and boosting"
-sleep ${wait_time}
-
-if grep -q "boost" "${STALLD_LOG4}"; then
+if wait_for_boost_detected "${STALLD_LOG4}"; then
     log "Boosting detected, waiting for duration cycle"
 
     # Wait for boost duration + buffer to see restoration
@@ -261,9 +248,9 @@ STALLD_LOG_DL="/tmp/stalld_test_force_fifo_deadline_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG_DL}")
 
 log "Running comparison test with SCHED_DEADLINE"
-start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -d ${comparison_duration} > "${STALLD_LOG_DL}" 2>&1
+start_stalld_with_log "${STALLD_LOG_DL}" -f -v -c "${TEST_CPU}" -t ${threshold} -d ${comparison_duration}
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${comparison_starvation}
-sleep $((threshold + 3))
+wait_for_boost_detected "${STALLD_LOG_DL}"
 
 deadline_boosts=$(grep -c "boost" "${STALLD_LOG_DL}" || echo 0)
 log "ℹ INFO: SCHED_DEADLINE boosts: $deadline_boosts"
@@ -277,9 +264,9 @@ STALLD_LOG_FIFO="/tmp/stalld_test_force_fifo_comparison_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG_FIFO}")
 
 log "Running comparison test with SCHED_FIFO"
-start_stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F -A -d ${comparison_duration} > "${STALLD_LOG_FIFO}" 2>&1
+start_stalld_with_log "${STALLD_LOG_FIFO}" -f -v -c "${TEST_CPU}" -t ${threshold} -F -A -d ${comparison_duration}
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${comparison_starvation}
-sleep $((threshold + 3))
+wait_for_boost_detected "${STALLD_LOG_FIFO}"
 
 fifo_boosts=$(grep -c "boost" "${STALLD_LOG_FIFO}" || echo 0)
 log "ℹ INFO: SCHED_FIFO boosts: $fifo_boosts"
diff --git a/tests/functional/test_idle_detection.sh b/tests/functional/test_idle_detection.sh
index 755a4b5..ef1c572 100755
--- a/tests/functional/test_idle_detection.sh
+++ b/tests/functional/test_idle_detection.sh
@@ -84,7 +84,7 @@ log "Idle CPUs should be skipped to reduce overhead"
 threshold=5
 log "Starting stalld with verbose logging"
 # Use -g 1 for 1-second granularity
-start_stalld -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Let stalld run while CPU is idle (no load)
 log "CPU ${TEST_CPU} should be idle (no load created)"
@@ -152,7 +152,7 @@ log "=========================================="
 threshold=5
 rm -f "${STALLD_LOG}"
 log "Starting stalld"
-start_stalld -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Initially idle
 log "CPU ${TEST_CPU} initially idle"
@@ -162,13 +162,11 @@ sleep 3
 log "Creating load on CPU ${TEST_CPU} to make it busy"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d 12
 
-# Wait for stalld to detect the busy CPU and starvation (threshold + granularity + buffer)
+# Wait for stalld to detect the busy CPU and starvation
 log "Waiting for stalld to detect busy CPU and starvation..."
-wait_time=$((threshold + 1 + 3))
-sleep ${wait_time}
 
 # Verify stalld detected starvation (meaning it resumed monitoring)
-if grep -q "starved" "${STALLD_LOG}"; then
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: stalld detected starvation on now-busy CPU"
     log "        Monitoring resumed when CPU became busy"
 else
@@ -226,15 +224,14 @@ else
 
     threshold=5
     rm -f "${STALLD_LOG}"
-    start_stalld -f -v -g 1 -l -t $threshold -c ${CPU0},${CPU1} > "${STALLD_LOG}" 2>&1
+    start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${CPU0},${CPU1}
 
     # Create load only on CPU1, leave CPU0 idle
     log "Creating load on CPU ${CPU1} only"
     start_starvation_gen -c ${CPU1} -p 80 -n 2 -d 12
 
-    # Wait for detection (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
+    # Wait for detection
+    wait_for_starvation_detected "${STALLD_LOG}"
 
     # Check which CPU had starvation detected
     cpu0_detections=$(grep -c "starved on CPU ${CPU0}" "${STALLD_LOG}")
diff --git a/tests/functional/test_log_only.sh b/tests/functional/test_log_only.sh
index 93c71cb..655560c 100755
--- a/tests/functional/test_log_only.sh
+++ b/tests/functional/test_log_only.sh
@@ -76,11 +76,10 @@ fi
 echo "stalld started with PID ${STALLD_PID}"
 
 echo "Starvation generator started (PID ${STARVGEN_PID})"
-echo "Waiting 7 seconds for starvation detection..."
-sleep 7
+echo "Waiting for starvation detection..."
 
 # Check if stalld detected the starvation (should log it)
-if grep -q "starved" "${LOG_FILE}"; then
+if wait_for_starvation_detected "${LOG_FILE}"; then
 	assert_equals "1" "1" "stalld detected and logged starvation"
 else
 	TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_runqueue_parsing.sh b/tests/functional/test_runqueue_parsing.sh
index 7edf936..ccb1982 100755
--- a/tests/functional/test_runqueue_parsing.sh
+++ b/tests/functional/test_runqueue_parsing.sh
@@ -100,12 +100,8 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
     log "Creating starvation for ${starvation_duration}s"
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
-    # Wait for detection (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
-
-    # Verify eBPF backend detected starvation
-    if grep -q "starved on CPU" "${STALLD_LOG_BPF}"; then
+    # Wait for starvation detection
+    if wait_for_starvation_detected "${STALLD_LOG_BPF}"; then
         log "✓ PASS: eBPF backend detected starving tasks"
 
         # Verify task info is present (PID, comm)
@@ -159,12 +155,8 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     log "Creating starvation for ${starvation_duration}s"
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
-    # Wait for detection (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
-
-    # Verify sched_debug backend detected starvation
-    if grep -q "starved on CPU" "${STALLD_LOG_SCHED}"; then
+    # Wait for starvation detection
+    if wait_for_starvation_detected "${STALLD_LOG_SCHED}"; then
         log "✓ PASS: sched_debug backend detected starving tasks"
 
         # Verify task info is present
@@ -227,8 +219,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
+    wait_for_starvation_detected "${STALLD_LOG_BPF}"
     bpf_detections=$(count_detected_tasks "${STALLD_LOG_BPF}")
     log "eBPF backend detected: ${bpf_detections} starvation events"
 
@@ -247,8 +238,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
+    wait_for_starvation_detected "${STALLD_LOG_SCHED}"
     sched_detections=$(count_detected_tasks "${STALLD_LOG_SCHED}")
     log "sched_debug backend detected: ${sched_detections} starvation events"
 
@@ -314,9 +304,8 @@ if [ -n "$test_backend" ]; then
     log "Creating starvation with known task name (starvation_gen)"
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 10 -v
 
-    # Wait for detection (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
+    # Wait for starvation detection
+    wait_for_starvation_detected "${log_file}"
 
     # Verify fields are present
     log ""
@@ -376,8 +365,8 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     # Create brief starvation just to initialize the backend
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 8
 
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
+    # Wait for starvation detection
+    wait_for_starvation_detected "${STALLD_LOG_SCHED}"
 
     # Check for format detection messages
     if grep -q "detect_task_format" "${STALLD_LOG_SCHED}"; then
diff --git a/tests/functional/test_starvation_detection.sh b/tests/functional/test_starvation_detection.sh
index 6a1821d..6b7d330 100755
--- a/tests/functional/test_starvation_detection.sh
+++ b/tests/functional/test_starvation_detection.sh
@@ -72,16 +72,9 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for detection (threshold + granularity + buffer)
-# With -g 1, stalld checks every 1 second. In worst case, it checks just before
-# threshold is reached, then waits another granularity period.
-# So we need: threshold + granularity + buffer for processing
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for starvation detection (threshold: ${threshold}s, granularity: 1s)..."
-sleep ${wait_time}
-
-# Verify starvation was detected
-if grep -qE "starvation_gen.*starved on CPU|starved on CPU.*starvation_gen" "${STALLD_LOG}"; then
+# Wait for starvation detection
+log "Waiting for starvation detection..."
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: Starvation detected"
 
     # Verify correct CPU is logged
@@ -129,10 +122,9 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-# Wait for detection (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for detection..."
-sleep ${wait_time}
+# Wait for starvation detection
+log "Waiting for starvation detection..."
+wait_for_starvation_detected "${STALLD_LOG}"
 
 # Try to find the starved task PID from starvation_gen children
 # The blockee thread is what gets starved
@@ -190,16 +182,14 @@ log "Starting stalld with ${threshold}s threshold (log-only mode)"
 log "Will monitor for multiple detection cycles to verify timestamp preservation"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU}
 
-# Wait for multiple detection cycles
-# First detection: threshold + granularity + buffer
+# Wait for first detection cycle
 log "Waiting for first detection cycle..."
-sleep $((threshold + 1 + 3))
+wait_for_starvation_detected "${STALLD_LOG}"
 log "First detection cycle should have occurred"
-# Second detection: wait for another granularity period + buffer
-sleep $((1 + 2))
+# Wait for additional detection cycles
+sleep 4
 log "Second detection cycle should have occurred"
-# Third detection: wait for another granularity period + buffer
-sleep $((1 + 2))
+sleep 4
 log "Third detection cycle should have occurred"
 
 # Stop stalld to flush output buffers before checking log
@@ -287,10 +277,9 @@ else
 
     start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU_MULTI}
 
-    # Wait for detection (threshold + granularity + buffer)
-    wait_time=$((threshold + 1 + 3))
-    log "Waiting ${wait_time}s for detection..."
-    sleep ${wait_time}
+    # Wait for starvation detection
+    log "Waiting for starvation detection..."
+    wait_for_starvation_detected "${STALLD_LOG}"
 
     # Check both CPUs detected - specifically look for starvation_gen tasks
     if grep -qE "starvation_gen.*starved on CPU ${CPU0}|starved on CPU ${CPU0}.*starvation_gen" "${STALLD_LOG}"; then
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index 45a4679..9b14009 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -69,18 +69,13 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold"
 # Use -i to ignore kernel workers that may starve before our test tasks
-start_stalld -f -v -N -M -g 1 -i "ksoftirqd,kworker" -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -N -M -g 1 -i "ksoftirqd,kworker" -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold}
 
-# Wait for threshold + granularity + buffer time
-# With -g 1, stalld checks every 1 second. In worst case, it checks just before
-# threshold is reached, then waits another granularity period.
-# So we need: threshold + granularity + buffer for processing
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for detection (threshold: ${threshold}s, granularity: 1s)"
-sleep ${wait_time}
+# Wait for starvation detection
+log "Waiting for detection (threshold: ${threshold}s)"
 
 # Check if starvation was detected - specifically look for starvation_gen tasks
-if grep -qE "starvation_gen.*starved on CPU ${TEST_CPU}|starved on CPU ${TEST_CPU}.*starvation_gen" "${STALLD_LOG}"; then
+if wait_for_starvation_detected "${STALLD_LOG}"; then
     log "✓ PASS: Starvation detected after ${threshold}s threshold"
 else
     log "✗ FAIL: Starvation not detected after ${threshold}s threshold"
@@ -113,7 +108,7 @@ log "Creating short starvation (${starvation_duration}s) with threshold of ${thr
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold"
-start_stalld -f -v -N -M -g 1 -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold} > "${STALLD_LOG2}" 2>&1
+start_stalld_with_log "${STALLD_LOG2}" -f -v -N -M -g 1 -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold}
 
 # Wait for starvation duration + small buffer
 sleep 8
@@ -159,15 +154,13 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold"
 # Use -i to ignore kernel workers that may starve before our test tasks
-start_stalld -f -v -N -M -g 1 -i "ksoftirqd,kworker" -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold} > "${STALLD_LOG3}" 2>&1
+start_stalld_with_log "${STALLD_LOG3}" -f -v -N -M -g 1 -i "ksoftirqd,kworker" -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold}
 
-# Wait for threshold + granularity + buffer
-wait_time=$((threshold + 1 + 3))
-log "Waiting ${wait_time}s for detection (threshold: ${threshold}s, granularity: 1s)"
-sleep ${wait_time}
+# Wait for starvation detection
+log "Waiting for detection (threshold: ${threshold}s)"
 
 # Check if starvation_gen was detected
-if grep -qE "starvation_gen.*starved on CPU ${TEST_CPU}|starved on CPU ${TEST_CPU}.*starvation_gen" "${STALLD_LOG3}"; then
+if wait_for_starvation_detected "${STALLD_LOG3}"; then
     log "✓ PASS: Starvation detected with ${threshold}s threshold"
 else
     log "✗ FAIL: Starvation not detected with ${threshold}s threshold"
diff --git a/tests/functional/test_task_merging.sh b/tests/functional/test_task_merging.sh
index 1bea759..5cd6406 100755
--- a/tests/functional/test_task_merging.sh
+++ b/tests/functional/test_task_merging.sh
@@ -65,17 +65,16 @@ log "Task merging: same PID + same ctxsw = preserved timestamp"
 threshold=3
 log "Starting stalld with ${threshold}s threshold (log-only, verbose)"
 # Use -g 1 for 1-second granularity
-start_stalld -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Create long starvation to span multiple monitoring cycles
 starvation_duration=18
 log "Creating starvation for ${starvation_duration}s (multiple detection cycles)"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
-# Wait for first detection (threshold + granularity + buffer)
+# Wait for first detection
 log "Waiting for first detection cycle..."
-wait_time=$((threshold + 1 + 3))
-sleep ${wait_time}
+wait_for_starvation_detected "${STALLD_LOG}"
 
 # Extract first starvation duration
 if grep -q "starved.*for [0-9]" "${STALLD_LOG}"; then
@@ -144,15 +143,14 @@ log "Merging occurs when: PID matches AND context switches unchanged"
 
 threshold=5
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Create starvation
 log "Creating starvation"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
-# Wait for detection (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-sleep ${wait_time}
+# Wait for starvation detection
+wait_for_starvation_detected "${STALLD_LOG}"
 
 # Find the starved task PID
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
@@ -218,15 +216,14 @@ log "When context switches change, timestamp should reset"
 
 threshold=5
 rm -f "${STALLD_LOG}"
-start_stalld -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d 2 > "${STALLD_LOG}" 2>&1
+start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d 2
 
 # Create starvation that will get boosted (allowing progress)
 log "Creating starvation that will be boosted"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
-# Wait for first detection and boost (threshold + granularity + buffer)
-wait_time=$((threshold + 1 + 3))
-sleep ${wait_time}
+# Wait for starvation detection
+wait_for_starvation_detected "${STALLD_LOG}"
 
 # Find tracked task
 STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
@@ -286,7 +283,7 @@ else
     log "Testing task merging on CPU ${CPU0} and CPU ${CPU1} independently"
 
     rm -f "${STALLD_LOG}"
-    start_stalld -f -v -g 1 -l -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU} > "${STALLD_LOG}" 2>&1
+    start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU}
 
     # Create starvation on both CPUs
     log "Creating starvation on CPU ${CPU0}"
@@ -297,9 +294,8 @@ else
     start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 15
     STARVE_PID1=${STARVE_PID}
 
-    # Wait for multiple detection cycles (threshold + granularity + buffer, then more)
-    wait_time=$((threshold + 1 + 3))
-    sleep ${wait_time}
+    # Wait for starvation detection on both CPUs
+    wait_for_starvation_detected "${STALLD_LOG}"
     sleep 4
 
     # Check CPU0 starvation accumulation
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 20/36] tests/functional: Remove duplicated -a flag in test_fifo_priority_starvation
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (18 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 19/36] tests/functional: Replace detection sleeps with event-driven helpers Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 21/36] tests/functional: Add missing -a flag in test_starvation_detection Wander Lairson Costa
                   ` (15 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The start_stalld_with_log call in Test 2 passes -a ${STALLD_CPU}
twice, which is a copy-paste error. Remove the duplicate.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_fifo_priority_starvation.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/functional/test_fifo_priority_starvation.sh b/tests/functional/test_fifo_priority_starvation.sh
index adb7b44..886357e 100755
--- a/tests/functional/test_fifo_priority_starvation.sh
+++ b/tests/functional/test_fifo_priority_starvation.sh
@@ -136,7 +136,7 @@ if [ -n "${blockee_pid}" ]; then
 fi
 
 log "Starting stalld with boosting enabled"
-start_stalld_with_log "${STALLD_LOG}" -f -v -N -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -a ${STALLD_CPU} -d ${boost_duration}
+start_stalld_with_log "${STALLD_LOG}" -f -v -N -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} -d ${boost_duration}
 
 # Wait for detection and boosting
 sleep $((threshold + boost_duration + 1))
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 21/36] tests/functional: Add missing -a flag in test_starvation_detection
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (19 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 20/36] tests/functional: Remove duplicated -a flag in test_fifo_priority_starvation Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 22/36] tests/functional: Use start_stalld_with_log() in test_log_only Wander Lairson Costa
                   ` (14 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The start_stalld_with_log call in Test 3 is missing the -a flag to
pin stalld to a separate CPU, unlike all other invocations in the
same file. Add it for consistency.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_starvation_detection.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/functional/test_starvation_detection.sh b/tests/functional/test_starvation_detection.sh
index 6b7d330..3c6fd6e 100755
--- a/tests/functional/test_starvation_detection.sh
+++ b/tests/functional/test_starvation_detection.sh
@@ -180,7 +180,7 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
 log "Starting stalld with ${threshold}s threshold (log-only mode)"
 log "Will monitor for multiple detection cycles to verify timestamp preservation"
-start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU}
+start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Wait for first detection cycle
 log "Waiting for first detection cycle..."
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 22/36] tests/functional: Use start_stalld_with_log() in test_log_only
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (20 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 21/36] tests/functional: Add missing -a flag in test_starvation_detection Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 23/36] tests/functional: Use start_stalld_with_log() in test_logging_destinations Wander Lairson Costa
                   ` (13 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Replace manual stalld invocation with start_stalld_with_log(),
which handles backend selection, PID tracking, and event-driven
readiness detection internally.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_log_only.sh | 23 +----------------------
 1 file changed, 1 insertion(+), 22 deletions(-)

diff --git a/tests/functional/test_log_only.sh b/tests/functional/test_log_only.sh
index 655560c..ba72b8e 100755
--- a/tests/functional/test_log_only.sh
+++ b/tests/functional/test_log_only.sh
@@ -52,28 +52,7 @@ STARVGEN_PID=${STARVE_PID}
 
 # Start stalld in log-only mode with verbose output to capture logs
 echo "Starting stalld in log-only mode with 5 second threshold"
-
-# Build stalld command with backend option if specified
-STALLD_ARGS="-f -v -l -t 5 -c ${TEST_CPU} -a ${STALLD_CPU}"
-if [ -n "${STALLD_TEST_BACKEND}" ]; then
-	STALLD_ARGS="-b ${STALLD_TEST_BACKEND} ${STALLD_ARGS}"
-	echo "Using backend: ${STALLD_TEST_BACKEND}"
-fi
-
-${TEST_ROOT}/../stalld ${STALLD_ARGS} > "${LOG_FILE}" 2>&1 &
-STALLD_PID=$!
-CLEANUP_PIDS+=("${STALLD_PID}")
-sleep 2
-
-# Verify stalld is running
-if ! assert_process_running "${STALLD_PID}" "stalld should be running"; then
-	echo "Failed to start stalld, aborting test"
-	echo "Log contents:"
-	cat "${LOG_FILE}"
-	end_test
-	exit 1
-fi
-echo "stalld started with PID ${STALLD_PID}"
+start_stalld_with_log "${LOG_FILE}" -f -v -l -t 5 -c ${TEST_CPU} -a ${STALLD_CPU}
 
 echo "Starvation generator started (PID ${STARVGEN_PID})"
 echo "Waiting for starvation detection..."
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 23/36] tests/functional: Use start_stalld_with_log() in test_logging_destinations
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (21 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 22/36] tests/functional: Use start_stalld_with_log() in test_log_only Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 24/36] tests/functional: Use start_stalld_with_log() in test_cpu_selection Wander Lairson Costa
                   ` (12 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Replace direct stalld invocations in Tests 1 and 4 with
start_stalld_with_log(), which handles backend selection, PID
tracking, and readiness detection internally. This also removes
the now-unnecessary BACKEND_FLAG variable.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_logging_destinations.sh | 17 ++---------------
 1 file changed, 2 insertions(+), 15 deletions(-)

diff --git a/tests/functional/test_logging_destinations.sh b/tests/functional/test_logging_destinations.sh
index 171be8b..b44c08d 100755
--- a/tests/functional/test_logging_destinations.sh
+++ b/tests/functional/test_logging_destinations.sh
@@ -33,16 +33,7 @@ echo "Test 1: Verbose mode (-v) logs to stdout"
 LOG_FILE="/tmp/stalld_test_verbose_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
 
-# Start stalld directly (not using start_stalld helper) to capture output
-# Add backend flag if specified via test runner
-BACKEND_FLAG=""
-if [ -n "${STALLD_TEST_BACKEND}" ]; then
-	BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
-fi
-"${TEST_ROOT}/../stalld" -f -v ${BACKEND_FLAG} -l -t 5 > "${LOG_FILE}" 2>&1 &
-STALLD_PID=$!
-CLEANUP_PIDS+=("${STALLD_PID}")
-sleep 2
+start_stalld_with_log "${LOG_FILE}" -f -v -l -t 5
 
 if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 	# Check that output was written to our log file
@@ -160,11 +151,7 @@ echo "Test 4: Combined logging modes"
 LOG_FILE="/tmp/stalld_test_combined_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
 
-# Start stalld directly (not using start_stalld helper) to capture output
-"${TEST_ROOT}/../stalld" -f -v -k -s ${BACKEND_FLAG} -l -t 5 > "${LOG_FILE}" 2>&1 &
-STALLD_PID=$!
-CLEANUP_PIDS+=("${STALLD_PID}")
-sleep 2
+start_stalld_with_log "${LOG_FILE}" -f -v -k -s -l -t 5
 
 if assert_process_running "${STALLD_PID}" "stalld with combined logging should be running"; then
 	# Verify verbose output
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 24/36] tests/functional: Use start_stalld_with_log() in test_cpu_selection
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (22 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 23/36] tests/functional: Use start_stalld_with_log() in test_logging_destinations Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 25/36] tests/functional: Use wait_for_stalld_ready() in test_backend_selection Wander Lairson Costa
                   ` (11 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Replace the local start_stalld_with_output() helper, which
duplicated start_stalld_with_log() functionality, with direct
calls to start_stalld_with_log(). For the invalid CPU test, run
stalld synchronously under timeout instead of backgrounding and
sleeping, and treat unexpected success or timeout as test failures
rather than warnings.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_cpu_selection.sh | 55 ++++++--------------------
 1 file changed, 11 insertions(+), 44 deletions(-)

diff --git a/tests/functional/test_cpu_selection.sh b/tests/functional/test_cpu_selection.sh
index 9d8ac48..7960f6c 100755
--- a/tests/functional/test_cpu_selection.sh
+++ b/tests/functional/test_cpu_selection.sh
@@ -25,24 +25,6 @@ check_rt_throttling
 STALLD_LOG="/tmp/stalld_cpu_selection_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG}")
 
-# Helper to start stalld with output capture
-start_stalld_with_output() {
-    local args="$@"
-
-    # Add backend option if specified
-    if [ -n "${STALLD_TEST_BACKEND}" ]; then
-        args="-b ${STALLD_TEST_BACKEND} ${args}"
-        echo "Using backend: ${STALLD_TEST_BACKEND}"
-    fi
-
-    # Start stalld with output redirected
-    ${TEST_ROOT}/../stalld ${args} > "${STALLD_LOG}" 2>&1 &
-    STALLD_PID=$!
-    CLEANUP_PIDS+=("${STALLD_PID}")
-    sleep 1
-    echo "stalld started with PID ${STALLD_PID}"
-}
-
 # Get available CPUs
 num_cpus=$(nproc)
 if [ "$num_cpus" -lt 2 ]; then
@@ -56,8 +38,7 @@ echo "System has $num_cpus CPUs"
 echo ""
 echo "Test 1: Single CPU monitoring (-c 0)"
 rm -f "${STALLD_LOG}"
-start_stalld_with_output -f -v -c 0 -l -t 5
-sleep 2
+start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 
 # Check that stalld mentions CPU 0
 if grep -q "cpu 0" "$STALLD_LOG"; then
@@ -74,8 +55,7 @@ if [ "$num_cpus" -ge 4 ]; then
     echo ""
     echo "Test 2: CPU list monitoring (-c 0,2)"
     rm -f "${STALLD_LOG}"
-    start_stalld_with_output -f -v -c 0,2 -l -t 5
-    sleep 2
+    start_stalld_with_log "${STALLD_LOG}" -f -v -c 0,2 -l -t 5
 
     # Check for CPU 0 and CPU 2 in output
     cpu0_found=0
@@ -104,8 +84,7 @@ if [ "$num_cpus" -ge 4 ]; then
     echo ""
     echo "Test 3: CPU range monitoring (-c 0-2)"
     rm -f "${STALLD_LOG}"
-    start_stalld_with_output -f -v -c 0-2 -l -t 5
-    sleep 2
+    start_stalld_with_log "${STALLD_LOG}" -f -v -c 0-2 -l -t 5
 
     # Check for CPUs 0, 1, 2 in output
     cpu0_found=0
@@ -138,8 +117,7 @@ if [ "$num_cpus" -ge 6 ]; then
     echo ""
     echo "Test 4: Combined format (-c 0,2-4)"
     rm -f "${STALLD_LOG}"
-    start_stalld_with_output -f -v -c 0,2-4 -l -t 5
-    sleep 2
+    start_stalld_with_log "${STALLD_LOG}" -f -v -c 0,2-4 -l -t 5
 
     # Should monitor CPUs 0, 2, 3, 4
     monitored_cpus=0
@@ -171,24 +149,14 @@ INVALID_LOG="/tmp/stalld_invalid_cpu_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG}")
 
 # Run stalld with invalid CPU and capture output
-"${TEST_ROOT}/../stalld" -f -v -c $invalid_cpu -l -t 5 > "${INVALID_LOG}" 2>&1 &
-INVALID_PID=$!
-
-# Wait a bit to see if it exits or produces error
-sleep 2
+timeout 5 "${TEST_ROOT}/../stalld" -f -v -c $invalid_cpu -l -t 5 > "${INVALID_LOG}" 2>&1
+ret=$?
 
-if ! kill -0 "$INVALID_PID" 2>/dev/null; then
-    # Process exited - check for error message
-    if grep -qi "error\|invalid\|failed" "$INVALID_LOG"; then
-        assert_equals "1" "1" "stalld rejected invalid CPU number with error"
-    else
-        assert_equals "1" "1" "stalld exited when given invalid CPU"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "stalld rejected invalid CPU number"
 else
-    # Process still running - it might have ignored the invalid CPU
-    echo -e "  ${YELLOW}WARNING${NC}: stalld still running with invalid CPU (may have ignored it)"
-    kill -TERM "$INVALID_PID" 2>/dev/null
-    wait "$INVALID_PID" 2>/dev/null
+    log "✗ FAIL: stalld did not reject invalid CPU"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Test 6: Verify non-selected CPUs are NOT monitored
@@ -196,8 +164,7 @@ if [ "$num_cpus" -ge 2 ]; then
     echo ""
     echo "Test 6: Verify non-selected CPUs not monitored (-c 0)"
     rm -f "${STALLD_LOG}"
-    start_stalld_with_output -f -v -c 0 -l -t 5
-    sleep 2
+    start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 
     # Check that CPU 1 is NOT mentioned (or mentioned as "not monitoring")
     if ! grep -q "cpu 1" "$STALLD_LOG" || grep -q "not monitoring.*cpu 1" "$STALLD_LOG"; then
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 25/36] tests/functional: Use wait_for_stalld_ready() in test_backend_selection
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (23 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 24/36] tests/functional: Use start_stalld_with_log() in test_cpu_selection Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 26/36] tests/functional: Use timeout for error path in test_force_fifo Wander Lairson Costa
                   ` (10 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Replace the post-startup sleep with wait_for_stalld_ready() for
event-driven readiness detection. Add -g 1 for consistency with
start_stalld_with_log().

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_backend_selection.sh | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/functional/test_backend_selection.sh b/tests/functional/test_backend_selection.sh
index 5d60072..c274025 100755
--- a/tests/functional/test_backend_selection.sh
+++ b/tests/functional/test_backend_selection.sh
@@ -33,15 +33,15 @@ test_backend_flag() {
 
 	CLEANUP_FILES+=("${log_file}")
 
-	"${TEST_ROOT}/../stalld" -v -f -l -b "${backend_flag}" -t 60 \
+	"${TEST_ROOT}/../stalld" -v -f -l -g 1 -b "${backend_flag}" -t 60 \
 		> "${log_file}" 2>&1 &
 	STALLD_PID=$!
 	CLEANUP_PIDS+=("${STALLD_PID}")
-	sleep 1
 
-	if ! kill -0 ${STALLD_PID} 2>/dev/null; then
+	if ! wait_for_stalld_ready "${log_file}" 15; then
 		TEST_FAILED=$((TEST_FAILED + 1))
 		echo -e "  ${RED}FAIL${NC}: stalld failed to start (${description})"
+		stop_stalld
 		return 1
 	fi
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 26/36] tests/functional: Use timeout for error path in test_force_fifo
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (24 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 25/36] tests/functional: Use wait_for_stalld_ready() in test_backend_selection Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 27/36] tests/functional: Use timeout for invalid argument tests Wander Lairson Costa
                   ` (9 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The single-threaded FIFO rejection test backgrounds stalld and
sleeps 3 seconds to check if it exited. Run it synchronously
under timeout instead, and treat unexpected success or timeout as
test failures rather than warnings.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_force_fifo.sh | 22 +++++++---------------
 1 file changed, 7 insertions(+), 15 deletions(-)

diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index 17bc66e..2c21ddd 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -214,22 +214,14 @@ FIFO_SINGLE_LOG="/tmp/stalld_test_force_fifo_single_$$.log"
 CLEANUP_FILES+=("${FIFO_SINGLE_LOG}")
 
 log "Testing single-threaded mode with -F (should exit)"
-${TEST_ROOT}/../stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F > "${FIFO_SINGLE_LOG}" 2>&1 &
-fifo_pid=$!
-sleep 3
-
-if ! kill -0 "${fifo_pid}" 2>/dev/null; then
-    # Process exited - this is expected
-    if grep -qi "error\|single.*thread\|not.*support" "${FIFO_SINGLE_LOG}"; then
-        log "✓ PASS: Single-threaded mode rejected FIFO with error message"
-    else
-        log "✓ PASS: Single-threaded mode with FIFO caused exit (as expected)"
-    fi
+timeout 5 ${TEST_ROOT}/../stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F > "${FIFO_SINGLE_LOG}" 2>&1
+ret=$?
+
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "single-threaded mode rejected FIFO"
 else
-    # Process still running - unexpected
-    log "⚠ WARNING: Single-threaded mode accepted FIFO (may have switched to multi-threaded)"
-    kill -TERM "${fifo_pid}" 2>/dev/null
-    wait "${fifo_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject -F in single-threaded mode"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 #=============================================================================
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 27/36] tests/functional: Use timeout for invalid argument tests
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (25 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 26/36] tests/functional: Use timeout for error path in test_force_fifo Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 28/36] tests: Add pass() helper and replace assert_equals hack Wander Lairson Costa
                   ` (8 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Invalid argument tests background stalld and sleep 2-3 seconds to
check whether it exited, wasting time on every run since stalld
rejects bad arguments almost instantly. Run stalld synchronously
under timeout instead, and treat unexpected success or timeout as
test failures rather than warnings.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_affinity.sh             | 20 ++----
 tests/functional/test_boost_duration.sh       | 38 ++++--------
 tests/functional/test_boost_period.sh         | 38 ++++--------
 tests/functional/test_boost_runtime.sh        | 62 +++++++------------
 tests/functional/test_fifo_boosting.sh        | 17 ++---
 tests/functional/test_pidfile.sh              | 21 ++-----
 tests/functional/test_starvation_threshold.sh | 36 ++++-------
 7 files changed, 77 insertions(+), 155 deletions(-)

diff --git a/tests/functional/test_affinity.sh b/tests/functional/test_affinity.sh
index c3888ba..eac61f5 100755
--- a/tests/functional/test_affinity.sh
+++ b/tests/functional/test_affinity.sh
@@ -264,22 +264,14 @@ if [ -n "${STALLD_TEST_BACKEND}" ]; then
     BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
 fi
 
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 -a ${invalid_cpu} > "${INVALID_LOG}" 2>&1 &
-invalid_pid=$!
-sleep 2
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 -a ${invalid_cpu} > "${INVALID_LOG}" 2>&1
+ret=$?
 
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    # Process exited
-    if grep -qi "error\|invalid\|failed" "${INVALID_LOG}"; then
-        log "✓ PASS: Invalid CPU affinity rejected with error"
-    else
-        log "ℹ INFO: Invalid CPU affinity caused exit"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Invalid CPU affinity rejected with error"
 else
-    # Process still running - might have been ignored
-    log "⚠ WARNING: stalld accepted invalid CPU affinity"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid CPU affinity"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 #=============================================================================
diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index af657db..ff43df3 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -196,20 +196,14 @@ if [ -n "${STALLD_TEST_BACKEND}" ]; then
     BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
 fi
 
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d 0 > "${INVALID_LOG}" 2>&1 &
-invalid_pid=$!
-sleep 2
-
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG}"; then
-        log "✓ PASS: Zero duration rejected with error"
-    else
-        log "ℹ INFO: Zero duration caused exit (may have been rejected)"
-    fi
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d 0 > "${INVALID_LOG}" 2>&1
+ret=$?
+
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Zero duration rejected with error"
 else
-    log "⚠ WARNING: stalld accepted zero duration"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid duration value 0"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Test 6: Negative duration
@@ -217,20 +211,14 @@ log "Testing with duration = -5"
 INVALID_LOG2="/tmp/stalld_test_boost_duration_invalid2_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG2}")
 
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d -5 > "${INVALID_LOG2}" 2>&1 &
-invalid_pid=$!
-sleep 2
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d -5 > "${INVALID_LOG2}" 2>&1
+ret=$?
 
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG2}"; then
-        log "✓ PASS: Negative duration rejected with error"
-    else
-        log "ℹ INFO: Negative duration caused exit"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Negative duration rejected with error"
 else
-    log "⚠ WARNING: stalld accepted negative duration"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid negative duration"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 log ""
diff --git a/tests/functional/test_boost_period.sh b/tests/functional/test_boost_period.sh
index 4838672..5b0ef2c 100755
--- a/tests/functional/test_boost_period.sh
+++ b/tests/functional/test_boost_period.sh
@@ -189,20 +189,14 @@ if [ -n "${STALLD_TEST_BACKEND}" ]; then
     BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
 fi
 
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p 0 > "${INVALID_LOG}" 2>&1 &
-invalid_pid=$!
-sleep 2
-
-if ! kill -0 "$invalid_pid" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG}"; then
-        log "✓ PASS: Zero period rejected with error"
-    else
-        log "ℹ INFO: Zero period caused exit"
-    fi
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p 0 > "${INVALID_LOG}" 2>&1
+ret=$?
+
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Zero period rejected with error"
 else
-    log "⚠ WARNING: stalld accepted zero period"
-    kill -TERM "$invalid_pid" 2>/dev/null || true
-    wait "$invalid_pid" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid period value 0"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 #=============================================================================
@@ -216,20 +210,14 @@ log "=========================================="
 INVALID_LOG2="/tmp/stalld_test_boost_period_invalid2_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG2}")
 
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p -1000000 > "${INVALID_LOG2}" 2>&1 &
-invalid_pid=$!
-sleep 2
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p -1000000 > "${INVALID_LOG2}" 2>&1
+ret=$?
 
-if ! kill -0 "$invalid_pid" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG2}"; then
-        log "✓ PASS: Negative period rejected with error"
-    else
-        log "ℹ INFO: Negative period caused exit"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Negative period rejected with error"
 else
-    log "⚠ WARNING: stalld accepted negative period"
-    kill -TERM "$invalid_pid" 2>/dev/null || true
-    wait "$invalid_pid" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid negative period"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 #=============================================================================
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index 734a6f8..83e8fc5 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -196,22 +196,14 @@ if [ -n "${STALLD_TEST_BACKEND}" ]; then
 fi
 
 log "Testing with runtime ${invalid_runtime}ns > period ${period}ns"
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r ${invalid_runtime} -p ${period} > "${INVALID_LOG}" 2>&1 &
-invalid_pid=$!
-sleep 2
-
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    # Process exited - this is expected behavior
-    if grep -qi "error\|invalid\|failed" "${INVALID_LOG}"; then
-        log "✓ PASS: Runtime > period rejected with error"
-    else
-        log "ℹ INFO: Runtime > period caused exit"
-    fi
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r ${invalid_runtime} -p ${period} > "${INVALID_LOG}" 2>&1
+ret=$?
+
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Runtime > period rejected with error"
 else
-    # Process still running - might be accepted or might fail later
-    log "⚠ WARNING: stalld accepted runtime > period"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid runtime > period"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 #=============================================================================
@@ -226,20 +218,14 @@ INVALID_LOG2="/tmp/stalld_test_boost_runtime_invalid2_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG2}")
 
 log "Testing with runtime = 0"
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r 0 > "${INVALID_LOG2}" 2>&1 &
-invalid_pid=$!
-sleep 2
-
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG2}"; then
-        log "✓ PASS: Zero runtime rejected with error"
-    else
-        log "ℹ INFO: Zero runtime caused exit"
-    fi
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r 0 > "${INVALID_LOG2}" 2>&1
+ret=$?
+
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Zero runtime rejected with error"
 else
-    log "⚠ WARNING: stalld accepted zero runtime"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid runtime value 0"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 #=============================================================================
@@ -254,20 +240,14 @@ INVALID_LOG3="/tmp/stalld_test_boost_runtime_invalid3_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG3}")
 
 log "Testing with runtime = -5000"
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r -5000 > "${INVALID_LOG3}" 2>&1 &
-invalid_pid=$!
-sleep 2
-
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG3}"; then
-        log "✓ PASS: Negative runtime rejected with error"
-    else
-        log "ℹ INFO: Negative runtime caused exit"
-    fi
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r -5000 > "${INVALID_LOG3}" 2>&1
+ret=$?
+
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Negative runtime rejected with error"
 else
-    log "⚠ WARNING: stalld accepted negative runtime"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid negative runtime"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 log ""
diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index 3088141..dee3250 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -331,19 +331,14 @@ CLEANUP_FILES+=("${STALLD_LOG_FAIL}")
 
 # Try to start stalld with -F but without -A (single-threaded mode)
 # This should fail because single-threaded mode only works with DEADLINE
-${TEST_ROOT}/../stalld -f -v -F -t 5 -c ${TEST_CPU} > "${STALLD_LOG_FAIL}" 2>&1 &
-FAIL_PID=$!
+timeout 5 ${TEST_ROOT}/../stalld -f -v -F -t 5 -c ${TEST_CPU} > "${STALLD_LOG_FAIL}" 2>&1
+ret=$?
 
-# Wait a bit for it to fail
-sleep 3
-
-# Check if it's still running (it shouldn't be)
-if ps -p ${FAIL_PID} > /dev/null 2>&1; then
-    log "⚠ WARNING: stalld is still running (should have exited)"
-    kill -TERM ${FAIL_PID} 2>/dev/null
-    wait ${FAIL_PID} 2>/dev/null
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "stalld exited as expected"
 else
-    log "✓ PASS: stalld exited as expected"
+    log "✗ FAIL: stalld did not reject FIFO in single-threaded mode"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Check for error message in log
diff --git a/tests/functional/test_pidfile.sh b/tests/functional/test_pidfile.sh
index 155855e..6964c0c 100755
--- a/tests/functional/test_pidfile.sh
+++ b/tests/functional/test_pidfile.sh
@@ -215,23 +215,14 @@ if [ -n "${STALLD_TEST_BACKEND}" ]; then
 fi
 
 log "Testing invalid pidfile path: ${invalid_pidfile}"
-${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 --pidfile "${invalid_pidfile}" > "${INVALID_LOG}" 2>&1 &
-invalid_pid=$!
-sleep 2
+timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 --pidfile "${invalid_pidfile}" > "${INVALID_LOG}" 2>&1
+ret=$?
 
-# Check if stalld is still running
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    # Process exited
-    if grep -qi "error\|permission\|denied\|failed" "${INVALID_LOG}"; then
-        log "✓ PASS: Invalid pidfile path rejected with error"
-    else
-        log "ℹ INFO: Invalid pidfile path caused exit"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Invalid pidfile path rejected with error"
 else
-    # Process still running - might have accepted it or created elsewhere
-    log "⚠ WARNING: stalld running despite potentially invalid pidfile path"
-    kill -TERM "${invalid_pid}" 2>/dev/null || true
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid pidfile path"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index 9b14009..709f565 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -187,20 +187,14 @@ log "Testing with threshold = 0"
 INVALID_LOG="/tmp/stalld_test_threshold_invalid_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG}")
 
-${TEST_ROOT}/../stalld -f -v -t 0 > "${INVALID_LOG}" 2>&1 &
-invalid_pid=$!
-sleep 2
+timeout 5 ${TEST_ROOT}/../stalld -f -v -t 0 > "${INVALID_LOG}" 2>&1
+ret=$?
 
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG}"; then
-        log "✓ PASS: Zero threshold rejected with error"
-    else
-        log "ℹ INFO: Zero threshold caused exit (may have been rejected)"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Zero threshold rejected with error"
 else
-    log "⚠ WARNING: stalld accepted zero threshold"
-    kill -TERM "${invalid_pid}" 2>/dev/null
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid threshold value 0"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Test with negative threshold
@@ -208,20 +202,14 @@ log "Testing with threshold = -5"
 INVALID_LOG2="/tmp/stalld_test_threshold_invalid2_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG2}")
 
-${TEST_ROOT}/../stalld -f -v -t -5 > "${INVALID_LOG2}" 2>&1 &
-invalid_pid=$!
-sleep 2
+timeout 5 ${TEST_ROOT}/../stalld -f -v -t -5 > "${INVALID_LOG2}" 2>&1
+ret=$?
 
-if ! kill -0 "${invalid_pid}" 2>/dev/null; then
-    if grep -qi "error\|invalid" "${INVALID_LOG2}"; then
-        log "✓ PASS: Negative threshold rejected with error"
-    else
-        log "ℹ INFO: Negative threshold caused exit"
-    fi
+if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+    assert_equals "1" "1" "Negative threshold rejected with error"
 else
-    log "⚠ WARNING: stalld accepted negative threshold"
-    kill -TERM "${invalid_pid}" 2>/dev/null
-    wait "${invalid_pid}" 2>/dev/null || true
+    log "✗ FAIL: stalld did not reject invalid negative threshold"
+    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 log ""
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 28/36] tests: Add pass() helper and replace assert_equals hack
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (26 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 27/36] tests/functional: Use timeout for invalid argument tests Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 29/36] tests/functional: Use pass() for all test pass reporting Wander Lairson Costa
                   ` (7 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Tests record pass results by calling assert_equals with identical
expected and actual values, which is non-obvious and misleading.
Add a pass() function that directly increments the pass counter
and logs the result, then replace all 33 occurrences of the hack
across 13 test files.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_affinity.sh             |  2 +-
 tests/functional/test_backend_selection.sh    |  2 +-
 tests/functional/test_boost_duration.sh       |  4 ++--
 tests/functional/test_boost_period.sh         |  4 ++--
 tests/functional/test_boost_runtime.sh        |  6 +++---
 tests/functional/test_cpu_selection.sh        | 12 ++++++------
 tests/functional/test_fifo_boosting.sh        |  2 +-
 tests/functional/test_force_fifo.sh           |  2 +-
 tests/functional/test_foreground.sh           |  8 ++++----
 tests/functional/test_log_only.sh             |  4 ++--
 tests/functional/test_logging_destinations.sh | 14 +++++++-------
 tests/functional/test_pidfile.sh              |  2 +-
 tests/functional/test_starvation_threshold.sh |  4 ++--
 tests/helpers/test_helpers.sh                 | 11 ++++++++++-
 14 files changed, 43 insertions(+), 34 deletions(-)

diff --git a/tests/functional/test_affinity.sh b/tests/functional/test_affinity.sh
index eac61f5..738822e 100755
--- a/tests/functional/test_affinity.sh
+++ b/tests/functional/test_affinity.sh
@@ -268,7 +268,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 -a ${invalid_cpu}
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Invalid CPU affinity rejected with error"
+    pass "Invalid CPU affinity rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid CPU affinity"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_backend_selection.sh b/tests/functional/test_backend_selection.sh
index c274025..8a22e0a 100755
--- a/tests/functional/test_backend_selection.sh
+++ b/tests/functional/test_backend_selection.sh
@@ -46,7 +46,7 @@ test_backend_flag() {
 	fi
 
 	if grep -q "${expected_msg}" "${log_file}"; then
-		assert_equals "0" "0" "${description}"
+		pass "${description}"
 	else
 		TEST_FAILED=$((TEST_FAILED + 1))
 		echo -e "  ${RED}FAIL${NC}: Backend message not found (${description})"
diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index ff43df3..104f298 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -200,7 +200,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d 0 > "$
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Zero duration rejected with error"
+    pass "Zero duration rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid duration value 0"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -215,7 +215,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d -5 > "
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Negative duration rejected with error"
+    pass "Negative duration rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid negative duration"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_boost_period.sh b/tests/functional/test_boost_period.sh
index 5b0ef2c..b516350 100755
--- a/tests/functional/test_boost_period.sh
+++ b/tests/functional/test_boost_period.sh
@@ -193,7 +193,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p 0 > "${I
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Zero period rejected with error"
+    pass "Zero period rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid period value 0"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -214,7 +214,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p -1000000
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Negative period rejected with error"
+    pass "Negative period rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid negative period"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index 83e8fc5..d516604 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -200,7 +200,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r ${inva
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Runtime > period rejected with error"
+    pass "Runtime > period rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid runtime > period"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -222,7 +222,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r 0 > "$
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Zero runtime rejected with error"
+    pass "Zero runtime rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid runtime value 0"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -244,7 +244,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r -5000
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Negative runtime rejected with error"
+    pass "Negative runtime rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid negative runtime"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_cpu_selection.sh b/tests/functional/test_cpu_selection.sh
index 7960f6c..2f9875d 100755
--- a/tests/functional/test_cpu_selection.sh
+++ b/tests/functional/test_cpu_selection.sh
@@ -42,7 +42,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 
 # Check that stalld mentions CPU 0
 if grep -q "cpu 0" "$STALLD_LOG"; then
-    assert_equals "1" "1" "stalld monitoring CPU 0"
+    pass "stalld monitoring CPU 0"
 else
     TEST_FAILED=$((TEST_FAILED + 1))
     echo -e "  ${RED}FAIL${NC}: stalld not monitoring CPU 0"
@@ -68,7 +68,7 @@ if [ "$num_cpus" -ge 4 ]; then
     fi
 
     if [ "$cpu0_found" -eq 1 ] && [ "$cpu2_found" -eq 1 ]; then
-        assert_equals "1" "1" "stalld monitoring CPUs 0 and 2"
+        pass "stalld monitoring CPUs 0 and 2"
     else
         TEST_FAILED=$((TEST_FAILED + 1))
         echo -e "  ${RED}FAIL${NC}: stalld not monitoring specified CPUs (0: $cpu0_found, 2: $cpu2_found)"
@@ -101,7 +101,7 @@ if [ "$num_cpus" -ge 4 ]; then
     fi
 
     if [ "$cpu0_found" -eq 1 ] && [ "$cpu1_found" -eq 1 ] && [ "$cpu2_found" -eq 1 ]; then
-        assert_equals "1" "1" "stalld monitoring CPUs 0-2"
+        pass "stalld monitoring CPUs 0-2"
     else
         TEST_FAILED=$((TEST_FAILED + 1))
         echo -e "  ${RED}FAIL${NC}: stalld not monitoring specified CPU range (0: $cpu0_found, 1: $cpu1_found, 2: $cpu2_found)"
@@ -128,7 +128,7 @@ if [ "$num_cpus" -ge 6 ]; then
     done
 
     if [ "$monitored_cpus" -eq 4 ]; then
-        assert_equals "1" "1" "stalld monitoring combined CPU specification (0,2-4)"
+        pass "stalld monitoring combined CPU specification (0,2-4)"
     else
         TEST_FAILED=$((TEST_FAILED + 1))
         echo -e "  ${RED}FAIL${NC}: stalld not monitoring all specified CPUs (found $monitored_cpus/4)"
@@ -153,7 +153,7 @@ timeout 5 "${TEST_ROOT}/../stalld" -f -v -c $invalid_cpu -l -t 5 > "${INVALID_LO
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "stalld rejected invalid CPU number"
+    pass "stalld rejected invalid CPU number"
 else
     log "✗ FAIL: stalld did not reject invalid CPU"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -168,7 +168,7 @@ if [ "$num_cpus" -ge 2 ]; then
 
     # Check that CPU 1 is NOT mentioned (or mentioned as "not monitoring")
     if ! grep -q "cpu 1" "$STALLD_LOG" || grep -q "not monitoring.*cpu 1" "$STALLD_LOG"; then
-        assert_equals "1" "1" "stalld not monitoring non-selected CPU 1"
+        pass "stalld not monitoring non-selected CPU 1"
     else
         TEST_FAILED=$((TEST_FAILED + 1))
         echo -e "  ${RED}FAIL${NC}: stalld appears to be monitoring CPU 1 when only CPU 0 selected"
diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index dee3250..1da1c09 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -335,7 +335,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v -F -t 5 -c ${TEST_CPU} > "${STALLD_LOG_FA
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "stalld exited as expected"
+    pass "stalld exited as expected"
 else
     log "✗ FAIL: stalld did not reject FIFO in single-threaded mode"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index 2c21ddd..f8f7b4c 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -218,7 +218,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v -c "${TEST_CPU}" -t ${threshold} -F > "${
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "single-threaded mode rejected FIFO"
+    pass "single-threaded mode rejected FIFO"
 else
     log "✗ FAIL: stalld did not reject -F in single-threaded mode"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_foreground.sh b/tests/functional/test_foreground.sh
index c5afbba..2bb0282 100755
--- a/tests/functional/test_foreground.sh
+++ b/tests/functional/test_foreground.sh
@@ -31,12 +31,12 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 	# Check parent process - should be init (PID 1) or systemd
 	PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
 	if [ "${PARENT_PID}" == "1" ] || [ "${PARENT_PID}" == "2" ]; then
-		assert_equals "1" "1" "stalld daemonized (parent is init/kthreadd)"
+		pass "stalld daemonized (parent is init/kthreadd)"
 	else
 		# On modern systems with session leaders, ppid might not be 1
 		# Just verify it's not our shell's PID
 		if [ "${PARENT_PID}" != "$$" ]; then
-			assert_equals "1" "1" "stalld daemonized (parent is not test shell)"
+			pass "stalld daemonized (parent is not test shell)"
 		else
 			TEST_FAILED=$((TEST_FAILED + 1))
 			echo -e "  ${RED}FAIL${NC}: stalld did not daemonize (parent is test shell)"
@@ -62,7 +62,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running with -f"; th
 	# The parent might be the subshell from start_stalld, not directly our shell
 	# So we just verify it's not PID 1
 	if [ "${PARENT_PID}" != "1" ]; then
-		assert_equals "1" "1" "stalld did not daemonize with -f (parent is not init)"
+		pass "stalld did not daemonize with -f (parent is not init)"
 	else
 		TEST_FAILED=$((TEST_FAILED + 1))
 		echo -e "  ${RED}FAIL${NC}: stalld daemonized even with -f flag"
@@ -82,7 +82,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running with -v"; th
 	PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
 
 	if [ "${PARENT_PID}" != "1" ]; then
-		assert_equals "1" "1" "-v implies foreground mode"
+		pass "-v implies foreground mode"
 	else
 		TEST_FAILED=$((TEST_FAILED + 1))
 		echo -e "  ${RED}FAIL${NC}: -v should imply foreground mode"
diff --git a/tests/functional/test_log_only.sh b/tests/functional/test_log_only.sh
index ba72b8e..d6257cf 100755
--- a/tests/functional/test_log_only.sh
+++ b/tests/functional/test_log_only.sh
@@ -59,7 +59,7 @@ echo "Waiting for starvation detection..."
 
 # Check if stalld detected the starvation (should log it)
 if wait_for_starvation_detected "${LOG_FILE}"; then
-	assert_equals "1" "1" "stalld detected and logged starvation"
+	pass "stalld detected and logged starvation"
 else
 	TEST_FAILED=$((TEST_FAILED + 1))
 	echo -e "  ${RED}FAIL${NC}: stalld did not detect starvation"
@@ -69,7 +69,7 @@ fi
 
 # Check that stalld did NOT boost (should not see "boosted" message with -l)
 if ! grep -q "boosted" "${LOG_FILE}"; then
-	assert_equals "1" "1" "stalld did not boost in log-only mode"
+	pass "stalld did not boost in log-only mode"
 else
 	TEST_FAILED=$((TEST_FAILED + 1))
 	echo -e "  ${RED}FAIL${NC}: stalld boosted despite -l flag"
diff --git a/tests/functional/test_logging_destinations.sh b/tests/functional/test_logging_destinations.sh
index b44c08d..35470c0 100755
--- a/tests/functional/test_logging_destinations.sh
+++ b/tests/functional/test_logging_destinations.sh
@@ -38,11 +38,11 @@ start_stalld_with_log "${LOG_FILE}" -f -v -l -t 5
 if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 	# Check that output was written to our log file
 	if [ -s "${LOG_FILE}" ]; then
-		assert_equals "1" "1" "verbose mode produces output"
+		pass "verbose mode produces output"
 
 		# Should contain initialization messages
 		if grep -q -E "(stalld|version|monitoring)" "${LOG_FILE}"; then
-			assert_equals "1" "1" "output contains expected messages"
+			pass "output contains expected messages"
 		fi
 	else
 		TEST_FAILED=$((TEST_FAILED + 1))
@@ -72,7 +72,7 @@ if command -v dmesg >/dev/null 2>&1; then
 		if [ ${DMESG_AFTER} -gt ${DMESG_BEFORE} ]; then
 			# Check if recent dmesg contains stalld messages
 			if dmesg | tail -10 | has_stalld_log; then
-				assert_equals "1" "1" "stalld messages in kernel log"
+				pass "stalld messages in kernel log"
 			else
 				echo -e "  ${YELLOW}SKIP${NC}: cannot verify kernel log messages"
 			fi
@@ -111,7 +111,7 @@ if [ -n "${SYSLOG_FILE}" ]; then
 		if [ ${SYSLOG_AFTER} -gt ${SYSLOG_BEFORE} ]; then
 			# Check for stalld messages in recent syslog
 			if tail -20 "${SYSLOG_FILE}" | has_stalld_log; then
-				assert_equals "1" "1" "stalld messages in syslog"
+				pass "stalld messages in syslog"
 			else
 				echo -e "  ${YELLOW}SKIP${NC}: no stalld messages found in syslog"
 			fi
@@ -131,9 +131,9 @@ elif command -v journalctl >/dev/null 2>&1; then
 	if assert_process_running "${STALLD_PID}" "stalld with -s should be running"; then
 		# Check journalctl for stalld messages
 		if journalctl -u stalld --since "1 minute ago" 2>/dev/null | has_stalld_log; then
-			assert_equals "1" "1" "stalld messages in journalctl"
+			pass "stalld messages in journalctl"
 		elif journalctl --since "1 minute ago" 2>/dev/null | has_stalld_log; then
-			assert_equals "1" "1" "stalld messages in system journal"
+			pass "stalld messages in system journal"
 		else
 			echo -e "  ${YELLOW}SKIP${NC}: no stalld messages in journal (may take time to appear)"
 		fi
@@ -156,7 +156,7 @@ start_stalld_with_log "${LOG_FILE}" -f -v -k -s -l -t 5
 if assert_process_running "${STALLD_PID}" "stalld with combined logging should be running"; then
 	# Verify verbose output
 	if [ -s "${LOG_FILE}" ]; then
-		assert_equals "1" "1" "combined logging produces output"
+		pass "combined logging produces output"
 	else
 		TEST_FAILED=$((TEST_FAILED + 1))
 		echo -e "  ${RED}FAIL${NC}: no output with combined logging"
diff --git a/tests/functional/test_pidfile.sh b/tests/functional/test_pidfile.sh
index 6964c0c..fbdc9fe 100755
--- a/tests/functional/test_pidfile.sh
+++ b/tests/functional/test_pidfile.sh
@@ -219,7 +219,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 --pidfile "${inva
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Invalid pidfile path rejected with error"
+    pass "Invalid pidfile path rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid pidfile path"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index 709f565..b904867 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -191,7 +191,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v -t 0 > "${INVALID_LOG}" 2>&1
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Zero threshold rejected with error"
+    pass "Zero threshold rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid threshold value 0"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -206,7 +206,7 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v -t -5 > "${INVALID_LOG2}" 2>&1
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    assert_equals "1" "1" "Negative threshold rejected with error"
+    pass "Negative threshold rejected with error"
 else
     log "✗ FAIL: stalld did not reject invalid negative threshold"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 864036d..144114c 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -105,6 +105,15 @@ end_test() {
 	fi
 }
 
+# Record a test pass with a description message.
+#
+# Usage: pass "description"
+pass() {
+	local message=${1:-""}
+	log "✓ PASS: ${message}"
+	TEST_PASSED=$((TEST_PASSED + 1))
+}
+
 # Assert functions
 assert_equals() {
 	local expected=$1
@@ -1121,7 +1130,7 @@ start_starvation_gen() {
 
 # Export functions for use in tests
 export -f start_test end_test
-export -f assert_equals assert_contains assert_not_contains
+export -f pass assert_equals assert_contains assert_not_contains
 export -f assert_file_exists assert_file_not_exists
 export -f assert_process_running assert_process_not_running
 export -f start_stalld stop_stalld kill_existing_stalld cleanup
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 29/36] tests/functional: Use pass() for all test pass reporting
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (27 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 28/36] tests: Add pass() helper and replace assert_equals hack Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 30/36] tests: Add fail() helper and use for all test failures Wander Lairson Costa
                   ` (6 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Replace inline log statements that manually format pass messages
with calls to the pass() helper introduced in the previous commit.
This ensures all pass results are consistently logged with the
journal and counted by the test framework.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_affinity.sh             | 14 ++++----
 tests/functional/test_boost_duration.sh       |  8 ++---
 tests/functional/test_boost_period.sh         |  8 ++---
 tests/functional/test_boost_restoration.sh    | 24 ++++++-------
 tests/functional/test_boost_runtime.sh        |  8 ++---
 tests/functional/test_deadline_boosting.sh    | 24 ++++++-------
 tests/functional/test_fifo_boosting.sh        | 14 ++++----
 .../test_fifo_priority_starvation.sh          | 18 +++++-----
 tests/functional/test_force_fifo.sh           |  8 ++---
 tests/functional/test_idle_detection.sh       | 12 +++----
 tests/functional/test_pidfile.sh              | 18 +++++-----
 tests/functional/test_runqueue_parsing.sh     | 36 +++++++++----------
 tests/functional/test_starvation_detection.sh | 22 ++++++------
 tests/functional/test_starvation_threshold.sh |  6 ++--
 tests/functional/test_task_merging.sh         | 16 ++++-----
 15 files changed, 118 insertions(+), 118 deletions(-)

diff --git a/tests/functional/test_affinity.sh b/tests/functional/test_affinity.sh
index 738822e..b74886d 100755
--- a/tests/functional/test_affinity.sh
+++ b/tests/functional/test_affinity.sh
@@ -83,7 +83,7 @@ log "ℹ INFO: Default affinity: $default_affinity"
 
 # Typically should be all CPUs
 if [ -n "$default_affinity" ]; then
-    log "✓ PASS: stalld has default affinity: $default_affinity"
+    pass "stalld has default affinity: $default_affinity"
 else
     log "⚠ WARNING: Could not determine default affinity"
 fi
@@ -107,7 +107,7 @@ sleep 2
 affinity=$(check_affinity "${STALLD_PID}")
 
 if [ "$affinity" = "0" ]; then
-    log "✓ PASS: stalld restricted to CPU 0"
+    pass "stalld restricted to CPU 0"
 else
     log "✗ FAIL: stalld affinity ($affinity) doesn't match requested (0)"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -134,7 +134,7 @@ if [ "$num_cpus" -ge 4 ]; then
 
     # Accept either "0,2" or "0-2" or "2,0" (different systems may report differently)
     if echo "$affinity" | grep -qE '^0,2$|^0-2$|^2,0$'; then
-        log "✓ PASS: stalld restricted to CPUs 0,2 (affinity: $affinity)"
+        pass "stalld restricted to CPUs 0,2 (affinity: $affinity)"
     else
         log "⚠ WARNING: stalld affinity ($affinity) may not match requested (0,2) - format may vary"
         # Not failing as different systems may report differently
@@ -164,7 +164,7 @@ if [ "$num_cpus" -ge 4 ]; then
 
     # Accept various formats: "0-2", "0,1,2", etc.
     if echo "$affinity" | grep -qE '0.*1.*2|0-2'; then
-        log "✓ PASS: stalld restricted to CPU range 0-2 (affinity: $affinity)"
+        pass "stalld restricted to CPU range 0-2 (affinity: $affinity)"
     else
         log "⚠ WARNING: stalld affinity ($affinity) may not match requested (0-2) - format may vary"
     fi
@@ -200,7 +200,7 @@ if [ "$num_cpus" -ge 2 ]; then
     fi
 
     if [ "$affinity" = "$test_cpu" ]; then
-        log "✓ PASS: stalld process affinity set to CPU $test_cpu"
+        pass "stalld process affinity set to CPU $test_cpu"
     else
         log "⚠ WARNING: stalld affinity ($affinity) doesn't exactly match CPU $test_cpu"
     fi
@@ -229,7 +229,7 @@ if [ "$num_cpus" -ge 2 ]; then
     affinity=$(check_affinity "${STALLD_PID}")
 
     if [ "$affinity" = "0" ]; then
-        log "✓ PASS: stalld affinity to CPU 0 while monitoring CPU 1"
+        pass "stalld affinity to CPU 0 while monitoring CPU 1"
     else
         log "⚠ WARNING: stalld affinity ($affinity) doesn't match requested (0)"
     fi
@@ -298,7 +298,7 @@ affinity_end=$(check_affinity "${STALLD_PID}")
 log "ℹ INFO: Affinity after 3s: $affinity_end"
 
 if [ "$affinity_start" = "$affinity_end" ]; then
-    log "✓ PASS: CPU affinity persisted over time"
+    pass "CPU affinity persisted over time"
 else
     log "⚠ WARNING: CPU affinity changed (start: $affinity_start, end: $affinity_end)"
 fi
diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index 104f298..61218e9 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -67,7 +67,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for starvation detection
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Starvation detection occurred with default duration"
+    pass "Starvation detection occurred with default duration"
 else
     log "✗ FAIL: No starvation detection"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -99,7 +99,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for starvation detection
 if wait_for_starvation_detected "${STALLD_LOG2}"; then
-    log "✓ PASS: Starvation detection with ${short_duration}s duration"
+    pass "Starvation detection with ${short_duration}s duration"
 else
     log "✗ FAIL: No starvation detection with short duration"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -133,7 +133,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${long_starvation}
 
 # Wait for starvation detection
 if wait_for_starvation_detected "${STALLD_LOG3}"; then
-    log "✓ PASS: Starvation detection with ${long_duration}s duration"
+    pass "Starvation detection with ${long_duration}s duration"
 else
     log "✗ FAIL: No starvation detection with long duration"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -166,7 +166,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 1 -d 15
 
 # Wait for starvation detection
 if wait_for_starvation_detected "${STALLD_LOG4}"; then
-    log "✓ PASS: Starvation detection with ${duration}s boost duration"
+    pass "Starvation detection with ${duration}s boost duration"
 else
     log "✗ FAIL: No starvation detection"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_boost_period.sh b/tests/functional/test_boost_period.sh
index b516350..671f7cb 100755
--- a/tests/functional/test_boost_period.sh
+++ b/tests/functional/test_boost_period.sh
@@ -64,7 +64,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for starvation detection and boosting
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred with default period"
+    pass "Boosting occurred with default period"
 
     # Try to find period value in logs
     if grep -qi "period" "${STALLD_LOG}"; then
@@ -101,7 +101,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for starvation detection and boosting
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred with custom period ${custom_period} ns"
+    pass "Boosting occurred with custom period ${custom_period} ns"
 else
     log "✗ FAIL: No boosting with custom period"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -131,7 +131,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for starvation detection and boosting
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred with short period ${short_period} ns"
+    pass "Boosting occurred with short period ${short_period} ns"
 else
     log "✗ FAIL: No boosting with short period"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -161,7 +161,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for starvation detection and boosting
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred with long period ${long_period} ns"
+    pass "Boosting occurred with long period ${long_period} ns"
 else
     log "✗ FAIL: No boosting with long period"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_boost_restoration.sh b/tests/functional/test_boost_restoration.sh
index fc5a25a..024d869 100755
--- a/tests/functional/test_boost_restoration.sh
+++ b/tests/functional/test_boost_restoration.sh
@@ -85,7 +85,7 @@ if [ -n "${tracked_pid}" ]; then
     log "Initial policy: ${initial_policy} (expected: 1=SCHED_FIFO), prio: ${initial_prio}"
 
     if [ "$initial_policy" = "1" ]; then
-        log "✓ PASS: Initial policy is SCHED_FIFO"
+        pass "Initial policy is SCHED_FIFO"
     else
         log "⚠ WARNING: Initial policy is ${initial_policy}, not SCHED_FIFO (1)"
     fi
@@ -100,7 +100,7 @@ if [ -n "${tracked_pid}" ]; then
         log "Policy during boost: ${boosted_policy}"
 
         if [ "$boosted_policy" = "6" ]; then
-            log "✓ PASS: Task boosted to SCHED_DEADLINE (6)"
+            pass "Task boosted to SCHED_DEADLINE (6)"
         else
             log "ℹ INFO: Policy is ${boosted_policy} (may be between boost cycles)"
         fi
@@ -117,7 +117,7 @@ if [ -n "${tracked_pid}" ]; then
         log "Policy after boost: ${post_boost_policy}, prio: ${post_boost_prio}"
 
         if [ "$post_boost_policy" = "1" ]; then
-            log "✓ PASS: Policy restored to SCHED_FIFO (1) during boost cycle"
+            pass "Policy restored to SCHED_FIFO (1) during boost cycle"
         elif [ "$post_boost_policy" = "6" ]; then
             log "ℹ INFO: Still in boost (DEADLINE), will check final restoration"
         else
@@ -139,9 +139,9 @@ if [ -n "${tracked_pid}" ] && [ -f "/proc/${tracked_pid}/sched" ]; then
     log "Final policy: ${final_policy}, prio: ${final_prio}"
 
     if [ "$final_policy" = "1" ]; then
-        log "✓ PASS: Policy restored to SCHED_FIFO (1)"
+        pass "Policy restored to SCHED_FIFO (1)"
         if [ "$final_prio" = "$initial_prio" ]; then
-            log "✓ PASS: Priority restored to ${initial_prio}"
+            pass "Priority restored to ${initial_prio}"
         else
             log "⚠ INFO: Priority is ${final_prio} (initial was ${initial_prio})"
         fi
@@ -219,7 +219,7 @@ if chrt -f -p 10 ${FIFO_TASK_PID} 2>/dev/null; then
     log "Initial: policy=${initial_policy} (1=FIFO), prio=${initial_prio}"
 
     if [ "$initial_policy" = "1" ]; then
-        log "✓ PASS: Initial policy is SCHED_FIFO (1)"
+        pass "Initial policy is SCHED_FIFO (1)"
     else
         log "⚠ WARNING: Could not set FIFO policy (got ${initial_policy})"
     fi
@@ -234,7 +234,7 @@ if chrt -f -p 10 ${FIFO_TASK_PID} 2>/dev/null; then
         log "Policy during detection window: ${boosted_policy}"
 
         if [ "$boosted_policy" = "6" ]; then
-            log "✓ PASS: Task boosted to SCHED_DEADLINE (6)"
+            pass "Task boosted to SCHED_DEADLINE (6)"
         elif [ "$boosted_policy" = "1" ]; then
             log "ℹ INFO: Still SCHED_FIFO (may not have starved yet)"
         fi
@@ -251,7 +251,7 @@ if chrt -f -p 10 ${FIFO_TASK_PID} 2>/dev/null; then
         log "Final: policy=${final_policy}, prio=${final_prio}"
 
         if [ "$final_policy" = "1" ]; then
-            log "✓ PASS: Policy restored to SCHED_FIFO (1)"
+            pass "Policy restored to SCHED_FIFO (1)"
             log "ℹ INFO: Priority after restoration: ${final_prio}"
         else
             log "⚠ INFO: Final policy is ${final_policy}"
@@ -340,7 +340,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     log "Time difference: ${time_diff}s"
 
     if [ ${time_diff} -le 2 ]; then
-        log "✓ PASS: Restoration timing within acceptable margin (±2s)"
+        pass "Restoration timing within acceptable margin (±2s)"
     else
         log "ℹ INFO: Restoration timing difference: ${time_diff}s"
         log "        (may be acceptable depending on system load)"
@@ -380,7 +380,7 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d ${short_duration}
 
 # Wait for starvation detection and boosting
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boost occurred"
+    pass "Boost occurred"
 
     # At this point (12s), starvation_gen has exited (at 8s) during the boost
     # stalld should still be running despite the task exiting during boost
@@ -389,7 +389,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
 
     # Verify stalld is still running and didn't crash after task exit
     if assert_process_running "${STALLD_PID}" "stalld still running after task exit"; then
-        log "✓ PASS: stalld handled task exit during boost gracefully"
+        pass "stalld handled task exit during boost gracefully"
     else
         log "✗ FAIL: stalld crashed or exited after task died during boost"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -401,7 +401,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
         grep -iE "error.*restor|fail.*restor" "${STALLD_LOG}"
         log "        These errors are normal when tasks exit during boost"
     else
-        log "✓ PASS: No restoration errors (clean handling)"
+        pass "No restoration errors (clean handling)"
     fi
 else
     log "⚠ WARNING: No boost detected in this test run"
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index d516604..b624fc9 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -67,7 +67,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Starvation detection with default runtime"
+    pass "Starvation detection with default runtime"
 else
     log "✗ FAIL: No starvation detection"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -99,7 +99,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 if wait_for_starvation_detected "${STALLD_LOG2}"; then
-    log "✓ PASS: Starvation detection with custom runtime ${custom_runtime}ns"
+    pass "Starvation detection with custom runtime ${custom_runtime}ns"
 else
     log "✗ FAIL: No starvation detection with custom runtime"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -131,7 +131,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 if wait_for_starvation_detected "${STALLD_LOG3}"; then
-    log "✓ PASS: Starvation detection with large runtime ${large_runtime}ns"
+    pass "Starvation detection with large runtime ${large_runtime}ns"
 else
     log "✗ FAIL: No starvation detection with large runtime"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -165,7 +165,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 
 # Wait for detection and boosting
 if wait_for_starvation_detected "${STALLD_LOG4}"; then
-    log "✓ PASS: Starvation detection with runtime < period"
+    pass "Starvation detection with runtime < period"
 else
     log "✗ FAIL: No starvation detection when runtime < period"
     TEST_FAILED=$((TEST_FAILED + 1))
diff --git a/tests/functional/test_deadline_boosting.sh b/tests/functional/test_deadline_boosting.sh
index a5727da..86ca0f1 100755
--- a/tests/functional/test_deadline_boosting.sh
+++ b/tests/functional/test_deadline_boosting.sh
@@ -68,11 +68,11 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 # Wait for boosting
 log "Waiting for boost detection..."
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred"
+    pass "Boosting occurred"
 
     # Verify SCHED_DEADLINE was used
     if grep -q "SCHED_DEADLINE" "${STALLD_LOG}"; then
-        log "✓ PASS: SCHED_DEADLINE boosting used (default)"
+        pass "SCHED_DEADLINE boosting used (default)"
     else
         log "✗ FAIL: SCHED_DEADLINE not mentioned in boost message"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -81,7 +81,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     # Verify boost happened after threshold
     # (starvation logged, then boosting)
     if grep -q "starved" "${STALLD_LOG}"; then
-        log "✓ PASS: Starvation detected before boosting"
+        pass "Starvation detected before boosting"
     else
         log "⚠ WARNING: No starvation message before boost"
     fi
@@ -140,7 +140,7 @@ for child_pid in ${STARVE_CHILDREN}; do
 
         # Policy 6 = SCHED_DEADLINE
         if [ "$policy" = "6" ]; then
-            log "✓ PASS: Task PID ${child_pid} boosted to SCHED_DEADLINE (policy 6)"
+            pass "Task PID ${child_pid} boosted to SCHED_DEADLINE (policy 6)"
             boosted_task_found=1
             break
         fi
@@ -151,7 +151,7 @@ if [ ${boosted_task_found} -eq 0 ]; then
     log "⚠ INFO: Could not verify DEADLINE policy in /proc (timing issue or boost already expired)"
     # Still check if boost happened in logs
     if grep -q "boosted.*SCHED_DEADLINE" "${STALLD_LOG}"; then
-        log "✓ PASS: SCHED_DEADLINE boost confirmed in logs"
+        pass "SCHED_DEADLINE boost confirmed in logs"
     else
         log "✗ FAIL: No SCHED_DEADLINE boost detected"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -213,7 +213,7 @@ if [ -n "${tracked_pid}" ]; then
     # Verify task made progress (context switches increased)
     ctxt_delta=$((ctxt_after - ctxt_before))
     if [ ${ctxt_delta} -gt 5 ]; then
-        log "✓ PASS: Task made progress during boost (${ctxt_delta} context switches)"
+        pass "Task made progress during boost (${ctxt_delta} context switches)"
     else
         log "⚠ INFO: Limited progress detected (${ctxt_delta} context switches)"
         log "        This may be acceptable depending on boost parameters"
@@ -224,7 +224,7 @@ fi
 
 # Verify boost happened
 if grep -q "boosted" "${STALLD_LOG}"; then
-    log "✓ PASS: Boost occurred as expected"
+    pass "Boost occurred as expected"
 else
     log "✗ FAIL: No boost detected"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -284,7 +284,7 @@ if [ -n "${tracked_pid}" ]; then
     log "Policy during boost window: ${boosted_policy} (6=SCHED_DEADLINE)"
 
     if [ "$boosted_policy" = "6" ]; then
-        log "✓ PASS: Policy changed to SCHED_DEADLINE during boost"
+        pass "Policy changed to SCHED_DEADLINE during boost"
     else
         log "⚠ INFO: Policy is ${boosted_policy} (may have already restored or not yet boosted)"
     fi
@@ -299,7 +299,7 @@ if [ -n "${tracked_pid}" ]; then
         log "Policy after boost: ${restored_policy}"
 
         if [ "$restored_policy" = "0" ]; then
-            log "✓ PASS: Policy restored to SCHED_OTHER (0)"
+            pass "Policy restored to SCHED_OTHER (0)"
         else
             log "⚠ INFO: Policy is ${restored_policy} after boost"
             log "        (task may have exited or restoration timing differs)"
@@ -360,18 +360,18 @@ else
     log "Number of boost events: ${boost_count}"
 
     if [ ${boost_count} -ge 2 ]; then
-        log "✓ PASS: Multiple boost events detected (${boost_count})"
+        pass "Multiple boost events detected (${boost_count})"
 
         # Verify both CPUs mentioned
         if grep -q "CPU ${CPU0}" "${STALLD_LOG}" && grep -q "CPU ${CPU1}" "${STALLD_LOG}"; then
-            log "✓ PASS: Boosts occurred on both CPUs"
+            pass "Boosts occurred on both CPUs"
         else
             log "⚠ INFO: Could not verify boosts on both specific CPUs"
         fi
 
         # Verify independent boost cycles
         if [ ${boost_count} -gt 2 ]; then
-            log "✓ PASS: Multiple boost cycles (${boost_count} total), showing independent operation"
+            pass "Multiple boost cycles (${boost_count} total), showing independent operation"
         fi
     else
         log "⚠ INFO: Only ${boost_count} boost event(s) detected"
diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index 1da1c09..df26483 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -66,11 +66,11 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -N -F -A -t $threshold -c ${TES
 # Wait for boosting
 log "Waiting for boost detection..."
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred with -F flag"
+    pass "Boosting occurred with -F flag"
 
     # Verify SCHED_FIFO was used
     if grep -q "SCHED_FIFO" "${STALLD_LOG}"; then
-        log "✓ PASS: SCHED_FIFO boosting used (as requested by -F)"
+        pass "SCHED_FIFO boosting used (as requested by -F)"
     else
         log "✗ FAIL: SCHED_FIFO not mentioned in boost message"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -120,7 +120,7 @@ for child_pid in ${STARVE_CHILDREN}; do
         # Policy 1 = SCHED_FIFO
         if [ "$policy" = "1" ]; then
             priority=$(get_sched_priority ${child_pid})
-            log "✓ PASS: Task PID ${child_pid} boosted to SCHED_FIFO (policy 1)"
+            pass "Task PID ${child_pid} boosted to SCHED_FIFO (policy 1)"
             log "        Priority: ${priority}"
             fifo_task_found=1
             break
@@ -132,7 +132,7 @@ if [ ${fifo_task_found} -eq 0 ]; then
     log "⚠ INFO: Could not verify FIFO policy in /proc (timing issue or boost already expired)"
     # FIFO emulation cycles between FIFO and OTHER, so we may catch it in OTHER state
     if grep -q "boosted.*SCHED_FIFO" "${STALLD_LOG}"; then
-        log "✓ PASS: SCHED_FIFO boost confirmed in logs"
+        pass "SCHED_FIFO boost confirmed in logs"
     else
         log "✗ FAIL: No SCHED_FIFO boost detected"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -186,7 +186,7 @@ boost_count=$(grep -c "boosted.*SCHED_FIFO" "${STALLD_LOG}")
 log "Number of FIFO boost events: ${boost_count}"
 
 if [ ${boost_count} -gt 1 ]; then
-    log "✓ PASS: Multiple FIFO boost events (${boost_count}) - emulation cycling detected"
+    pass "Multiple FIFO boost events (${boost_count}) - emulation cycling detected"
     log "        (FIFO emulation boosts, sleeps, restores, repeats)"
 else
     log "⚠ INFO: Only ${boost_count} FIFO boost event(s)"
@@ -303,7 +303,7 @@ log "  DEADLINE: ${deadline_progress} context switches"
 log "  FIFO: ${fifo_progress} context switches"
 
 if [ ${deadline_progress} -gt 0 ] && [ ${fifo_progress} -gt 0 ]; then
-    log "✓ PASS: Both DEADLINE and FIFO allowed tasks to make progress"
+    pass "Both DEADLINE and FIFO allowed tasks to make progress"
 
     # Both should be effective, but exact numbers may vary
     if [ ${deadline_progress} -gt ${fifo_progress} ]; then
@@ -343,7 +343,7 @@ fi
 
 # Check for error message in log
 if grep -qiE "single.*thread.*fifo|fifo.*single.*thread|can.*only.*deadline" "${STALLD_LOG_FAIL}"; then
-    log "✓ PASS: Error message about FIFO+single-threaded incompatibility found"
+    pass "Error message about FIFO+single-threaded incompatibility found"
 else
     log "ℹ INFO: Checking exit status or error messages..."
     if [ -s "${STALLD_LOG_FAIL}" ]; then
diff --git a/tests/functional/test_fifo_priority_starvation.sh b/tests/functional/test_fifo_priority_starvation.sh
index 886357e..3776e6b 100755
--- a/tests/functional/test_fifo_priority_starvation.sh
+++ b/tests/functional/test_fifo_priority_starvation.sh
@@ -72,11 +72,11 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a $
 # Wait for starvation detection
 log "Waiting for starvation detection..."
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: FIFO-on-FIFO starvation detected"
+    pass "FIFO-on-FIFO starvation detected"
 
     # Verify correct CPU is logged
     if grep "starved on CPU ${TEST_CPU}" "${STALLD_LOG}"; then
-        log "✓ PASS: Correct CPU ID logged (CPU ${TEST_CPU})"
+        pass "Correct CPU ID logged (CPU ${TEST_CPU})"
     else
         log "✗ FAIL: Wrong CPU ID in log"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -84,7 +84,7 @@ if wait_for_starvation_detected "${STALLD_LOG}"; then
 
     # Verify duration is logged
     if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}"; then
-        log "✓ PASS: Starvation duration logged"
+        pass "Starvation duration logged"
     else
         log "✗ FAIL: Starvation duration not logged"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -152,7 +152,7 @@ ctxt_delta=$((ctxt_after - ctxt_before))
 log "Context switch delta: ${ctxt_delta}"
 
 if [ ${ctxt_delta} -gt 0 ]; then
-    log "✓ PASS: Blockee task made progress (${ctxt_delta} context switches)"
+    pass "Blockee task made progress (${ctxt_delta} context switches)"
 else
     log "⚠ WARNING: Could not verify progress (timing issue or blockee not found)"
     # Check if boosting occurred at least
@@ -204,7 +204,7 @@ log "Third detection cycle should have occurred"
 # Check if we see accumulating starvation time in logs
 # Task merging means the timestamp is preserved, so duration increases
 if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}" | wc -l | grep -q "[2-9]"; then
-    log "✓ PASS: Multiple starvation reports found"
+    pass "Multiple starvation reports found"
 
     # Extract starvation durations from log
     durations=$(grep -oE "starved on CPU ${TEST_CPU} for [0-9]+" "${STALLD_LOG}" | grep -oE "[0-9]+$")
@@ -215,7 +215,7 @@ if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}" | wc
     last_duration=$(echo "$durations" | tail -1)
 
     if [ ${last_duration} -gt ${first_duration} ]; then
-        log "✓ PASS: Starvation duration increased (${first_duration}s -> ${last_duration}s)"
+        pass "Starvation duration increased (${first_duration}s -> ${last_duration}s)"
         log "        This confirms task merging preserved the timestamp"
     else
         log "✗ FAIL: Starvation duration did not increase (timestamp may have been reset)"
@@ -258,7 +258,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a $
 # Wait for starvation detection
 log "Waiting for starvation detection..."
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Starvation detected even with close priority gap (6 vs 5)"
+    pass "Starvation detected even with close priority gap (6 vs 5)"
 else
     log "⚠ WARNING: Starvation not detected with close priority gap"
     log "        (May be due to queue_track backend limitation)"
@@ -297,12 +297,12 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -N -t $threshold -c ${TEST_CPU} -a $
 # Wait for boosting
 log "Waiting for boost detection..."
 if wait_for_boost_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Boosting occurred"
+    pass "Boosting occurred"
 
     # Try to verify the correct task was boosted
     # stalld logs should show the blockee task name (starvation_gen thread)
     if grep "boosted.*starvation_gen" "${STALLD_LOG}"; then
-        log "✓ PASS: starvation_gen task was boosted (likely the blockee)"
+        pass "starvation_gen task was boosted (likely the blockee)"
     else
         log "ℹ INFO: Could not verify specific task from logs"
     fi
diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index f8f7b4c..fec2e00 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -64,7 +64,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
 
     # Look for SCHED_DEADLINE indicators
     if grep -qi "deadline\|SCHED_DEADLINE" "${STALLD_LOG}"; then
-        log "✓ PASS: SCHED_DEADLINE used by default"
+        pass "SCHED_DEADLINE used by default"
     elif grep -qi "fifo\|SCHED_FIFO" "${STALLD_LOG}"; then
         log "⚠ WARNING: SCHED_FIFO used instead of SCHED_DEADLINE"
     else
@@ -105,7 +105,7 @@ if wait_for_boost_detected "${STALLD_LOG2}"; then
 
     # Look for SCHED_FIFO indicators
     if grep -qi "fifo\|SCHED_FIFO" "${STALLD_LOG2}"; then
-        log "✓ PASS: SCHED_FIFO used with -F flag"
+        pass "SCHED_FIFO used with -F flag"
     elif grep -qi "deadline\|SCHED_DEADLINE" "${STALLD_LOG2}"; then
         log "✗ FAIL: SCHED_DEADLINE used despite -F flag"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -148,7 +148,7 @@ if grep -qi "priority\|prio" "${STALLD_LOG3}"; then
 fi
 
 if grep -q "boost" "${STALLD_LOG3}"; then
-    log "✓ PASS: FIFO boosting with priority setting completed"
+    pass "FIFO boosting with priority setting completed"
 else
     log "⚠ WARNING: No boosting detected"
 fi
@@ -187,7 +187,7 @@ if wait_for_boost_detected "${STALLD_LOG4}"; then
 
     # Check for restoration messages (part of FIFO emulation)
     if grep -qi "restor\|unboosted\|normal\|original" "${STALLD_LOG4}"; then
-        log "✓ PASS: FIFO emulation with restoration detected"
+        pass "FIFO emulation with restoration detected"
     else
         log "ℹ INFO: FIFO boosting completed (restoration may be implicit)"
     fi
diff --git a/tests/functional/test_idle_detection.sh b/tests/functional/test_idle_detection.sh
index ef1c572..af4d7a4 100755
--- a/tests/functional/test_idle_detection.sh
+++ b/tests/functional/test_idle_detection.sh
@@ -100,7 +100,7 @@ fi
 
 # Verify CPU is actually idle
 if is_cpu_idle ${TEST_CPU}; then
-    log "✓ PASS: CPU ${TEST_CPU} is currently idle (idle time increasing)"
+    pass "CPU ${TEST_CPU} is currently idle (idle time increasing)"
 else
     log "⚠ INFO: CPU ${TEST_CPU} appears busy (background activity)"
 fi
@@ -130,10 +130,10 @@ if [ -n "${idle_time1}" ] && [ -n "${idle_time2}" ]; then
     log "Idle time delta: ${delta}"
 
     if [ ${delta} -gt 0 ]; then
-        log "✓ PASS: Idle time increased (CPU is idle)"
+        pass "Idle time increased (CPU is idle)"
         log "        stalld would skip this CPU"
     else
-        log "✓ PASS: Idle time unchanged (CPU is busy)"
+        pass "Idle time unchanged (CPU is busy)"
         log "        stalld would parse this CPU"
     fi
 else
@@ -167,7 +167,7 @@ log "Waiting for stalld to detect busy CPU and starvation..."
 
 # Verify stalld detected starvation (meaning it resumed monitoring)
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: stalld detected starvation on now-busy CPU"
+    pass "stalld detected starvation on now-busy CPU"
     log "        Monitoring resumed when CPU became busy"
 else
     log "⚠ INFO: No starvation detected"
@@ -241,9 +241,9 @@ else
     log "CPU ${CPU1} detections: ${cpu1_detections} (should be >0, it's busy)"
 
     if [ ${cpu0_detections} -eq 0 ] && [ ${cpu1_detections} -gt 0 ]; then
-        log "✓ PASS: Idle CPU skipped, busy CPU monitored"
+        pass "Idle CPU skipped, busy CPU monitored"
     elif [ ${cpu1_detections} -gt 0 ]; then
-        log "✓ PASS: Busy CPU ${CPU1} monitored"
+        pass "Busy CPU ${CPU1} monitored"
         if [ ${cpu0_detections} -gt 0 ]; then
             log "⚠ INFO: CPU ${CPU0} also had detections (may have background activity)"
         fi
diff --git a/tests/functional/test_pidfile.sh b/tests/functional/test_pidfile.sh
index fbdc9fe..fd4fedc 100755
--- a/tests/functional/test_pidfile.sh
+++ b/tests/functional/test_pidfile.sh
@@ -54,7 +54,7 @@ for pidfile in /var/run/stalld.pid /run/stalld.pid; do
         # Verify PID matches
         pid_from_file=$(cat "$pidfile")
         if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-            log "✓ PASS: Default pidfile contains correct PID"
+            pass "Default pidfile contains correct PID"
         else
             log "✗ FAIL: Default pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
             TEST_FAILED=$((TEST_FAILED + 1))
@@ -92,12 +92,12 @@ sleep 2
 
 # Verify pidfile was created
 if [ -f "${custom_pidfile}" ]; then
-    log "✓ PASS: Custom pidfile created at ${custom_pidfile}"
+    pass "Custom pidfile created at ${custom_pidfile}"
 
     # Verify content
     pid_from_file=$(cat "${custom_pidfile}")
     if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-        log "✓ PASS: Custom pidfile contains correct PID ($pid_from_file)"
+        pass "Custom pidfile contains correct PID ($pid_from_file)"
     else
         log "✗ FAIL: Custom pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -113,7 +113,7 @@ log "Test 3: Verify pidfile removed on clean shutdown"
 stop_stalld
 
 if [ ! -f "${custom_pidfile}" ]; then
-    log "✓ PASS: Pidfile removed on clean shutdown"
+    pass "Pidfile removed on clean shutdown"
 else
     log "⚠ WARNING: Pidfile still exists after shutdown (may be expected)"
     # Not failing - some implementations keep pidfile
@@ -139,11 +139,11 @@ start_stalld -l -t 5 --pidfile "${tmp_pidfile}"
 sleep 2
 
 if [ -f "${tmp_pidfile}" ]; then
-    log "✓ PASS: Pidfile created in /tmp directory"
+    pass "Pidfile created in /tmp directory"
 
     pid_from_file=$(cat "${tmp_pidfile}")
     if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-        log "✓ PASS: /tmp pidfile contains correct PID"
+        pass "/tmp pidfile contains correct PID"
     else
         log "✗ FAIL: /tmp pidfile has incorrect PID"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -175,11 +175,11 @@ start_stalld -f -v -l -t 5 --pidfile "${fg_pidfile}"
 sleep 2
 
 if [ -f "${fg_pidfile}" ]; then
-    log "✓ PASS: Pidfile created in foreground mode"
+    pass "Pidfile created in foreground mode"
 
     pid_from_file=$(cat "${fg_pidfile}")
     if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-        log "✓ PASS: Foreground mode pidfile contains correct PID"
+        pass "Foreground mode pidfile contains correct PID"
     else
         log "✗ FAIL: Foreground mode pidfile has incorrect PID"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -250,7 +250,7 @@ sleep 2
 if [ -f "${readable_pidfile}" ]; then
     # Try to read the pidfile as a regular user would
     if cat "${readable_pidfile}" > /dev/null 2>&1; then
-        log "✓ PASS: Pidfile is readable"
+        pass "Pidfile is readable"
 
         # Check permissions
         perms=$(stat -c "%a" "${readable_pidfile}" 2>/dev/null || stat -f "%Lp" "${readable_pidfile}" 2>/dev/null)
diff --git a/tests/functional/test_runqueue_parsing.sh b/tests/functional/test_runqueue_parsing.sh
index ccb1982..fc69fa0 100755
--- a/tests/functional/test_runqueue_parsing.sh
+++ b/tests/functional/test_runqueue_parsing.sh
@@ -102,11 +102,11 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
 
     # Wait for starvation detection
     if wait_for_starvation_detected "${STALLD_LOG_BPF}"; then
-        log "✓ PASS: eBPF backend detected starving tasks"
+        pass "eBPF backend detected starving tasks"
 
         # Verify task info is present (PID, comm)
         if grep -E "starvation_gen.*starved on CPU ${TEST_CPU}" "${STALLD_LOG_BPF}"; then
-            log "✓ PASS: Task name (comm) correctly extracted"
+            pass "Task name (comm) correctly extracted"
         else
             log "✗ FAIL: Task name not found in eBPF backend output"
             TEST_FAILED=$((TEST_FAILED + 1))
@@ -114,7 +114,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
 
         # Verify PID is logged
         if grep -E "\[[0-9]+\].*starved on CPU" "${STALLD_LOG_BPF}"; then
-            log "✓ PASS: Task PID correctly extracted"
+            pass "Task PID correctly extracted"
         else
             log "⚠ INFO: PID format may have changed"
         fi
@@ -157,11 +157,11 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
     # Wait for starvation detection
     if wait_for_starvation_detected "${STALLD_LOG_SCHED}"; then
-        log "✓ PASS: sched_debug backend detected starving tasks"
+        pass "sched_debug backend detected starving tasks"
 
         # Verify task info is present
         if grep -E "starvation_gen.*starved on CPU ${TEST_CPU}" "${STALLD_LOG_SCHED}"; then
-            log "✓ PASS: Task name (comm) correctly extracted"
+            pass "Task name (comm) correctly extracted"
         else
             log "✗ FAIL: Task name not found in sched_debug backend output"
             TEST_FAILED=$((TEST_FAILED + 1))
@@ -169,7 +169,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
         # Verify PID is logged
         if grep -E "\[[0-9]+\].*starved on CPU" "${STALLD_LOG_SCHED}"; then
-            log "✓ PASS: Task PID correctly extracted"
+            pass "Task PID correctly extracted"
         else
             log "⚠ INFO: PID format may have changed"
         fi
@@ -249,14 +249,14 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     # Compare results
     log ""
     if [ ${bpf_detections} -gt 0 ] && [ ${sched_detections} -gt 0 ]; then
-        log "✓ PASS: Both backends detected starvation"
+        pass "Both backends detected starvation"
 
         # Check if detection counts are similar (within reasonable variance)
         diff=$((bpf_detections - sched_detections))
         diff=${diff#-}  # absolute value
 
         if [ ${diff} -le 2 ]; then
-            log "✓ PASS: Detection counts are consistent (eBPF: ${bpf_detections}, sched_debug: ${sched_detections})"
+            pass "Detection counts are consistent (eBPF: ${bpf_detections}, sched_debug: ${sched_detections})"
         else
             log "⚠ INFO: Detection counts differ (eBPF: ${bpf_detections}, sched_debug: ${sched_detections})"
             log "        This may be due to timing differences between backends"
@@ -313,7 +313,7 @@ if [ -n "$test_backend" ]; then
 
     # Check for task name (comm field)
     if grep -q "starvation_gen" "${log_file}"; then
-        log "✓ PASS: Task name (comm) field extracted"
+        pass "Task name (comm) field extracted"
     else
         log "✗ FAIL: Task name (comm) field not found"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -321,7 +321,7 @@ if [ -n "$test_backend" ]; then
 
     # Check for PID field (format: name-PID or [PID])
     if grep -qE "(starvation_gen-[0-9]+|\[[0-9]+\])" "${log_file}"; then
-        log "✓ PASS: PID field extracted"
+        pass "PID field extracted"
     else
         log "✗ FAIL: PID field not found"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -329,7 +329,7 @@ if [ -n "$test_backend" ]; then
 
     # Check for CPU ID
     if grep -q "CPU ${TEST_CPU}" "${log_file}"; then
-        log "✓ PASS: CPU ID field extracted"
+        pass "CPU ID field extracted"
     else
         log "✗ FAIL: CPU ID field not found"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -337,7 +337,7 @@ if [ -n "$test_backend" ]; then
 
     # Check for starvation duration
     if grep -qE "for [0-9]+ seconds" "${log_file}"; then
-        log "✓ PASS: Starvation duration calculated from context switches/time"
+        pass "Starvation duration calculated from context switches/time"
     else
         log "✗ FAIL: Starvation duration not found"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -371,21 +371,21 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     # Check for format detection messages
     if grep -q "detect_task_format" "${STALLD_LOG_SCHED}"; then
         detected_format=$(grep "detect_task_format" "${STALLD_LOG_SCHED}" | grep "detected" | tail -1)
-        log "✓ PASS: Kernel format auto-detection occurred"
+        pass "Kernel format auto-detection occurred"
         log "ℹ INFO: ${detected_format}"
 
         # Check if field offsets were detected
         if grep -q "found 'task' at word" "${STALLD_LOG_SCHED}"; then
-            log "✓ PASS: Task field offset detected"
+            pass "Task field offset detected"
         fi
         if grep -q "found 'PID' at word" "${STALLD_LOG_SCHED}"; then
-            log "✓ PASS: PID field offset detected"
+            pass "PID field offset detected"
         fi
         if grep -q "found 'switches' at word" "${STALLD_LOG_SCHED}"; then
-            log "✓ PASS: Switches field offset detected"
+            pass "Switches field offset detected"
         fi
         if grep -q "found 'prio' at word" "${STALLD_LOG_SCHED}"; then
-            log "✓ PASS: Priority field offset detected"
+            pass "Priority field offset detected"
         fi
     else
         log "⚠ INFO: Format detection messages not in log (may not be verbose enough)"
@@ -393,7 +393,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
     # Verify the backend still works despite format
     if grep -q "starved on CPU" "${STALLD_LOG_SCHED}"; then
-        log "✓ PASS: Backend successfully parsed tasks despite kernel format"
+        pass "Backend successfully parsed tasks despite kernel format"
     else
         log "⚠ INFO: No starvation detected in this test run"
     fi
diff --git a/tests/functional/test_starvation_detection.sh b/tests/functional/test_starvation_detection.sh
index 3c6fd6e..6d4d7c5 100755
--- a/tests/functional/test_starvation_detection.sh
+++ b/tests/functional/test_starvation_detection.sh
@@ -75,11 +75,11 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${TEST_CPU} -
 # Wait for starvation detection
 log "Waiting for starvation detection..."
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Starvation detected"
+    pass "Starvation detected"
 
     # Verify correct CPU is logged
     if grep "starved on CPU ${TEST_CPU}" "${STALLD_LOG}"; then
-        log "✓ PASS: Correct CPU ID logged (CPU ${TEST_CPU})"
+        pass "Correct CPU ID logged (CPU ${TEST_CPU})"
     else
         log "✗ FAIL: Wrong CPU ID in log"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -87,7 +87,7 @@ if wait_for_starvation_detected "${STALLD_LOG}"; then
 
     # Verify duration is logged
     if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}"; then
-        log "✓ PASS: Starvation duration logged"
+        pass "Starvation duration logged"
     else
         log "✗ FAIL: Starvation duration not logged"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -145,7 +145,7 @@ if [ -n "${STARVE_CHILDREN}" ]; then
 
             ctxt_delta=$((ctxt_after - ctxt_before))
             if [ ${ctxt_delta} -lt 5 ]; then
-                log "✓ PASS: Context switch count remained low (delta: ${ctxt_delta})"
+                pass "Context switch count remained low (delta: ${ctxt_delta})"
             else
                 log "✗ FAIL: Context switches increased significantly (delta: ${ctxt_delta})"
                 TEST_FAILED=$((TEST_FAILED + 1))
@@ -199,7 +199,7 @@ stop_stalld
 # Task merging means the timestamp is preserved, so duration increases
 report_count=$(grep -cE "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}")
 if [ "${report_count}" -ge 2 ]; then
-    log "✓ PASS: Multiple starvation reports found (${report_count} reports)"
+    pass "Multiple starvation reports found (${report_count} reports)"
 
     # Extract starvation durations from log
     durations=$(grep -oE "starved on CPU ${TEST_CPU} for [0-9]+" "${STALLD_LOG}" | grep -oE "[0-9]+$")
@@ -210,7 +210,7 @@ if [ "${report_count}" -ge 2 ]; then
     last_duration=$(echo "$durations" | tail -1)
 
     if [ ${last_duration} -gt ${first_duration} ]; then
-        log "✓ PASS: Starvation duration increased (${first_duration}s -> ${last_duration}s)"
+        pass "Starvation duration increased (${first_duration}s -> ${last_duration}s)"
         log "        This confirms task merging preserved the timestamp"
     else
         log "✗ FAIL: Starvation duration did not increase (timestamp may have been reset)"
@@ -283,14 +283,14 @@ else
 
     # Check both CPUs detected - specifically look for starvation_gen tasks
     if grep -qE "starvation_gen.*starved on CPU ${CPU0}|starved on CPU ${CPU0}.*starvation_gen" "${STALLD_LOG}"; then
-        log "✓ PASS: Starvation detected on CPU ${CPU0}"
+        pass "Starvation detected on CPU ${CPU0}"
     else
         log "✗ FAIL: Starvation not detected on CPU ${CPU0}"
         TEST_FAILED=$((TEST_FAILED + 1))
     fi
 
     if grep -qE "starvation_gen.*starved on CPU ${CPU1}|starved on CPU ${CPU1}.*starvation_gen" "${STALLD_LOG}"; then
-        log "✓ PASS: Starvation detected on CPU ${CPU1}"
+        pass "Starvation detected on CPU ${CPU1}"
     else
         log "✗ FAIL: Starvation not detected on CPU ${CPU1}"
         TEST_FAILED=$((TEST_FAILED + 1))
@@ -330,7 +330,7 @@ log "Creating a busy task that should NOT be starved"
     # Verify this task was NOT reported as starved
     # Since it's making progress, stalld shouldn't detect it
     if ! grep "starved" "${STALLD_LOG}"; then
-        log "✓ PASS: No false positive - task making progress not reported as starved"
+        pass "No false positive - task making progress not reported as starved"
     else
         # Check if our specific task was reported
         log "Log shows starvation, checking if it's our progress-making task..."
@@ -370,7 +370,7 @@ sleep 5
 
 # Verify stalld is still running (didn't crash)
 if assert_process_running "${STALLD_PID}" "stalld still running after task exit"; then
-    log "✓ PASS: stalld handled task exit gracefully"
+    pass "stalld handled task exit gracefully"
 else
     log "✗ FAIL: stalld crashed or exited unexpectedly"
     TEST_FAILED=$((TEST_FAILED + 1))
@@ -383,7 +383,7 @@ if grep -iE "error|segfault|crash" "${STALLD_LOG}"; then
     grep -iE "error|segfault|crash" "${STALLD_LOG}"
     TEST_FAILED=$((TEST_FAILED + 1))
 else
-    log "✓ PASS: No error messages in log"
+    pass "No error messages in log"
 fi
 
 stop_stalld
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index b904867..a35f898 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -76,7 +76,7 @@ log "Waiting for detection (threshold: ${threshold}s)"
 
 # Check if starvation was detected - specifically look for starvation_gen tasks
 if wait_for_starvation_detected "${STALLD_LOG}"; then
-    log "✓ PASS: Starvation detected after ${threshold}s threshold"
+    pass "Starvation detected after ${threshold}s threshold"
 else
     log "✗ FAIL: Starvation not detected after ${threshold}s threshold"
     log "Log contents:"
@@ -121,7 +121,7 @@ sleep 2
 
 # Check that starvation_gen was NOT detected (duration less than threshold)
 if ! grep -qE "starvation_gen.*starved on CPU ${TEST_CPU}|starved on CPU ${TEST_CPU}.*starvation_gen" "${STALLD_LOG2}"; then
-    log "✓ PASS: No starvation detected for duration less than threshold"
+    pass "No starvation detected for duration less than threshold"
 else
     log "✗ FAIL: Starvation detected before threshold"
     log "Found starvation_gen task in logs:"
@@ -161,7 +161,7 @@ log "Waiting for detection (threshold: ${threshold}s)"
 
 # Check if starvation_gen was detected
 if wait_for_starvation_detected "${STALLD_LOG3}"; then
-    log "✓ PASS: Starvation detected with ${threshold}s threshold"
+    pass "Starvation detected with ${threshold}s threshold"
 else
     log "✗ FAIL: Starvation not detected with ${threshold}s threshold"
     log "Log contents:"
diff --git a/tests/functional/test_task_merging.sh b/tests/functional/test_task_merging.sh
index 5cd6406..b89230e 100755
--- a/tests/functional/test_task_merging.sh
+++ b/tests/functional/test_task_merging.sh
@@ -103,7 +103,7 @@ log "Second detection: task starved for ${second_duration}s"
 # Verify timestamp was preserved (duration increased)
 if [ "${second_duration}" -gt "${first_duration}" ]; then
     delta=$((second_duration - first_duration))
-    log "✓ PASS: Starvation duration increased by ${delta}s"
+    pass "Starvation duration increased by ${delta}s"
     log "        Timestamp preserved across monitoring cycles"
 else
     log "✗ FAIL: Duration did not increase (${first_duration}s -> ${second_duration}s)"
@@ -122,7 +122,7 @@ fi
 log "Third detection: task starved for ${third_duration}s"
 
 if [ "${third_duration}" -gt "${second_duration}" ]; then
-    log "✓ PASS: Duration continues to accumulate (${third_duration}s total)"
+    pass "Duration continues to accumulate (${third_duration}s total)"
 else
     log "⚠ INFO: Duration did not increase in third cycle"
 fi
@@ -179,7 +179,7 @@ if [ -n "${tracked_pid}" ]; then
 
         delta=$((ctxsw_after - ctxsw_before))
         if [ ${delta} -lt 5 ]; then
-            log "✓ PASS: Context switches remained low (delta: ${delta})"
+            pass "Context switches remained low (delta: ${delta})"
             log "        Task meeting merge criteria (same PID, same ctxsw)"
         else
             log "⚠ INFO: Context switches increased by ${delta}"
@@ -191,7 +191,7 @@ if [ -n "${tracked_pid}" ]; then
         log "Total starvation detections: ${detections}"
 
         if [ ${detections} -ge 2 ]; then
-            log "✓ PASS: Multiple detections indicate task merging across cycles"
+            pass "Multiple detections indicate task merging across cycles"
         fi
     else
         log "⚠ WARNING: Task exited before second check"
@@ -242,7 +242,7 @@ if [ -n "${tracked_pid}" ]; then
     # If task was boosted, context switches should have changed
     # meaning timestamp should reset for next starvation period
     if grep -q "boosted" "${STALLD_LOG}"; then
-        log "✓ PASS: Task was boosted (made progress)"
+        pass "Task was boosted (made progress)"
 
         # Check if we see a new starvation period starting
         # (This is harder to verify, but context switches changing = no merge)
@@ -311,7 +311,7 @@ else
         log "CPU ${CPU0}: ${cpu0_first}s -> ${cpu0_last}s"
 
         if [ "${cpu0_last}" -gt "${cpu0_first}" ]; then
-            log "✓ PASS: CPU ${CPU0} task merging working (timestamp preserved)"
+            pass "CPU ${CPU0} task merging working (timestamp preserved)"
         fi
     fi
 
@@ -327,12 +327,12 @@ else
         log "CPU ${CPU1}: ${cpu1_first}s -> ${cpu1_last}s"
 
         if [ "${cpu1_last}" -gt "${cpu1_first}" ]; then
-            log "✓ PASS: CPU ${CPU1} task merging working (timestamp preserved)"
+            pass "CPU ${CPU1} task merging working (timestamp preserved)"
         fi
     fi
 
     if [ ${cpu0_detections} -ge 2 ] && [ ${cpu1_detections} -ge 2 ]; then
-        log "✓ PASS: Independent task merging on both CPUs"
+        pass "Independent task merging on both CPUs"
     fi
 
     # Cleanup
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 30/36] tests: Add fail() helper and use for all test failures
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (28 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 29/36] tests/functional: Use pass() for all test pass reporting Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 31/36] tests/helpers: Use pass()/fail() in assert functions Wander Lairson Costa
                   ` (5 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Add a fail() function that mirrors pass(), logging a failure
message and incrementing the failure counter. Replace all manual
fail patterns across the test suite, including both the log-based
and echo-based variants.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_affinity.sh             |  6 ++--
 tests/functional/test_backend_selection.sh    |  6 ++--
 tests/functional/test_boost_duration.sh       | 18 ++++-------
 tests/functional/test_boost_period.sh         | 18 ++++-------
 tests/functional/test_boost_restoration.sh    |  3 +-
 tests/functional/test_boost_runtime.sh        | 21 +++++--------
 tests/functional/test_cpu_selection.sh        | 18 ++++-------
 tests/functional/test_deadline_boosting.sh    | 12 +++-----
 tests/functional/test_fifo_boosting.sh        | 12 +++-----
 .../test_fifo_priority_starvation.sh          | 15 ++++------
 tests/functional/test_force_fifo.sh           |  6 ++--
 tests/functional/test_foreground.sh           |  9 ++----
 tests/functional/test_idle_detection.sh       |  3 +-
 tests/functional/test_log_only.sh             |  6 ++--
 tests/functional/test_logging_destinations.sh |  6 ++--
 tests/functional/test_pidfile.sh              | 27 ++++++-----------
 tests/functional/test_runqueue_parsing.sh     | 27 ++++++-----------
 tests/functional/test_starvation_detection.sh | 30 +++++++------------
 tests/functional/test_starvation_threshold.sh | 15 ++++------
 tests/functional/test_task_merging.sh         |  3 +-
 tests/helpers/test_helpers.sh                 | 11 ++++++-
 21 files changed, 97 insertions(+), 175 deletions(-)

diff --git a/tests/functional/test_affinity.sh b/tests/functional/test_affinity.sh
index b74886d..7ecd85f 100755
--- a/tests/functional/test_affinity.sh
+++ b/tests/functional/test_affinity.sh
@@ -109,8 +109,7 @@ affinity=$(check_affinity "${STALLD_PID}")
 if [ "$affinity" = "0" ]; then
     pass "stalld restricted to CPU 0"
 else
-    log "✗ FAIL: stalld affinity ($affinity) doesn't match requested (0)"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld affinity ($affinity) doesn't match requested (0)"
 fi
 
 stop_stalld
@@ -270,8 +269,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Invalid CPU affinity rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid CPU affinity"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid CPU affinity"
 fi
 
 #=============================================================================
diff --git a/tests/functional/test_backend_selection.sh b/tests/functional/test_backend_selection.sh
index 8a22e0a..2db8353 100755
--- a/tests/functional/test_backend_selection.sh
+++ b/tests/functional/test_backend_selection.sh
@@ -39,8 +39,7 @@ test_backend_flag() {
 	CLEANUP_PIDS+=("${STALLD_PID}")
 
 	if ! wait_for_stalld_ready "${log_file}" 15; then
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: stalld failed to start (${description})"
+		fail "stalld failed to start (${description})"
 		stop_stalld
 		return 1
 	fi
@@ -48,8 +47,7 @@ test_backend_flag() {
 	if grep -q "${expected_msg}" "${log_file}"; then
 		pass "${description}"
 	else
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: Backend message not found (${description})"
+		fail "Backend message not found (${description})"
 		echo "  Expected: ${expected_msg}"
 		echo "  Log contents:"
 		cat "${log_file}"
diff --git a/tests/functional/test_boost_duration.sh b/tests/functional/test_boost_duration.sh
index 61218e9..83d8355 100755
--- a/tests/functional/test_boost_duration.sh
+++ b/tests/functional/test_boost_duration.sh
@@ -69,8 +69,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_starvation_detected "${STALLD_LOG}"; then
     pass "Starvation detection occurred with default duration"
 else
-    log "✗ FAIL: No starvation detection"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection"
 fi
 
 # Cleanup
@@ -101,8 +100,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_starvation_detected "${STALLD_LOG2}"; then
     pass "Starvation detection with ${short_duration}s duration"
 else
-    log "✗ FAIL: No starvation detection with short duration"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection with short duration"
 fi
 
 # Cleanup
@@ -135,8 +133,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${long_starvation}
 if wait_for_starvation_detected "${STALLD_LOG3}"; then
     pass "Starvation detection with ${long_duration}s duration"
 else
-    log "✗ FAIL: No starvation detection with long duration"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection with long duration"
 fi
 
 # Cleanup
@@ -168,8 +165,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 1 -d 15
 if wait_for_starvation_detected "${STALLD_LOG4}"; then
     pass "Starvation detection with ${duration}s boost duration"
 else
-    log "✗ FAIL: No starvation detection"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection"
 fi
 
 # Cleanup
@@ -202,8 +198,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Zero duration rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid duration value 0"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid duration value 0"
 fi
 
 # Test 6: Negative duration
@@ -217,8 +212,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Negative duration rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid negative duration"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid negative duration"
 fi
 
 log ""
diff --git a/tests/functional/test_boost_period.sh b/tests/functional/test_boost_period.sh
index 671f7cb..3f3ef46 100755
--- a/tests/functional/test_boost_period.sh
+++ b/tests/functional/test_boost_period.sh
@@ -71,10 +71,9 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
         log "ℹ INFO: Period information found in logs"
     fi
 else
-    log "✗ FAIL: No boosting detected"
+    fail "No boosting detected"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -103,8 +102,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_boost_detected "${STALLD_LOG}"; then
     pass "Boosting occurred with custom period ${custom_period} ns"
 else
-    log "✗ FAIL: No boosting with custom period"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No boosting with custom period"
 fi
 
 # Cleanup
@@ -133,8 +131,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_boost_detected "${STALLD_LOG}"; then
     pass "Boosting occurred with short period ${short_period} ns"
 else
-    log "✗ FAIL: No boosting with short period"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No boosting with short period"
 fi
 
 # Cleanup
@@ -163,8 +160,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_boost_detected "${STALLD_LOG}"; then
     pass "Boosting occurred with long period ${long_period} ns"
 else
-    log "✗ FAIL: No boosting with long period"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No boosting with long period"
 fi
 
 # Cleanup
@@ -195,8 +191,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Zero period rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid period value 0"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid period value 0"
 fi
 
 #=============================================================================
@@ -216,8 +211,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Negative period rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid negative period"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid negative period"
 fi
 
 #=============================================================================
diff --git a/tests/functional/test_boost_restoration.sh b/tests/functional/test_boost_restoration.sh
index 024d869..bac73ba 100755
--- a/tests/functional/test_boost_restoration.sh
+++ b/tests/functional/test_boost_restoration.sh
@@ -391,8 +391,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     if assert_process_running "${STALLD_PID}" "stalld still running after task exit"; then
         pass "stalld handled task exit during boost gracefully"
     else
-        log "✗ FAIL: stalld crashed or exited after task died during boost"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "stalld crashed or exited after task died during boost"
     fi
 
     # Check for error messages
diff --git a/tests/functional/test_boost_runtime.sh b/tests/functional/test_boost_runtime.sh
index b624fc9..71e25f1 100755
--- a/tests/functional/test_boost_runtime.sh
+++ b/tests/functional/test_boost_runtime.sh
@@ -69,8 +69,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_starvation_detected "${STALLD_LOG}"; then
     pass "Starvation detection with default runtime"
 else
-    log "✗ FAIL: No starvation detection"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection"
 fi
 
 # Cleanup
@@ -101,8 +100,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_starvation_detected "${STALLD_LOG2}"; then
     pass "Starvation detection with custom runtime ${custom_runtime}ns"
 else
-    log "✗ FAIL: No starvation detection with custom runtime"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection with custom runtime"
 fi
 
 # Cleanup
@@ -133,8 +131,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_starvation_detected "${STALLD_LOG3}"; then
     pass "Starvation detection with large runtime ${large_runtime}ns"
 else
-    log "✗ FAIL: No starvation detection with large runtime"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection with large runtime"
 fi
 
 # Cleanup
@@ -167,8 +164,7 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 if wait_for_starvation_detected "${STALLD_LOG4}"; then
     pass "Starvation detection with runtime < period"
 else
-    log "✗ FAIL: No starvation detection when runtime < period"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No starvation detection when runtime < period"
 fi
 
 # Cleanup
@@ -202,8 +198,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Runtime > period rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid runtime > period"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid runtime > period"
 fi
 
 #=============================================================================
@@ -224,8 +219,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Zero runtime rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid runtime value 0"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid runtime value 0"
 fi
 
 #=============================================================================
@@ -246,8 +240,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Negative runtime rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid negative runtime"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid negative runtime"
 fi
 
 log ""
diff --git a/tests/functional/test_cpu_selection.sh b/tests/functional/test_cpu_selection.sh
index 2f9875d..99eac44 100755
--- a/tests/functional/test_cpu_selection.sh
+++ b/tests/functional/test_cpu_selection.sh
@@ -44,8 +44,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 if grep -q "cpu 0" "$STALLD_LOG"; then
     pass "stalld monitoring CPU 0"
 else
-    TEST_FAILED=$((TEST_FAILED + 1))
-    echo -e "  ${RED}FAIL${NC}: stalld not monitoring CPU 0"
+    fail "stalld not monitoring CPU 0"
 fi
 
 stop_stalld
@@ -70,8 +69,7 @@ if [ "$num_cpus" -ge 4 ]; then
     if [ "$cpu0_found" -eq 1 ] && [ "$cpu2_found" -eq 1 ]; then
         pass "stalld monitoring CPUs 0 and 2"
     else
-        TEST_FAILED=$((TEST_FAILED + 1))
-        echo -e "  ${RED}FAIL${NC}: stalld not monitoring specified CPUs (0: $cpu0_found, 2: $cpu2_found)"
+        fail "stalld not monitoring specified CPUs (0: $cpu0_found, 2: $cpu2_found)"
     fi
 
     stop_stalld
@@ -103,8 +101,7 @@ if [ "$num_cpus" -ge 4 ]; then
     if [ "$cpu0_found" -eq 1 ] && [ "$cpu1_found" -eq 1 ] && [ "$cpu2_found" -eq 1 ]; then
         pass "stalld monitoring CPUs 0-2"
     else
-        TEST_FAILED=$((TEST_FAILED + 1))
-        echo -e "  ${RED}FAIL${NC}: stalld not monitoring specified CPU range (0: $cpu0_found, 1: $cpu1_found, 2: $cpu2_found)"
+        fail "stalld not monitoring specified CPU range (0: $cpu0_found, 1: $cpu1_found, 2: $cpu2_found)"
     fi
 
     stop_stalld
@@ -130,8 +127,7 @@ if [ "$num_cpus" -ge 6 ]; then
     if [ "$monitored_cpus" -eq 4 ]; then
         pass "stalld monitoring combined CPU specification (0,2-4)"
     else
-        TEST_FAILED=$((TEST_FAILED + 1))
-        echo -e "  ${RED}FAIL${NC}: stalld not monitoring all specified CPUs (found $monitored_cpus/4)"
+        fail "stalld not monitoring all specified CPUs (found $monitored_cpus/4)"
     fi
 
     stop_stalld
@@ -155,8 +151,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "stalld rejected invalid CPU number"
 else
-    log "✗ FAIL: stalld did not reject invalid CPU"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid CPU"
 fi
 
 # Test 6: Verify non-selected CPUs are NOT monitored
@@ -170,8 +165,7 @@ if [ "$num_cpus" -ge 2 ]; then
     if ! grep -q "cpu 1" "$STALLD_LOG" || grep -q "not monitoring.*cpu 1" "$STALLD_LOG"; then
         pass "stalld not monitoring non-selected CPU 1"
     else
-        TEST_FAILED=$((TEST_FAILED + 1))
-        echo -e "  ${RED}FAIL${NC}: stalld appears to be monitoring CPU 1 when only CPU 0 selected"
+        fail "stalld appears to be monitoring CPU 1 when only CPU 0 selected"
     fi
 
     stop_stalld
diff --git a/tests/functional/test_deadline_boosting.sh b/tests/functional/test_deadline_boosting.sh
index 86ca0f1..544222a 100755
--- a/tests/functional/test_deadline_boosting.sh
+++ b/tests/functional/test_deadline_boosting.sh
@@ -74,8 +74,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     if grep -q "SCHED_DEADLINE" "${STALLD_LOG}"; then
         pass "SCHED_DEADLINE boosting used (default)"
     else
-        log "✗ FAIL: SCHED_DEADLINE not mentioned in boost message"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "SCHED_DEADLINE not mentioned in boost message"
     fi
 
     # Verify boost happened after threshold
@@ -86,10 +85,9 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
         log "⚠ WARNING: No starvation message before boost"
     fi
 else
-    log "✗ FAIL: No boosting detected"
+    fail "No boosting detected"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -153,8 +151,7 @@ if [ ${boosted_task_found} -eq 0 ]; then
     if grep -q "boosted.*SCHED_DEADLINE" "${STALLD_LOG}"; then
         pass "SCHED_DEADLINE boost confirmed in logs"
     else
-        log "✗ FAIL: No SCHED_DEADLINE boost detected"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "No SCHED_DEADLINE boost detected"
     fi
 fi
 
@@ -226,8 +223,7 @@ fi
 if grep -q "boosted" "${STALLD_LOG}"; then
     pass "Boost occurred as expected"
 else
-    log "✗ FAIL: No boost detected"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "No boost detected"
 fi
 
 # Cleanup
diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index df26483..50c8d14 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -72,14 +72,12 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     if grep -q "SCHED_FIFO" "${STALLD_LOG}"; then
         pass "SCHED_FIFO boosting used (as requested by -F)"
     else
-        log "✗ FAIL: SCHED_FIFO not mentioned in boost message"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "SCHED_FIFO not mentioned in boost message"
     fi
 else
-    log "✗ FAIL: No boosting detected with -F flag"
+    fail "No boosting detected with -F flag"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -134,8 +132,7 @@ if [ ${fifo_task_found} -eq 0 ]; then
     if grep -q "boosted.*SCHED_FIFO" "${STALLD_LOG}"; then
         pass "SCHED_FIFO boost confirmed in logs"
     else
-        log "✗ FAIL: No SCHED_FIFO boost detected"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "No SCHED_FIFO boost detected"
     fi
 fi
 
@@ -337,8 +334,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "stalld exited as expected"
 else
-    log "✗ FAIL: stalld did not reject FIFO in single-threaded mode"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject FIFO in single-threaded mode"
 fi
 
 # Check for error message in log
diff --git a/tests/functional/test_fifo_priority_starvation.sh b/tests/functional/test_fifo_priority_starvation.sh
index 3776e6b..9554d49 100755
--- a/tests/functional/test_fifo_priority_starvation.sh
+++ b/tests/functional/test_fifo_priority_starvation.sh
@@ -78,22 +78,19 @@ if wait_for_starvation_detected "${STALLD_LOG}"; then
     if grep "starved on CPU ${TEST_CPU}" "${STALLD_LOG}"; then
         pass "Correct CPU ID logged (CPU ${TEST_CPU})"
     else
-        log "✗ FAIL: Wrong CPU ID in log"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Wrong CPU ID in log"
     fi
 
     # Verify duration is logged
     if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}"; then
         pass "Starvation duration logged"
     else
-        log "✗ FAIL: Starvation duration not logged"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation duration not logged"
     fi
 else
-    log "✗ FAIL: FIFO-on-FIFO starvation not detected"
+    fail "FIFO-on-FIFO starvation not detected"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -159,8 +156,7 @@ else
     if grep -q "boosted" "${STALLD_LOG}"; then
         log "ℹ INFO: Boosting did occur according to logs"
     else
-        log "✗ FAIL: No boosting detected"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "No boosting detected"
     fi
 fi
 
@@ -218,8 +214,7 @@ if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}" | wc
         pass "Starvation duration increased (${first_duration}s -> ${last_duration}s)"
         log "        This confirms task merging preserved the timestamp"
     else
-        log "✗ FAIL: Starvation duration did not increase (timestamp may have been reset)"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation duration did not increase (timestamp may have been reset)"
     fi
 else
     log "⚠ WARNING: Not enough starvation reports to verify task merging"
diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index fec2e00..ecaa7ac 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -107,8 +107,7 @@ if wait_for_boost_detected "${STALLD_LOG2}"; then
     if grep -qi "fifo\|SCHED_FIFO" "${STALLD_LOG2}"; then
         pass "SCHED_FIFO used with -F flag"
     elif grep -qi "deadline\|SCHED_DEADLINE" "${STALLD_LOG2}"; then
-        log "✗ FAIL: SCHED_DEADLINE used despite -F flag"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "SCHED_DEADLINE used despite -F flag"
     else
         log "⚠ WARNING: Scheduling policy not explicitly mentioned in logs"
     fi
@@ -220,8 +219,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "single-threaded mode rejected FIFO"
 else
-    log "✗ FAIL: stalld did not reject -F in single-threaded mode"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject -F in single-threaded mode"
 fi
 
 #=============================================================================
diff --git a/tests/functional/test_foreground.sh b/tests/functional/test_foreground.sh
index 2bb0282..8b89d02 100755
--- a/tests/functional/test_foreground.sh
+++ b/tests/functional/test_foreground.sh
@@ -38,8 +38,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 		if [ "${PARENT_PID}" != "$$" ]; then
 			pass "stalld daemonized (parent is not test shell)"
 		else
-			TEST_FAILED=$((TEST_FAILED + 1))
-			echo -e "  ${RED}FAIL${NC}: stalld did not daemonize (parent is test shell)"
+			fail "stalld did not daemonize (parent is test shell)"
 		fi
 	fi
 fi
@@ -64,8 +63,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running with -f"; th
 	if [ "${PARENT_PID}" != "1" ]; then
 		pass "stalld did not daemonize with -f (parent is not init)"
 	else
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: stalld daemonized even with -f flag"
+		fail "stalld daemonized even with -f flag"
 	fi
 fi
 
@@ -84,8 +82,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running with -v"; th
 	if [ "${PARENT_PID}" != "1" ]; then
 		pass "-v implies foreground mode"
 	else
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: -v should imply foreground mode"
+		fail "-v should imply foreground mode"
 	fi
 fi
 
diff --git a/tests/functional/test_idle_detection.sh b/tests/functional/test_idle_detection.sh
index af4d7a4..8757d74 100755
--- a/tests/functional/test_idle_detection.sh
+++ b/tests/functional/test_idle_detection.sh
@@ -137,8 +137,7 @@ if [ -n "${idle_time1}" ] && [ -n "${idle_time2}" ]; then
         log "        stalld would parse this CPU"
     fi
 else
-    log "✗ FAIL: Could not read idle time from /proc/stat"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "Could not read idle time from /proc/stat"
 fi
 
 #=============================================================================
diff --git a/tests/functional/test_log_only.sh b/tests/functional/test_log_only.sh
index d6257cf..21518a4 100755
--- a/tests/functional/test_log_only.sh
+++ b/tests/functional/test_log_only.sh
@@ -61,8 +61,7 @@ echo "Waiting for starvation detection..."
 if wait_for_starvation_detected "${LOG_FILE}"; then
 	pass "stalld detected and logged starvation"
 else
-	TEST_FAILED=$((TEST_FAILED + 1))
-	echo -e "  ${RED}FAIL${NC}: stalld did not detect starvation"
+	fail "stalld did not detect starvation"
 	echo "Log contents:"
 	cat "${LOG_FILE}"
 fi
@@ -71,8 +70,7 @@ fi
 if ! grep -q "boosted" "${LOG_FILE}"; then
 	pass "stalld did not boost in log-only mode"
 else
-	TEST_FAILED=$((TEST_FAILED + 1))
-	echo -e "  ${RED}FAIL${NC}: stalld boosted despite -l flag"
+	fail "stalld boosted despite -l flag"
 	echo "Log contents:"
 	cat "${LOG_FILE}"
 fi
diff --git a/tests/functional/test_logging_destinations.sh b/tests/functional/test_logging_destinations.sh
index 35470c0..f05cd00 100755
--- a/tests/functional/test_logging_destinations.sh
+++ b/tests/functional/test_logging_destinations.sh
@@ -45,8 +45,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 			pass "output contains expected messages"
 		fi
 	else
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: no output in verbose mode"
+		fail "no output in verbose mode"
 	fi
 fi
 
@@ -158,8 +157,7 @@ if assert_process_running "${STALLD_PID}" "stalld with combined logging should b
 	if [ -s "${LOG_FILE}" ]; then
 		pass "combined logging produces output"
 	else
-		TEST_FAILED=$((TEST_FAILED + 1))
-		echo -e "  ${RED}FAIL${NC}: no output with combined logging"
+		fail "no output with combined logging"
 	fi
 fi
 
diff --git a/tests/functional/test_pidfile.sh b/tests/functional/test_pidfile.sh
index fd4fedc..0e4c623 100755
--- a/tests/functional/test_pidfile.sh
+++ b/tests/functional/test_pidfile.sh
@@ -56,8 +56,7 @@ for pidfile in /var/run/stalld.pid /run/stalld.pid; do
         if [ "$pid_from_file" = "${STALLD_PID}" ]; then
             pass "Default pidfile contains correct PID"
         else
-            log "✗ FAIL: Default pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
-            TEST_FAILED=$((TEST_FAILED + 1))
+            fail "Default pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
         fi
         break
     fi
@@ -99,12 +98,10 @@ if [ -f "${custom_pidfile}" ]; then
     if [ "$pid_from_file" = "${STALLD_PID}" ]; then
         pass "Custom pidfile contains correct PID ($pid_from_file)"
     else
-        log "✗ FAIL: Custom pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Custom pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
     fi
 else
-    log "✗ FAIL: Custom pidfile not created at ${custom_pidfile}"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "Custom pidfile not created at ${custom_pidfile}"
 fi
 
 # Test 3: Verify pidfile removed on clean shutdown
@@ -145,12 +142,10 @@ if [ -f "${tmp_pidfile}" ]; then
     if [ "$pid_from_file" = "${STALLD_PID}" ]; then
         pass "/tmp pidfile contains correct PID"
     else
-        log "✗ FAIL: /tmp pidfile has incorrect PID"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "/tmp pidfile has incorrect PID"
     fi
 else
-    log "✗ FAIL: Pidfile not created in /tmp"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "Pidfile not created in /tmp"
 fi
 
 stop_stalld
@@ -181,8 +176,7 @@ if [ -f "${fg_pidfile}" ]; then
     if [ "$pid_from_file" = "${STALLD_PID}" ]; then
         pass "Foreground mode pidfile contains correct PID"
     else
-        log "✗ FAIL: Foreground mode pidfile has incorrect PID"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Foreground mode pidfile has incorrect PID"
     fi
 else
     log "⚠ WARNING: Pidfile not created in foreground mode (may be expected)"
@@ -221,8 +215,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Invalid pidfile path rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid pidfile path"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid pidfile path"
 fi
 
 # Cleanup
@@ -256,12 +249,10 @@ if [ -f "${readable_pidfile}" ]; then
         perms=$(stat -c "%a" "${readable_pidfile}" 2>/dev/null || stat -f "%Lp" "${readable_pidfile}" 2>/dev/null)
         log "ℹ INFO: Pidfile permissions: $perms"
     else
-        log "✗ FAIL: Pidfile not readable"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Pidfile not readable"
     fi
 else
-    log "✗ FAIL: Pidfile not created"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "Pidfile not created"
 fi
 
 stop_stalld
diff --git a/tests/functional/test_runqueue_parsing.sh b/tests/functional/test_runqueue_parsing.sh
index fc69fa0..ac5190e 100755
--- a/tests/functional/test_runqueue_parsing.sh
+++ b/tests/functional/test_runqueue_parsing.sh
@@ -108,8 +108,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
         if grep -E "starvation_gen.*starved on CPU ${TEST_CPU}" "${STALLD_LOG_BPF}"; then
             pass "Task name (comm) correctly extracted"
         else
-            log "✗ FAIL: Task name not found in eBPF backend output"
-            TEST_FAILED=$((TEST_FAILED + 1))
+            fail "Task name not found in eBPF backend output"
         fi
 
         # Verify PID is logged
@@ -119,10 +118,9 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
             log "⚠ INFO: PID format may have changed"
         fi
     else
-        log "✗ FAIL: eBPF backend did not detect starvation"
+        fail "eBPF backend did not detect starvation"
         log "Log contents:"
         cat "${STALLD_LOG_BPF}"
-        TEST_FAILED=$((TEST_FAILED + 1))
     fi
 
     # Cleanup
@@ -163,8 +161,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
         if grep -E "starvation_gen.*starved on CPU ${TEST_CPU}" "${STALLD_LOG_SCHED}"; then
             pass "Task name (comm) correctly extracted"
         else
-            log "✗ FAIL: Task name not found in sched_debug backend output"
-            TEST_FAILED=$((TEST_FAILED + 1))
+            fail "Task name not found in sched_debug backend output"
         fi
 
         # Verify PID is logged
@@ -180,10 +177,9 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
             log "ℹ INFO: Kernel format detected: $format"
         fi
     else
-        log "✗ FAIL: sched_debug backend did not detect starvation"
+        fail "sched_debug backend did not detect starvation"
         log "Log contents:"
         cat "${STALLD_LOG_SCHED}"
-        TEST_FAILED=$((TEST_FAILED + 1))
     fi
 
     # Cleanup
@@ -262,8 +258,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
             log "        This may be due to timing differences between backends"
         fi
     else
-        log "✗ FAIL: One or both backends failed to detect starvation"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "One or both backends failed to detect starvation"
     fi
 else
     log ""
@@ -315,32 +310,28 @@ if [ -n "$test_backend" ]; then
     if grep -q "starvation_gen" "${log_file}"; then
         pass "Task name (comm) field extracted"
     else
-        log "✗ FAIL: Task name (comm) field not found"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Task name (comm) field not found"
     fi
 
     # Check for PID field (format: name-PID or [PID])
     if grep -qE "(starvation_gen-[0-9]+|\[[0-9]+\])" "${log_file}"; then
         pass "PID field extracted"
     else
-        log "✗ FAIL: PID field not found"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "PID field not found"
     fi
 
     # Check for CPU ID
     if grep -q "CPU ${TEST_CPU}" "${log_file}"; then
         pass "CPU ID field extracted"
     else
-        log "✗ FAIL: CPU ID field not found"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "CPU ID field not found"
     fi
 
     # Check for starvation duration
     if grep -qE "for [0-9]+ seconds" "${log_file}"; then
         pass "Starvation duration calculated from context switches/time"
     else
-        log "✗ FAIL: Starvation duration not found"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation duration not found"
     fi
 
     # Cleanup
diff --git a/tests/functional/test_starvation_detection.sh b/tests/functional/test_starvation_detection.sh
index 6d4d7c5..680cf39 100755
--- a/tests/functional/test_starvation_detection.sh
+++ b/tests/functional/test_starvation_detection.sh
@@ -81,22 +81,19 @@ if wait_for_starvation_detected "${STALLD_LOG}"; then
     if grep "starved on CPU ${TEST_CPU}" "${STALLD_LOG}"; then
         pass "Correct CPU ID logged (CPU ${TEST_CPU})"
     else
-        log "✗ FAIL: Wrong CPU ID in log"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Wrong CPU ID in log"
     fi
 
     # Verify duration is logged
     if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}"; then
         pass "Starvation duration logged"
     else
-        log "✗ FAIL: Starvation duration not logged"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation duration not logged"
     fi
 else
-    log "✗ FAIL: Starvation not detected"
+    fail "Starvation not detected"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -147,8 +144,7 @@ if [ -n "${STARVE_CHILDREN}" ]; then
             if [ ${ctxt_delta} -lt 5 ]; then
                 pass "Context switch count remained low (delta: ${ctxt_delta})"
             else
-                log "✗ FAIL: Context switches increased significantly (delta: ${ctxt_delta})"
-                TEST_FAILED=$((TEST_FAILED + 1))
+                fail "Context switches increased significantly (delta: ${ctxt_delta})"
             fi
             break
         fi
@@ -213,14 +209,12 @@ if [ "${report_count}" -ge 2 ]; then
         pass "Starvation duration increased (${first_duration}s -> ${last_duration}s)"
         log "        This confirms task merging preserved the timestamp"
     else
-        log "✗ FAIL: Starvation duration did not increase (timestamp may have been reset)"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation duration did not increase (timestamp may have been reset)"
     fi
 else
-    log "✗ FAIL: Not enough starvation reports to verify task merging (found ${report_count}, need >= 2)"
+    fail "Not enough starvation reports to verify task merging (found ${report_count}, need >= 2)"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup starvation generator
@@ -285,15 +279,13 @@ else
     if grep -qE "starvation_gen.*starved on CPU ${CPU0}|starved on CPU ${CPU0}.*starvation_gen" "${STALLD_LOG}"; then
         pass "Starvation detected on CPU ${CPU0}"
     else
-        log "✗ FAIL: Starvation not detected on CPU ${CPU0}"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation not detected on CPU ${CPU0}"
     fi
 
     if grep -qE "starvation_gen.*starved on CPU ${CPU1}|starved on CPU ${CPU1}.*starvation_gen" "${STALLD_LOG}"; then
         pass "Starvation detected on CPU ${CPU1}"
     else
-        log "✗ FAIL: Starvation not detected on CPU ${CPU1}"
-        TEST_FAILED=$((TEST_FAILED + 1))
+        fail "Starvation not detected on CPU ${CPU1}"
     fi
 
     # Cleanup
@@ -372,16 +364,14 @@ sleep 5
 if assert_process_running "${STALLD_PID}" "stalld still running after task exit"; then
     pass "stalld handled task exit gracefully"
 else
-    log "✗ FAIL: stalld crashed or exited unexpectedly"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld crashed or exited unexpectedly"
 fi
 
 # Check for error messages
 if grep -iE "error|segfault|crash" "${STALLD_LOG}"; then
-    log "✗ FAIL: Error messages found in log"
+    fail "Error messages found in log"
     log "Errors:"
     grep -iE "error|segfault|crash" "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 else
     pass "No error messages in log"
 fi
diff --git a/tests/functional/test_starvation_threshold.sh b/tests/functional/test_starvation_threshold.sh
index a35f898..a2cd420 100755
--- a/tests/functional/test_starvation_threshold.sh
+++ b/tests/functional/test_starvation_threshold.sh
@@ -78,10 +78,9 @@ log "Waiting for detection (threshold: ${threshold}s)"
 if wait_for_starvation_detected "${STALLD_LOG}"; then
     pass "Starvation detected after ${threshold}s threshold"
 else
-    log "✗ FAIL: Starvation not detected after ${threshold}s threshold"
+    fail "Starvation not detected after ${threshold}s threshold"
     log "Log contents:"
     cat "${STALLD_LOG}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -123,10 +122,9 @@ sleep 2
 if ! grep -qE "starvation_gen.*starved on CPU ${TEST_CPU}|starved on CPU ${TEST_CPU}.*starvation_gen" "${STALLD_LOG2}"; then
     pass "No starvation detected for duration less than threshold"
 else
-    log "✗ FAIL: Starvation detected before threshold"
+    fail "Starvation detected before threshold"
     log "Found starvation_gen task in logs:"
     grep -E "starvation_gen.*starved on CPU|starved on CPU.*starvation_gen" "${STALLD_LOG2}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -163,10 +161,9 @@ log "Waiting for detection (threshold: ${threshold}s)"
 if wait_for_starvation_detected "${STALLD_LOG3}"; then
     pass "Starvation detected with ${threshold}s threshold"
 else
-    log "✗ FAIL: Starvation not detected with ${threshold}s threshold"
+    fail "Starvation not detected with ${threshold}s threshold"
     log "Log contents:"
     cat "${STALLD_LOG3}"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Cleanup
@@ -193,8 +190,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Zero threshold rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid threshold value 0"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid threshold value 0"
 fi
 
 # Test with negative threshold
@@ -208,8 +204,7 @@ ret=$?
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "Negative threshold rejected with error"
 else
-    log "✗ FAIL: stalld did not reject invalid negative threshold"
-    TEST_FAILED=$((TEST_FAILED + 1))
+    fail "stalld did not reject invalid negative threshold"
 fi
 
 log ""
diff --git a/tests/functional/test_task_merging.sh b/tests/functional/test_task_merging.sh
index b89230e..e1a4cc1 100755
--- a/tests/functional/test_task_merging.sh
+++ b/tests/functional/test_task_merging.sh
@@ -106,9 +106,8 @@ if [ "${second_duration}" -gt "${first_duration}" ]; then
     pass "Starvation duration increased by ${delta}s"
     log "        Timestamp preserved across monitoring cycles"
 else
-    log "✗ FAIL: Duration did not increase (${first_duration}s -> ${second_duration}s)"
+    fail "Duration did not increase (${first_duration}s -> ${second_duration}s)"
     log "        Timestamp may have been reset (task merging failed)"
-    TEST_FAILED=$((TEST_FAILED + 1))
 fi
 
 # Wait for third detection to confirm continued accumulation
diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 144114c..20f8a3c 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -114,6 +114,15 @@ pass() {
 	TEST_PASSED=$((TEST_PASSED + 1))
 }
 
+# Record a test failure with a description message.
+#
+# Usage: fail "description"
+fail() {
+	local message=${1:-""}
+	log "✗ FAIL: ${message}"
+	TEST_FAILED=$((TEST_FAILED + 1))
+}
+
 # Assert functions
 assert_equals() {
 	local expected=$1
@@ -1130,7 +1139,7 @@ start_starvation_gen() {
 
 # Export functions for use in tests
 export -f start_test end_test
-export -f pass assert_equals assert_contains assert_not_contains
+export -f pass fail assert_equals assert_contains assert_not_contains
 export -f assert_file_exists assert_file_not_exists
 export -f assert_process_running assert_process_not_running
 export -f start_stalld stop_stalld kill_existing_stalld cleanup
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 31/36] tests/helpers: Use pass()/fail() in assert functions
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (29 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 30/36] tests: Add fail() helper and use for all test failures Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 32/36] tests/functional: Fix multi-CPU detection in test_starvation_detection Wander Lairson Costa
                   ` (4 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Refactor all assert functions to delegate to pass() and fail()
instead of duplicating the echo formatting and counter increment
logic. Diagnostic context lines in assert_equals, assert_contains,
and assert_not_contains are changed from echo to log() so they
appear in the journal.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/helpers/test_helpers.sh | 50 +++++++++++++----------------------
 1 file changed, 18 insertions(+), 32 deletions(-)

diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 20f8a3c..3a7164d 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -130,14 +130,12 @@ assert_equals() {
 	local message=${3:-""}
 
 	if [ "${expected}" == "${actual}" ]; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		echo "    Expected: ${expected}"
-		echo "    Actual:   ${actual}"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
+		log "    Expected: ${expected}"
+		log "    Actual:   ${actual}"
 		return 1
 	fi
 }
@@ -148,13 +146,11 @@ assert_contains() {
 	local message=${3:-""}
 
 	if echo "${haystack}" | grep -q "${needle}"; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		echo "    String '${needle}' not found"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
+		log "    String '${needle}' not found"
 		return 1
 	fi
 }
@@ -165,13 +161,11 @@ assert_not_contains() {
 	local message=${3:-""}
 
 	if ! echo "${haystack}" | grep -q "${needle}"; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		echo "    String '${needle}' found but should not be present"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
+		log "    String '${needle}' found but should not be present"
 		return 1
 	fi
 }
@@ -181,12 +175,10 @@ assert_file_exists() {
 	local message=${2:-"File should exist: ${file}"}
 
 	if [ -f "${file}" ]; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
 		return 1
 	fi
 }
@@ -196,12 +188,10 @@ assert_file_not_exists() {
 	local message=${2:-"File should not exist: ${file}"}
 
 	if [ ! -f "${file}" ]; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
 		return 1
 	fi
 }
@@ -211,12 +201,10 @@ assert_process_running() {
 	local message=${2:-"Process ${pid} should be running"}
 
 	if kill -0 ${pid} 2>/dev/null; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
 		return 1
 	fi
 }
@@ -226,12 +214,10 @@ assert_process_not_running() {
 	local message=${2:-"Process ${pid} should not be running"}
 
 	if ! kill -0 ${pid} 2>/dev/null; then
-		echo -e "  ${GREEN}PASS${NC}: ${message}"
-		TEST_PASSED=$((TEST_PASSED + 1))
+		pass "${message}"
 		return 0
 	else
-		echo -e "  ${RED}FAIL${NC}: ${message}"
-		TEST_FAILED=$((TEST_FAILED + 1))
+		fail "${message}"
 		return 1
 	fi
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 32/36] tests/functional: Fix multi-CPU detection in test_starvation_detection
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (30 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 31/36] tests/helpers: Use pass()/fail() in assert functions Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 33/36] tests/functional: Accept FIFO fallback in test_fifo_boosting Wander Lairson Costa
                   ` (3 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The multi-CPU test greps for "starvation_gen" in the starvation log,
but stalld might detect other starving tasks on the target CPU, such
as kworker threads. Relax the grep to match any task starving on
the expected CPU. To support this, add an optional cpu parameter to
wait_for_starvation_detected() so the test waits for each CPU
independently.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_starvation_detection.sh | 12 +++++-------
 tests/helpers/test_helpers.sh                 |  9 +++++++--
 2 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/tests/functional/test_starvation_detection.sh b/tests/functional/test_starvation_detection.sh
index 680cf39..6ca2121 100755
--- a/tests/functional/test_starvation_detection.sh
+++ b/tests/functional/test_starvation_detection.sh
@@ -271,18 +271,16 @@ else
 
     start_stalld_with_log "${STALLD_LOG}" -f -v -N -l -t $threshold -c ${CPU0},${CPU1} -a ${STALLD_CPU_MULTI}
 
-    # Wait for starvation detection
-    log "Waiting for starvation detection..."
-    wait_for_starvation_detected "${STALLD_LOG}"
-
-    # Check both CPUs detected - specifically look for starvation_gen tasks
-    if grep -qE "starvation_gen.*starved on CPU ${CPU0}|starved on CPU ${CPU0}.*starvation_gen" "${STALLD_LOG}"; then
+    # Wait for starvation detection on both CPUs
+    log "Waiting for starvation detection on CPU ${CPU0}..."
+    if wait_for_starvation_detected "${STALLD_LOG}" 30 "${CPU0}"; then
         pass "Starvation detected on CPU ${CPU0}"
     else
         fail "Starvation not detected on CPU ${CPU0}"
     fi
 
-    if grep -qE "starvation_gen.*starved on CPU ${CPU1}|starved on CPU ${CPU1}.*starvation_gen" "${STALLD_LOG}"; then
+    log "Waiting for starvation detection on CPU ${CPU1}..."
+    if wait_for_starvation_detected "${STALLD_LOG}" 30 "${CPU1}"; then
         pass "Starvation detected on CPU ${CPU1}"
     else
         fail "Starvation not detected on CPU ${CPU1}"
diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 3a7164d..6e29a3b 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -542,11 +542,16 @@ wait_for_stalld_ready() {
 
 # Wait for stalld to detect a starving task.
 #
-# Usage: wait_for_starvation_detected <log_file> [timeout]
+# Usage: wait_for_starvation_detected <log_file> [timeout] [cpu]
 wait_for_starvation_detected() {
 	local log_file=$1
 	local timeout=${2:-30}
-	wait_for_log_message "starved on CPU" "${timeout}" "${log_file}"
+	local cpu=${3:-}
+	local pattern="starved on CPU"
+	if [ -n "${cpu}" ]; then
+		pattern="starved on CPU ${cpu}"
+	fi
+	wait_for_log_message "${pattern}" "${timeout}" "${log_file}"
 }
 
 # Wait for stalld to boost a starving task.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 33/36] tests/functional: Accept FIFO fallback in test_fifo_boosting
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (31 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 32/36] tests/functional: Fix multi-CPU detection in test_starvation_detection Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 34/36] tests/functional: Fix readiness detection and FIFO fallback in test_force_fifo Wander Lairson Costa
                   ` (2 subsequent siblings)
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Test 5 expects stalld to exit when given -F without -A, but stalld
prints a warning and falls back to adaptive mode instead. The test
had contradictory results: a FAIL for not exiting and a PASS for
finding the warning message. Accept the fallback as valid behavior
and only fail if stalld silently ignores the incompatibility.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_fifo_boosting.sh | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

diff --git a/tests/functional/test_fifo_boosting.sh b/tests/functional/test_fifo_boosting.sh
index 50c8d14..a003deb 100755
--- a/tests/functional/test_fifo_boosting.sh
+++ b/tests/functional/test_fifo_boosting.sh
@@ -332,20 +332,11 @@ timeout 5 ${TEST_ROOT}/../stalld -f -v -F -t 5 -c ${TEST_CPU} > "${STALLD_LOG_FA
 ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "stalld exited as expected"
+    pass "stalld rejected FIFO in single-threaded mode"
+elif grep -qiE "single.*thread|falling back|adaptive" "${STALLD_LOG_FAIL}"; then
+    pass "stalld detected incompatibility and fell back to adaptive mode"
 else
-    fail "stalld did not reject FIFO in single-threaded mode"
-fi
-
-# Check for error message in log
-if grep -qiE "single.*thread.*fifo|fifo.*single.*thread|can.*only.*deadline" "${STALLD_LOG_FAIL}"; then
-    pass "Error message about FIFO+single-threaded incompatibility found"
-else
-    log "ℹ INFO: Checking exit status or error messages..."
-    if [ -s "${STALLD_LOG_FAIL}" ]; then
-        log "Log contents:"
-        cat "${STALLD_LOG_FAIL}"
-    fi
+    fail "stalld silently accepted FIFO in single-threaded mode"
 fi
 
 #=============================================================================
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 34/36] tests/functional: Fix readiness detection and FIFO fallback in test_force_fifo
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (32 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 33/36] tests/functional: Accept FIFO fallback in test_fifo_boosting Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 35/36] tests/functional: Fix invalid pidfile test in test_pidfile Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 36/36] stalld: die on invalid CPU affinity Wander Lairson Costa
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

Add "skipping" to the wait_for_stalld_ready() pattern to handle
aggressive mode with idle detection, where stalld prints "skipping
next phase" instead of "checking cpu" or "waiting tasks". Also
accept stalld's fallback to adaptive mode when given -F without -A,
matching the fix already applied to test_fifo_boosting.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_force_fifo.sh | 4 +++-
 tests/helpers/test_helpers.sh       | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/tests/functional/test_force_fifo.sh b/tests/functional/test_force_fifo.sh
index ecaa7ac..239bc37 100755
--- a/tests/functional/test_force_fifo.sh
+++ b/tests/functional/test_force_fifo.sh
@@ -218,8 +218,10 @@ ret=$?
 
 if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
     pass "single-threaded mode rejected FIFO"
+elif grep -qiE "single.*thread|falling back|adaptive" "${FIFO_SINGLE_LOG}"; then
+    pass "stalld detected incompatibility and fell back to adaptive mode"
 else
-    fail "stalld did not reject -F in single-threaded mode"
+    fail "stalld silently accepted FIFO in single-threaded mode"
 fi
 
 #=============================================================================
diff --git a/tests/helpers/test_helpers.sh b/tests/helpers/test_helpers.sh
index 6e29a3b..9d49b55 100755
--- a/tests/helpers/test_helpers.sh
+++ b/tests/helpers/test_helpers.sh
@@ -537,7 +537,7 @@ wait_for_log_message() {
 wait_for_stalld_ready() {
 	local log_file=$1
 	local timeout=${2:-15}
-	wait_for_log_message "checking cpu\|waiting tasks" "${timeout}" "${log_file}"
+	wait_for_log_message "checking cpu\|waiting tasks\|skipping" "${timeout}" "${log_file}"
 }
 
 # Wait for stalld to detect a starving task.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 35/36] tests/functional: Fix invalid pidfile test in test_pidfile
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (33 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 34/36] tests/functional: Fix readiness detection and FIFO fallback in test_force_fifo Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  2026-03-30 19:43 ` [PATCH stalld 36/36] stalld: die on invalid CPU affinity Wander Lairson Costa
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

The test created a directory with no write permission and expected
stalld to fail writing the pidfile. Since the tests run as root,
the permission restriction has no effect and stalld succeeds. Use
a non-existent parent directory instead so fopen() fails regardless
of privileges.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 tests/functional/test_pidfile.sh | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/tests/functional/test_pidfile.sh b/tests/functional/test_pidfile.sh
index 0e4c623..76be14a 100755
--- a/tests/functional/test_pidfile.sh
+++ b/tests/functional/test_pidfile.sh
@@ -192,12 +192,8 @@ log "=========================================="
 log "Test 6: Invalid pidfile path (permission denied)"
 log "=========================================="
 
-# Create a directory with no write permissions
-test_dir="/tmp/stalld_test_no_write_$$"
-mkdir -p "${test_dir}"
-chmod 555 "${test_dir}"
-invalid_pidfile="${test_dir}/stalld.pid"
-CLEANUP_FILES+=("${test_dir}")
+# Use a non-existent parent directory so fopen() fails even as root
+invalid_pidfile="/nonexistent_${$}/stalld.pid"
 
 INVALID_LOG="/tmp/stalld_test_pidfile_invalid_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG}")
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH stalld 36/36] stalld: die on invalid CPU affinity
  2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
                   ` (34 preceding siblings ...)
  2026-03-30 19:43 ` [PATCH stalld 35/36] tests/functional: Fix invalid pidfile test in test_pidfile Wander Lairson Costa
@ 2026-03-30 19:43 ` Wander Lairson Costa
  35 siblings, 0 replies; 37+ messages in thread
From: Wander Lairson Costa @ 2026-03-30 19:43 UTC (permalink / raw)
  To: williams, jkacur, juri.lelli, luffyluo, davidlt, linux-rt-users
  Cc: Wander Lairson Costa

set_cpu_affinity() validates the CPU list and returns -1 on error,
but main() ignored the return value, allowing stalld to run with
an invalid or empty affinity mask.

Signed-off-by: Wander Lairson Costa <wander@redhat.com>
---
 src/stalld.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/stalld.c b/src/stalld.c
index b9372db..d95b48f 100644
--- a/src/stalld.c
+++ b/src/stalld.c
@@ -1166,11 +1166,9 @@ int main(int argc, char **argv)
 
 	parse_args(argc, argv);
 
-	/*
-	 * it will not die...
-	 */
 	if (config_affinity_cpus)
-		set_cpu_affinity(config_affinity_cpus);
+		if (set_cpu_affinity(config_affinity_cpus))
+			die("invalid CPU affinity: %s\n", config_affinity_cpus);
 
 	if (!check_dl_server_dir_exists())
 		log_msg("DL-server detected.\n");
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2026-03-30 19:46 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-30 19:43 [PATCH stalld 00/36] tests: Replace timing-dependent synchronization with event-driven helpers Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 01/36] tests: Add pre-test and post-test cleanup of stalld processes Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 02/36] tests/helpers: Fix stalld daemon detection in start_stalld() Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 03/36] tests/helpers: Remove duplicate log() function Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 04/36] tests: Add per-test runtime measurement to run_tests.sh Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 05/36] tests/functional: Fix and refactor test_backend_selection.sh Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 06/36] tests/functional: Fix test_logging_destinations.sh path and backend Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 07/36] tests/helpers: Replace sleep with poll in start_stalld_with_log() Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 08/36] tests/helpers: Fix stop_stalld() timeout and shutdown logic Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 09/36] tests/helpers: Fix relative path in backend detection functions Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 10/36] tests/functional: Remove redundant post-stop_stalld sleeps Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 11/36] tests/functional: Fix false positive log matching in test_logging_destinations Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 12/36] tests/helpers: Rewrite wait_for_log_message() with process substitution Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 13/36] tests/helpers: Add wait_for_stalld_ready() and use in start_stalld_with_log() Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 14/36] tests/helpers: Fix fractional sleep timeout bugs Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 15/36] tests/helpers: Flush stdout after starvation_gen startup messages Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 16/36] tests/helpers: Add start_starvation_gen() helper function Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 17/36] tests/helpers: Add wait_for_starvation_detected() and wait_for_boost_detected() Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 18/36] tests/functional: Use start_starvation_gen() helper Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 19/36] tests/functional: Replace detection sleeps with event-driven helpers Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 20/36] tests/functional: Remove duplicated -a flag in test_fifo_priority_starvation Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 21/36] tests/functional: Add missing -a flag in test_starvation_detection Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 22/36] tests/functional: Use start_stalld_with_log() in test_log_only Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 23/36] tests/functional: Use start_stalld_with_log() in test_logging_destinations Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 24/36] tests/functional: Use start_stalld_with_log() in test_cpu_selection Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 25/36] tests/functional: Use wait_for_stalld_ready() in test_backend_selection Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 26/36] tests/functional: Use timeout for error path in test_force_fifo Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 27/36] tests/functional: Use timeout for invalid argument tests Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 28/36] tests: Add pass() helper and replace assert_equals hack Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 29/36] tests/functional: Use pass() for all test pass reporting Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 30/36] tests: Add fail() helper and use for all test failures Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 31/36] tests/helpers: Use pass()/fail() in assert functions Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 32/36] tests/functional: Fix multi-CPU detection in test_starvation_detection Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 33/36] tests/functional: Accept FIFO fallback in test_fifo_boosting Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 34/36] tests/functional: Fix readiness detection and FIFO fallback in test_force_fifo Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 35/36] tests/functional: Fix invalid pidfile test in test_pidfile Wander Lairson Costa
2026-03-30 19:43 ` [PATCH stalld 36/36] stalld: die on invalid CPU affinity Wander Lairson Costa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox