Skip to contentSkip to conversation

00/33] Test suite hardening, correctness fixes, and BPF optimization

PATCH New Wander Lairson Costa 1 participant 34 messages May 20

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 00/33] Test suite hardening, correctness fixes, and BPF optimization

The stalld functional test suite has accumulated significant reliability and maintainability issues over time. Several tests were silently passing due to missing assertions, shell scoping bugs that discarded subshell results, and grep substring matches causing false positives on multi-digit CPU systems. Massive boilerplate duplication existed across the suite, timing-dependent sleeps caused flakiness, and a daemon bug was discovered where --force_fifo combined with single-threaded mode silently fell back to adaptive mode instead of erroring out. Additionally, the BPF queue_track backend suffered from an O(n) linear array scan on every sched_wakeup event, consuming significant real-time latency budget in Telco workloads.

This series addresses these issues in three areas. First, the daemon's --force_fifo validation is fixed to reject the incompatible single-threaded mode at startup. Second, the test suite is overhauled by introducing eight shared helpers (test_section, cleanup_scenario, find_starved_child, init_functional_test, assert_stalld_rejects, assert_log_contains, assert_success, and starvation/boost assertion helpers) that replace duplicated validation patterns across all functional tests. The tests are then hardened with proper assertions, transitioned to fail-fast semantics where any failure immediately aborts the run, and timing-dependent sleeps are replaced with event-driven synchronization. The starvation_gen helper's signal handler is fixed for async-signal safety. Legacy C test infrastructure, stale documentation, and unreachable code are removed. Third, the BPF queue_track backend replaces its per-CPU linear array with a hash map keyed by (cpu, pid) for O(1) task lookups, reducing thread latency from 5-6us to 3us on real-time systems.

As a result, approximately seven previously silent tests now correctly validate daemon behavior, the suite runs deterministically via event-driven waits rather than fixed sleeps, and the BPF backend eliminates its lookup overhead entirely. The series yields a net reduction of roughly 9,850 lines (+874/-10,725).

Wander Lairson Costa (33):
stalld: Reject --force_fifo in single-threaded mode
tests: Introduce test_section() helper
tests: Introduce cleanup_scenario() helper
tests: Introduce starvation and boost asserts
tests: Introduce find_starved_child() helper
tests: Fix task exit timing in test_boost_restoration
tests: Consolidate and adopt init_functional_test()
tests: Introduce assert_stalld_rejects() helper
tests: Fix boost verification in runtime and duration tests
tests: Fix subshell swallowing test results
tests: Fix repeated log match finding same line
chore: Remove legacy test infrastructure and stale docs
tests: Add assertions to SCHED_OTHER restoration test
tests: Fix CPU selection grep substring matches
tests: Add idle CPU skipping assertion
tests: Remove redundant pkill from cleanup
tests: Introduce and adopt assert_log_contains() helper
tests: Remove weak, redundant, and assertion-free test blocks
tests: Introduce and adopt assert_success() helper
tests: Replace wait conditionals with asserts
tests: Remove if-wrappers around assert calls
tests: Abort immediately on test failure
tests: Remove dead code after making fail() fatal
tests: Introduce and adopt process helpers
tests: Extract wait_for_process_exit helper
tests: Reduce default wait timeouts
tests: Reduce starvation_gen durations
tests: Replace init sleeps in test_affinity
tests: Drop redundant sleeps in test_pidfile
tests: Remove redundant sleeps after start_stalld
tests: Reduce timing and replace sleeps with event waits
tests: Fix async-signal-unsafe handler
bpf: Replace linear task scan with hash map

 .claude/CLAUDE.md                             |  585 ---------
 .claude/agents/agent-prompt-engineer.md       |  135 --
 .claude/agents/c-expert.md                    |   53 -
 .claude/agents/code-reviewer.md               |  104 --
 .claude/agents/get-agent-hash                 |   99 --
 .claude/agents/git-scm-master.md              | 1154 -----------------
 .claude/agents/kernel-hacker.md               |  231 ----
 .claude/agents/plan-validator.md              |  130 --
 .claude/agents/project-historian.md           |  285 ----
 .claude/agents/project-librarian.md           |  604 ---------
 .claude/agents/project-manager.md             |  388 ------
 .claude/agents/project-scope-guardian.md      |  326 -----
 .claude/agents/python-expert.md               |   57 -
 .claude/agents/test-specialist.md             |  656 ----------
 .claude/agents/update-agent-hashes            |   96 --
 .claude/context-snapshot.json                 |  103 --
 .claude/rules                                 |   42 -
 .gitignore                                    |    5 +-
 bpf/stalld.bpf.c                              |  206 ++-
 src/queue_track.c                             |   95 +-
 src/queue_track.h                             |  107 +-
 src/utils.c                                   |    9 +-
 tests/BACKEND_USAGE.md                        |  269 ----
 tests/CONTEXT_SNAPSHOT_2025-10-31.md          |  231 ----
 tests/Makefile                                |   29 +-
 tests/README.md                               |   29 +-
 tests/TODO.md                                 |  727 -----------
 tests/functional/test_affinity.sh             |  178 +--
 tests/functional/test_backend_selection.sh    |   24 +-
 tests/functional/test_boost_duration.sh       |  164 +--
 tests/functional/test_boost_period.sh         |  151 +--
 tests/functional/test_boost_restoration.sh    |  310 +----
 tests/functional/test_boost_runtime.sh        |  179 +--
 tests/functional/test_cpu_selection.sh        |   76 +-
 tests/functional/test_deadline_boosting.sh    |  266 +---
 tests/functional/test_fifo_boosting.sh        |  269 +---
 .../test_fifo_priority_starvation.sh          |  197 +--
 tests/functional/test_force_fifo.sh           |  196 +--
 tests/functional/test_foreground.sh           |   65 +-
 tests/functional/test_idle_detection.sh       |  195 +--
 tests/functional/test_log_only.sh             |   30 +-
 tests/functional/test_logging_destinations.sh |   38 +-
 tests/functional/test_pidfile.sh              |  166 +--
 tests/functional/test_runqueue_parsing.sh     |  417 ------
 tests/functional/test_starvation_detection.sh |  281 +---
 tests/functional/test_starvation_threshold.sh |  138 +-
 tests/functional/test_task_merging.sh         |  161 +--
 tests/helpers/starvation_gen.c                |    2 +-
 tests/helpers/test_helpers.sh                 |  356 +++--
 tests/legacy/README.md                        |  169 ---
 tests/legacy/test01.c                         |  506 --------
 tests/legacy/test01_wrapper.sh                |  161 ---
 tests/run_tests.sh                            |  149 +--
 53 files changed, 874 insertions(+), 10725 deletions(-)
 delete mode 100644 .claude/CLAUDE.md
 delete mode 100644 .claude/agents/agent-prompt-engineer.md
 delete mode 100644 .claude/agents/c-expert.md
 delete mode 100644 .claude/agents/code-reviewer.md
 delete mode 100755 .claude/agents/get-agent-hash
 delete mode 100644 .claude/agents/git-scm-master.md
 delete mode 100644 .claude/agents/kernel-hacker.md
 delete mode 100644 .claude/agents/plan-validator.md
 delete mode 100644 .claude/agents/project-historian.md
 delete mode 100644 .claude/agents/project-librarian.md
 delete mode 100644 .claude/agents/project-manager.md
 delete mode 100644 .claude/agents/project-scope-guardian.md
 delete mode 100644 .claude/agents/python-expert.md
 delete mode 100644 .claude/agents/test-specialist.md
 delete mode 100755 .claude/agents/update-agent-hashes
 delete mode 100644 .claude/context-snapshot.json
 delete mode 100644 .claude/rules
 delete mode 100644 tests/BACKEND_USAGE.md
 delete mode 100644 tests/CONTEXT_SNAPSHOT_2025-10-31.md
 delete mode 100644 tests/TODO.md
 delete mode 100755 tests/functional/test_runqueue_parsing.sh
 delete mode 100644 tests/legacy/README.md
 delete mode 100644 tests/legacy/test01.c
 delete mode 100755 tests/legacy/test01_wrapper.sh
Signature · 1 line
2.54.0
#1 lore raw

PATCH 1/1 01/33] stalld: Reject --force_fifo in single-threaded mode

When --force_fifo (-F) was combined with single-threaded mode (-O), stalld silently fell back to adaptive mode instead of honoring the requested configuration. This violates the principle of least surprise since the user explicitly requested single-threaded mode but gets a different threading model without warning.

Replace the silent fallback with a usage() error that exits immediately, consistent with the other argument validation checks in parse_args(). Also fix the corresponding test which was missing the -O flag and therefore tested adaptive mode instead of single-threaded mode.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 src/utils.c                         |  9 ++-------
 tests/functional/test_force_fifo.sh | 18 ++----------------
 2 files changed, 4 insertions(+), 23 deletions(-)
src/utils.c +2 -7
index 8f1fe92..3dddb0f 100644
@@ -1182,13 +1182,8 @@ int parse_args(int argc, char **argv)
 	if (config_boost_duration > config_starving_threshold)
 		usage("the boost duration cannot be longer than the starving threshold ");
 
-	if (config_force_fifo && config_single_threaded) {
-		log_msg("-F/--force_fifo does not work in single-threaded mode\n");
-		log_msg("falling back to the adaptive mode\n");
-		config_adaptive_multi_threaded = 1;
-		config_single_threaded = 0;
-		config_aggressive = 0;
-	}
+	if (config_force_fifo && config_single_threaded)
+		usage("--force_fifo does not work in single-threaded mode");
 
 	if (config_reservation && (config_aggressive || config_adaptive_multi_threaded))
 		usage("-R/--reservation only works in the single-threaded mode");
tests/functional/test_force_fifo.sh +2 -16
index 239bc37..4fdb13b 100755
@@ -207,22 +207,8 @@ log "=========================================="
 log "Test 5: Single-threaded mode with FIFO (should fail)"
 log "=========================================="
 
-# Try to run stalld with -F but without -A (single-threaded mode)
-# According to CLAUDE.md, this should die/exit
-FIFO_SINGLE_LOG="/tmp/stalld_test_force_fifo_single_$$.log"
-CLEANUP_FILES+=("${FIFO_SINGLE_LOG}")
-
-log "Testing single-threaded mode with -F (should exit)"
-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
-    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 silently accepted FIFO in single-threaded mode"
-fi
+log "Testing single-threaded mode (-O) with -F (should exit)"
+assert_stalld_rejects "Single-threaded mode rejected FIFO" -f -v -c "${TEST_CPU}" -t ${threshold} -F -O
 
 #=============================================================================
 # Test 6: Compare effectiveness (informational)
Signature · 1 line
2.54.0
#2 lore raw

PATCH 1/1 03/33] tests: Introduce cleanup_scenario() helper

In reply to Wander Lairson Costa

Functional tests currently duplicate boilerplate code to terminate background processes and stop the stalld daemon between test scenarios. This repetition clutters the test files and occasionally leads to errors, such as missing wait calls after sending termination signals.

Introduce a variadic cleanup_scenario() helper function in the test helpers script to consolidate this teardown logic. The function accepts an arbitrary number of process IDs, safely terminates each one, waits for them to exit, and then stops the stalld daemon. It is exported via export -f to ensure availability within subshells.

Applying this helper across the functional test suite removes redundant cleanup sequences and resolves missing wait calls in the boost restoration tests.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_boost_duration.sh       | 16 ++++---------
 tests/functional/test_boost_period.sh         | 16 ++++---------
 tests/functional/test_boost_restoration.sh    | 17 ++++---------
 tests/functional/test_boost_runtime.sh        | 16 ++++---------
 tests/functional/test_deadline_boosting.sh    | 22 ++++-------------
 tests/functional/test_fifo_boosting.sh        | 20 ++++------------
 .../test_fifo_priority_starvation.sh          | 20 ++++------------
 tests/functional/test_force_fifo.sh           | 24 +++++--------------
 tests/functional/test_idle_detection.sh       |  8 ++-----
 tests/functional/test_log_only.sh             |  4 +---
 tests/functional/test_runqueue_parsing.sh     | 24 +++++--------------
 tests/functional/test_starvation_detection.sh | 17 ++++---------
 tests/functional/test_starvation_threshold.sh | 12 +++-------
 tests/functional/test_task_merging.sh         | 18 ++++----------
 tests/helpers/test_helpers.sh                 | 14 ++++++++++-
 15 files changed, 71 insertions(+), 177 deletions(-)
tests/functional/test_boost_duration.sh +4 -12
index d677da8..9a4c11c 100755
@@ -70,9 +70,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Short duration (1 second)
@@ -98,9 +96,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Long duration (10 seconds)
@@ -128,9 +124,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Verify task policy is restored after boost duration
@@ -157,9 +151,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 5: Invalid duration values
tests/functional/test_boost_period.sh +4 -12
index 534879f..d7cb75f 100755
@@ -74,9 +74,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-wait ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Custom period (500ms = 500,000,000 ns)
@@ -100,9 +98,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-wait ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Very short period (100ms = 100,000,000 ns)
@@ -126,9 +122,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-wait ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Very long period (10s = 10,000,000,000 ns)
@@ -152,9 +146,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-wait ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 5: Invalid period (0)
tests/functional/test_boost_restoration.sh +5 -12
index 0a77945..5dcaeea 100755
@@ -150,8 +150,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Restore Original RT Policy (SCHED_FIFO)
@@ -261,8 +260,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${BLOCKER_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${BLOCKER_PID}"
 
 #=============================================================================
 # Test 3: SCHED_OTHER Policy Restoration
@@ -289,8 +287,7 @@ wait ${STARVE_PID} 2>/dev/null || true
 # The starvation_gen output will show if blockees completed
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Restoration Timing Verification
@@ -338,9 +335,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-wait ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 # Give stalld time to fully exit before next test
 sleep 1
@@ -392,9 +387,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null || true
-wait ${STARVE_PID} 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Final Summary
tests/functional/test_boost_runtime.sh +4 -12
index bf22250..d8dba70 100755
@@ -70,9 +70,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Custom runtime (10,000 ns = 10 microseconds, less than default)
@@ -98,9 +96,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Larger runtime (100,000 ns = 100 microseconds)
@@ -126,9 +122,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Runtime < period (valid configuration)
@@ -156,9 +150,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null || true
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 5: Runtime > period (should error or be rejected)
tests/functional/test_deadline_boosting.sh +5 -17
index 3fd93c7..12bbd3e 100755
@@ -88,9 +88,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: DEADLINE Parameters Verification
@@ -150,9 +148,7 @@ if [ ${boosted_task_found} -eq 0 ]; then
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Task Makes Progress During Boost
@@ -218,9 +214,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Policy Restoration After Boost
@@ -296,9 +290,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 5: Multiple Simultaneous Boosts
@@ -360,11 +352,7 @@ else
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID0} 2>/dev/null
-    kill -TERM ${STARVE_PID1} 2>/dev/null
-    wait ${STARVE_PID0} 2>/dev/null
-    wait ${STARVE_PID1} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID0}" "${STARVE_PID1}"
 fi
 
 #=============================================================================
tests/functional/test_fifo_boosting.sh +5 -15
index d1b65fb..ceac769 100755
@@ -78,9 +78,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: FIFO Priority Verification
@@ -131,9 +129,7 @@ if [ ${fifo_task_found} -eq 0 ]; then
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: FIFO Emulation Behavior
@@ -182,9 +178,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: FIFO vs DEADLINE Comparison
@@ -232,9 +226,7 @@ fi
 deadline_progress=$((ctxt_after_deadline - ctxt_before_deadline))
 log "DEADLINE progress: ${deadline_progress} context switches"
 
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 # Small delay between tests
 sleep 2
@@ -277,9 +269,7 @@ fi
 fifo_progress=$((ctxt_after_fifo - ctxt_before_fifo))
 log "FIFO progress: ${fifo_progress} context switches"
 
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 # Compare effectiveness
 log ""
tests/functional/test_fifo_priority_starvation.sh +5 -15
index 7c401f3..bf3bfc8 100755
@@ -91,9 +91,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Boosting Effectiveness
@@ -155,9 +153,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Starvation Duration Tracking
@@ -216,9 +212,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Close Priority Gap
@@ -251,9 +245,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 5: Correct Task Boosted
@@ -295,9 +287,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Final Summary
tests/functional/test_force_fifo.sh +6 -18
index 321e2c0..8ee4f82 100755
@@ -72,9 +72,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Force FIFO mode (-F)
@@ -110,9 +108,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Verify FIFO priority setting
@@ -144,9 +140,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Verify FIFO emulation behavior (sleep runtime, restore, sleep remainder)
@@ -183,9 +177,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 5: Single-threaded mode with FIFO (should fail/exit)
@@ -215,9 +207,7 @@ wait_for_boost_detected "${STALLD_LOG_DL}"
 deadline_boosts=$(grep -c "boost" "${STALLD_LOG_DL}" || echo 0)
 log "ℹ INFO: SCHED_DEADLINE boosts: $deadline_boosts"
 
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 # Run with FIFO
 STALLD_LOG_FIFO="/tmp/stalld_test_force_fifo_comparison_$$.log"
@@ -231,9 +221,7 @@ wait_for_boost_detected "${STALLD_LOG_FIFO}"
 fifo_boosts=$(grep -c "boost" "${STALLD_LOG_FIFO}" || echo 0)
 log "ℹ INFO: SCHED_FIFO boosts: $fifo_boosts"
 
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 log "ℹ INFO: Comparison complete (DEADLINE: $deadline_boosts, FIFO: $fifo_boosts)"
 
tests/functional/test_idle_detection.sh +2 -6
index cd4e0f3..a242fca 100755
@@ -165,9 +165,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Idle Detection Overhead Reduction
@@ -236,9 +234,7 @@ else
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 fi
 
 #=============================================================================
tests/functional/test_log_only.sh +1 -3
index 21518a4..0e6d41b 100755
@@ -76,9 +76,7 @@ else
 fi
 
 # Cleanup
-kill ${STARVGEN_PID} 2>/dev/null
-wait ${STARVGEN_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVGEN_PID}"
 
 echo ""
 echo "Log file contents:"
tests/functional/test_runqueue_parsing.sh +6 -18
index 9aa7471..f5c2861 100755
@@ -121,9 +121,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 else
     test_section "Test 1: eBPF Backend - SKIPPED"
     log "eBPF backend not available on this system"
@@ -174,9 +172,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 else
     test_section "Test 2: sched_debug Backend - SKIPPED"
     log "sched_debug backend not available on this system"
@@ -204,9 +200,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     bpf_detections=$(count_detected_tasks "${STALLD_LOG_BPF}")
     log "eBPF backend detected: ${bpf_detections} starvation events"
 
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 
     # Small delay between tests
     sleep 2
@@ -223,9 +217,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     sched_detections=$(count_detected_tasks "${STALLD_LOG_SCHED}")
     log "sched_debug backend detected: ${sched_detections} starvation events"
 
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 
     # Compare results
     log ""
@@ -314,9 +306,7 @@ if [ -n "$test_backend" ]; then
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 fi
 
 #=============================================================================
@@ -366,9 +356,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID} 2>/dev/null
-    wait ${STARVE_PID} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID}"
 else
     test_section "Test 5: Kernel Format Detection - SKIPPED"
     log "sched_debug backend required for format detection tests"
tests/functional/test_starvation_detection.sh +4 -13
index cd60e27..919148b 100755
@@ -94,9 +94,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Context Switch Count Tracking
@@ -148,9 +146,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Task Merging (Timestamp Preservation)
@@ -209,8 +205,7 @@ else
 fi
 
 # Cleanup starvation generator
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Multiple CPUs Detection
@@ -275,11 +270,7 @@ else
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID0} 2>/dev/null
-    kill -TERM ${STARVE_PID1} 2>/dev/null
-    wait ${STARVE_PID0} 2>/dev/null
-    wait ${STARVE_PID1} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID0}" "${STARVE_PID1}"
 fi
 
 #=============================================================================
tests/functional/test_starvation_threshold.sh +3 -9
index 836f079..8b54b95 100755
@@ -81,9 +81,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Verify no detection before threshold
@@ -122,9 +120,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Shorter threshold (3 seconds)
@@ -158,9 +154,7 @@ else
 fi
 
 # Cleanup
-kill -TERM "${STARVE_PID}" 2>/dev/null
-wait "${STARVE_PID}" 2>/dev/null || true
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Invalid threshold values
tests/functional/test_task_merging.sh +4 -14
index 305dc4b..366b89b 100755
@@ -124,9 +124,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 2: Same PID + Same Context Switches = Merged
@@ -194,9 +192,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 3: Task Making Progress (No Merge)
@@ -246,9 +242,7 @@ else
 fi
 
 # Cleanup
-kill -TERM ${STARVE_PID} 2>/dev/null
-wait ${STARVE_PID} 2>/dev/null
-stop_stalld
+cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
 # Test 4: Multiple CPUs with Independent Task Merging
@@ -323,11 +317,7 @@ else
     fi
 
     # Cleanup
-    kill -TERM ${STARVE_PID0} 2>/dev/null
-    kill -TERM ${STARVE_PID1} 2>/dev/null
-    wait ${STARVE_PID0} 2>/dev/null
-    wait ${STARVE_PID1} 2>/dev/null
-    stop_stalld
+    cleanup_scenario "${STARVE_PID0}" "${STARVE_PID1}"
 fi
 
 #=============================================================================
tests/helpers/test_helpers.sh +13 -1
index 5d61b39..d4e98f7 100755
@@ -114,6 +114,18 @@ test_section() {
 	log "=========================================="
 }
 
+# Tear down starvation workloads and stalld between test sections.
+# Usage: cleanup_scenario [PID ...]
+cleanup_scenario() {
+	for pid in "$@"; do
+		if [ -n "$pid" ]; then
+			kill -TERM "$pid" 2>/dev/null || true
+			wait "$pid" 2>/dev/null || true
+		fi
+	done
+	stop_stalld
+}
+
 # Record a test pass with a description message.
 #
 # Usage: pass "description"
@@ -1138,7 +1150,7 @@ start_starvation_gen() {
 }
 
 # Export functions for use in tests
-export -f start_test end_test test_section
+export -f start_test end_test test_section cleanup_scenario
 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
Signature · 1 line
2.54.0
#3 lore raw

PATCH 1/1 06/33] tests: Fix task exit timing in test_boost_restoration

In reply to Wander Lairson Costa

Test 5 verifies that stalld handles a task exiting during an active boost. The starvation duration was set to threshold minus two seconds, causing the task to exit before the detection threshold was reached. As a result, the task was never boosted and the test silently passed without exercising the intended code path.

Lower the threshold and compute the starvation duration as threshold plus three seconds so that the task survives long enough to be detected and boosted, then exits mid-boost as originally intended.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_boost_restoration.sh | 10 +++++-----
 tests/functional/test_runqueue_parsing.sh  | 12 ++++++------
 2 files changed, 11 insertions(+), 11 deletions(-)
tests/functional/test_boost_restoration.sh +5 -5
index fb886bd..c2a04f3 100755
@@ -338,16 +338,16 @@ sleep 1
 #=============================================================================
 test_section "Test 5: Graceful Handling of Task Exit During Boost"
 
-threshold=10
-boost_duration=5  # Task will exit during boost (after 8s, boost is 5s)
+threshold=5
+boost_duration=5
 
 log "Starting stalld with ${threshold}s threshold, ${boost_duration}s boost (task will exit during boost)"
 rm -f "${STALLD_LOG}"
 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
-short_duration=$((threshold - 2))
+# Task must survive past the threshold to be detected and boosted,
+# then exit during the boost window.
+short_duration=$((threshold + 3))
 log "Creating starvation that will exit after ${short_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d ${short_duration}
 
tests/functional/test_runqueue_parsing.sh +6 -6
index f5c2861..a4dc180 100755
@@ -90,7 +90,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ]; then
     threshold=5
     log "Starting stalld with eBPF backend (queue_track)"
     # Use -g 1 for 1-second granularity
-    start_stalld -f -v -g 1 -l -b queue_track -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_BPF}" 2>&1
+    start_stalld -f -v -g 1 -N -l -i "kworker,ksoftirqd" -b queue_track -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_BPF}" 2>&1
 
     # Create starvation to generate task data
     starvation_duration=$((threshold + 5))
@@ -135,7 +135,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
     threshold=5
     log "Starting stalld with sched_debug backend"
-    start_stalld -f -v -g 1 -l -b sched_debug -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_SCHED}" 2>&1
+    start_stalld -f -v -g 1 -N -l -i "kworker,ksoftirqd" -b sched_debug -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_SCHED}" 2>&1
 
     # Create starvation
     starvation_duration=$((threshold + 5))
@@ -192,7 +192,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     log ""
     log "Running with eBPF backend..."
     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
+    start_stalld -f -v -g 1 -N -l -i "kworker,ksoftirqd" -b queue_track -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_BPF}" 2>&1
 
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
@@ -209,7 +209,7 @@ if [ ${BPF_AVAILABLE} -eq 1 ] && [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
     log ""
     log "Running with sched_debug backend..."
     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
+    start_stalld -f -v -g 1 -N -l -i "kworker,ksoftirqd" -b sched_debug -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_SCHED}" 2>&1
 
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
@@ -264,7 +264,7 @@ if [ -n "$test_backend" ]; then
     log "Testing task field extraction with ${test_backend} backend"
 
     rm -f "${log_file}"
-    start_stalld -f -v -g 1 -l -b ${test_backend} -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${log_file}" 2>&1
+    start_stalld -f -v -g 1 -N -l -i "kworker,ksoftirqd" -b ${test_backend} -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${log_file}" 2>&1
 
     # Create starvation with known parameters
     log "Creating starvation with known task name (starvation_gen)"
@@ -317,7 +317,7 @@ if [ ${SCHED_DEBUG_AVAILABLE} -eq 1 ]; then
 
     threshold=5
     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
+    start_stalld -f -v -g 1 -N -l -i "kworker,ksoftirqd" -b sched_debug -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU} > "${STALLD_LOG_SCHED}" 2>&1
 
     # Create brief starvation just to initialize the backend
     start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 8
Signature · 1 line
2.54.0
#4 lore raw

PATCH 1/1 05/33] tests: Introduce find_starved_child() helper

In reply to Wander Lairson Costa

Multiple test scripts duplicate a pattern of using pgrep and a loop to identify the child process of starvation_gen that is most likely to be starving. This duplication clutters the tests and is prone to subtle errors. Notably, test_fifo_priority_starvation.sh contained a logic gap where it claimed to check for the lower-priority blockee but simply grabbed the first child process instead.

Introduce a centralized find_starved_child() helper in test_helpers.sh and export it for use across the test suite. This function reliably identifies the starving child by selecting the one with the lowest scheduling priority, determined by finding the highest kernel priority value exposed in /proc/PID/sched.

Replace twelve instances of the duplicated search pattern across six different test files with the new helper. This refactoring resolves the logic gap in the FIFO priority starvation test and improves test maintainability while reducing the overall line count.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_boost_restoration.sh    |  9 +---
 tests/functional/test_deadline_boosting.sh    | 51 ++++++-------------
 tests/functional/test_fifo_boosting.sh        | 45 +++++-----------
 .../test_fifo_priority_starvation.sh          | 11 +---
 tests/functional/test_starvation_detection.sh | 41 ++++++---------
 tests/functional/test_task_merging.sh         | 23 ++-------
 tests/helpers/test_helpers.sh                 | 30 ++++++++++-
 7 files changed, 79 insertions(+), 131 deletions(-)
tests/functional/test_boost_restoration.sh +1 -8
index 5dcaeea..fb886bd 100755
@@ -64,14 +64,7 @@ log "Creating starvation with SCHED_FIFO tasks (blocker prio 80, blockee prio 1)
 start_starvation_gen -c ${TEST_CPU} -p 80 -b 1 -n 1 -d 20
 
 # Find the starved task
-STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
-tracked_pid=""
-for child_pid in ${STARVE_CHILDREN}; do
-    if [ -f "/proc/${child_pid}/sched" ]; then
-        tracked_pid=${child_pid}
-        break
-    fi
-done
+tracked_pid=$(find_starved_child "${STARVE_PID}")
 
 if [ -n "${tracked_pid}" ]; then
     log "Tracking task PID ${tracked_pid}"
tests/functional/test_deadline_boosting.sh +15 -36

77 lines not on this page · raw message

tests/functional/test_fifo_boosting.sh +13 -32

70 lines not on this page · raw message

tests/functional/test_fifo_priority_starvation.sh +1 -10
index bf3bfc8..11bcf33 100755
@@ -107,16 +107,7 @@ log "Creating FIFO-on-FIFO starvation on CPU ${TEST_CPU}"
 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)
-blockee_pid=""
-for child_pid in ${STARVE_CHILDREN}; do
-    if [ -f "/proc/${child_pid}/status" ]; then
-        # Check if it's the lower priority task (blockee)
-        # The blockee should have lower priority than blocker
-        blockee_pid=${child_pid}
-        break
-    fi
-done
+blockee_pid=$(find_starved_child "${STARVE_PID}")
 
 ctxt_before=0
 if [ -n "${blockee_pid}" ]; then
tests/functional/test_starvation_detection.sh +16 -25

47 lines not on this page · raw message

tests/functional/test_task_merging.sh +4 -19
index 366b89b..c49499c 100755
@@ -144,14 +144,7 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 wait_for_starvation_detected "${STALLD_LOG}"
 
 # Find the starved task PID
-STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
-tracked_pid=""
-for child_pid in ${STARVE_CHILDREN}; do
-    if [ -f "/proc/${child_pid}/status" ]; then
-        tracked_pid=${child_pid}
-        break
-    fi
-done
+tracked_pid=$(find_starved_child "${STARVE_PID}")
 
 if [ -n "${tracked_pid}" ]; then
     log "Tracking starved task PID ${tracked_pid}"
@@ -208,19 +201,11 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a
 log "Creating starvation that will be boosted"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
 
+# Find tracked task while it's guaranteed to be starving
+tracked_pid=$(find_starved_child "${STARVE_PID}")
+
 # Wait for starvation detection
 wait_for_starvation_detected "${STALLD_LOG}"
-
-# Find tracked task
-STARVE_CHILDREN=$(pgrep -P ${STARVE_PID} 2>/dev/null)
-tracked_pid=""
-for child_pid in ${STARVE_CHILDREN}; do
-    if [ -f "/proc/${child_pid}/status" ]; then
-        tracked_pid=${child_pid}
-        break
-    fi
-done
-
 if [ -n "${tracked_pid}" ]; then
     # Wait for boost to complete and task to starve again
     sleep 5
tests/helpers/test_helpers.sh +29 -1
index fb6c9d1..ced067c 100755
@@ -126,6 +126,34 @@ cleanup_scenario() {
 	stop_stalld
 }
 
+# Find the child of a starvation_gen process most likely to be starving.
+# When multiple children exist, selects the one with the lowest scheduling
+# priority (highest kernel prio value) since that task loses the CPU to
+# higher-priority siblings.
+# Usage: tracked_pid=$(find_starved_child <parent_pid>)
+find_starved_child() {
+	local parent_pid=$1
+	local candidate=""
+	local max_prio=-100
+	local prio
+	for child in $(pgrep -P "${parent_pid}" 2>/dev/null); do
+		if [ -d "/proc/${child}" ]; then
+			prio=$(get_sched_priority "${child}" 2>/dev/null)
+			prio=${prio:--1}
+			if [ "${prio}" -gt "${max_prio}" ] 2>/dev/null; then
+				candidate="${child}"
+				max_prio="${prio}"
+			fi
+		fi
+	done
+
+	if [ -n "${candidate}" ]; then
+		echo "${candidate}"
+		return 0
+	fi
+	return 1
+}
+
 # Assert that stalld detects a starving task within the timeout.
 # Usage: assert_starvation_detected <log_file> <message> [timeout] [cpu]
 assert_starvation_detected() {
@@ -1187,7 +1215,7 @@ start_starvation_gen() {
 }
 
 # Export functions for use in tests
-export -f start_test end_test test_section cleanup_scenario assert_starvation_detected assert_boost_detected
+export -f start_test end_test test_section cleanup_scenario find_starved_child assert_starvation_detected assert_boost_detected
 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
Signature · 1 line
2.54.0
#5 lore raw

PATCH 1/1 04/33] tests: Introduce starvation and boost asserts

In reply to Wander Lairson Costa

Add assert_starvation_detected() and assert_boost_detected() wrappers to tests/helpers/test_helpers.sh. These functions wrap the common pattern of evaluating wait_for_* conditions, logging passes, and dumping logs on failure into a single function call. Both helpers return 0 or 1 to match the existing assert_* convention used throughout the test suite.

Update 6 test files to replace 16 simple if/else evaluation blocks with the new helpers, reducing boilerplate and yielding a net reduction of 33 lines. Complex evaluation blocks containing nested logic are intentionally left intact to preserve their specific behaviors.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_boost_duration.sh       | 24 ++----------
 tests/functional/test_boost_period.sh         | 18 ++-------
 tests/functional/test_boost_runtime.sh        | 24 ++----------
 tests/functional/test_log_only.sh             |  8 +---
 tests/functional/test_starvation_detection.sh | 12 +-----
 tests/functional/test_starvation_threshold.sh | 17 +-------
 tests/helpers/test_helpers.sh                 | 39 ++++++++++++++++++-
 7 files changed, 54 insertions(+), 88 deletions(-)
tests/functional/test_boost_duration.sh +4 -20
index 9a4c11c..d1cdd1e 100755
@@ -63,11 +63,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 starvation detection
-if wait_for_starvation_detected "${STALLD_LOG}"; then
-    pass "Starvation detection occurred with default duration"
-else
-    fail "No starvation detection"
-fi
+assert_starvation_detected "${STALLD_LOG}" "Starvation detection occurred with default duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -89,11 +85,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 starvation detection
-if wait_for_starvation_detected "${STALLD_LOG2}"; then
-    pass "Starvation detection with ${short_duration}s duration"
-else
-    fail "No starvation detection with short duration"
-fi
+assert_starvation_detected "${STALLD_LOG2}" "Starvation detection with ${short_duration}s duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -117,11 +109,7 @@ 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 starvation detection
-if wait_for_starvation_detected "${STALLD_LOG3}"; then
-    pass "Starvation detection with ${long_duration}s duration"
-else
-    fail "No starvation detection with long duration"
-fi
+assert_starvation_detected "${STALLD_LOG3}" "Starvation detection with ${long_duration}s duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -144,11 +132,7 @@ log "Creating starvation on CPU ${TEST_CPU} for 15s"
 start_starvation_gen -c "${TEST_CPU}" -p 80 -n 1 -d 15
 
 # Wait for starvation detection
-if wait_for_starvation_detected "${STALLD_LOG4}"; then
-    pass "Starvation detection with ${duration}s boost duration"
-else
-    fail "No starvation detection"
-fi
+assert_starvation_detected "${STALLD_LOG4}" "Starvation detection with ${duration}s boost duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_boost_period.sh +3 -15
index d7cb75f..d7112bb 100755
@@ -91,11 +91,7 @@ log "Creating starvation on CPU ${TEST_CPU}"
 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
-    pass "Boosting occurred with custom period ${custom_period} ns"
-else
-    fail "No boosting with custom period"
-fi
+assert_boost_detected "${STALLD_LOG}" "Boosting occurred with custom period ${custom_period} ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -115,11 +111,7 @@ log "Creating starvation on CPU ${TEST_CPU}"
 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
-    pass "Boosting occurred with short period ${short_period} ns"
-else
-    fail "No boosting with short period"
-fi
+assert_boost_detected "${STALLD_LOG}" "Boosting occurred with short period ${short_period} ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -139,11 +131,7 @@ log "Creating starvation on CPU ${TEST_CPU}"
 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
-    pass "Boosting occurred with long period ${long_period} ns"
-else
-    fail "No boosting with long period"
-fi
+assert_boost_detected "${STALLD_LOG}" "Boosting occurred with long period ${long_period} ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_boost_runtime.sh +4 -20
index d8dba70..c17ac27 100755
@@ -63,11 +63,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
-if wait_for_starvation_detected "${STALLD_LOG}"; then
-    pass "Starvation detection with default runtime"
-else
-    fail "No starvation detection"
-fi
+assert_starvation_detected "${STALLD_LOG}" "Starvation detection with default runtime"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -89,11 +85,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
-if wait_for_starvation_detected "${STALLD_LOG2}"; then
-    pass "Starvation detection with custom runtime ${custom_runtime}ns"
-else
-    fail "No starvation detection with custom runtime"
-fi
+assert_starvation_detected "${STALLD_LOG2}" "Starvation detection with custom runtime ${custom_runtime}ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -115,11 +107,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
-if wait_for_starvation_detected "${STALLD_LOG3}"; then
-    pass "Starvation detection with large runtime ${large_runtime}ns"
-else
-    fail "No starvation detection with large runtime"
-fi
+assert_starvation_detected "${STALLD_LOG3}" "Starvation detection with large runtime ${large_runtime}ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -143,11 +131,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
-if wait_for_starvation_detected "${STALLD_LOG4}"; then
-    pass "Starvation detection with runtime < period"
-else
-    fail "No starvation detection when runtime < period"
-fi
+assert_starvation_detected "${STALLD_LOG4}" "Starvation detection with runtime < period"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_log_only.sh +1 -7
index 0e6d41b..b10b4d9 100755
@@ -58,13 +58,7 @@ echo "Starvation generator started (PID ${STARVGEN_PID})"
 echo "Waiting for starvation detection..."
 
 # Check if stalld detected the starvation (should log it)
-if wait_for_starvation_detected "${LOG_FILE}"; then
-	pass "stalld detected and logged starvation"
-else
-	fail "stalld did not detect starvation"
-	echo "Log contents:"
-	cat "${LOG_FILE}"
-fi
+assert_starvation_detected "${LOG_FILE}" "stalld detected and logged starvation"
 
 # Check that stalld did NOT boost (should not see "boosted" message with -l)
 if ! grep -q "boosted" "${LOG_FILE}"; then
tests/functional/test_starvation_detection.sh +2 -10
index 919148b..3307edb 100755
@@ -256,18 +256,10 @@ else
 
     # 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
+    assert_starvation_detected "${STALLD_LOG}" "Starvation detected on CPU ${CPU0}" "30" "${CPU0}"
 
     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}"
-    fi
+    assert_starvation_detected "${STALLD_LOG}" "Starvation detected on CPU ${CPU1}" "30" "${CPU1}"
 
     # Cleanup
     cleanup_scenario "${STARVE_PID0}" "${STARVE_PID1}"
tests/functional/test_starvation_threshold.sh +2 -15
index 8b54b95..62f7420 100755
@@ -71,14 +71,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -N -M -g 1 -i "ksoftirqd,kworker" -c
 # Wait for starvation detection
 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
-    pass "Starvation detected after ${threshold}s threshold"
-else
-    fail "Starvation not detected after ${threshold}s threshold"
-    log "Log contents:"
-    cat "${STALLD_LOG}"
-fi
+assert_starvation_detected "${STALLD_LOG}" "Starvation detected after ${threshold}s threshold"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -145,13 +138,7 @@ start_stalld_with_log "${STALLD_LOG3}" -f -v -N -M -g 1 -i "ksoftirqd,kworker" -
 log "Waiting for detection (threshold: ${threshold}s)"
 
 # Check if starvation_gen was detected
-if wait_for_starvation_detected "${STALLD_LOG3}"; then
-    pass "Starvation detected with ${threshold}s threshold"
-else
-    fail "Starvation not detected with ${threshold}s threshold"
-    log "Log contents:"
-    cat "${STALLD_LOG3}"
-fi
+assert_starvation_detected "${STALLD_LOG3}" "Starvation detected with ${threshold}s threshold"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/helpers/test_helpers.sh +38 -1
index d4e98f7..fb6c9d1 100755
@@ -126,6 +126,43 @@ cleanup_scenario() {
 	stop_stalld
 }
 
+# Assert that stalld detects a starving task within the timeout.
+# Usage: assert_starvation_detected <log_file> <message> [timeout] [cpu]
+assert_starvation_detected() {
+	local log_file=$1
+	local message=${2:-"Starvation detected"}
+	local timeout=${3:-30}
+	local cpu=${4:-}
+
+	if wait_for_starvation_detected "${log_file}" "${timeout}" "${cpu}"; then
+		pass "${message}"
+		return 0
+	else
+		fail "${message}"
+		log "Log contents:"
+		cat "${log_file}"
+		return 1
+	fi
+}
+
+# Assert that stalld boosts a starving task within the timeout.
+# Usage: assert_boost_detected <log_file> <message> [timeout]
+assert_boost_detected() {
+	local log_file=$1
+	local message=${2:-"Boost detected"}
+	local timeout=${3:-30}
+
+	if wait_for_boost_detected "${log_file}" "${timeout}"; then
+		pass "${message}"
+		return 0
+	else
+		fail "${message}"
+		log "Log contents:"
+		cat "${log_file}"
+		return 1
+	fi
+}
+
 # Record a test pass with a description message.
 #
 # Usage: pass "description"
@@ -1150,7 +1187,7 @@ start_starvation_gen() {
 }
 
 # Export functions for use in tests
-export -f start_test end_test test_section cleanup_scenario
+export -f start_test end_test test_section cleanup_scenario assert_starvation_detected assert_boost_detected
 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
Signature · 1 line
2.54.0
#6 lore raw

PATCH 1/1 07/33] tests: Consolidate and adopt init_functional_test()

In reply to Wander Lairson Costa

Several functional tests use starvation_gen to create controlled starvation scenarios but do not verify the binary exists before attempting to run it. Add the existence check to init_functional_test() so that all tests using the shared initialization skip gracefully when the binary is missing and migrate standard tests to init_functional_test().

Additionally, standardize log file management by removing numbered log variable declarations in favor of reusing a single STALLD_LOG cleared between test sections. This fixes a broken log assertion in test_affinity where a numbered variable masked an unpopulated file.

Finally, move the BACKEND_FLAG construction into init_functional_test() and export it alongside the other shared variables. Five test files previously duplicated this block for direct stalld invocations.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_affinity.sh             | 49 ++-----------
 tests/functional/test_boost_duration.sh       | 67 ++++-------------
 tests/functional/test_boost_period.sh         | 46 +-----------
 tests/functional/test_boost_restoration.sh    | 30 +-------
 tests/functional/test_boost_runtime.sh        | 72 ++++---------------
 tests/functional/test_deadline_boosting.sh    | 30 +-------
 tests/functional/test_fifo_boosting.sh        | 30 +-------
 .../test_fifo_priority_starvation.sh          | 30 +-------
 tests/functional/test_force_fifo.sh           | 28 +-------
 tests/functional/test_idle_detection.sh       | 30 +-------
 tests/functional/test_pidfile.sh              | 39 +---------
 tests/functional/test_starvation_detection.sh | 30 +-------
 tests/functional/test_starvation_threshold.sh | 56 +++------------
 tests/functional/test_task_merging.sh         | 32 +--------
 tests/helpers/test_helpers.sh                 | 13 +++-
 15 files changed, 64 insertions(+), 518 deletions(-)
tests/functional/test_affinity.sh +4 -45
index d745b08..ec6ff3b 100755
@@ -35,21 +35,8 @@ check_affinity() {
     fi
 }
 
-start_test "CPU Affinity Option (-a)"
+init_functional_test "CPU Affinity Option (-a)" "test_affinity"
 
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Get number of CPUs
 num_cpus=$(nproc)
 log "System has $num_cpus CPUs"
 
@@ -58,15 +45,10 @@ if [ "$num_cpus" -lt 2 ]; then
     exit 77
 fi
 
-# Check if taskset is available
 if ! command -v taskset > /dev/null 2>&1; then
     log "⚠ WARNING: taskset not found, will use /proc fallback"
 fi
 
-# Setup paths
-STALLD_LOG="/tmp/stalld_test_affinity_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
-
 #=============================================================================
 # Test 1: Default behavior (no -a specified)
 #=============================================================================
@@ -92,9 +74,6 @@ stop_stalld
 #=============================================================================
 test_section "Test 2: Single CPU affinity (-a 0)"
 
-STALLD_LOG2="/tmp/stalld_test_affinity_test2_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG2}")
-
 start_stalld -f -v -l -t 5 -a 0
 sleep 2
 
@@ -114,9 +93,6 @@ stop_stalld
 test_section "Test 3: Multi-CPU affinity (-a 0,2)"
 
 if [ "$num_cpus" -ge 4 ]; then
-    STALLD_LOG3="/tmp/stalld_test_affinity_test3_$$.log"
-    CLEANUP_FILES+=("${STALLD_LOG3}")
-
     start_stalld -f -v -l -t 5 -a 0,2
     sleep 2
 
@@ -141,9 +117,6 @@ fi
 test_section "Test 4: CPU range affinity (-a 0-2)"
 
 if [ "$num_cpus" -ge 4 ]; then
-    STALLD_LOG4="/tmp/stalld_test_affinity_test4_$$.log"
-    CLEANUP_FILES+=("${STALLD_LOG4}")
-
     start_stalld -f -v -l -t 5 -a 0-2
     sleep 2
 
@@ -168,9 +141,6 @@ test_section "Test 5: Verify stalld threads run on specified CPU"
 
 if [ "$num_cpus" -ge 2 ]; then
     test_cpu=1
-    STALLD_LOG5="/tmp/stalld_test_affinity_test5_$$.log"
-    CLEANUP_FILES+=("${STALLD_LOG5}")
-
     start_stalld -f -v -l -t 5 -a "$test_cpu"
     sleep 2
 
@@ -200,12 +170,10 @@ fi
 test_section "Test 6: Combined affinity and monitoring (-a 0 -c 1)"
 
 if [ "$num_cpus" -ge 2 ]; then
-    STALLD_LOG6="/tmp/stalld_test_affinity_test6_$$.log"
-    CLEANUP_FILES+=("${STALLD_LOG6}")
+    rm -f "${STALLD_LOG}"
 
     # Run stalld on CPU 0, but monitor CPU 1
-    start_stalld -f -v -l -t 5 -a 0 -c 1
-    sleep 2
+    start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5 -a 0 -c 1
 
     affinity=$(check_affinity "${STALLD_PID}")
 
@@ -216,7 +184,7 @@ if [ "$num_cpus" -ge 2 ]; then
     fi
 
     # Verify it's monitoring CPU 1 by checking logs
-    if grep -q "cpu 1" "${STALLD_LOG6}" || grep -q "monitoring.*1" "${STALLD_LOG6}"; then
+    if grep -q "cpu 1" "${STALLD_LOG}" || grep -q "monitoring.*1" "${STALLD_LOG}"; then
         log "ℹ INFO: stalld monitoring CPU 1 as requested"
     else
         log "ℹ INFO: CPU 1 monitoring not explicitly confirmed in logs"
@@ -236,12 +204,6 @@ invalid_cpu=999
 INVALID_LOG="/tmp/stalld_test_affinity_invalid_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG}")
 
-# Add backend flag for consistency
-BACKEND_FLAG=""
-if [ -n "${STALLD_TEST_BACKEND}" ]; then
-    BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
-fi
-
 timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 -a ${invalid_cpu} > "${INVALID_LOG}" 2>&1
 ret=$?
 
@@ -256,9 +218,6 @@ fi
 #=============================================================================
 test_section "Test 8: Verify affinity persists over time"
 
-STALLD_LOG8="/tmp/stalld_test_affinity_test8_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG8}")
-
 start_stalld -f -v -l -t 5 -a 0
 sleep 2
 
tests/functional/test_boost_duration.sh +12 -55

128 lines not on this page · raw message

tests/functional/test_boost_period.sh +3 -43

65 lines not on this page · raw message

tests/functional/test_boost_restoration.sh +1 -29
index c2a04f3..09dbb60 100755
@@ -17,35 +17,7 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "Policy Restoration After Boosting"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
-
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_restoration_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
+init_functional_test "Policy Restoration After Boosting" "test_restoration"
 
 #=============================================================================
 # Test 1: Restore SCHED_FIFO Policy (starvation_gen creates SCHED_FIFO threads)
tests/functional/test_boost_runtime.sh +14 -58

142 lines not on this page · raw message

tests/functional/test_deadline_boosting.sh +1 -29
index d05daf2..eacce98 100755
@@ -14,39 +14,11 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "SCHED_DEADLINE Boosting Mechanism"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
+init_functional_test "SCHED_DEADLINE Boosting Mechanism" "test_deadline_boost"
 
 # Get number of CPUs for multi-CPU tests
 NUM_CPUS=$(get_num_cpus)
 
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_deadline_boost_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
-
 #=============================================================================
 # Test 1: Basic DEADLINE Boost Detection
 #=============================================================================
tests/functional/test_fifo_boosting.sh +1 -29
index a669fe4..3d17e5e 100755
@@ -14,35 +14,7 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "SCHED_FIFO Boosting Mechanism"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
-
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_fifo_boost_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
+init_functional_test "SCHED_FIFO Boosting Mechanism" "test_fifo_boost"
 
 #=============================================================================
 # Test 1: FIFO Boost with -F Flag
tests/functional/test_fifo_priority_starvation.sh +1 -29
index 11bcf33..172bc9a 100755
@@ -18,35 +18,7 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "FIFO-on-FIFO Priority Starvation Detection"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
-
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_fifo_prio_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
+init_functional_test "FIFO-on-FIFO Priority Starvation Detection" "test_fifo_prio"
 
 #=============================================================================
 # Test 1: Basic FIFO-on-FIFO Starvation Detection
tests/functional/test_force_fifo.sh +1 -27
index 8ee4f82..1e841b8 100755
@@ -13,33 +13,7 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "Force FIFO Option (-F)"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_force_fifo_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
-
-if [ ! -x "${STARVE_GEN}" ]; then
-    echo -e "${YELLOW}SKIP: starvation_gen not found or not executable${NC}"
-    exit 77
-fi
+init_functional_test "Force FIFO Option (-F)" "test_force_fifo"
 
 #=============================================================================
 # Test 1: Default behavior (should use SCHED_DEADLINE)
tests/functional/test_idle_detection.sh +1 -29
index a242fca..8017675 100755
@@ -36,35 +36,7 @@ is_cpu_idle() {
     fi
 }
 
-start_test "Idle CPU Detection"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
-
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_idle_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
+init_functional_test "Idle CPU Detection" "test_idle"
 
 # Check if idle detection is enabled by default
 log ""
tests/functional/test_pidfile.sh +1 -38
index 6b476cd..f24c836 100755
@@ -13,23 +13,7 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "PID File Option (-P)"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Setup paths
-STALLD_LOG="/tmp/stalld_test_pidfile_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
+init_functional_test "PID File Option (-P)" "test_pidfile"
 
 #=============================================================================
 # Test 1: Default pidfile location (no -P specified)
@@ -76,9 +60,6 @@ CLEANUP_FILES+=("${custom_pidfile}")
 # Ensure pidfile doesn't exist before test
 rm -f "${custom_pidfile}"
 
-STALLD_LOG2="/tmp/stalld_test_pidfile_test2_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG2}")
-
 log "Starting stalld with custom pidfile: ${custom_pidfile}"
 start_stalld -l -t 5 --pidfile "${custom_pidfile}"
 sleep 2
@@ -118,9 +99,6 @@ tmp_pidfile="/tmp/stalld_test_tmp_$$.pid"
 CLEANUP_FILES+=("${tmp_pidfile}")
 rm -f "${tmp_pidfile}"
 
-STALLD_LOG4="/tmp/stalld_test_pidfile_test4_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG4}")
-
 log "Starting stalld with /tmp pidfile: ${tmp_pidfile}"
 start_stalld -l -t 5 --pidfile "${tmp_pidfile}"
 sleep 2
@@ -149,9 +127,6 @@ fg_pidfile="/tmp/stalld_test_pidfile_foreground_$$.pid"
 CLEANUP_FILES+=("${fg_pidfile}")
 rm -f "${fg_pidfile}"
 
-STALLD_LOG5="/tmp/stalld_test_pidfile_test5_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG5}")
-
 log "Starting stalld in foreground mode with pidfile: ${fg_pidfile}"
 start_stalld -f -v -l -t 5 --pidfile "${fg_pidfile}"
 sleep 2
@@ -182,12 +157,6 @@ invalid_pidfile="/nonexistent_${$}/stalld.pid"
 INVALID_LOG="/tmp/stalld_test_pidfile_invalid_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG}")
 
-# Add backend flag for consistency
-BACKEND_FLAG=""
-if [ -n "${STALLD_TEST_BACKEND}" ]; then
-    BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
-fi
-
 log "Testing invalid pidfile path: ${invalid_pidfile}"
 timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 --pidfile "${invalid_pidfile}" > "${INVALID_LOG}" 2>&1
 ret=$?
@@ -198,9 +167,6 @@ else
     fail "stalld did not reject invalid pidfile path"
 fi
 
-# Cleanup
-chmod 755 "${test_dir}"
-
 #=============================================================================
 # Test 7: Verify pidfile is readable by other processes
 #=============================================================================
@@ -210,9 +176,6 @@ readable_pidfile="/tmp/stalld_test_pidfile_readable_$$.pid"
 CLEANUP_FILES+=("${readable_pidfile}")
 rm -f "${readable_pidfile}"
 
-STALLD_LOG7="/tmp/stalld_test_pidfile_test7_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG7}")
-
 log "Starting stalld with readable pidfile: ${readable_pidfile}"
 start_stalld -l -t 5 --pidfile "${readable_pidfile}"
 sleep 2
tests/functional/test_starvation_detection.sh +1 -29
index 553a7bc..d8f874a 100755
@@ -21,39 +21,11 @@ get_starved_pid() {
     grep "starved on CPU" "$log_file" | tail -1 | sed -E 's/.*\[([0-9]+)\].*/\1/' | head -1
 }
 
-start_test "Starvation Detection Logic"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
+init_functional_test "Starvation Detection Logic" "test_detection"
 
 # Get number of CPUs for multi-CPU tests
 NUM_CPUS=$(get_num_cpus)
 
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_detection_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
-
 #=============================================================================
 # Test 1: Basic Starvation Detection
 #=============================================================================
tests/functional/test_starvation_threshold.sh +10 -46

108 lines not on this page · raw message

tests/functional/test_task_merging.sh +1 -31
index c49499c..72f7c63 100755
@@ -14,45 +14,15 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
-start_test "Task Merging Logic"
-
-# Setup test environment
-setup_test_environment
-
-# Require root for this test
-require_root
-
-# Check RT throttling
-if ! check_rt_throttling; then
-    echo -e "${YELLOW}SKIP: RT throttling must be disabled for this test${NC}"
-    exit 77
-fi
-
-# Pick a CPU for testing
-TEST_CPU=$(pick_test_cpu)
-log "Using CPU ${TEST_CPU} for testing"
-
-# Pick a different CPU for stalld to run on (avoid interference)
-STALLD_CPU=0
-if [ ${TEST_CPU} -eq 0 ]; then
-    STALLD_CPU=1
-fi
-log "Stalld will run on CPU ${STALLD_CPU}"
+init_functional_test "Task Merging Logic" "test_merge"
 
 # Check for DL-server (kernel automatic starvation handling)
-# If DL-server is present, the kernel handles starvation automatically,
-# so stalld won't detect starvation and we can't test task merging logic
 if [ -d "/sys/kernel/debug/sched/fair_server" ]; then
     echo -e "${YELLOW}SKIP: DL-server detected - kernel handles starvation automatically${NC}"
     echo "      Task merging cannot be tested when DL-server prevents starvation"
     exit 77
 fi
 
-# Setup paths
-STARVE_GEN="${TEST_ROOT}/helpers/starvation_gen"
-STALLD_LOG="/tmp/stalld_test_merge_$$.log"
-CLEANUP_FILES+=("${STALLD_LOG}")
-
 #=============================================================================
 # Test 1: Timestamp Preservation for Non-Progressing Tasks
 #=============================================================================
tests/helpers/test_helpers.sh +12 -1
index ced067c..389310b 100755
@@ -1154,8 +1154,19 @@ init_functional_test() {
 	STALLD_LOG="/tmp/stalld_${log_suffix}_$$.log"
 	CLEANUP_FILES+=("${STALLD_LOG}")
 
+	if [ ! -x "${STARVE_GEN}" ]; then
+		echo -e "${YELLOW}SKIP: starvation_gen not found or not executable${NC}"
+		exit 77
+	fi
+
+	# Build backend flag for direct stalld invocations
+	BACKEND_FLAG=""
+	if [ -n "${STALLD_TEST_BACKEND}" ]; then
+		BACKEND_FLAG="-b ${STALLD_TEST_BACKEND}"
+	fi
+
 	# Export variables for use in test
-	export TEST_CPU STALLD_CPU STARVE_GEN STALLD_LOG
+	export TEST_CPU STALLD_CPU STARVE_GEN STALLD_LOG BACKEND_FLAG
 }
 
 # Start starvation_gen in background with readiness detection
Signature · 1 line
2.54.0
#7 lore raw

PATCH 1/1 02/33] tests: Introduce test_section() helper

In reply to Wander Lairson Costa

The functional test suite contains over a hundred instances of repetitive boilerplate for printing section banners. This clutters the test files and leads to inconsistent logging, with some tests using standard echo while others use the journal-integrated log function.

Introduce a centralized test_section() helper function in the test helpers script to standardize section banner formatting. Update all functional test files to replace the multi-line banner boilerplate with single-line calls to this new helper.

The helper is exported to ensure it remains available within subshells used throughout the test suite. This migration standardizes all section banners to use the journal-integrated log function, improving test traceability while removing nearly 300 lines of redundant code.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_affinity.sh             | 40 +++------------
 tests/functional/test_backend_selection.sh    | 13 ++---
 tests/functional/test_boost_duration.sh       | 25 ++--------
 tests/functional/test_boost_period.sh         | 35 +++----------
 tests/functional/test_boost_restoration.sh    | 30 +++--------
 tests/functional/test_boost_runtime.sh        | 35 +++----------
 tests/functional/test_cpu_selection.sh        | 18 +++----
 tests/functional/test_deadline_boosting.sh    | 30 +++--------
 tests/functional/test_fifo_boosting.sh        | 30 +++--------
 .../test_fifo_priority_starvation.sh          | 30 +++--------
 tests/functional/test_force_fifo.sh           | 30 +++--------
 tests/functional/test_foreground.sh           |  8 ++-
 tests/functional/test_idle_detection.sh       | 30 +++--------
 tests/functional/test_logging_destinations.sh | 11 ++--
 tests/functional/test_pidfile.sh              | 33 +++---------
 tests/functional/test_runqueue_parsing.sh     | 50 ++++---------------
 tests/functional/test_starvation_detection.sh | 35 +++----------
 tests/functional/test_starvation_threshold.sh | 20 ++------
 tests/functional/test_task_merging.sh         | 25 ++--------
 tests/helpers/test_helpers.sh                 | 11 +++-
 20 files changed, 124 insertions(+), 415 deletions(-)
tests/functional/test_affinity.sh +8 -32
index 7ecd85f..d745b08 100755
@@ -70,10 +70,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Default behavior (no -a specified)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Default behavior (no affinity restriction)"
-log "=========================================="
+test_section "Test 1: Default behavior (no affinity restriction)"
 
 start_stalld -f -v -l -t 5
 sleep 2
@@ -93,10 +90,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Single CPU affinity
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Single CPU affinity (-a 0)"
-log "=========================================="
+test_section "Test 2: Single CPU affinity (-a 0)"
 
 STALLD_LOG2="/tmp/stalld_test_affinity_test2_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG2}")
@@ -117,10 +111,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Multi-CPU affinity (CPU list)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Multi-CPU affinity (-a 0,2)"
-log "=========================================="
+test_section "Test 3: Multi-CPU affinity (-a 0,2)"
 
 if [ "$num_cpus" -ge 4 ]; then
     STALLD_LOG3="/tmp/stalld_test_affinity_test3_$$.log"
@@ -147,10 +138,7 @@ fi
 #=============================================================================
 # Test 4: CPU range affinity
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: CPU range affinity (-a 0-2)"
-log "=========================================="
+test_section "Test 4: CPU range affinity (-a 0-2)"
 
 if [ "$num_cpus" -ge 4 ]; then
     STALLD_LOG4="/tmp/stalld_test_affinity_test4_$$.log"
@@ -176,10 +164,7 @@ fi
 #=============================================================================
 # Test 5: Verify stalld actually runs on specified CPU
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Verify stalld threads run on specified CPU"
-log "=========================================="
+test_section "Test 5: Verify stalld threads run on specified CPU"
 
 if [ "$num_cpus" -ge 2 ]; then
     test_cpu=1
@@ -212,10 +197,7 @@ fi
 #=============================================================================
 # Test 6: Combined with CPU monitoring (-c and -a)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 6: Combined affinity and monitoring (-a 0 -c 1)"
-log "=========================================="
+test_section "Test 6: Combined affinity and monitoring (-a 0 -c 1)"
 
 if [ "$num_cpus" -ge 2 ]; then
     STALLD_LOG6="/tmp/stalld_test_affinity_test6_$$.log"
@@ -248,10 +230,7 @@ fi
 #=============================================================================
 # Test 7: Invalid CPU affinity
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 7: Invalid CPU affinity (-a 999)"
-log "=========================================="
+test_section "Test 7: Invalid CPU affinity (-a 999)"
 
 invalid_cpu=999
 INVALID_LOG="/tmp/stalld_test_affinity_invalid_$$.log"
@@ -275,10 +254,7 @@ fi
 #=============================================================================
 # Test 8: Verify affinity persists
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 8: Verify affinity persists over time"
-log "=========================================="
+test_section "Test 8: Verify affinity persists over time"
 
 STALLD_LOG8="/tmp/stalld_test_affinity_test8_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG8}")
tests/functional/test_backend_selection.sh +5 -8
index 2db8353..049277a 100755
@@ -57,13 +57,12 @@ test_backend_flag() {
 }
 
 # Test 1: sched_debug backend (full name)
-echo "Test 1: Starting stalld with sched_debug backend"
+test_section "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 2: Check queue_track (BPF) backend"
+test_section "Test 2: Check queue_track (BPF) backend"
 if is_backend_available "queue_track"; then
 	test_backend_flag "queue_track" "using queue_track backend" \
 		"queue_track backend selected"
@@ -74,15 +73,13 @@ else
 fi
 
 # Test 3: Short name 'S' for sched_debug
-echo ""
-echo "Test 3: Testing short name 'S' for sched_debug"
+test_section "Test 3: Testing short name 'S' for sched_debug"
 test_backend_flag "S" "using sched_debug backend" \
 	"Short name 'S' works for sched_debug"
 
 # Test 4: STALLD_TEST_BACKEND environment variable
-echo ""
 if [ -n "${STALLD_TEST_BACKEND}" ]; then
-	echo "Test 4: Testing STALLD_TEST_BACKEND=${STALLD_TEST_BACKEND}"
+	test_section "Test 4: Testing STALLD_TEST_BACKEND=${STALLD_TEST_BACKEND}"
 	# Normalize short names for expected message
 	BACKEND_NORMALIZED="${STALLD_TEST_BACKEND}"
 	case "${STALLD_TEST_BACKEND}" in
@@ -93,7 +90,7 @@ if [ -n "${STALLD_TEST_BACKEND}" ]; then
 		"using ${BACKEND_NORMALIZED} backend" \
 		"STALLD_TEST_BACKEND environment variable respected"
 else
-	echo "Test 4: Skipping (STALLD_TEST_BACKEND not set)"
+	test_section "Test 4: Skipping (STALLD_TEST_BACKEND not set)"
 	TEST_PASSED=$((TEST_PASSED + 1))
 fi
 
tests/functional/test_boost_duration.sh +5 -20
index 83d8355..d677da8 100755
@@ -51,10 +51,7 @@ fi
 #=============================================================================
 # Test 1: Default duration (should be 3 seconds)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Default boost duration (no -d specified)"
-log "=========================================="
+test_section "Test 1: Default boost duration (no -d specified)"
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default boost duration)"
@@ -80,10 +77,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Short duration (1 second)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Short boost duration of 1 second"
-log "=========================================="
+test_section "Test 2: Short boost duration of 1 second"
 
 short_duration=1
 STALLD_LOG2="/tmp/stalld_test_boost_duration_test2_$$.log"
@@ -111,10 +105,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Long duration (10 seconds)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Long boost duration of 10 seconds"
-log "=========================================="
+test_section "Test 3: Long boost duration of 10 seconds"
 
 long_duration=10
 long_starvation=20
@@ -144,10 +135,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Verify task policy is restored after boost duration
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Verify policy restoration after boost duration"
-log "=========================================="
+test_section "Test 4: Verify policy restoration after boost duration"
 
 threshold=3
 duration=2
@@ -176,10 +164,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Invalid duration values
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Invalid duration values"
-log "=========================================="
+test_section "Test 5: Invalid duration values"
 
 # Test with zero duration
 log "Testing with duration = 0"
tests/functional/test_boost_period.sh +7 -28
index 3f3ef46..534879f 100755
@@ -48,10 +48,7 @@ fi
 #=============================================================================
 # Test 1: Default period (should be 1,000,000,000 ns = 1 second)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Default period (no -p specified)"
-log "=========================================="
+test_section "Test 1: Default period (no -p specified)"
 
 threshold=5
 log "Starting stalld with default period"
@@ -84,10 +81,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Custom period (500ms = 500,000,000 ns)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Custom period of 500,000,000 ns (500ms)"
-log "=========================================="
+test_section "Test 2: Custom period of 500,000,000 ns (500ms)"
 
 custom_period=500000000
 rm -f "${STALLD_LOG}"
@@ -113,10 +107,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Very short period (100ms = 100,000,000 ns)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Short period of 200,000,000 ns (200ms)"
-log "=========================================="
+test_section "Test 3: Short period of 200,000,000 ns (200ms)"
 
 short_period=200000000
 rm -f "${STALLD_LOG}"
@@ -142,10 +133,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Very long period (10s = 10,000,000,000 ns)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Long period of 3,000,000,000 ns (3s)"
-log "=========================================="
+test_section "Test 4: Long period of 3,000,000,000 ns (3s)"
 
 long_period=3000000000
 rm -f "${STALLD_LOG}"
@@ -171,10 +159,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Invalid period (0)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Invalid period value (0)"
-log "=========================================="
+test_section "Test 5: Invalid period value (0)"
 
 INVALID_LOG="/tmp/stalld_test_boost_period_invalid_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG}")
@@ -197,10 +182,7 @@ fi
 #=============================================================================
 # Test 6: Negative period
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 6: Invalid period value (negative)"
-log "=========================================="
+test_section "Test 6: Invalid period value (negative)"
 
 INVALID_LOG2="/tmp/stalld_test_boost_period_invalid2_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG2}")
@@ -217,10 +199,7 @@ fi
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Total failures: ${TEST_FAILED}"
 
 end_test
tests/functional/test_boost_restoration.sh +6 -24
index bac73ba..0a77945 100755
@@ -50,10 +50,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Restore SCHED_FIFO Policy (starvation_gen creates SCHED_FIFO threads)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Restore SCHED_FIFO Policy"
-log "=========================================="
+test_section "Test 1: Restore SCHED_FIFO Policy"
 
 threshold=5
 boost_duration=3
@@ -159,10 +156,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Restore Original RT Policy (SCHED_FIFO)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Restore Original SCHED_FIFO Policy"
-log "=========================================="
+test_section "Test 2: Restore Original SCHED_FIFO Policy"
 log "Creating a SCHED_FIFO task that gets starved, verify restoration"
 
 threshold=5
@@ -273,10 +267,7 @@ stop_stalld
 #=============================================================================
 # Test 3: SCHED_OTHER Policy Restoration
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Restore SCHED_OTHER Policy"
-log "=========================================="
+test_section "Test 3: Restore SCHED_OTHER Policy"
 log "Test that SCHED_OTHER tasks are correctly restored after boosting"
 
 threshold=5
@@ -304,10 +295,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Restoration Timing Verification
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Restoration Timing Verification"
-log "=========================================="
+test_section "Test 4: Restoration Timing Verification"
 
 threshold=5
 boost_duration=4  # 4 second boost
@@ -360,10 +348,7 @@ sleep 1
 #=============================================================================
 # Test 5: Task Exit During Boost
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Graceful Handling of Task Exit During Boost"
-log "=========================================="
+test_section "Test 5: Graceful Handling of Task Exit During Boost"
 
 threshold=10
 boost_duration=5  # Task will exit during boost (after 8s, boost is 5s)
@@ -414,10 +399,7 @@ stop_stalld
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Total failures: ${TEST_FAILED}"
 
 end_test
tests/functional/test_boost_runtime.sh +7 -28
index 71e25f1..bf22250 100755
@@ -51,10 +51,7 @@ fi
 #=============================================================================
 # Test 1: Default runtime (should be 20,000 ns = 20 microseconds)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Default runtime (no -r specified)"
-log "=========================================="
+test_section "Test 1: Default runtime (no -r specified)"
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default boost runtime)"
@@ -80,10 +77,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Custom runtime (10,000 ns = 10 microseconds, less than default)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Custom runtime of 10,000 ns (10μs)"
-log "=========================================="
+test_section "Test 2: Custom runtime of 10,000 ns (10μs)"
 
 custom_runtime=10000
 STALLD_LOG2="/tmp/stalld_test_boost_runtime_test2_$$.log"
@@ -111,10 +105,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Larger runtime (100,000 ns = 100 microseconds)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Larger runtime of 100,000 ns (100μs)"
-log "=========================================="
+test_section "Test 3: Larger runtime of 100,000 ns (100μs)"
 
 large_runtime=100000
 STALLD_LOG3="/tmp/stalld_test_boost_runtime_test3_$$.log"
@@ -142,10 +133,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Runtime < period (valid configuration)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Runtime < period (valid)"
-log "=========================================="
+test_section "Test 4: Runtime < period (valid)"
 
 # Default period is 1,000,000,000 ns, so runtime of 500,000 ns should be valid
 valid_runtime=500000
@@ -175,10 +163,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Runtime > period (should error or be rejected)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Runtime > period (invalid)"
-log "=========================================="
+test_section "Test 5: Runtime > period (invalid)"
 
 invalid_runtime=2000000000
 period=1000000000
@@ -204,10 +189,7 @@ fi
 #=============================================================================
 # Test 6: Invalid runtime (0)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 6: Invalid runtime value (0)"
-log "=========================================="
+test_section "Test 6: Invalid runtime value (0)"
 
 INVALID_LOG2="/tmp/stalld_test_boost_runtime_invalid2_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG2}")
@@ -225,10 +207,7 @@ fi
 #=============================================================================
 # Test 7: Negative runtime
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 7: Invalid runtime value (negative)"
-log "=========================================="
+test_section "Test 7: Invalid runtime value (negative)"
 
 INVALID_LOG3="/tmp/stalld_test_boost_runtime_invalid3_$$.log"
 CLEANUP_FILES+=("${INVALID_LOG3}")
tests/functional/test_cpu_selection.sh +6 -12
index 99eac44..3fa2128 100755
@@ -35,8 +35,7 @@ fi
 echo "System has $num_cpus CPUs"
 
 # Test 1: Single CPU monitoring
-echo ""
-echo "Test 1: Single CPU monitoring (-c 0)"
+test_section "Test 1: Single CPU monitoring (-c 0)"
 rm -f "${STALLD_LOG}"
 start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 
@@ -51,8 +50,7 @@ stop_stalld
 
 # Test 2: CPU list (comma-separated)
 if [ "$num_cpus" -ge 4 ]; then
-    echo ""
-    echo "Test 2: CPU list monitoring (-c 0,2)"
+    test_section "Test 2: CPU list monitoring (-c 0,2)"
     rm -f "${STALLD_LOG}"
     start_stalld_with_log "${STALLD_LOG}" -f -v -c 0,2 -l -t 5
 
@@ -79,8 +77,7 @@ fi
 
 # Test 3: CPU range
 if [ "$num_cpus" -ge 4 ]; then
-    echo ""
-    echo "Test 3: CPU range monitoring (-c 0-2)"
+    test_section "Test 3: CPU range monitoring (-c 0-2)"
     rm -f "${STALLD_LOG}"
     start_stalld_with_log "${STALLD_LOG}" -f -v -c 0-2 -l -t 5
 
@@ -111,8 +108,7 @@ fi
 
 # Test 4: Combined format (list and range)
 if [ "$num_cpus" -ge 6 ]; then
-    echo ""
-    echo "Test 4: Combined format (-c 0,2-4)"
+    test_section "Test 4: Combined format (-c 0,2-4)"
     rm -f "${STALLD_LOG}"
     start_stalld_with_log "${STALLD_LOG}" -f -v -c 0,2-4 -l -t 5
 
@@ -136,8 +132,7 @@ else
 fi
 
 # Test 5: Invalid CPU number (should handle gracefully)
-echo ""
-echo "Test 5: Invalid CPU number (-c 999)"
+test_section "Test 5: Invalid CPU number (-c 999)"
 invalid_cpu=999
 
 # Create temporary log file for this specific test
@@ -156,8 +151,7 @@ fi
 
 # Test 6: Verify non-selected CPUs are NOT monitored
 if [ "$num_cpus" -ge 2 ]; then
-    echo ""
-    echo "Test 6: Verify non-selected CPUs not monitored (-c 0)"
+    test_section "Test 6: Verify non-selected CPUs not monitored (-c 0)"
     rm -f "${STALLD_LOG}"
     start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 
tests/functional/test_deadline_boosting.sh +6 -24
index 544222a..3fd93c7 100755
@@ -50,10 +50,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Basic DEADLINE Boost Detection
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Basic DEADLINE Boost Detection"
-log "=========================================="
+test_section "Test 1: Basic DEADLINE Boost Detection"
 
 threshold=5
 log "Starting stalld with ${threshold}s threshold (default DEADLINE boosting)"
@@ -98,10 +95,7 @@ stop_stalld
 #=============================================================================
 # Test 2: DEADLINE Parameters Verification
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: DEADLINE Parameters Verification"
-log "=========================================="
+test_section "Test 2: DEADLINE Parameters Verification"
 
 threshold=5
 # Custom DEADLINE parameters
@@ -163,10 +157,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Task Makes Progress During Boost
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Task Makes Progress During Boost"
-log "=========================================="
+test_section "Test 3: Task Makes Progress During Boost"
 
 threshold=5
 boost_duration=5
@@ -234,10 +225,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Policy Restoration After Boost
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Policy Restoration After Boost"
-log "=========================================="
+test_section "Test 4: Policy Restoration After Boost"
 
 threshold=5
 boost_duration=3
@@ -315,10 +303,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Multiple Simultaneous Boosts
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Multiple Simultaneous Boosts"
-log "=========================================="
+test_section "Test 5: Multiple Simultaneous Boosts"
 
 if [ ${NUM_CPUS} -lt 2 ]; then
     log "⚠ SKIP: Need at least 2 CPUs for this test (have ${NUM_CPUS})"
@@ -385,10 +370,7 @@ fi
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Total failures: ${TEST_FAILED}"
 
 end_test
tests/functional/test_fifo_boosting.sh +6 -24
index a003deb..d1b65fb 100755
@@ -47,10 +47,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: FIFO Boost with -F Flag
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: FIFO Boost with -F Flag"
-log "=========================================="
+test_section "Test 1: FIFO Boost with -F Flag"
 
 threshold=5
 # Create starvation FIRST (before stalld starts)
@@ -88,10 +85,7 @@ stop_stalld
 #=============================================================================
 # Test 2: FIFO Priority Verification
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: FIFO Priority Verification"
-log "=========================================="
+test_section "Test 2: FIFO Priority Verification"
 
 threshold=5
 rm -f "${STALLD_LOG}"
@@ -144,10 +138,7 @@ stop_stalld
 #=============================================================================
 # Test 3: FIFO Emulation Behavior
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: FIFO Emulation Behavior"
-log "=========================================="
+test_section "Test 3: FIFO Emulation Behavior"
 log "FIFO emulation cycles: boost→sleep(runtime)→restore→sleep(remainder)"
 
 threshold=5
@@ -198,10 +189,7 @@ stop_stalld
 #=============================================================================
 # Test 4: FIFO vs DEADLINE Comparison
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: FIFO vs DEADLINE Effectiveness Comparison"
-log "=========================================="
+test_section "Test 4: FIFO vs DEADLINE Effectiveness Comparison"
 
 threshold=5
 boost_duration=3
@@ -317,10 +305,7 @@ fi
 #=============================================================================
 # Test 5: Single-Threaded Mode Fails with FIFO
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Single-Threaded Mode with FIFO (Should Fail)"
-log "=========================================="
+test_section "Test 5: Single-Threaded Mode with FIFO (Should Fail)"
 
 log "Attempting to start stalld with -F without -A (single-threaded + FIFO)"
 STALLD_LOG_FAIL="/tmp/stalld_test_fifo_fail_$$.log"
@@ -342,10 +327,7 @@ fi
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Total failures: ${TEST_FAILED}"
 
 end_test
tests/functional/test_fifo_priority_starvation.sh +6 -24
index 9554d49..7c401f3 100755
@@ -51,10 +51,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Basic FIFO-on-FIFO Starvation Detection
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Basic FIFO-on-FIFO Starvation Detection"
-log "=========================================="
+test_section "Test 1: Basic FIFO-on-FIFO Starvation Detection"
 log "Testing: FIFO:10 blocker starves FIFO:5 blockee"
 
 threshold=5
@@ -101,10 +98,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Boosting Effectiveness
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Boosting Allows Progress"
-log "=========================================="
+test_section "Test 2: Boosting Allows Progress"
 log "Verify boosting allows FIFO:5 task to make progress despite FIFO:10 blocker"
 
 rm -f "${STALLD_LOG}"
@@ -168,10 +162,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Starvation Duration Tracking
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Starvation Duration Tracking"
-log "=========================================="
+test_section "Test 3: Starvation Duration Tracking"
 log "Verify duration accumulates correctly (task merging)"
 
 rm -f "${STALLD_LOG}"
@@ -232,10 +223,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Close Priority Gap
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Close Priority Gap (FIFO:6 vs FIFO:5)"
-log "=========================================="
+test_section "Test 4: Close Priority Gap (FIFO:6 vs FIFO:5)"
 log "Testing edge case with only 1 priority difference"
 
 rm -f "${STALLD_LOG}"
@@ -270,10 +258,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Correct Task Boosted
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Verify Correct Task is Boosted"
-log "=========================================="
+test_section "Test 5: Verify Correct Task is Boosted"
 log "Ensure stalld boosts the blockee (FIFO:5), not the blocker (FIFO:10)"
 
 rm -f "${STALLD_LOG}"
@@ -317,10 +302,7 @@ stop_stalld
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Total failures: ${TEST_FAILED}"
 
 if [ -n "${STALLD_TEST_BACKEND}" ] && [ "${STALLD_TEST_BACKEND}" = "queue_track" ]; then
tests/functional/test_force_fifo.sh +6 -24
index 4fdb13b..321e2c0 100755
@@ -44,10 +44,7 @@ fi
 #=============================================================================
 # Test 1: Default behavior (should use SCHED_DEADLINE)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Default behavior (no -F, should use SCHED_DEADLINE)"
-log "=========================================="
+test_section "Test 1: Default behavior (no -F, should use SCHED_DEADLINE)"
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default, no -F)"
@@ -82,10 +79,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Force FIFO mode (-F)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Force FIFO mode (-F)"
-log "=========================================="
+test_section "Test 2: Force FIFO mode (-F)"
 
 # Note: Single-threaded mode only works with SCHED_DEADLINE (dies with FIFO)
 # So we need to use aggressive mode (-A) when testing FIFO
@@ -123,10 +117,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Verify FIFO priority setting
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Verify FIFO priority is set"
-log "=========================================="
+test_section "Test 3: Verify FIFO priority is set"
 
 STALLD_LOG3="/tmp/stalld_test_force_fifo_test3_$$.log"
 CLEANUP_FILES+=("${STALLD_LOG3}")
@@ -160,10 +151,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Verify FIFO emulation behavior (sleep runtime, restore, sleep remainder)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: FIFO emulation behavior"
-log "=========================================="
+test_section "Test 4: FIFO emulation behavior"
 
 boost_duration=3
 long_starvation=12
@@ -202,10 +190,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Single-threaded mode with FIFO (should fail/exit)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Single-threaded mode with FIFO (should fail)"
-log "=========================================="
+test_section "Test 5: Single-threaded mode with FIFO (should fail)"
 
 log "Testing single-threaded mode (-O) with -F (should exit)"
 assert_stalld_rejects "Single-threaded mode rejected FIFO" -f -v -c "${TEST_CPU}" -t ${threshold} -F -O
@@ -213,10 +198,7 @@ assert_stalld_rejects "Single-threaded mode rejected FIFO" -f -v -c "${TEST_CPU}
 #=============================================================================
 # Test 6: Compare effectiveness (informational)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 6: FIFO vs DEADLINE comparison (informational)"
-log "=========================================="
+test_section "Test 6: FIFO vs DEADLINE comparison (informational)"
 
 comparison_duration=2
 comparison_starvation=8
tests/functional/test_foreground.sh +3 -5
index 8b89d02..e07bee3 100755
@@ -22,7 +22,7 @@ setup_test_environment
 require_root
 
 # Test 1: Without -f flag, stalld should daemonize
-echo "Test 1: stalld daemonizes by default"
+test_section "Test 1: stalld daemonizes by default"
 start_stalld -l -t 5
 sleep 2
 
@@ -46,8 +46,7 @@ fi
 stop_stalld
 
 # Test 2: With -f flag, stalld should stay in foreground
-echo ""
-echo "Test 2: stalld stays in foreground with -f"
+test_section "Test 2: stalld stays in foreground with -f"
 
 # Start stalld in foreground but in background job
 start_stalld -f -l -t 5
@@ -70,8 +69,7 @@ fi
 stop_stalld
 
 # Test 3: With -v flag, foreground mode should be implicit
-echo ""
-echo "Test 3: -v implies foreground mode"
+test_section "Test 3: -v implies foreground mode"
 
 start_stalld -v -l -t 5
 sleep 2
tests/functional/test_idle_detection.sh +6 -24
index 8757d74..cd4e0f3 100755
@@ -75,10 +75,7 @@ log "Reads: /proc/stat for per-CPU idle time"
 #=============================================================================
 # Test 1: Idle CPUs Skipped (No Parsing)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Idle CPUs Skipped"
-log "=========================================="
+test_section "Test 1: Idle CPUs Skipped"
 log "Idle CPUs should be skipped to reduce overhead"
 
 threshold=5
@@ -110,10 +107,7 @@ stop_stalld
 #=============================================================================
 # Test 2: /proc/stat Parsing
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: /proc/stat Idle Time Parsing"
-log "=========================================="
+test_section "Test 2: /proc/stat Idle Time Parsing"
 
 # Read idle time for test CPU
 idle_time1=$(get_cpu_idle_time ${TEST_CPU})
@@ -143,10 +137,7 @@ fi
 #=============================================================================
 # Test 3: Monitoring Resumes When CPU Becomes Busy
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Monitoring Resumes for Busy CPUs"
-log "=========================================="
+test_section "Test 3: Monitoring Resumes for Busy CPUs"
 
 threshold=5
 rm -f "${STALLD_LOG}"
@@ -181,10 +172,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Idle Detection Overhead Reduction
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Idle Detection Reduces Overhead"
-log "=========================================="
+test_section "Test 4: Idle Detection Reduces Overhead"
 log "Comparing overhead with and without idle detection (informational)"
 
 # This is informational - we can't easily measure overhead in tests
@@ -203,10 +191,7 @@ log "        Function: cpu_had_idle_time() and get_cpu_busy_list()"
 #=============================================================================
 # Test 5: Idle Detection with Multiple CPUs
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Per-CPU Independent Idle Detection"
-log "=========================================="
+test_section "Test 5: Per-CPU Independent Idle Detection"
 
 NUM_CPUS=$(get_num_cpus)
 if [ ${NUM_CPUS} -lt 2 ]; then
@@ -259,10 +244,7 @@ fi
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Idle detection functions:"
 log "  - cpu_had_idle_time() in stalld.c:226-260"
 log "  - get_cpu_busy_list() in stalld.c:262-308"
tests/functional/test_logging_destinations.sh +4 -7
index f05cd00..7b15c68 100755
@@ -28,7 +28,7 @@ has_stalld_log() {
 }
 
 # Test 1: Verbose mode (-v) logs to stdout/stderr
-echo "Test 1: Verbose mode (-v) logs to stdout"
+test_section "Test 1: Verbose mode (-v) logs to stdout"
 
 LOG_FILE="/tmp/stalld_test_verbose_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
@@ -52,8 +52,7 @@ fi
 stop_stalld
 
 # Test 2: Kernel message log (-k)
-echo ""
-echo "Test 2: Kernel message log (-k)"
+test_section "Test 2: Kernel message log (-k)"
 
 # Clear dmesg if possible (requires root)
 if command -v dmesg >/dev/null 2>&1; then
@@ -86,8 +85,7 @@ else
 fi
 
 # Test 3: Syslog (-s, default)
-echo ""
-echo "Test 3: Syslog (-s, default)"
+test_section "Test 3: Syslog (-s, default)"
 
 # Check if syslog is available
 SYSLOG_FILE=""
@@ -144,8 +142,7 @@ else
 fi
 
 # Test 4: Combined logging (-v -k -s)
-echo ""
-echo "Test 4: Combined logging modes"
+test_section "Test 4: Combined logging modes"
 
 LOG_FILE="/tmp/stalld_test_combined_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
tests/functional/test_pidfile.sh +7 -26
index 76be14a..6b476cd 100755
@@ -34,10 +34,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Default pidfile location (no -P specified)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Default behavior (no -P specified)"
-log "=========================================="
+test_section "Test 1: Default behavior (no -P specified)"
 
 start_stalld -l -t 5
 
@@ -71,10 +68,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Custom pidfile location
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Custom pidfile location"
-log "=========================================="
+test_section "Test 2: Custom pidfile location"
 
 custom_pidfile="/tmp/stalld_test_pidfile_custom_$$.pid"
 CLEANUP_FILES+=("${custom_pidfile}")
@@ -105,8 +99,7 @@ else
 fi
 
 # Test 3: Verify pidfile removed on clean shutdown
-log ""
-log "Test 3: Verify pidfile removed on clean shutdown"
+test_section "Test 3: Verify pidfile removed on clean shutdown"
 stop_stalld
 
 if [ ! -f "${custom_pidfile}" ]; then
@@ -119,10 +112,7 @@ fi
 #=============================================================================
 # Test 4: Custom pidfile in /tmp
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Custom pidfile in /tmp directory"
-log "=========================================="
+test_section "Test 4: Custom pidfile in /tmp directory"
 
 tmp_pidfile="/tmp/stalld_test_tmp_$$.pid"
 CLEANUP_FILES+=("${tmp_pidfile}")
@@ -153,10 +143,7 @@ stop_stalld
 #=============================================================================
 # Test 5: Test with foreground mode
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: Pidfile with foreground mode (-f)"
-log "=========================================="
+test_section "Test 5: Pidfile with foreground mode (-f)"
 
 fg_pidfile="/tmp/stalld_test_pidfile_foreground_$$.pid"
 CLEANUP_FILES+=("${fg_pidfile}")
@@ -187,10 +174,7 @@ stop_stalld
 #=============================================================================
 # Test 6: Invalid pidfile path (permission denied)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 6: Invalid pidfile path (permission denied)"
-log "=========================================="
+test_section "Test 6: Invalid pidfile path (permission denied)"
 
 # Use a non-existent parent directory so fopen() fails even as root
 invalid_pidfile="/nonexistent_${$}/stalld.pid"
@@ -220,10 +204,7 @@ chmod 755 "${test_dir}"
 #=============================================================================
 # Test 7: Verify pidfile is readable by other processes
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 7: Verify pidfile is readable"
-log "=========================================="
+test_section "Test 7: Verify pidfile is readable"
 
 readable_pidfile="/tmp/stalld_test_pidfile_readable_$$.pid"
 CLEANUP_FILES+=("${readable_pidfile}")
tests/functional/test_runqueue_parsing.sh +10 -40

110 lines not on this page · raw message

tests/functional/test_starvation_detection.sh +7 -28
index 6ca2121..cd60e27 100755
@@ -57,10 +57,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Basic Starvation Detection
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Basic Starvation Detection"
-log "=========================================="
+test_section "Test 1: Basic Starvation Detection"
 
 threshold=5
 
@@ -104,10 +101,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Context Switch Count Tracking
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Context Switch Count Tracking"
-log "=========================================="
+test_section "Test 2: Context Switch Count Tracking"
 
 rm -f "${STALLD_LOG}"
 threshold=5
@@ -161,10 +155,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Task Merging (Timestamp Preservation)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Task Merging - Timestamp Preservation"
-log "=========================================="
+test_section "Test 3: Task Merging - Timestamp Preservation"
 
 rm -f "${STALLD_LOG}"
 threshold=3
@@ -224,10 +215,7 @@ wait ${STARVE_PID} 2>/dev/null
 #=============================================================================
 # Test 4: Multiple CPUs Detection
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Multiple CPUs Detection"
-log "=========================================="
+test_section "Test 4: Multiple CPUs Detection"
 
 if [ ${NUM_CPUS} -lt 2 ]; then
     log "⚠ SKIP: Need at least 2 CPUs for this test (have ${NUM_CPUS})"
@@ -297,10 +285,7 @@ fi
 #=============================================================================
 # Test 5: No False Positives (Task Making Progress)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 5: No False Positives"
-log "=========================================="
+test_section "Test 5: No False Positives"
 
 rm -f "${STALLD_LOG}"
 threshold=5
@@ -341,10 +326,7 @@ stop_stalld
 #=============================================================================
 # Test 6: Edge Case - Task Exits During Monitoring
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 6: Task Exits During Monitoring"
-log "=========================================="
+test_section "Test 6: Task Exits During Monitoring"
 
 rm -f "${STALLD_LOG}"
 threshold=10
@@ -379,10 +361,7 @@ stop_stalld
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Total failures: ${TEST_FAILED}"
 
 end_test
tests/functional/test_starvation_threshold.sh +4 -16
index a2cd420..836f079 100755
@@ -55,10 +55,7 @@ fi
 #=============================================================================
 # Test 1: Custom threshold (5 seconds)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Custom threshold of 5 seconds"
-log "=========================================="
+test_section "Test 1: Custom threshold of 5 seconds"
 
 threshold=5
 
@@ -91,10 +88,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Verify no detection before threshold
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: No detection before threshold"
-log "=========================================="
+test_section "Test 2: No detection before threshold"
 
 threshold=10
 STALLD_LOG2="/tmp/stalld_test_threshold_test2_$$.log"
@@ -135,10 +129,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Shorter threshold (3 seconds)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: Shorter threshold (3 seconds)"
-log "=========================================="
+test_section "Test 3: Shorter threshold (3 seconds)"
 
 threshold=3
 STALLD_LOG3="/tmp/stalld_test_threshold_test3_$$.log"
@@ -174,10 +165,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Invalid threshold values
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Invalid threshold values"
-log "=========================================="
+test_section "Test 4: Invalid threshold values"
 
 # Test with zero threshold
 log "Testing with threshold = 0"
tests/functional/test_task_merging.sh +5 -20
index e1a4cc1..305dc4b 100755
@@ -56,10 +56,7 @@ CLEANUP_FILES+=("${STALLD_LOG}")
 #=============================================================================
 # Test 1: Timestamp Preservation for Non-Progressing Tasks
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 1: Timestamp Preservation Across Cycles"
-log "=========================================="
+test_section "Test 1: Timestamp Preservation Across Cycles"
 log "Task merging: same PID + same ctxsw = preserved timestamp"
 
 threshold=3
@@ -134,10 +131,7 @@ stop_stalld
 #=============================================================================
 # Test 2: Same PID + Same Context Switches = Merged
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 2: Merge Condition Verification"
-log "=========================================="
+test_section "Test 2: Merge Condition Verification"
 log "Merging occurs when: PID matches AND context switches unchanged"
 
 threshold=5
@@ -207,10 +201,7 @@ stop_stalld
 #=============================================================================
 # Test 3: Task Making Progress (No Merge)
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 3: No Merge When Task Makes Progress"
-log "=========================================="
+test_section "Test 3: No Merge When Task Makes Progress"
 log "When context switches change, timestamp should reset"
 
 threshold=5
@@ -262,10 +253,7 @@ stop_stalld
 #=============================================================================
 # Test 4: Multiple CPUs with Independent Task Merging
 #=============================================================================
-log ""
-log "=========================================="
-log "Test 4: Per-CPU Independent Task Merging"
-log "=========================================="
+test_section "Test 4: Per-CPU Independent Task Merging"
 
 NUM_CPUS=$(get_num_cpus)
 if [ ${NUM_CPUS} -lt 2 ]; then
@@ -345,10 +333,7 @@ fi
 #=============================================================================
 # Final Summary
 #=============================================================================
-log ""
-log "=========================================="
-log "Test Summary"
-log "=========================================="
+test_section "Test Summary"
 log "Task merging function: merge_tasks_info() in stalld.c:370-397"
 log "Merge logic: if (PID == PID && ctxsw == ctxsw) preserve timestamp"
 log ""
tests/helpers/test_helpers.sh +10 -1
index 9d49b55..5d61b39 100755
@@ -105,6 +105,15 @@ end_test() {
 	fi
 }
 
+# Print a section banner for a test sub-section or summary block.
+test_section() {
+	local title="$1"
+	log ""
+	log "=========================================="
+	log "${title}"
+	log "=========================================="
+}
+
 # Record a test pass with a description message.
 #
 # Usage: pass "description"
@@ -1129,7 +1138,7 @@ start_starvation_gen() {
 }
 
 # Export functions for use in tests
-export -f start_test end_test
+export -f start_test end_test test_section
 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
Signature · 1 line
2.54.0
#8 lore raw

PATCH 1/1 08/33] tests: Introduce assert_stalld_rejects() helper

In reply to Wander Lairson Costa

Tests that verify stalld rejects invalid arguments repeat the same pattern of invoking stalld under a timeout, capturing the exit code, and comparing it against zero and the timeout exit code. This block appears twelve times across seven test files.

Introduce an assert_stalld_rejects() helper that encapsulates the invocation, exit code check, and temporary log cleanup into a single function call. The helper automatically includes the backend flag set by init_functional_test() so that rejection tests run under the correct backend when the test suite is invoked with an explicit backend selection.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_affinity.sh             | 13 +-------
 tests/functional/test_boost_duration.sh       | 25 ++------------
 tests/functional/test_boost_period.sh         | 23 ++-----------
 tests/functional/test_boost_runtime.sh        | 33 ++-----------------
 tests/functional/test_cpu_selection.sh        | 14 +-------
 tests/functional/test_pidfile.sh              | 17 ++--------
 tests/functional/test_starvation_threshold.sh | 25 ++------------
 tests/helpers/test_helpers.sh                 | 21 +++++++++++-
 8 files changed, 33 insertions(+), 138 deletions(-)
tests/functional/test_affinity.sh +1 -12
index ec6ff3b..12b83c9 100755
@@ -200,18 +200,7 @@ fi
 #=============================================================================
 test_section "Test 7: Invalid CPU affinity (-a 999)"
 
-invalid_cpu=999
-INVALID_LOG="/tmp/stalld_test_affinity_invalid_$$.log"
-CLEANUP_FILES+=("${INVALID_LOG}")
-
-timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 -a ${invalid_cpu} > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Invalid CPU affinity rejected with error"
-else
-    fail "stalld did not reject invalid CPU affinity"
-fi
+assert_stalld_rejects "Invalid CPU affinity rejected with error" -f -v -l -t 5 -a 999
 
 #=============================================================================
 # Test 8: Verify affinity persists
tests/functional/test_boost_duration.sh +2 -23
index b0319d2..e9a2135 100755
@@ -106,32 +106,11 @@ cleanup_scenario "${STARVE_PID}"
 #=============================================================================
 test_section "Test 5: Invalid duration values"
 
-# Test with zero duration
 log "Testing with duration = 0"
-INVALID_LOG="/tmp/stalld_test_boost_duration_invalid_$$.log"
-CLEANUP_FILES+=("${INVALID_LOG}")
+assert_stalld_rejects "Zero duration rejected with error" -f -v -t ${threshold} -d 0
 
-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
-    pass "Zero duration rejected with error"
-else
-    fail "stalld did not reject invalid duration value 0"
-fi
-
-# Test 6: Negative duration
 log "Testing with duration = -5"
-rm -f "${INVALID_LOG}"
-
-timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -d -5 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Negative duration rejected with error"
-else
-    fail "stalld did not reject invalid negative duration"
-fi
+assert_stalld_rejects "Negative duration rejected with error" -f -v -t ${threshold} -d -5
 
 log ""
 log "All boost duration tests completed"
tests/functional/test_boost_period.sh +2 -21
index b4f2339..bdbb03a 100755
@@ -108,33 +108,14 @@ cleanup_scenario "${STARVE_PID}"
 #=============================================================================
 test_section "Test 5: Invalid period value (0)"
 
-INVALID_LOG="/tmp/stalld_test_boost_period_invalid_$$.log"
-CLEANUP_FILES+=("${INVALID_LOG}")
-
-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
-    pass "Zero period rejected with error"
-else
-    fail "stalld did not reject invalid period value 0"
-fi
+assert_stalld_rejects "Zero period rejected with error" -f -v -t $threshold -p 0
 
 #=============================================================================
 # Test 6: Negative period
 #=============================================================================
 test_section "Test 6: Invalid period value (negative)"
 
-rm -f "${INVALID_LOG}"
-
-timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t $threshold -p -1000000 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Negative period rejected with error"
-else
-    fail "stalld did not reject invalid negative period"
-fi
+assert_stalld_rejects "Negative period rejected with error" -f -v -t $threshold -p -1000000
 
 #=============================================================================
 # Final Summary
tests/functional/test_boost_runtime.sh +3 -30
index 7585e6b..dfecccc 100755
@@ -107,52 +107,25 @@ test_section "Test 5: Runtime > period (invalid)"
 
 invalid_runtime=2000000000
 period=1000000000
-INVALID_LOG="/tmp/stalld_test_boost_runtime_invalid_$$.log"
-CLEANUP_FILES+=("${INVALID_LOG}")
 
 log "Testing with runtime ${invalid_runtime}ns > period ${period}ns"
-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
-    pass "Runtime > period rejected with error"
-else
-    fail "stalld did not reject invalid runtime > period"
-fi
+assert_stalld_rejects "Runtime > period rejected with error" -f -v -t ${threshold} -r ${invalid_runtime} -p ${period}
 
 #=============================================================================
 # Test 6: Invalid runtime (0)
 #=============================================================================
 test_section "Test 6: Invalid runtime value (0)"
 
-rm -f "${INVALID_LOG}"
-
 log "Testing with runtime = 0"
-timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r 0 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Zero runtime rejected with error"
-else
-    fail "stalld did not reject invalid runtime value 0"
-fi
+assert_stalld_rejects "Zero runtime rejected with error" -f -v -t ${threshold} -r 0
 
 #=============================================================================
 # Test 7: Negative runtime
 #=============================================================================
 test_section "Test 7: Invalid runtime value (negative)"
 
-rm -f "${INVALID_LOG}"
-
 log "Testing with runtime = -5000"
-timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -t ${threshold} -r -5000 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Negative runtime rejected with error"
-else
-    fail "stalld did not reject invalid negative runtime"
-fi
+assert_stalld_rejects "Negative runtime rejected with error" -f -v -t ${threshold} -r -5000
 
 log ""
 log "All boost runtime tests completed"
tests/functional/test_cpu_selection.sh +1 -13
index 3fa2128..32cbe79 100755
@@ -135,19 +135,7 @@ fi
 test_section "Test 5: Invalid CPU number (-c 999)"
 invalid_cpu=999
 
-# Create temporary log file for this specific test
-INVALID_LOG="/tmp/stalld_invalid_cpu_$$.log"
-CLEANUP_FILES+=("${INVALID_LOG}")
-
-# Run stalld with invalid CPU and capture output
-timeout 5 "${TEST_ROOT}/../stalld" -f -v -c $invalid_cpu -l -t 5 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "stalld rejected invalid CPU number"
-else
-    fail "stalld did not reject invalid CPU"
-fi
+assert_stalld_rejects "stalld rejected invalid CPU number" -f -v -c $invalid_cpu -l -t 5
 
 # Test 6: Verify non-selected CPUs are NOT monitored
 if [ "$num_cpus" -ge 2 ]; then
tests/functional/test_pidfile.sh +2 -15
index f24c836..4509307 100755
@@ -151,21 +151,8 @@ stop_stalld
 #=============================================================================
 test_section "Test 6: Invalid pidfile path (permission denied)"
 
-# 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}")
-
-log "Testing invalid pidfile path: ${invalid_pidfile}"
-timeout 5 ${TEST_ROOT}/../stalld -f -v ${BACKEND_FLAG} -l -t 5 --pidfile "${invalid_pidfile}" > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Invalid pidfile path rejected with error"
-else
-    fail "stalld did not reject invalid pidfile path"
-fi
+log "Testing invalid pidfile path"
+assert_stalld_rejects "Invalid pidfile path rejected with error" -f -v -l -t 5 --pidfile "/nonexistent_$$/stalld.pid"
 
 #=============================================================================
 # Test 7: Verify pidfile is readable by other processes
tests/functional/test_starvation_threshold.sh +2 -23
index 88d4d4c..79fa17b 100755
@@ -113,32 +113,11 @@ cleanup_scenario "${STARVE_PID}"
 #=============================================================================
 test_section "Test 4: Invalid threshold values"
 
-# Test with zero threshold
 log "Testing with threshold = 0"
-INVALID_LOG="/tmp/stalld_test_threshold_invalid_$$.log"
-CLEANUP_FILES+=("${INVALID_LOG}")
+assert_stalld_rejects "Zero threshold rejected with error" -f -v -t 0
 
-timeout 5 ${TEST_ROOT}/../stalld -f -v -t 0 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Zero threshold rejected with error"
-else
-    fail "stalld did not reject invalid threshold value 0"
-fi
-
-# Test with negative threshold
 log "Testing with threshold = -5"
-rm -f "${INVALID_LOG}"
-
-timeout 5 ${TEST_ROOT}/../stalld -f -v -t -5 > "${INVALID_LOG}" 2>&1
-ret=$?
-
-if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
-    pass "Negative threshold rejected with error"
-else
-    fail "stalld did not reject invalid negative threshold"
-fi
+assert_stalld_rejects "Negative threshold rejected with error" -f -v -t -5
 
 log ""
 log "All starvation threshold tests completed"
tests/helpers/test_helpers.sh +20 -1
index 389310b..742b15e 100755
@@ -191,6 +191,25 @@ assert_boost_detected() {
 	fi
 }
 
+# Assert that stalld rejects invalid arguments and exits non-zero.
+# Usage: assert_stalld_rejects <message> [stalld_args...]
+assert_stalld_rejects() {
+	local message=$1
+	shift
+
+	local log="/tmp/stalld_reject_$$.log"
+	timeout 5 ${TEST_ROOT}/../stalld ${BACKEND_FLAG} "$@" > "${log}" 2>&1
+	local ret=$?
+	if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
+		pass "${message}"
+	else
+		fail "${message}"
+		log "stalld output:"
+		cat "${log}"
+	fi
+	rm -f "${log}"
+}
+
 # Record a test pass with a description message.
 #
 # Usage: pass "description"
@@ -1226,7 +1245,7 @@ start_starvation_gen() {
 }
 
 # Export functions for use in tests
-export -f start_test end_test test_section cleanup_scenario find_starved_child assert_starvation_detected assert_boost_detected
+export -f start_test end_test test_section cleanup_scenario find_starved_child assert_starvation_detected assert_boost_detected assert_stalld_rejects
 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
Signature · 1 line
2.54.0
#9 lore raw

PATCH 1/1 09/33] tests: Fix boost verification in runtime and duration tests

In reply to Wander Lairson Costa

The boost runtime and duration tests were previously passing the log-only flag (-l) to stalld. This caused the daemon to detect starvation but never actually boost tasks. Furthermore, the tests used assert_starvation_detected, which only verified that starvation was noticed rather than confirming a successful boost. As a result, the tests passed trivially without validating the -r and -d options.

Remove the log-only flag so stalld operates in normal mode and switch the assertions to assert_boost_detected.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_boost_duration.sh | 24 ++++++++++++------------
 tests/functional/test_boost_runtime.sh  | 24 ++++++++++++------------
 2 files changed, 24 insertions(+), 24 deletions(-)
tests/functional/test_boost_duration.sh +12 -12
index e9a2135..aa46e84 100755
@@ -22,15 +22,15 @@ test_section "Test 1: Default boost duration (no -d specified)"
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default boost duration)"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold}
 
 # 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 starvation detection
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection occurred with default duration"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with default duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -44,14 +44,14 @@ short_duration=1
 rm -f "${STALLD_LOG}"
 
 log "Starting stalld with ${threshold}s threshold and ${short_duration}s boost duration"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${short_duration} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${short_duration}
 
 # 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 starvation detection
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with ${short_duration}s duration"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with ${short_duration}s duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -67,14 +67,14 @@ threshold=10
 rm -f "${STALLD_LOG}"
 
 log "Starting stalld with ${threshold}s threshold and ${long_duration}s boost duration"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${long_duration} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${long_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 starvation detection
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with ${long_duration}s duration"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with ${long_duration}s duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -89,14 +89,14 @@ duration=2
 rm -f "${STALLD_LOG}"
 
 log "Starting stalld with ${threshold}s threshold and ${duration}s boost duration"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${duration} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${duration}
 
 # 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 starvation detection
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with ${duration}s boost duration"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with ${duration}s boost duration"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_boost_runtime.sh +12 -12
index dfecccc..caaeb1d 100755
@@ -22,15 +22,15 @@ test_section "Test 1: Default runtime (no -r specified)"
 
 threshold=3
 log "Starting stalld with ${threshold}s threshold (default boost runtime)"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold}
 
 # Create starvation
 starvation_duration=10
 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
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with default runtime"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with default runtime"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -44,14 +44,14 @@ custom_runtime=10000
 rm -f "${STALLD_LOG}"
 
 log "Starting stalld with ${threshold}s threshold and ${custom_runtime}ns runtime"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${custom_runtime} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${custom_runtime}
 
 # 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
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with custom runtime ${custom_runtime}ns"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with custom runtime ${custom_runtime}ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -65,14 +65,14 @@ large_runtime=100000
 rm -f "${STALLD_LOG}"
 
 log "Starting stalld with ${threshold}s threshold and ${large_runtime}ns runtime"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${large_runtime} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${large_runtime}
 
 # 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
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with large runtime ${large_runtime}ns"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with large runtime ${large_runtime}ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -88,14 +88,14 @@ period=1000000000
 rm -f "${STALLD_LOG}"
 
 log "Starting stalld with runtime ${valid_runtime}ns < period ${period}ns"
-start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${valid_runtime} -p ${period} -l
+start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -r ${valid_runtime} -p ${period}
 
 # 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
-assert_starvation_detected "${STALLD_LOG}" "Starvation detection with runtime < period"
+# Wait for actual boosting
+assert_boost_detected "${STALLD_LOG}" "Boost with runtime ${valid_runtime}ns < period ${period}ns"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
Signature · 1 line
2.54.0
#10 lore raw

PATCH 1/1 10/33] tests: Fix subshell swallowing test results

In reply to Wander Lairson Costa

Test 5 in the starvation detection suite runs its assertions inside a backgrounded subshell. The pass and fail functions increment the TEST_PASSED and TEST_FAILED counters, but since those variables are copied into the subshell, the increments are discarded when the child exits. The test result is silently lost regardless of outcome.

Remove the subshell wrapper and run the test logic directly in the main shell. Also replace the else branch, which logged informational messages but never called pass or fail, with an actual check that greps for the busy task PID in the starvation log to distinguish false positives from unrelated starvation events.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_starvation_detection.sh | 35 ++++++++-----------
 1 file changed, 15 insertions(+), 20 deletions(-)
tests/functional/test_starvation_detection.sh +15 -20
index d8f874a..44024c7 100755
@@ -241,31 +241,26 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a $
 # Create a task that gets CPU time but isn't starved
 # Use a SCHED_OTHER task with lower priority that still gets scheduled
 log "Creating a busy task that should NOT be starved"
-(
-    taskset -c ${TEST_CPU} bash -c 'for i in {1..100}; do sleep 0.1; done' &
-    BUSY_PID=$!
+taskset -c ${TEST_CPU} bash -c 'for i in {1..100}; do sleep 0.1; done' &
+BUSY_PID=$!
 
-    # Wait beyond threshold
-    sleep $((threshold + 3))
+# Wait beyond threshold
+sleep $((threshold + 3))
 
-    # Verify this task was NOT reported as starved
-    # Since it's making progress, stalld shouldn't detect it
-    if ! grep "starved" "${STALLD_LOG}"; then
-        pass "No false positive - task making progress not reported as 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
+    pass "No false positive - task making progress not reported as starved"
+else
+    if grep "${BUSY_PID}" "${STALLD_LOG}" | grep -q "starved"; then
+        fail "False positive - progress-making task ${BUSY_PID} reported as starved"
     else
-        # Check if our specific task was reported
-        log "Log shows starvation, checking if it's our progress-making task..."
-        log "Log contents:"
-        cat "${STALLD_LOG}"
-        # This is not necessarily a failure - there might be other starved tasks
-        log "⚠ INFO: Starvation detected, but may be from other tasks"
+        pass "No false positive - starvation detected from other tasks, not ours"
     fi
+fi
 
-    kill ${BUSY_PID} 2>/dev/null
-    wait ${BUSY_PID} 2>/dev/null
-) &
-SUBSHELL_PID=$!
-wait ${SUBSHELL_PID}
+kill ${BUSY_PID} 2>/dev/null
+wait ${BUSY_PID} 2>/dev/null
 
 stop_stalld
 
Signature · 1 line
2.54.0
#11 lore raw

PATCH 1/1 11/33] tests: Fix repeated log match finding same line

In reply to Wander Lairson Costa

Test 3 in the FIFO priority starvation suite calls wait_for_starvation_detected three times on the same log file to wait for three detection cycles. However, the underlying wait_for_log_message helper uses tail -f -n +1 which reads from the beginning of the file on every invocation, so all three calls instantly match the same first line.

Introduce a wait_for_n_log_matches helper that polls grep -c until the match count reaches the requested threshold, and use it to replace the repeated calls.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 .../test_fifo_priority_starvation.sh          | 10 +++------
 tests/helpers/test_helpers.sh                 | 21 ++++++++++++++++++-
 2 files changed, 23 insertions(+), 8 deletions(-)
tests/functional/test_fifo_priority_starvation.sh +3 -7
index 172bc9a..8f78a75 100755
@@ -139,13 +139,9 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a $
 # Wait for multiple detection cycles
 log "Waiting for first detection cycle..."
 wait_for_starvation_detected "${STALLD_LOG}"
-log "First detection cycle should have occurred"
-log "Waiting for second detection cycle..."
-wait_for_starvation_detected "${STALLD_LOG}"
-log "Second detection cycle should have occurred"
-log "Waiting for third detection cycle..."
-wait_for_starvation_detected "${STALLD_LOG}"
-log "Third detection cycle should have occurred"
+log "First detection cycle occurred, waiting for additional cycles..."
+wait_for_n_log_matches "starved on CPU" 3 "${STALLD_LOG}"
+log "Multiple detection cycles should have occurred"
 
 # Check if we see accumulating starvation time in logs
 # Task merging means the timestamp is preserved, so duration increases
tests/helpers/test_helpers.sh +20 -1
index 742b15e..757e5e0 100755
@@ -668,6 +668,25 @@ wait_for_boost_detected() {
 	wait_for_log_message "boosted pid" "${timeout}" "${log_file}"
 }
 
+# Wait until a log file contains at least N matches of a pattern.
+#
+# Usage: wait_for_n_log_matches <pattern> <count> <log_file> [timeout]
+wait_for_n_log_matches() {
+	local pattern=$1
+	local count=$2
+	local log_file=$3
+	local timeout=${4:-30}
+	local end=$((SECONDS + timeout))
+
+	while [ $SECONDS -lt $end ]; do
+		local matches
+		matches=$(grep -c "${pattern}" "${log_file}" 2>/dev/null || true)
+		[ "${matches:-0}" -ge "${count}" ] && return 0
+		sleep 1
+	done
+	return 1
+}
+
 # Get thread scheduling policy
 get_thread_policy() {
 	local pid=$1
@@ -1250,7 +1269,7 @@ 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
-export -f wait_for_log_message wait_for_stalld_ready wait_for_starvation_detected wait_for_boost_detected
+export -f wait_for_log_message wait_for_stalld_ready wait_for_starvation_detected wait_for_boost_detected wait_for_n_log_matches
 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
Signature · 1 line
2.54.0
#12 lore raw

PATCH 1/1 12/33] chore: Remove legacy test infrastructure and stale docs

In reply to Wander Lairson Costa

The legacy test01 wrapper had an undefined STALLD_LOG variable that caused its starvation detection and boost verification checks to silently fail. Rather than fixing the wrapper, remove the entire legacy directory since all its test scenarios are already covered by the modern functional test suite.

Also remove the unit test infrastructure which has been unused since the test suite was created. For a daemon that interacts exclusively with kernel interfaces, unit testing would require impractical mocking while the functional tests validate behavior against real kernels.

As we are deleting out of date files, delete files that were missed in the earlier AI tooling removal commit: the .claude directory with agent definitions, project rules, and context snapshots, along with obsolete test documentation files
that are no longer maintained.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 .claude/CLAUDE.md                        |  585 -----------
 .claude/agents/agent-prompt-engineer.md  |  135 ---
 .claude/agents/c-expert.md               |   53 -
 .claude/agents/code-reviewer.md          |  104 --
 .claude/agents/get-agent-hash            |   99 --
 .claude/agents/git-scm-master.md         | 1154 ----------------------
 .claude/agents/kernel-hacker.md          |  231 -----
 .claude/agents/plan-validator.md         |  130 ---
 .claude/agents/project-historian.md      |  285 ------
 .claude/agents/project-librarian.md      |  604 -----------
 .claude/agents/project-manager.md        |  388 --------
 .claude/agents/project-scope-guardian.md |  326 ------
 .claude/agents/python-expert.md          |   57 --
 .claude/agents/test-specialist.md        |  656 ------------
 .claude/agents/update-agent-hashes       |   96 --
 .claude/context-snapshot.json            |  103 --
 .claude/rules                            |   42 -
 .gitignore                               |    5 +-
 tests/BACKEND_USAGE.md                   |  269 -----
 tests/CONTEXT_SNAPSHOT_2025-10-31.md     |  231 -----
 tests/Makefile                           |   29 +-
 tests/README.md                          |   29 +-
 tests/TODO.md                            |  727 --------------
 tests/legacy/README.md                   |  169 ----
 tests/legacy/test01.c                    |  506 ----------
 tests/legacy/test01_wrapper.sh           |  161 ---
 tests/run_tests.sh                       |  143 +--
 27 files changed, 12 insertions(+), 7305 deletions(-)
 delete mode 100644 .claude/CLAUDE.md
 delete mode 100644 .claude/agents/agent-prompt-engineer.md
 delete mode 100644 .claude/agents/c-expert.md
 delete mode 100644 .claude/agents/code-reviewer.md
 delete mode 100755 .claude/agents/get-agent-hash
 delete mode 100644 .claude/agents/git-scm-master.md
 delete mode 100644 .claude/agents/kernel-hacker.md
 delete mode 100644 .claude/agents/plan-validator.md
 delete mode 100644 .claude/agents/project-historian.md
 delete mode 100644 .claude/agents/project-librarian.md
 delete mode 100644 .claude/agents/project-manager.md
 delete mode 100644 .claude/agents/project-scope-guardian.md
 delete mode 100644 .claude/agents/python-expert.md
 delete mode 100644 .claude/agents/test-specialist.md
 delete mode 100755 .claude/agents/update-agent-hashes
 delete mode 100644 .claude/context-snapshot.json
 delete mode 100644 .claude/rules
 delete mode 100644 tests/BACKEND_USAGE.md
 delete mode 100644 tests/CONTEXT_SNAPSHOT_2025-10-31.md
 delete mode 100644 tests/TODO.md
 delete mode 100644 tests/legacy/README.md
 delete mode 100644 tests/legacy/test01.c
 delete mode 100755 tests/legacy/test01_wrapper.sh
.claude/CLAUDE.md deleted +0 -585
deleted file mode 100644
index 2cd84ba..0000000
@@ -1,585 +0,0 @@
-# CLAUDE.md
-
-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
-
-## Important: Read These Files First
-
-At the start of every session, **ALWAYS read these files in order**:
-
-1. **`.claude/rules`** - Critical project-specific rules including:
-   - Which agents to use for specific tasks (git-scm-master, c-expert, test-specialist, plan-validator)
-   - Workflow requirements and best practices
-   - Project conventions and standards
-
-2. **`.claude/context-snapshot.json`** - Session context and progress tracking:
-   - Recent work and completed tasks
-   - Current project state and test coverage
-   - Implementation details and next steps
-   - Notes from previous sessions
-
-## Overview
-
-`stalld` is a starvation detection and avoidance daemon for Linux
-systems. It monitors CPU run queues for threads that are starving
-(ready to run but not getting CPU time), and temporarily boosts their
-priority using SCHED_DEADLINE (or SCHED_FIFO as fallback) to allow
-them to make progress. This prevents indefinite starvation when
-high-priority RT tasks monopolize CPUs, at the cost of small latencies
-for the application monopolizing the CPU. 
-
-**Primary use case**: DPDK deployments with isolated CPUs running single busy-loop RT tasks, where kernel threads can starve.
-
-**Not recommended for**: Safety-critical systems (see README.md).
-
-## Repository Structure
-
-```
-stalld/
-├── src/               # Main source code (5 C files)
-│   ├── stalld.c      # Main daemon logic (1,218 LOC), entry point, boosting
-│   ├── sched_debug.c # debugfs/procfs backend for parsing /sys/.../debug or /proc sched_debug
-│   ├── queue_track.c # eBPF backend for BPF-based task tracking
-│   ├── utils.c       # Utilities: logging, CPU parsing, argument parsing
-│   ├── throttling.c  # RT throttling detection and control
-│   └── *.h           # Headers (stalld.h, sched_debug.h, queue_track.h)
-├── bpf/              # eBPF code
-│   └── stalld.bpf.c  # BPF tracepoint programs for task tracking
-├── tests/            # Comprehensive test suite
-│   ├── run_tests.sh              # Main test runner (auto-discovery, color output)
-│   ├── test01.c                  # Original starvation test (fixed)
-│   ├── helpers/
-│   │   ├── test_helpers.sh       # Helper library (20+ functions)
-│   │   └── starvation_gen.c      # Configurable starvation generator
-│   ├── functional/               # Functional tests (shell scripts)
-│   │   ├── test_foreground.sh
-│   │   ├── test_log_only.sh
-│   │   └── test_logging_destinations.sh
-│   ├── unit/                     # Unit tests (C programs)
-│   ├── integration/              # Integration tests (shell scripts)
-│   ├── fixtures/                 # Test data and configurations
-│   ├── results/                  # Test output logs (gitignored)
-│   └── README.md                 # Test documentation
-├── systemd/          # systemd integration (service file, config)
-├── man/              # Man page (stalld.8)
-├── scripts/          # Helper scripts (throttlectl.sh)
-└── Makefile          # Build system with arch/kernel detection
-```
-
-## Source File Guide
-
-### Core Implementation Files
-
-**src/stalld.c** (1,218 LOC) - Main daemon
-- Entry point: `main()` at line 1121
-- Boosting logic: `boost_with_deadline()`, `boost_with_fifo()` (lines 438-563)
-- Threading modes: `single_threaded_main()`, `conservative_main()`, `aggressive_main()`
-- Task merging: `merge_tasks_info()` preserves starvation timestamps (lines 370-397)
-
-**src/utils.c** - Utilities
-- Command-line parsing: `parse_args()`
-- CPU list parsing and affinity setting
-- Logging infrastructure (syslog, kmsg, verbose)
-- sched_debug path detection: `find_sched_debug_path()`
-- Buffer resizing and memory allocation
-
-**src/sched_debug.c** - debugfs/procfs backend
-- Parses `/sys/kernel/debug/sched/debug` (debugfs) or `/proc/sched_debug` (procfs, older kernels)
-- Auto-detects kernel format (3.x, 4.18+, 6.12+)
-- Implements `sched_debug_backend` interface defined in stalld.h
-- Fallback when eBPF unavailable (i686, powerpc, ppc64le, legacy kernels)
-
-**src/queue_track.c** - eBPF backend
-- Loads and manages BPF programs via skeleton (`stalld.skel.h`)
-- Implements `queue_track_backend` interface defined in stalld.h
-- Reads task data from BPF maps populated by kernel-side programs
-- Default on x86_64, aarch64, s390x with modern kernels
-
-**src/throttling.c** - RT throttling management
-- Checks if RT throttling is disabled (`sched_rt_runtime_us == -1`)
-- Disables throttling when needed
-- Dies if throttling is enabled (unless running under systemd)
-
-### eBPF Components
-
-**bpf/stalld.bpf.c** - Kernel-side BPF programs
-- Tracepoints: `sched_wakeup`, `sched_switch`, `sched_migrate_task`, `sched_process_exit`
-- Maintains per-CPU task queues in BPF maps
-- Tracks task state changes in real-time without polling
-- Generated files: `bpf/vmlinux.h` (kernel BTF), `src/stalld.skel.h` (userspace skeleton)
-
-## Program Flow
-
-### Startup Sequence (src/stalld.c:main)
-
-1. Parse command-line arguments (`parse_args()`)
-2. Set CPU affinity if configured (`-a` option)
-3. Check for DL-server presence (newer kernels have built-in starvation handling)
-4. **Verify RT throttling is disabled** (die if enabled, unless systemd manages it)
-5. **Detect boost method**: Try SCHED_DEADLINE first, fall back to SCHED_FIFO if unavailable
-6. Initialize backend: `queue_track_backend` (eBPF) or `sched_debug_backend` (debugfs/procfs)
-7. Allocate per-CPU info structures
-8. Setup signal handling
-9. Daemonize (unless `-f/--foreground`)
-10. Enter main monitoring loop (single/adaptive/aggressive mode)
-
-### Monitoring Loop (per-CPU or global depending on mode)
-
-1. **Idle detection** (if enabled): Check `/proc/stat` for idle CPUs, skip if idle
-2. **Get task info**: Call backend's `get()` or `get_cpu()` to read task data
-3. **Parse tasks**: Call backend's `parse()` to populate `cpu_info` structures
-4. **Merge tasks**: Preserve starvation timestamps for tasks making no progress (same context switch count)
-5. **Check for starvation**: Identify tasks on runqueue for ≥`starving_threshold` with no context switches
-6. **Apply denylist**: Skip tasks matching ignore patterns (`-i` option)
-7. **Boost starving tasks**: Apply SCHED_DEADLINE (or FIFO) for `boost_duration` seconds
-8. **Restore policy**: Return task to original scheduling policy
-9. **Sleep**: Wait `granularity` seconds before next check cycle
-
-### Threading Modes
-
-- **Power/Single-threaded** (`-O/--power_mode`): One thread calls `boost_cpu_starving_vector()` to boost all CPUs at once, lower CPU usage, only works with SCHED_DEADLINE (not FIFO)
-- **Adaptive** (`-M/--adaptive_mode`, default): Spawns per-CPU threads when tasks approach ½ starvation threshold, threads exit after 10 idle cycles
-- **Aggressive** (`-A/--aggressive_mode`): Per-CPU threads from startup, never exit, continuous monitoring, highest precision
-
-## Command Line Interface
-
-### Key Options (see man/stalld.8 for complete list)
-
-**Monitoring:**
-- `-c/--cpu <list>`: CPUs to monitor (default: all)
-- `-t/--starving_threshold <sec>`: Starvation threshold in seconds (default: 60s)
-
-**Boosting:**
-- `-p/--boost_period <ns>`: SCHED_DEADLINE period (default: 1,000,000,000 ns = 1s)
-- `-r/--boost_runtime <ns>`: SCHED_DEADLINE runtime (default: 20,000 ns = 20μs)
-- `-d/--boost_duration <sec>`: Boost duration (default: 3s)
-- `-F/--force_fifo`: Force SCHED_FIFO instead of SCHED_DEADLINE
-
-**Threading:**
-- `-O/--power_mode`: Power/single-threaded mode (only works with SCHED_DEADLINE)
-- `-M/--adaptive_mode`: Adaptive mode (default)
-- `-A/--aggressive_mode`: Aggressive mode (per-CPU threads)
-
-**Filtering:**
-- `-i <regex>`: Ignore thread names matching regex (comma-separated)
-- `-I <regex>`: Ignore process names matching regex
-
-**Logging:**
-- `-v/--verbose`: Print to stdout
-- `-k/--log_kmsg`: Log to kernel buffer (dmesg)
-- `-s/--log_syslog`: Log to syslog (default: true)
-- `-l/--log_only`: Only log, don't boost (testing mode)
-
-**Backend:**
-- `-b/--backend <name>`: Select backend (sched_debug, queue_track, S, Q)
-
-**Daemon:**
-- `-f/--foreground`: Run in foreground (don't daemonize)
-- `-P/--pidfile <path>`: Write PID file
-- `-a/--affinity <cpu-list>`: Set stalld affinity to specific CPUs
-
-Entry point: `main()` in `src/stalld.c:1121`
-Argument parsing: `parse_args()` in `src/utils.c`
-
-## Build Commands
-
-### Standard Build
-```bash
-make                  # Build stalld and tests
-make stalld           # Build only stalld executable
-make static           # Build statically linked stalld-static
-make tests            # Build tests only
-```
-
-### Architecture-Specific Notes
-- The build system auto-detects architecture and kernel version
-- eBPF support: Disabled on i686, powerpc, ppc64le, and kernels ≤3.x
-- On legacy kernels (≤3.x), build uses `LEGACY=1` and disables BPF
-
-### Clean and Install
-```bash
-make clean            # Clean all build artifacts
-make install          # Install to system directories
-make uninstall        # Remove installed files
-```
-
-### Development
-```bash
-make DEBUG=1          # Build with debug symbols (-g3)
-make annocheck        # Run security analysis on stalld executable
-```
-
-## Testing
-
-### Automated Test Suite
-
-The `tests/` directory contains a comprehensive test suite with automated test runner, helper library, and multiple test categories.
-
-```bash
-# Run all tests
-make test
-cd tests && ./run_tests.sh
-
-# Run specific test categories
-make test-unit           # Unit tests only
-make test-functional     # Functional tests only
-make test-integration    # Integration tests only
-
-# Run individual tests
-cd tests && ./run_tests.sh --functional-only
-cd tests && functional/test_foreground.sh
-
-# Matrix testing (test multiple backends/modes)
-cd tests && ./run_tests.sh                          # Default: backend matrix (2× runtime)
-cd tests && ./run_tests.sh --full-matrix            # Full matrix: backends × modes (6× runtime)
-cd tests && ./run_tests.sh --backend-only           # Backends only, adaptive mode (2× runtime)
-cd tests && ./run_tests.sh --quick                  # Quick: sched_debug + adaptive (1× runtime)
-
-# Run tests with specific backend/mode
-cd tests && ./run_tests.sh --backend sched_debug    # Use debugfs/procfs backend
-cd tests && ./run_tests.sh -m power                 # Use power/single-threaded mode
-cd tests && functional/test_log_only.sh -b queue_track -m aggressive  # Specific test
-```
-
-**Test Infrastructure:**
-- **run_tests.sh** (~785 lines): Main test orchestrator with auto-discovery, color output, matrix testing (backend × threading mode), per-backend/mode statistics
-- **helpers/test_helpers.sh** (~706 lines): Helper library with 20+ functions for assertions, stalld management, backend/mode selection via `parse_test_options()`
-- **helpers/starvation_gen.c** (~290 lines): Configurable starvation generator for controlled testing
-  - Creates SCHED_FIFO blocker at specified priority (default 10, `-p` flag)
-  - Creates SCHED_FIFO blockees at specified priority (default 1, `-b` flag)
-  - Enables testing both standard starvation and FIFO-on-FIFO priority starvation scenarios
-- **Test organization**: `unit/`, `functional/`, `integration/`, `fixtures/`, `results/`
-- **Matrix testing**: Default tests 2 backends (sched_debug, queue_track), optional 3 threading modes (power, adaptive, aggressive)
-- **Skip logic**: Power mode automatically skips FIFO tests (incompatible with single-threaded)
-
-**Backend Selection in Tests:**
-
-Both the test runner and individual test scripts support runtime backend selection:
-
-```bash
-# Run all tests with specific backend
-./run_tests.sh --backend sched_debug    # Use debugfs/procfs backend
-./run_tests.sh --backend queue_track    # Use eBPF backend
-
-# Run individual test with specific backend
-./functional/test_log_only.sh -b sched_debug
-./functional/test_log_only.sh -b S      # Short form for sched_debug
-./functional/test_log_only.sh -b Q      # Short form for queue_track
-
-# Show test-specific help
-./functional/test_log_only.sh -h
-```
-
-Supported backends:
-- `sched_debug` or `S`: debugfs/procfs backend (parses /sys/kernel/debug/sched/debug or /proc/sched_debug)
-- `queue_track` or `Q`: eBPF backend (uses BPF tracepoints)
-
-Supported threading modes:
-- `power`: Power/single-threaded mode (`-O` flag) - only works with SCHED_DEADLINE
-- `adaptive`: Adaptive/conservative mode (`-M` flag) - default
-- `aggressive`: Aggressive mode (`-A` flag) - per-CPU threads
-
-Tests use `parse_test_options()` from `test_helpers.sh` to handle backend and threading mode selection via `-b/--backend` and `-m/--threading-mode` flags.
-
-**Current Test Coverage:**
-
-✅ **Phase 1 Complete** (Foundation - 4 tests):
-- `test01.c` - Fixed original starvation test (7 critical fixes: error handling, buffer safety, memory cleanup)
-- `test_foreground.sh` - Tests `-f` flag prevents daemonization
-- `test_log_only.sh` - Tests `-l` flag logs but doesn't boost (supports backend selection)
-- `test_logging_destinations.sh` - Tests `-v`, `-k`, `-s` logging options
-
-✅ **Phase 2 Complete** (Command-Line Options - 9 of 10 tests):
-- `test_backend_selection.sh` - Tests `-b` backend selection (argument ordering fix)
-- `test_cpu_selection.sh` - Tests `-c` CPU selection
-- `test_starvation_threshold.sh` - Tests `-t` threshold option (fixed segfault, documented queue_track limitation)
-- `test_boost_period.sh` - Tests `-p` period option (6 tests)
-- `test_boost_runtime.sh` - Tests `-r` runtime option (7 tests)
-- `test_boost_duration.sh` - Tests `-d` duration option (6 tests)
-- `test_affinity.sh` - Tests `-a` affinity option (8 tests)
-- `test_pidfile.sh` - Tests `--pidfile` option (7 tests, fixed -P→--pidfile bug)
-- `test_boost_restoration.sh` - Verifies policy restoration after boosting (5 tests, 3 pass on sched_debug)
-- ⚠️ `test_force_fifo.sh` - SKIPPED (user requested, may return later)
-
-✅ **Phase 3 Complete** (Core Logic - 7 tests):
-- `test_starvation_detection.sh` - Verifies starvation detection (6 tests)
-- `test_idle_detection.sh` - Tests `-N` idle detection disable (5 tests)
-- `test_task_merging.sh` - Verifies timestamp preservation (4 tests)
-- `test_deadline_boosting.sh` - Tests SCHED_DEADLINE boosting (5 tests)
-- `test_fifo_boosting.sh` - Tests SCHED_FIFO boosting (5 tests, 3 pass on sched_debug)
-- `test_fifo_priority_starvation.sh` - Tests FIFO-on-FIFO priority starvation (5 tests, sched_debug only)
-- `test_runqueue_parsing.sh` - Verifies runqueue parsing (5 tests)
-
-**Known Issues:**
-- **queue_track backend limitation**: BPF backend cannot detect SCHED_FIFO tasks waiting on runqueue due to `task_running()` check in `stalld.bpf.c:273` only tracking `__state == TASK_RUNNING`. Tests using `starvation_gen` (SCHED_FIFO workloads) pass on sched_debug but fail on queue_track.
-- **Segfault fix**: Fixed critical bug in `merge_tasks_info()` that caused crashes in adaptive/aggressive modes (commit 7af4f55a5765)
-
-🔄 **Phase 4 Planned** (Advanced Features):
-- Threading modes (adaptive vs aggressive)
-- Filtering (`-i`, `-I` options)
-- Backend comparison tests (eBPF vs debugfs/procfs)
-- Integration and stress tests
-
-**Test Requirements:**
-- Root privileges for most tests
-- RT throttling disabled: `echo -1 > /proc/sys/kernel/sched_rt_runtime_us`
-- stalld built: `make` in project root
-
-**Helper Functions Available:**
-```bash
-# Test Options Parsing
-parse_test_options "$@"     # Parse -b/--backend, -m/--threading-mode, and -h/--help flags
-                            # Sets STALLD_TEST_BACKEND and STALLD_TEST_THREADING_MODE env vars
-
-# Assertions
-assert_equals expected actual "message"
-assert_contains haystack needle "message"
-assert_file_exists "/path/to/file"
-assert_process_running $PID
-
-# stalld Management
-start_stalld [args...]      # Start stalld, track PID
-stop_stalld                 # Stop stalld gracefully
-
-# System Helpers
-require_root                # Skip test if not root
-check_rt_throttling         # Check RT throttling status
-pick_test_cpu               # Pick CPU for testing
-wait_for_log_message "pattern" timeout
-
-# Starvation Generator
-../helpers/starvation_gen -c CPU -p blocker_priority -b blockee_priority -n num_threads -d duration -v
-# Examples:
-#   starvation_gen -c 2 -p 80 -n 2 -d 30        # Standard: blocker prio 80, blockees prio 1
-#   starvation_gen -c 2 -p 10 -b 5 -n 2 -d 30   # FIFO-on-FIFO: blocker prio 10, blockees prio 5
-```
-
-See `tests/README.md` for complete test documentation, writing tests, and troubleshooting.
-
-### Manual Testing Workflow
-
-1. Run stalld in foreground with verbose mode:
-   ```bash
-   sudo ./stalld -f -v -t 5  # 5 second threshold for faster testing
-   ```
-
-2. In another terminal, create a CPU-intensive RT task to monopolize a CPU
-
-3. Create a normal task on the same CPU that will starve
-
-4. Observe stalld detecting and boosting the starving task
-
-## Development Workflow
-
-### Debugging
-
-```bash
-make DEBUG=1          # Build with -g3 debug symbols
-make clean && make    # Full rebuild after changing build options
-```
-
-**Runtime debugging options:**
-- Use `-v` (verbose) to see detailed logging to stdout
-- Use `-l` (log-only) to test starvation detection without actually boosting tasks
-- Use `-k` to log to kernel buffer (view with `dmesg`)
-- Check `/var/log/messages` or `journalctl -u stalld` for syslog output
-- Use `-f` to run in foreground (don't daemonize)
-
-### Code Navigation Tips
-
-**Starting points for common tasks:**
-- Adding new command-line option: `parse_args()` in `src/utils.c`
-- Modifying boost behavior: `boost_with_deadline()` and `boost_with_fifo()` in `src/stalld.c:438-563`
-- Changing detection logic: `check_starving_tasks()` in `src/stalld.c:616-659`
-- Backend implementation: `struct stalld_backend` in `src/stalld.h:79-110`
-- eBPF tracepoints: `bpf/stalld.bpf.c` (requires kernel rebuild/reload)
-
-### Understanding Backend Selection
-
-**Compile-time default backend** is chosen based on architecture and kernel:
-
-```c
-// src/stalld.c:158-162
-#if USE_BPF
-    backend = &queue_track_backend;  // eBPF backend (default)
-#else
-    backend = &sched_debug_backend;  // debugfs/procfs backend (default)
-#endif
-```
-
-`USE_BPF` is set in Makefile based on:
-- Architecture (disabled on i686, powerpc, ppc64le)
-- Kernel version (disabled on kernels ≤3.x)
-
-**Runtime backend selection** (via `-b` flag):
-```bash
-# Force debugfs/procfs backend
-sudo ./stalld -b sched_debug -f -v
-
-# Force eBPF backend
-sudo ./stalld -b queue_track -f -v
-
-# Short forms also supported
-sudo ./stalld -b S -f -v    # sched_debug
-sudo ./stalld -b Q -f -v    # queue_track
-```
-
-If a backend is explicitly requested but unavailable (e.g., eBPF not compiled in, or BPF programs fail to load), stalld will fail to start.
-
-## Architecture
-
-### Backend System (src/stalld.h lines 79-110)
-
-`stalld` uses a **pluggable backend architecture** to collect task information:
-
-1. **queue_track_backend** (eBPF-based, default on x86_64/aarch64/s390x)
-   - Uses BPF tracepoints to track task queue state in real-time
-   - Source: `bpf/stalld.bpf.c` + `src/queue_track.c`
-   - More efficient, lower overhead
-   - Tracks: `sched_wakeup`, `sched_switch`, `sched_migrate_task`, `sched_process_exit`
-
-2. **sched_debug_backend** (debugfs/procfs-based, fallback)
-   - Parses `/sys/kernel/debug/sched/debug` (debugfs) or `/proc/sched_debug` (procfs, older kernels)
-   - Source: `src/sched_debug.c`
-   - Used on i686, powerpc, ppc64le, and legacy kernels (≤3.x)
-   - Handles multiple kernel sched_debug formats (3.x, 4.18+, 6.12+)
-
-Backend selection is automatic at compile time (src/stalld.c:158-162) based on architecture and kernel version.
-
-### Operating Modes (src/stalld.c)
-
-Three threading modes controlled by `-A` and internal flags:
-
-1. **Power/Single-threaded mode** (`-O/--power_mode`, `config_single_threaded=1`)
-   - One thread monitors all CPUs
-   - Uses `boost_cpu_starving_vector()` to boost all starving tasks at once
-   - Lower CPU usage, lower precision
-   - **Only works with SCHED_DEADLINE** (not FIFO)
-
-2. **Adaptive/Conservative mode** (`-M/--adaptive_mode`, `config_adaptive_multi_threaded=1`, default)
-   - Starts with single thread
-   - Spawns per-CPU threads when tasks approach starvation (½ threshold)
-   - Per-CPU threads exit after 10 idle cycles
-
-3. **Aggressive mode** (`-A/--aggressive_mode`, `config_aggressive=1`)
-   - Dedicated thread per monitored CPU from start
-   - Highest precision, highest CPU usage
-   - Never exit, continuous monitoring
-
-### Key Data Structures
-
-- **`struct task_info`** (src/stalld.h:53-60): Per-task tracking (PID, comm, priority, context switches, starvation timestamp)
-- **`struct cpu_info`** (src/stalld.h:65-77): Per-CPU state (running tasks, RT tasks, starving tasks array)
-- **`struct stalld_cpu_data`** (src/queue_track.h:19-24): eBPF per-CPU map data
-- **`struct queued_task`** (src/queue_track.h:11-17): Task entry in eBPF queue
-
-### Boosting Logic (src/stalld.c:438-563)
-
-1. Detect starvation: Task on runqueue for ≥`starving_threshold` seconds with no context switches
-2. Save current scheduling policy
-3. Boost to SCHED_DEADLINE (runtime/period) or SCHED_FIFO (priority)
-4. Sleep for `boost_duration` seconds
-5. Restore original policy
-
-**Important**: FIFO boosting emulates DEADLINE behavior by manually sleeping runtime, restoring policy, sleeping remainder (src/stalld.c:500-526).
-
-### Task Format Auto-Detection (src/sched_debug.h:43-48)
-
-The sched_debug backend handles 3 different kernel formats:
-- **OLD_TASK_FORMAT**: 3.x kernels (no state column, 'R' prefix for running task)
-- **NEW_TASK_FORMAT**: 4.18+ kernels (has 'S' state column)
-- **6.12+ format**: Added EEVDF fields (vruntime, eligible, deadline, slice)
-
-Parser auto-detects format on first read and sets offsets accordingly.
-
-### eBPF Build Process (Makefile:161-189)
-
-When `USE_BPF=1`:
-1. Generate `bpf/vmlinux.h` from kernel BTF via `bpftool`
-2. Compile `bpf/stalld.bpf.c` → `bpf/stalld.bpf.o` using `clang -target bpf`
-3. Generate `src/stalld.skel.h` skeleton from `.bpf.o` via `bpftool gen skeleton`
-4. Include skeleton in userspace code compilation
-
-### Idle Detection Optimization (src/stalld.c:226-308)
-
-When `config_idle_detection=1` (default):
-- Parse `/proc/stat` to check CPU idle time before expensive parsing
-- Skip parsing for CPUs with increasing idle counter
-- Reduces overhead when CPUs aren't busy
-
-## Configuration Files
-
-- **systemd/stalld.service**: systemd unit file
-- **systemd/stalld.conf**: Configuration options for systemd deployment
-- **scripts/throttlectl.sh**: Helper script for RT throttling control
-
-## RT Throttling
-
-`stalld` requires RT throttling to be disabled. The daemon handles this automatically unless running under systemd (where systemd should handle it via `CPUQuota=-1`).
-
-Check: `/proc/sys/kernel/sched_rt_runtime_us` should be `-1`.
-
-## Important Code Patterns
-
-### Task Merging (src/stalld.c:370-397)
-When re-parsing tasks, `merge_tasks_info()` preserves starvation timestamps for tasks that haven't made progress (same PID, same context switch count).
-
-### Denylist/Ignore Feature
-- `-i` flag: Ignore threads/processes matching regex patterns
-- Uses POSIX regex via `regexec()`
-- Check both thread name and process group name (src/stalld.c:570-614)
-
-### Buffer Management
-The buffer for sched_debug automatically grows when content increases (src/sched_debug.c:55-58, src/stalld.h:192).
-
-## Common Gotchas
-
-1. **Single-threaded mode only works with SCHED_DEADLINE**, not FIFO (dies at src/stalld.c:973)
-2. **RT throttling must be off** or stalld exits (src/stalld.c:1154-1161)
-3. **sched_debug path varies**: `/sys/kernel/debug/sched/debug` or `/proc/sched_debug` (auto-detected in `utils.c`)
-4. **Architecture differences**: eBPF not available on all platforms
-5. **Kernel version differences**: Legacy kernels (≤3.x) need special handling
-
-## Quick Reference
-
-### Critical Files and Functions
-
-**Entry points:**
-- Main entry: `src/stalld.c:main()` line 1121
-- Boost logic: `src/stalld.c:boost_with_deadline()` line 438
-- Starvation detection: `src/stalld.c:check_starving_tasks()` line 616
-- Backend interface: `src/stalld.h:struct stalld_backend` line 79
-
-**Backends:**
-- eBPF backend: `src/queue_track.c` + `bpf/stalld.bpf.c`
-- debugfs/procfs backend: `src/sched_debug.c`
-
-**Configuration:**
-- Argument parsing: `src/utils.c:parse_args()`
-- Defaults in: `src/stalld.c` global variables (lines 49-169)
-
-### Build Quick Reference
-
-```bash
-make                  # Build stalld + tests
-make DEBUG=1          # Debug build with -g3
-make static           # Static binary
-make clean            # Clean build artifacts
-make install          # Install to system
-```
-
-### Key Runtime Requirements
-
-- **RT throttling must be disabled**: `/proc/sys/kernel/sched_rt_runtime_us == -1`
-- **Default starvation threshold**: 60 seconds
-- **Default boost**: 20μs runtime / 1s period for 3 seconds
-- **Minimum kernel**: 3.10+ (older kernels untested)
-- **eBPF requires**: Modern kernel (4.x+), x86_64/aarch64/s390x architecture
-
-### Debugging Commands
-
-```bash
-sudo ./stalld -f -v -t 5    # Foreground, verbose, 5s threshold
-sudo ./stalld -l -v         # Log-only mode (no boosting)
-dmesg | grep stalld         # Check kernel messages (if -k used)
-journalctl -u stalld -f     # Follow systemd logs
-```
.claude/agents/agent-prompt-engineer.md deleted +0 -135

136 lines not on this page · raw message

.claude/agents/c-expert.md deleted +0 -53

53 lines not on this page · raw message

.claude/agents/code-reviewer.md deleted +0 -104

105 lines not on this page · raw message

.claude/agents/get-agent-hash deleted +0 -99

100 lines not on this page · raw message

.claude/agents/git-scm-master.md deleted +0 -1154

1,154 lines not on this page · raw message

.claude/agents/kernel-hacker.md deleted +0 -231

232 lines not on this page · raw message

.claude/agents/plan-validator.md deleted +0 -130

131 lines not on this page · raw message

.claude/agents/project-historian.md deleted +0 -285

286 lines not on this page · raw message

.claude/agents/project-librarian.md deleted +0 -604

218 lines not on this page · raw message

.claude/agents/project-manager.md deleted +0 -388

388 lines not on this page · raw message

.claude/agents/project-scope-guardian.md deleted +0 -326

326 lines not on this page · raw message

.claude/agents/python-expert.md deleted +0 -57

57 lines not on this page · raw message

.claude/agents/test-specialist.md deleted +0 -656

656 lines not on this page · raw message

.claude/agents/update-agent-hashes deleted +0 -96

96 lines not on this page · raw message

.claude/context-snapshot.json deleted +0 -103

103 lines not on this page · raw message

.claude/rules deleted +0 -42

42 lines not on this page · raw message

.gitignore +3 -2
index 62fddc0..c291b54 100644

Lines of this file are not shown. View the raw message.

tests/BACKEND_USAGE.md deleted +0 -269

269 lines not on this page · raw message

tests/CONTEXT_SNAPSHOT_2025-10-31.md deleted +0 -231

231 lines not on this page · raw message

tests/Makefile +4 -25
index 6ffc945..4957c91 100644

Lines of this file are not shown. View the raw message.

tests/README.md +2 -27
index acffb92..f3628a0 100644

Lines of this file are not shown. View the raw message.

tests/TODO.md deleted +0 -727

727 lines not on this page · raw message

tests/legacy/README.md deleted +0 -169

169 lines not on this page · raw message

tests/legacy/test01.c deleted +0 -506

506 lines not on this page · raw message

tests/legacy/test01_wrapper.sh deleted +0 -161

161 lines not on this page · raw message

tests/run_tests.sh +3 -140

143 lines not on this page · raw message

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.54.0

Part of the diff is not shown here. View the raw message.

#13 lore raw

PATCH 1/1 13/33] tests: Add assertions to SCHED_OTHER restoration test

In reply to Wander Lairson Costa

Test 3 in the boost restoration suite creates a SCHED_OTHER blockee but never verifies that the scheduling policy is actually restored after boosting. It waits for starvation_gen to complete and has a comment about checking the result, but no pass or fail call exists.

Add proper verification by tracking the starved child PID, waiting for the boost to occur and expire, then checking that the policy returns to SCHED_OTHER.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_boost_restoration.sh | 37 +++++++++++++++++++---
 1 file changed, 32 insertions(+), 5 deletions(-)
tests/functional/test_boost_restoration.sh +32 -5
index 09dbb60..63e2bc3 100755
@@ -244,12 +244,39 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${ST
 log "Creating SCHED_OTHER starvation (RT blocker prio 80, SCHED_OTHER blockee)"
 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..."
-wait ${STARVE_PID} 2>/dev/null || true
+# Find the starved SCHED_OTHER task
+tracked_pid=$(find_starved_child "${STARVE_PID}")
+
+if [ -n "${tracked_pid}" ]; then
+    initial_policy=$(get_sched_policy ${tracked_pid})
+    log "Tracking task PID ${tracked_pid}, initial policy: ${initial_policy} (expected: 0=SCHED_OTHER)"
+
+    # Wait for boost
+    log "Waiting for starvation detection and boost..."
+    if wait_for_boost_detected "${STALLD_LOG}"; then
+        pass "SCHED_OTHER task boosted"
+
+        # Wait for boost to expire and policy to be restored
+        sleep $((boost_duration + 1))
 
-# Check if blockee completed (proves SCHED_OTHER → boost → SCHED_OTHER restoration worked)
-# The starvation_gen output will show if blockees completed
+        if [ -f "/proc/${tracked_pid}/sched" ]; then
+            final_policy=$(get_sched_policy ${tracked_pid})
+            log "Policy after boost: ${final_policy}"
+
+            if [ "$final_policy" = "0" ]; then
+                pass "Policy restored to SCHED_OTHER (0)"
+            else
+                fail "Policy not restored to SCHED_OTHER (got ${final_policy})"
+            fi
+        else
+            log "⚠ INFO: Task exited before restoration check"
+        fi
+    else
+        fail "No boost detected for SCHED_OTHER task"
+    fi
+else
+    log "⚠ WARNING: Could not find starved task to track"
+fi
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
Signature · 1 line
2.54.0
#14 lore raw

PATCH 1/1 15/33] tests: Add idle CPU skipping assertion

In reply to Wander Lairson Costa

Test 4 in the idle detection suite was purely informational, printing explanatory text about what idle detection does without any assertions. Replace it with an actual test that starts stalld on an idle CPU and verifies that "skipping" messages appear in the log, confirming the idle detection optimization is working.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_idle_detection.sh | 32 +++++++++++++------------
 1 file changed, 17 insertions(+), 15 deletions(-)
tests/functional/test_idle_detection.sh +17 -15
index 8017675..f238c7e 100755
@@ -140,23 +140,25 @@ fi
 cleanup_scenario "${STARVE_PID}"
 
 #=============================================================================
-# Test 4: Idle Detection Overhead Reduction
+# Test 4: Idle CPUs Are Skipped
 #=============================================================================
-test_section "Test 4: Idle Detection Reduces Overhead"
-log "Comparing overhead with and without idle detection (informational)"
-
-# This is informational - we can't easily measure overhead in tests
-log "With idle detection (default):"
-log "  - /proc/stat read before parsing"
-log "  - Idle CPUs skipped (no sched_debug/BPF parsing)"
-log "  - Reduces CPU usage when system mostly idle"
-log ""
-log "Without idle detection would:"
-log "  - Always parse all CPUs"
-log "  - Higher overhead even when CPUs idle"
+test_section "Test 4: Idle CPUs Are Skipped"
+
+rm -f "${STALLD_LOG}"
+threshold=3
+
+log "Starting stalld with idle detection on an idle CPU"
+start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
+
+sleep 3
+
+if grep -q "skipping" "${STALLD_LOG}"; then
+    pass "Idle CPU correctly skipped"
+else
+    fail "No skipping messages for idle CPU"
+fi
 
-log "ℹ INFO: Idle detection enabled by default for efficiency"
-log "        Function: cpu_had_idle_time() and get_cpu_busy_list()"
+cleanup_scenario
 
 #=============================================================================
 # Test 5: Idle Detection with Multiple CPUs
Signature · 1 line
2.54.0
#15 lore raw

PATCH 1/1 14/33] tests: Fix CPU selection grep substring matches

In reply to Wander Lairson Costa

The CPU selection test uses grep patterns like "cpu 1" which can match "cpu 10" or "cpu 12" on systems with more than 9 CPUs. This causes false positives where the test passes even when the wrong CPUs are being monitored.

Introduce a local is_cpu_monitored() helper that greps for the exact stalld log format "adding cpu N$" with an end-of-line anchor, preventing substring matches.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_cpu_selection.sh | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)
tests/functional/test_cpu_selection.sh +13 -9
index 32cbe79..ff5b879 100755
@@ -13,6 +13,10 @@ source "${TEST_ROOT}/helpers/test_helpers.sh"
 # Parse command-line options
 parse_test_options "$@" || exit $?
 
+is_cpu_monitored() {
+    grep -q "adding cpu ${1}$" "$STALLD_LOG"
+}
+
 start_test "CPU Selection (-c option)"
 
 # Setup test environment
@@ -40,7 +44,7 @@ rm -f "${STALLD_LOG}"
 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
+if is_cpu_monitored 0; then
     pass "stalld monitoring CPU 0"
 else
     fail "stalld not monitoring CPU 0"
@@ -57,10 +61,10 @@ if [ "$num_cpus" -ge 4 ]; then
     # Check for CPU 0 and CPU 2 in output
     cpu0_found=0
     cpu2_found=0
-    if grep -q "cpu 0" "$STALLD_LOG"; then
+    if is_cpu_monitored 0; then
         cpu0_found=1
     fi
-    if grep -q "cpu 2" "$STALLD_LOG"; then
+    if is_cpu_monitored 2; then
         cpu2_found=1
     fi
 
@@ -85,13 +89,13 @@ if [ "$num_cpus" -ge 4 ]; then
     cpu0_found=0
     cpu1_found=0
     cpu2_found=0
-    if grep -q "cpu 0" "$STALLD_LOG"; then
+    if is_cpu_monitored 0; then
         cpu0_found=1
     fi
-    if grep -q "cpu 1" "$STALLD_LOG"; then
+    if is_cpu_monitored 1; then
         cpu1_found=1
     fi
-    if grep -q "cpu 2" "$STALLD_LOG"; then
+    if is_cpu_monitored 2; then
         cpu2_found=1
     fi
 
@@ -115,7 +119,7 @@ if [ "$num_cpus" -ge 6 ]; then
     # Should monitor CPUs 0, 2, 3, 4
     monitored_cpus=0
     for cpu in 0 2 3 4; do
-        if grep -q "cpu $cpu" "$STALLD_LOG"; then
+        if is_cpu_monitored ${cpu}; then
             ((monitored_cpus++))
         fi
     done
@@ -143,8 +147,8 @@ if [ "$num_cpus" -ge 2 ]; then
     rm -f "${STALLD_LOG}"
     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
+    # Check that CPU 1 is NOT being monitored
+    if ! is_cpu_monitored 1; then
         pass "stalld not monitoring non-selected CPU 1"
     else
         fail "stalld appears to be monitoring CPU 1 when only CPU 0 selected"
Signature · 1 line
2.54.0
#16 lore raw

PATCH 1/1 16/33] tests: Remove redundant pkill from cleanup

In reply to Wander Lairson Costa

The cleanup function uses pkill -9 -f starvation_gen to kill all starvation generator processes system-wide. This is redundant because the CLEANUP_PIDS array already tracks every spawned process by PID and terminates them individually.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/helpers/test_helpers.sh | 4 ----
 1 file changed, 4 deletions(-)
tests/helpers/test_helpers.sh +0 -4
index 757e5e0..2b744d0 100755
@@ -548,10 +548,6 @@ cleanup() {
 	# Stop stalld
 	stop_stalld
 
-	# Kill any starvation generators first (these often have child processes)
-	# Use pkill which handles process trees better
-	pkill -9 -f starvation_gen 2>/dev/null || true
-
 	# Small delay to let processes terminate
 	sleep 0.2
 
Signature · 1 line
2.54.0
#17 lore raw

PATCH 1/1 17/33] tests: Introduce and adopt assert_log_contains() helper

In reply to Wander Lairson Costa

Multiple functional tests repeat the pattern of grepping a log file for a specific pattern and calling pass or fail based on the result. This leads to verbose and duplicated boilerplate code across the test suite.

Introduce an assert_log_contains() helper that encapsulates the grep, pass/fail evaluation, and diagnostic output into a single function call. To support various test requirements, the helper includes:
* A --negate flag for asserting the absence of a pattern.
* An --ignore-case flag to easily perform case-insensitive matching, avoiding verbose regex workarounds (like [Ee]rror).

Adopt this new helper across the functional test suite. This refactor simplifies several tests, including replacing nested false-positive checks in test_starvation_detection and cleaning up if/elif/else policy detection blocks in test_force_fifo.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_backend_selection.sh    |  9 +---
 tests/functional/test_deadline_boosting.sh    | 25 ++--------
 tests/functional/test_fifo_boosting.sh        | 12 +----
 .../test_fifo_priority_starvation.sh          | 20 ++------
 tests/functional/test_force_fifo.sh           | 48 ++-----------------
 tests/functional/test_idle_detection.sh       |  6 +--
 tests/functional/test_log_only.sh             |  8 +---
 tests/functional/test_logging_destinations.sh |  4 +-
 tests/functional/test_starvation_detection.sh | 32 +++----------
 tests/functional/test_starvation_threshold.sh | 10 ++--
 tests/helpers/test_helpers.sh                 | 41 +++++++++++++++-
 11 files changed, 66 insertions(+), 149 deletions(-)
tests/functional/test_backend_selection.sh +1 -8
index 049277a..d65695b 100755
@@ -44,14 +44,7 @@ test_backend_flag() {
 		return 1
 	fi
 
-	if grep -q "${expected_msg}" "${log_file}"; then
-		pass "${description}"
-	else
-		fail "Backend message not found (${description})"
-		echo "  Expected: ${expected_msg}"
-		echo "  Log contents:"
-		cat "${log_file}"
-	fi
+	assert_log_contains "${log_file}" "${expected_msg}" "${description}"
 
 	stop_stalld
 }
tests/functional/test_deadline_boosting.sh +3 -22
index eacce98..e777ea0 100755
@@ -40,11 +40,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     pass "Boosting occurred"
 
     # Verify SCHED_DEADLINE was used
-    if grep -q "SCHED_DEADLINE" "${STALLD_LOG}"; then
-        pass "SCHED_DEADLINE boosting used (default)"
-    else
-        fail "SCHED_DEADLINE not mentioned in boost message"
-    fi
+    assert_log_contains "${STALLD_LOG}" "SCHED_DEADLINE" "SCHED_DEADLINE boosting used (default)"
 
     # Verify boost happened after threshold
     # (starvation logged, then boosting)
@@ -106,11 +102,7 @@ fi
 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
-        pass "SCHED_DEADLINE boost confirmed in logs"
-    else
-        fail "No SCHED_DEADLINE boost detected"
-    fi
+    assert_log_contains "${STALLD_LOG}" "boosted.*SCHED_DEADLINE" "SCHED_DEADLINE boost confirmed in logs"
 fi
 
 # Cleanup
@@ -165,11 +157,7 @@ else
 fi
 
 # Verify boost happened
-if grep -q "boosted" "${STALLD_LOG}"; then
-    pass "Boost occurred as expected"
-else
-    fail "No boost detected"
-fi
+assert_log_contains "${STALLD_LOG}" "boosted" "Boost occurred as expected"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
@@ -286,13 +274,6 @@ else
     if [ ${boost_count} -ge 2 ]; then
         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
-            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
             pass "Multiple boost cycles (${boost_count} total), showing independent operation"
tests/functional/test_fifo_boosting.sh +2 -10
index 3d17e5e..38b0b14 100755
@@ -38,11 +38,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     pass "Boosting occurred with -F flag"
 
     # Verify SCHED_FIFO was used
-    if grep -q "SCHED_FIFO" "${STALLD_LOG}"; then
-        pass "SCHED_FIFO boosting used (as requested by -F)"
-    else
-        fail "SCHED_FIFO not mentioned in boost message"
-    fi
+    assert_log_contains "${STALLD_LOG}" "SCHED_FIFO" "SCHED_FIFO boosting used (as requested by -F)"
 else
     fail "No boosting detected with -F flag"
     log "Log contents:"
@@ -88,11 +84,7 @@ fi
 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
-        pass "SCHED_FIFO boost confirmed in logs"
-    else
-        fail "No SCHED_FIFO boost detected"
-    fi
+    assert_log_contains "${STALLD_LOG}" "boosted.*SCHED_FIFO" "SCHED_FIFO boost confirmed in logs"
 fi
 
 # Cleanup
tests/functional/test_fifo_priority_starvation.sh +3 -17
index 8f78a75..6df2c9b 100755
@@ -44,18 +44,10 @@ if wait_for_starvation_detected "${STALLD_LOG}"; then
     pass "FIFO-on-FIFO starvation detected"
 
     # Verify correct CPU is logged
-    if grep "starved on CPU ${TEST_CPU}" "${STALLD_LOG}"; then
-        pass "Correct CPU ID logged (CPU ${TEST_CPU})"
-    else
-        fail "Wrong CPU ID in log"
-    fi
+    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU}" "Correct CPU ID logged (CPU ${TEST_CPU})"
 
     # Verify duration is logged
-    if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}"; then
-        pass "Starvation duration logged"
-    else
-        fail "Starvation duration not logged"
-    fi
+    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU} for [0-9]" "Starvation duration logged"
 else
     fail "FIFO-on-FIFO starvation not detected"
     log "Log contents:"
@@ -106,13 +98,7 @@ log "Context switch delta: ${ctxt_delta}"
 if [ ${ctxt_delta} -gt 0 ]; then
     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
-    if grep -q "boosted" "${STALLD_LOG}"; then
-        log "ℹ INFO: Boosting did occur according to logs"
-    else
-        fail "No boosting detected"
-    fi
+    assert_log_contains "${STALLD_LOG}" "boosted" "Boosting occurred despite no measurable progress"
 fi
 
 # Cleanup
tests/functional/test_force_fifo.sh +5 -43

66 lines not on this page · raw message

tests/functional/test_idle_detection.sh +1 -5
index f238c7e..5647129 100755
@@ -152,11 +152,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a $
 
 sleep 3
 
-if grep -q "skipping" "${STALLD_LOG}"; then
-    pass "Idle CPU correctly skipped"
-else
-    fail "No skipping messages for idle CPU"
-fi
+assert_log_contains "${STALLD_LOG}" "skipping" "Idle CPU correctly skipped"
 
 cleanup_scenario
 
tests/functional/test_log_only.sh +1 -7
index b10b4d9..6a26b93 100755
@@ -61,13 +61,7 @@ echo "Waiting for starvation detection..."
 assert_starvation_detected "${LOG_FILE}" "stalld detected and logged starvation"
 
 # Check that stalld did NOT boost (should not see "boosted" message with -l)
-if ! grep -q "boosted" "${LOG_FILE}"; then
-	pass "stalld did not boost in log-only mode"
-else
-	fail "stalld boosted despite -l flag"
-	echo "Log contents:"
-	cat "${LOG_FILE}"
-fi
+assert_log_contains --negate "${LOG_FILE}" "boosted" "stalld did not boost in log-only mode"
 
 # Cleanup
 cleanup_scenario "${STARVGEN_PID}"
tests/functional/test_logging_destinations.sh +1 -3
index 7b15c68..2ec61af 100755
@@ -41,9 +41,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 		pass "verbose mode produces output"
 
 		# Should contain initialization messages
-		if grep -q -E "(stalld|version|monitoring)" "${LOG_FILE}"; then
-			pass "output contains expected messages"
-		fi
+		assert_log_contains "${LOG_FILE}" "stalld\|version\|monitoring" "output contains expected messages"
 	else
 		fail "no output in verbose mode"
 	fi
tests/functional/test_starvation_detection.sh +6 -26
index 44024c7..2d2e9b1 100755
@@ -47,18 +47,10 @@ if wait_for_starvation_detected "${STALLD_LOG}"; then
     pass "Starvation detected"
 
     # Verify correct CPU is logged
-    if grep "starved on CPU ${TEST_CPU}" "${STALLD_LOG}"; then
-        pass "Correct CPU ID logged (CPU ${TEST_CPU})"
-    else
-        fail "Wrong CPU ID in log"
-    fi
+    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU}" "Correct CPU ID logged (CPU ${TEST_CPU})"
 
     # Verify duration is logged
-    if grep -E "starved on CPU ${TEST_CPU} for [0-9]+ seconds" "${STALLD_LOG}"; then
-        pass "Starvation duration logged"
-    else
-        fail "Starvation duration not logged"
-    fi
+    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU} for [0-9]" "Starvation duration logged"
 else
     fail "Starvation not detected"
     log "Log contents:"
@@ -249,15 +241,9 @@ sleep $((threshold + 3))
 
 # Verify this task was NOT reported as starved
 # Since it's making progress, stalld shouldn't detect it
-if ! grep "starved" "${STALLD_LOG}"; then
-    pass "No false positive - task making progress not reported as starved"
-else
-    if grep "${BUSY_PID}" "${STALLD_LOG}" | grep -q "starved"; then
-        fail "False positive - progress-making task ${BUSY_PID} reported as starved"
-    else
-        pass "No false positive - starvation detected from other tasks, not ours"
-    fi
-fi
+assert_log_contains --negate "${STALLD_LOG}" \
+    "${BUSY_PID}.*starved" \
+    "No false positive - progress-making task not reported as starved"
 
 kill ${BUSY_PID} 2>/dev/null
 wait ${BUSY_PID} 2>/dev/null
@@ -289,13 +275,7 @@ else
 fi
 
 # Check for error messages
-if grep -iE "error|segfault|crash" "${STALLD_LOG}"; then
-    fail "Error messages found in log"
-    log "Errors:"
-    grep -iE "error|segfault|crash" "${STALLD_LOG}"
-else
-    pass "No error messages in log"
-fi
+assert_log_contains --negate --ignore-case "${STALLD_LOG}" "error\|segfault\|crash" "No error messages in log"
 
 stop_stalld
 
tests/functional/test_starvation_threshold.sh +3 -7
index 79fa17b..b0eeb59 100755
@@ -70,13 +70,9 @@ wait "${STARVE_PID}" 2>/dev/null || true
 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_LOG}"; then
-    pass "No starvation detected for duration less than threshold"
-else
-    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_LOG}"
-fi
+assert_log_contains --negate "${STALLD_LOG}" \
+    "starvation_gen.*starved on CPU ${TEST_CPU}\|starved on CPU ${TEST_CPU}.*starvation_gen" \
+    "No starvation detected for duration less than threshold"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/helpers/test_helpers.sh +40 -1

53 lines not on this page · raw message

Signature · 1 line
2.54.0
#18 lore raw

PATCH 1/1 18/33] tests: Remove weak, redundant, and assertion-free test blocks

In reply to Wander Lairson Costa

Clean up the functional test suite by removing tests and code blocks that provide no meaningful verification, duplicate existing coverage, or contain no enforceable assertions.

Specific cleanups include:
* Remove tests lacking assertions: Drop blocks that log informational messages but pass on all branches, making them impossible to fail (e.g., in idle detection, FIFO comparison, and boost restoration).
* Consolidate duplicate coverage: Remove tests whose verification is already handled elsewhere, such as:
- Runqueue parsing (covered by test_starvation_detection and test_backend_selection).
- SCHED_FIFO restoration and deadline policy restoration.
- FIFO vs DEADLINE comparison and single-threaded mode rejection (already covered and fixed in test_force_fifo).
- Duplicate task merging tests.
* Remove dead and broken code: Drop the unused get_starved_pid() helper, broken relative paths, unused helper functions in idle detection, and manual task creation scripts that duplicated starvation_gen.
* Refactor redundant checks: Replace informative grep checks that follow wait_for_boost_detected with standard assert_boost_detected calls.

This consolidation reduces test suite execution time, removes buggy legacy code, and ensures all remaining test cases have clear, enforceable pass/fail criteria.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_affinity.sh             |   7 -
 tests/functional/test_boost_period.sh         |  13 +-
 tests/functional/test_boost_restoration.sh    | 177 +--------
 tests/functional/test_deadline_boosting.sh    |  82 +---
 tests/functional/test_fifo_boosting.sh        | 124 +-----
 tests/functional/test_force_fifo.sh           |  51 +--
 tests/functional/test_idle_detection.sh       | 111 +-----
 tests/functional/test_runqueue_parsing.sh     | 375 ------------------
 tests/functional/test_starvation_detection.sh |  81 +---
 tests/functional/test_task_merging.sh         |  53 +--
 10 files changed, 29 insertions(+), 1045 deletions(-)
 delete mode 100755 tests/functional/test_runqueue_parsing.sh
tests/functional/test_affinity.sh +0 -7
index 12b83c9..db832f0 100755
@@ -183,13 +183,6 @@ if [ "$num_cpus" -ge 2 ]; then
         log "⚠ WARNING: stalld affinity ($affinity) doesn't match requested (0)"
     fi
 
-    # Verify it's monitoring CPU 1 by checking logs
-    if grep -q "cpu 1" "${STALLD_LOG}" || grep -q "monitoring.*1" "${STALLD_LOG}"; then
-        log "ℹ INFO: stalld monitoring CPU 1 as requested"
-    else
-        log "ℹ INFO: CPU 1 monitoring not explicitly confirmed in logs"
-    fi
-
     stop_stalld
 else
     log "⊘ SKIP: Test 6 requires at least 2 CPUs"
tests/functional/test_boost_period.sh +1 -12
index bdbb03a..1bb4e12 100755
@@ -27,18 +27,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 starvation detection and boosting
-if wait_for_boost_detected "${STALLD_LOG}"; then
-    pass "Boosting occurred with default period"
-
-    # Try to find period value in logs
-    if grep -qi "period" "${STALLD_LOG}"; then
-        log "ℹ INFO: Period information found in logs"
-    fi
-else
-    fail "No boosting detected"
-    log "Log contents:"
-    cat "${STALLD_LOG}"
-fi
+assert_boost_detected "${STALLD_LOG}" "Boosting occurred with default period"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_boost_restoration.sh +4 -173

197 lines not on this page · raw message

tests/functional/test_deadline_boosting.sh +6 -76

103 lines not on this page · raw message

tests/functional/test_fifo_boosting.sh +2 -122

136 lines not on this page · raw message

tests/functional/test_force_fifo.sh +1 -50

63 lines not on this page · raw message

tests/functional/test_idle_detection.sh +6 -105

141 lines not on this page · raw message

tests/functional/test_runqueue_parsing.sh deleted +0 -375

375 lines not on this page · raw message

tests/functional/test_starvation_detection.sh +7 -74

114 lines not on this page · raw message

tests/functional/test_task_merging.sh +2 -51

66 lines not on this page · raw message

Signature · 1 line
2.54.0
#19 lore raw

PATCH 1/1 19/33] tests: Introduce and adopt assert_success() helper

In reply to Wander Lairson Costa

Multiple functional tests repeat a verbose pattern: running a command, checking its exit status, and conditionally calling pass, fail, or printing a warning. These if/else blocks are repeated dozens of times across the test suite. Furthermore, tests that only printed warnings on failure silently masked real test problems.

Introduce an assert_success() helper that encapsulates command execution, exit code evaluation, and standardized pass/fail reporting into a single function call. To support various test requirements, the helper includes a --negate flag for asserting expected command failures. Crucially, this helper unconditionally calls fail() on error, eliminating the weak warning-only pattern.

Adopt this new helper across the functional test suite. This refactor:
* Eliminates over 20 duplicated if/else blocks across the suite.
* Tightens previously weak assertions (e.g., in test_affinity) that only warned on failure to actual test failures.
* Flattens nested conditional logic into clear, sequential assertions (e.g., in test_fifo_priority_starvation).
* Removes redundant checks that were already covered by existing wait_for_boost_detected assertions.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_affinity.sh             | 44 +++----------------
 tests/functional/test_boost_restoration.sh    | 30 +++----------
 tests/functional/test_cpu_selection.sh        | 24 ++--------
 tests/functional/test_deadline_boosting.sh    | 14 +-----
 .../test_fifo_priority_starvation.sh          | 44 +++++--------------
 tests/functional/test_foreground.sh           | 18 ++------
 tests/functional/test_logging_destinations.sh |  6 +--
 tests/functional/test_pidfile.sh              | 24 ++--------
 tests/functional/test_starvation_detection.sh |  6 +--
 tests/helpers/test_helpers.sh                 | 34 +++++++++++++-
 10 files changed, 71 insertions(+), 173 deletions(-)
tests/functional/test_affinity.sh +7 -37
index db832f0..15a8756 100755
@@ -60,12 +60,7 @@ sleep 2
 default_affinity=$(check_affinity "${STALLD_PID}")
 log "ℹ INFO: Default affinity: $default_affinity"
 
-# Typically should be all CPUs
-if [ -n "$default_affinity" ]; then
-    pass "stalld has default affinity: $default_affinity"
-else
-    log "⚠ WARNING: Could not determine default affinity"
-fi
+assert_success "stalld has default affinity" test -n "$default_affinity"
 
 stop_stalld
 
@@ -79,11 +74,7 @@ sleep 2
 
 affinity=$(check_affinity "${STALLD_PID}")
 
-if [ "$affinity" = "0" ]; then
-    pass "stalld restricted to CPU 0"
-else
-    fail "stalld affinity ($affinity) doesn't match requested (0)"
-fi
+assert_success "stalld restricted to CPU 0" test "$affinity" = "0"
 
 stop_stalld
 
@@ -99,12 +90,7 @@ if [ "$num_cpus" -ge 4 ]; then
     affinity=$(check_affinity "${STALLD_PID}")
 
     # 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
-        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
-    fi
+    assert_success "stalld restricted to CPUs 0,2" test -n "$(echo "$affinity" | grep -E '^0,2$|^0-2$|^2,0$')"
 
     stop_stalld
 else
@@ -123,11 +109,7 @@ if [ "$num_cpus" -ge 4 ]; then
     affinity=$(check_affinity "${STALLD_PID}")
 
     # Accept various formats: "0-2", "0,1,2", etc.
-    if echo "$affinity" | grep -qE '0.*1.*2|0-2'; then
-        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
+    assert_success "stalld restricted to CPU range 0-2" test -n "$(echo "$affinity" | grep -E '0.*1.*2|0-2')"
 
     stop_stalld
 else
@@ -153,11 +135,7 @@ if [ "$num_cpus" -ge 2 ]; then
         log "ℹ INFO: Found $child_threads threads for stalld"
     fi
 
-    if [ "$affinity" = "$test_cpu" ]; then
-        pass "stalld process affinity set to CPU $test_cpu"
-    else
-        log "⚠ WARNING: stalld affinity ($affinity) doesn't exactly match CPU $test_cpu"
-    fi
+    assert_success "stalld process affinity set to CPU $test_cpu" test "$affinity" = "$test_cpu"
 
     stop_stalld
 else
@@ -177,11 +155,7 @@ if [ "$num_cpus" -ge 2 ]; then
 
     affinity=$(check_affinity "${STALLD_PID}")
 
-    if [ "$affinity" = "0" ]; then
-        pass "stalld affinity to CPU 0 while monitoring CPU 1"
-    else
-        log "⚠ WARNING: stalld affinity ($affinity) doesn't match requested (0)"
-    fi
+    assert_success "stalld affinity to CPU 0 while monitoring CPU 1" test "$affinity" = "0"
 
     stop_stalld
 else
@@ -212,11 +186,7 @@ sleep 3
 affinity_end=$(check_affinity "${STALLD_PID}")
 log "ℹ INFO: Affinity after 3s: $affinity_end"
 
-if [ "$affinity_start" = "$affinity_end" ]; then
-    pass "CPU affinity persisted over time"
-else
-    log "⚠ WARNING: CPU affinity changed (start: $affinity_start, end: $affinity_end)"
-fi
+assert_success "CPU affinity persisted over time" test "$affinity_start" = "$affinity_end"
 
 stop_stalld
 
tests/functional/test_boost_restoration.sh +5 -25
index 0a75bb7..32a5a02 100755
@@ -46,11 +46,7 @@ if [ -n "${tracked_pid}" ]; then
     initial_prio=$(get_sched_priority ${tracked_pid})
     log "Initial policy: ${initial_policy} (expected: 1=SCHED_FIFO), prio: ${initial_prio}"
 
-    if [ "$initial_policy" = "1" ]; then
-        pass "Initial policy is SCHED_FIFO"
-    else
-        log "⚠ WARNING: Initial policy is ${initial_policy}, not SCHED_FIFO (1)"
-    fi
+    assert_success "Initial policy is SCHED_FIFO" test "$initial_policy" = "1"
 
     # Wait for starvation detection and boosting
     log "Waiting for starvation detection and boost..."
@@ -61,11 +57,7 @@ if [ -n "${tracked_pid}" ]; then
         boosted_policy=$(get_sched_policy ${tracked_pid})
         log "Policy during boost: ${boosted_policy}"
 
-        if [ "$boosted_policy" = "6" ]; then
-            pass "Task boosted to SCHED_DEADLINE (6)"
-        else
-            log "ℹ INFO: Policy is ${boosted_policy} (may be between boost cycles)"
-        fi
+        assert_success "Task boosted to SCHED_DEADLINE (6)" test "$boosted_policy" = "6"
     fi
 
     # Wait for boost duration to complete
@@ -102,11 +94,7 @@ if [ -n "${tracked_pid}" ] && [ -f "/proc/${tracked_pid}/sched" ]; then
 
     if [ "$final_policy" = "1" ]; then
         pass "Policy restored to SCHED_FIFO (1)"
-        if [ "$final_prio" = "$initial_prio" ]; then
-            pass "Priority restored to ${initial_prio}"
-        else
-            log "⚠ INFO: Priority is ${final_prio} (initial was ${initial_prio})"
-        fi
+        assert_success "Priority restored to ${initial_prio}" test "$final_prio" = "$initial_prio"
     else
         log "ℹ INFO: Final policy is ${final_policy} (task may have exited)"
     fi
@@ -153,11 +141,7 @@ if [ -n "${tracked_pid}" ]; then
             final_policy=$(get_sched_policy ${tracked_pid})
             log "Policy after boost: ${final_policy}"
 
-            if [ "$final_policy" = "0" ]; then
-                pass "Policy restored to SCHED_OTHER (0)"
-            else
-                fail "Policy not restored to SCHED_OTHER (got ${final_policy})"
-            fi
+            assert_success "Policy restored to SCHED_OTHER (0)" test "$final_policy" = "0"
         else
             log "⚠ INFO: Task exited before restoration check"
         fi
@@ -199,11 +183,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     sleep 1
 
     # 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
-        pass "stalld handled task exit during boost gracefully"
-    else
-        fail "stalld crashed or exited after task died during boost"
-    fi
+    assert_success "stalld handled task exit during boost gracefully" kill -0 ${STALLD_PID}
 
 else
     log "⚠ WARNING: No boost detected in this test run"
tests/functional/test_cpu_selection.sh +4 -20
index ff5b879..11d9c45 100755
@@ -68,11 +68,7 @@ if [ "$num_cpus" -ge 4 ]; then
         cpu2_found=1
     fi
 
-    if [ "$cpu0_found" -eq 1 ] && [ "$cpu2_found" -eq 1 ]; then
-        pass "stalld monitoring CPUs 0 and 2"
-    else
-        fail "stalld not monitoring specified CPUs (0: $cpu0_found, 2: $cpu2_found)"
-    fi
+    assert_success "stalld monitoring CPUs 0 and 2" test "$cpu0_found" -eq 1 -a "$cpu2_found" -eq 1
 
     stop_stalld
 else
@@ -99,11 +95,7 @@ if [ "$num_cpus" -ge 4 ]; then
         cpu2_found=1
     fi
 
-    if [ "$cpu0_found" -eq 1 ] && [ "$cpu1_found" -eq 1 ] && [ "$cpu2_found" -eq 1 ]; then
-        pass "stalld monitoring CPUs 0-2"
-    else
-        fail "stalld not monitoring specified CPU range (0: $cpu0_found, 1: $cpu1_found, 2: $cpu2_found)"
-    fi
+    assert_success "stalld monitoring CPUs 0-2" test "$cpu0_found" -eq 1 -a "$cpu1_found" -eq 1 -a "$cpu2_found" -eq 1
 
     stop_stalld
 else
@@ -124,11 +116,7 @@ if [ "$num_cpus" -ge 6 ]; then
         fi
     done
 
-    if [ "$monitored_cpus" -eq 4 ]; then
-        pass "stalld monitoring combined CPU specification (0,2-4)"
-    else
-        fail "stalld not monitoring all specified CPUs (found $monitored_cpus/4)"
-    fi
+    assert_success "stalld monitoring combined CPU specification (0,2-4)" test "$monitored_cpus" -eq 4
 
     stop_stalld
 else
@@ -148,11 +136,7 @@ if [ "$num_cpus" -ge 2 ]; then
     start_stalld_with_log "${STALLD_LOG}" -f -v -c 0 -l -t 5
 
     # Check that CPU 1 is NOT being monitored
-    if ! is_cpu_monitored 1; then
-        pass "stalld not monitoring non-selected CPU 1"
-    else
-        fail "stalld appears to be monitoring CPU 1 when only CPU 0 selected"
-    fi
+    assert_success --negate "stalld not monitoring non-selected CPU 1" is_cpu_monitored 1
 
     stop_stalld
 fi
tests/functional/test_deadline_boosting.sh +1 -13
index 51bdc2f..5f10b48 100755
@@ -41,14 +41,6 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
 
     # Verify SCHED_DEADLINE was used
     assert_log_contains "${STALLD_LOG}" "SCHED_DEADLINE" "SCHED_DEADLINE boosting used (default)"
-
-    # Verify boost happened after threshold
-    # (starvation logged, then boosting)
-    if grep -q "starved" "${STALLD_LOG}"; then
-        pass "Starvation detected before boosting"
-    else
-        log "⚠ WARNING: No starvation message before boost"
-    fi
 else
     fail "No boosting detected"
     log "Log contents:"
@@ -146,11 +138,7 @@ if [ -n "${tracked_pid}" ]; then
 
     # Verify task made progress (context switches increased)
     ctxt_delta=$((ctxt_after - ctxt_before))
-    if [ ${ctxt_delta} -gt 0 ]; then
-        pass "Task made progress during boost (${ctxt_delta} context switches)"
-    else
-        fail "No progress during boost (${ctxt_delta} context switches)"
-    fi
+    assert_success "Task made progress during boost" test ${ctxt_delta} -gt 0
 else
     log "⚠ WARNING: Could not track starved task PID for progress verification"
 fi
tests/functional/test_fifo_priority_starvation.sh +12 -32

56 lines not on this page · raw message

tests/functional/test_foreground.sh +3 -15
index e07bee3..f5de736 100755
@@ -35,11 +35,7 @@ if assert_process_running "${STALLD_PID}" "stalld should be running"; then
 	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
-			pass "stalld daemonized (parent is not test shell)"
-		else
-			fail "stalld did not daemonize (parent is test shell)"
-		fi
+		assert_success "stalld daemonized (parent is not test shell)" test "${PARENT_PID}" != "$$"
 	fi
 fi
 
@@ -59,11 +55,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
-		pass "stalld did not daemonize with -f (parent is not init)"
-	else
-		fail "stalld daemonized even with -f flag"
-	fi
+	assert_success "stalld did not daemonize with -f (parent is not init)" test "${PARENT_PID}" != "1"
 fi
 
 stop_stalld
@@ -77,11 +69,7 @@ sleep 2
 if assert_process_running "${STALLD_PID}" "stalld should be running with -v"; then
 	PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
 
-	if [ "${PARENT_PID}" != "1" ]; then
-		pass "-v implies foreground mode"
-	else
-		fail "-v should imply foreground mode"
-	fi
+	assert_success "-v implies foreground mode" test "${PARENT_PID}" != "1"
 fi
 
 stop_stalld
tests/functional/test_logging_destinations.sh +1 -5
index 2ec61af..5a8cc5b 100755
@@ -149,11 +149,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
-		pass "combined logging produces output"
-	else
-		fail "no output with combined logging"
-	fi
+	assert_success "combined logging produces output" test -s "${LOG_FILE}"
 fi
 
 stop_stalld
tests/functional/test_pidfile.sh +4 -20
index 4509307..23c60e5 100755
@@ -34,11 +34,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
-            pass "Default pidfile contains correct PID"
-        else
-            fail "Default pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
-        fi
+        assert_success "Default pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
         break
     fi
 done
@@ -70,11 +66,7 @@ if [ -f "${custom_pidfile}" ]; then
 
     # Verify content
     pid_from_file=$(cat "${custom_pidfile}")
-    if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-        pass "Custom pidfile contains correct PID ($pid_from_file)"
-    else
-        fail "Custom pidfile PID ($pid_from_file) doesn't match stalld PID (${STALLD_PID})"
-    fi
+    assert_success "Custom pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
 else
     fail "Custom pidfile not created at ${custom_pidfile}"
 fi
@@ -107,11 +99,7 @@ if [ -f "${tmp_pidfile}" ]; then
     pass "Pidfile created in /tmp directory"
 
     pid_from_file=$(cat "${tmp_pidfile}")
-    if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-        pass "/tmp pidfile contains correct PID"
-    else
-        fail "/tmp pidfile has incorrect PID"
-    fi
+    assert_success "/tmp pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
 else
     fail "Pidfile not created in /tmp"
 fi
@@ -135,11 +123,7 @@ if [ -f "${fg_pidfile}" ]; then
     pass "Pidfile created in foreground mode"
 
     pid_from_file=$(cat "${fg_pidfile}")
-    if [ "$pid_from_file" = "${STALLD_PID}" ]; then
-        pass "Foreground mode pidfile contains correct PID"
-    else
-        fail "Foreground mode pidfile has incorrect PID"
-    fi
+    assert_success "Foreground mode pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
 else
     log "⚠ WARNING: Pidfile not created in foreground mode (may be expected)"
 fi
tests/functional/test_starvation_detection.sh +1 -5
index 1887d13..943503b 100755
@@ -83,11 +83,7 @@ if [ -n "${tracked_pid}" ]; then
     log "Context switches after 2s: ${ctxt_after}"
 
     ctxt_delta=$((ctxt_after - ctxt_before))
-    if [ ${ctxt_delta} -lt 5 ]; then
-        pass "Context switch count remained low (delta: ${ctxt_delta})"
-    else
-        fail "Context switches increased significantly (delta: ${ctxt_delta})"
-    fi
+    assert_success "Context switch count remained low" test ${ctxt_delta} -lt 5
 else
     log "⚠ WARNING: Could not find starved task PIDs to verify context switches"
 fi
tests/helpers/test_helpers.sh +33 -1
index 3807670..49292aa 100755
@@ -248,6 +248,38 @@ assert_stalld_rejects() {
 	rm -f "${log}"
 }
 
+# Assert that a command exits successfully (or unsuccessfully with --negate).
+#
+# Without --negate the assertion passes when the command returns zero.
+# With --negate the assertion passes when the command returns non-zero.
+#
+# Usage: assert_success <message> <command> [args...]
+#        assert_success --negate <message> <command> [args...]
+assert_success() {
+	local negate=0
+	if [ "$1" = "--negate" ]; then
+		negate=1
+		shift
+	fi
+	local message=$1
+	shift
+
+	"$@" >/dev/null 2>&1
+	local success=$(( $? == 0 ))
+
+	if [ $negate -eq 1 ]; then
+		success=$((1 - success))
+	fi
+
+	if [ $success -eq 1 ]; then
+		pass "${message}"
+		return 0
+	else
+		fail "${message}"
+		return 1
+	fi
+}
+
 # Record a test pass with a description message.
 #
 # Usage: pass "description"
@@ -1299,7 +1331,7 @@ start_starvation_gen() {
 
 # Export functions for use in tests
 export -f start_test end_test test_section cleanup_scenario find_starved_child
-export -f assert_starvation_detected assert_boost_detected assert_stalld_rejects assert_log_contains
+export -f assert_starvation_detected assert_boost_detected assert_stalld_rejects assert_log_contains assert_success
 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
Signature · 1 line
2.54.0
#20 lore raw

PATCH 1/1 20/33] tests: Replace wait conditionals with asserts

In reply to Wander Lairson Costa

Several test files currently use custom conditional blocks to wait for starvation or boost detection, manually handling success and failure logging. Replace these verbose conditionals with flat calls to the existing assert_starvation_detected and assert_boost_detected helpers. The previously nested log assertions are subsequently moved to the top level. The assert helpers provide identical behavior by passing on success or failing and dumping the log on error.

Flattening these blocks deduplicates logic already encapsulated in the test helpers and removes unnecessary nesting. This refactoring is a direct prerequisite for introducing fail-fast semantics to the test framework, where calling fail will immediately terminate the test.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_deadline_boosting.sh       | 12 ++----------
 tests/functional/test_fifo_boosting.sh           | 12 ++----------
 .../functional/test_fifo_priority_starvation.sh  | 16 +++-------------
 tests/functional/test_starvation_detection.sh    | 16 +++-------------
 4 files changed, 10 insertions(+), 46 deletions(-)
tests/functional/test_deadline_boosting.sh +2 -10
index 5f10b48..50e949c 100755
@@ -36,16 +36,8 @@ 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
-    pass "Boosting occurred"
-
-    # Verify SCHED_DEADLINE was used
-    assert_log_contains "${STALLD_LOG}" "SCHED_DEADLINE" "SCHED_DEADLINE boosting used (default)"
-else
-    fail "No boosting detected"
-    log "Log contents:"
-    cat "${STALLD_LOG}"
-fi
+assert_boost_detected "${STALLD_LOG}" "Boosting occurred"
+assert_log_contains "${STALLD_LOG}" "SCHED_DEADLINE" "SCHED_DEADLINE boosting used (default)"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_fifo_boosting.sh +2 -10
index 8f70c0a..4da1182 100755
@@ -34,16 +34,8 @@ 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
-    pass "Boosting occurred with -F flag"
-
-    # Verify SCHED_FIFO was used
-    assert_log_contains "${STALLD_LOG}" "SCHED_FIFO" "SCHED_FIFO boosting used (as requested by -F)"
-else
-    fail "No boosting detected with -F flag"
-    log "Log contents:"
-    cat "${STALLD_LOG}"
-fi
+assert_boost_detected "${STALLD_LOG}" "Boosting occurred with -F flag"
+assert_log_contains "${STALLD_LOG}" "SCHED_FIFO" "SCHED_FIFO boosting used (as requested by -F)"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_fifo_priority_starvation.sh +3 -13
index a8d3a31..a4e1a65 100755
@@ -40,19 +40,9 @@ 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
-    pass "FIFO-on-FIFO starvation detected"
-
-    # Verify correct CPU is logged
-    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU}" "Correct CPU ID logged (CPU ${TEST_CPU})"
-
-    # Verify duration is logged
-    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU} for [0-9]" "Starvation duration logged"
-else
-    fail "FIFO-on-FIFO starvation not detected"
-    log "Log contents:"
-    cat "${STALLD_LOG}"
-fi
+assert_starvation_detected "${STALLD_LOG}" "FIFO-on-FIFO starvation detected"
+assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU}" "Correct CPU ID logged (CPU ${TEST_CPU})"
+assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU} for [0-9]" "Starvation duration logged"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/functional/test_starvation_detection.sh +3 -13
index 943503b..1df6bb0 100755
@@ -35,19 +35,9 @@ 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
-    pass "Starvation detected"
-
-    # Verify correct CPU is logged
-    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU}" "Correct CPU ID logged (CPU ${TEST_CPU})"
-
-    # Verify duration is logged
-    assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU} for [0-9]" "Starvation duration logged"
-else
-    fail "Starvation not detected"
-    log "Log contents:"
-    cat "${STALLD_LOG}"
-fi
+assert_starvation_detected "${STALLD_LOG}" "Starvation detected"
+assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU}" "Correct CPU ID logged (CPU ${TEST_CPU})"
+assert_log_contains "${STALLD_LOG}" "starved on CPU ${TEST_CPU} for [0-9]" "Starvation duration logged"
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
Signature · 1 line
2.54.0
#21 lore raw

PATCH 1/1 21/33] tests: Remove if-wrappers around assert calls

In reply to Wander Lairson Costa

This is the second commit in a series introducing fail-fast semantics to the test suite. Currently, several test scripts wrap assertion calls in conditional blocks to guard subsequent execution steps.

Since the guarded test logic only makes sense when the initial assertion passes, these conditional wrappers create unnecessary nesting. Once the failure mechanism is updated to be immediately fatal in the next commit, these protective conditional structures will become completely redundant.

Removing the conditional structures and flattening the code flow across the foreground, logging, pidfile, and starvation test scripts improves overall readability and maintainability. Branches handling non-fatal conditions such as skips or informational warnings have been preserved in their original form.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_foreground.sh           | 36 +++++++----------
 tests/functional/test_logging_destinations.sh | 20 +++-------
 tests/functional/test_pidfile.sh              | 39 +++++--------------
 tests/functional/test_starvation_detection.sh |  7 +---
 4 files changed, 29 insertions(+), 73 deletions(-)
tests/functional/test_foreground.sh +13 -23
index f5de736..792aee0 100755
@@ -26,17 +26,13 @@ test_section "Test 1: stalld daemonizes by default"
 start_stalld -l -t 5
 sleep 2
 
-# Check if stalld is running
-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
-		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
-		assert_success "stalld daemonized (parent is not test shell)" test "${PARENT_PID}" != "$$"
-	fi
+assert_process_running "${STALLD_PID}" "stalld should be running"
+
+PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
+if [ "${PARENT_PID}" == "1" ] || [ "${PARENT_PID}" == "2" ]; then
+	pass "stalld daemonized (parent is init/kthreadd)"
+else
+	assert_success "stalld daemonized (parent is not test shell)" test "${PARENT_PID}" != "$$"
 fi
 
 stop_stalld
@@ -48,15 +44,10 @@ test_section "Test 2: stalld stays in foreground with -f"
 start_stalld -f -l -t 5
 sleep 2
 
-# Check if stalld is running
-if assert_process_running "${STALLD_PID}" "stalld should be running with -f"; then
-	# With -f, it should NOT daemonize, parent should be our shell
-	PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
+assert_process_running "${STALLD_PID}" "stalld should be running with -f"
 
-	# The parent might be the subshell from start_stalld, not directly our shell
-	# So we just verify it's not PID 1
-	assert_success "stalld did not daemonize with -f (parent is not init)" test "${PARENT_PID}" != "1"
-fi
+PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
+assert_success "stalld did not daemonize with -f (parent is not init)" test "${PARENT_PID}" != "1"
 
 stop_stalld
 
@@ -66,11 +57,10 @@ test_section "Test 3: -v implies foreground mode"
 start_stalld -v -l -t 5
 sleep 2
 
-if assert_process_running "${STALLD_PID}" "stalld should be running with -v"; then
-	PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
+assert_process_running "${STALLD_PID}" "stalld should be running with -v"
 
-	assert_success "-v implies foreground mode" test "${PARENT_PID}" != "1"
-fi
+PARENT_PID=$(ps -o ppid= -p ${STALLD_PID} 2>/dev/null | tr -d ' ')
+assert_success "-v implies foreground mode" test "${PARENT_PID}" != "1"
 
 stop_stalld
 
tests/functional/test_logging_destinations.sh +5 -15
index 5a8cc5b..a25e571 100755
@@ -35,17 +35,9 @@ CLEANUP_FILES+=("${LOG_FILE}")
 
 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
-		pass "verbose mode produces output"
-
-		# Should contain initialization messages
-		assert_log_contains "${LOG_FILE}" "stalld\|version\|monitoring" "output contains expected messages"
-	else
-		fail "no output in verbose mode"
-	fi
-fi
+assert_process_running "${STALLD_PID}" "stalld should be running"
+assert_success "verbose mode produces output" test -s "${LOG_FILE}"
+assert_log_contains "${LOG_FILE}" "stalld\|version\|monitoring" "output contains expected messages"
 
 stop_stalld
 
@@ -147,10 +139,8 @@ CLEANUP_FILES+=("${LOG_FILE}")
 
 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
-	assert_success "combined logging produces output" test -s "${LOG_FILE}"
-fi
+assert_process_running "${STALLD_PID}" "stalld with combined logging should be running"
+assert_success "combined logging produces output" test -s "${LOG_FILE}"
 
 stop_stalld
 
tests/functional/test_pidfile.sh +10 -29
index 23c60e5..75b5359 100755
@@ -60,16 +60,10 @@ log "Starting stalld with custom pidfile: ${custom_pidfile}"
 start_stalld -l -t 5 --pidfile "${custom_pidfile}"
 sleep 2
 
-# Verify pidfile was created
-if [ -f "${custom_pidfile}" ]; then
-    pass "Custom pidfile created at ${custom_pidfile}"
+assert_file_exists "${custom_pidfile}" "Custom pidfile created at ${custom_pidfile}"
 
-    # Verify content
-    pid_from_file=$(cat "${custom_pidfile}")
-    assert_success "Custom pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
-else
-    fail "Custom pidfile not created at ${custom_pidfile}"
-fi
+pid_from_file=$(cat "${custom_pidfile}")
+assert_success "Custom pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
 
 # Test 3: Verify pidfile removed on clean shutdown
 test_section "Test 3: Verify pidfile removed on clean shutdown"
@@ -95,14 +89,10 @@ log "Starting stalld with /tmp pidfile: ${tmp_pidfile}"
 start_stalld -l -t 5 --pidfile "${tmp_pidfile}"
 sleep 2
 
-if [ -f "${tmp_pidfile}" ]; then
-    pass "Pidfile created in /tmp directory"
+assert_file_exists "${tmp_pidfile}" "Pidfile created in /tmp directory"
 
-    pid_from_file=$(cat "${tmp_pidfile}")
-    assert_success "/tmp pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
-else
-    fail "Pidfile not created in /tmp"
-fi
+pid_from_file=$(cat "${tmp_pidfile}")
+assert_success "/tmp pidfile contains correct PID" test "$pid_from_file" = "${STALLD_PID}"
 
 stop_stalld
 
@@ -151,20 +141,11 @@ log "Starting stalld with readable pidfile: ${readable_pidfile}"
 start_stalld -l -t 5 --pidfile "${readable_pidfile}"
 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
-        pass "Pidfile is readable"
+assert_file_exists "${readable_pidfile}" "Pidfile created"
+assert_success "Pidfile is readable" cat "${readable_pidfile}"
 
-        # Check permissions
-        perms=$(stat -c "%a" "${readable_pidfile}" 2>/dev/null || stat -f "%Lp" "${readable_pidfile}" 2>/dev/null)
-        log "ℹ INFO: Pidfile permissions: $perms"
-    else
-        fail "Pidfile not readable"
-    fi
-else
-    fail "Pidfile not created"
-fi
+perms=$(stat -c "%a" "${readable_pidfile}" 2>/dev/null || stat -f "%Lp" "${readable_pidfile}" 2>/dev/null)
+log "ℹ INFO: Pidfile permissions: $perms"
 
 stop_stalld
 
tests/functional/test_starvation_detection.sh +1 -6
index 1df6bb0..1d43516 100755
@@ -186,12 +186,7 @@ start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 3
 # Wait for task to exit
 sleep 5
 
-# Verify stalld is still running (didn't crash)
-if assert_process_running "${STALLD_PID}" "stalld still running after task exit"; then
-    pass "stalld handled task exit gracefully"
-else
-    fail "stalld crashed or exited unexpectedly"
-fi
+assert_process_running "${STALLD_PID}" "stalld still running after task exit"
 
 # Check for error messages
 assert_log_contains --negate --ignore-case "${STALLD_LOG}" "error\|segfault\|crash" "No error messages in log"
Signature · 1 line
2.54.0
#22 lore raw

PATCH 1/1 22/33] tests: Abort immediately on test failure

In reply to Wander Lairson Costa

This is the third commit in a series introducing fail-fast semantics to the test suite. By modifying the fail helper to invoke end_test and exit immediately, tests will now abort on the first failure. This provides faster feedback and produces much shorter, more focused error logs rather than cascading failures.

To support exiting directly from the fail helper, diagnostic logging in seven assert functions is relocated to occur before the failure is triggered. Dead return statements are removed from all twelve assert functions since fail never returns and the return after pass is unnecessary. Additionally, the stalld rejection assertion now registers its temporary file in the cleanup tracking array to ensure trap-based exit handlers correctly reap the resource even when the test aborts prematurely.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/helpers/test_helpers.sh | 41 ++++++++++-------------------------
 1 file changed, 11 insertions(+), 30 deletions(-)
tests/helpers/test_helpers.sh +11 -30
index 49292aa..c51361f 100755
@@ -164,12 +164,10 @@ assert_starvation_detected() {
 
 	if wait_for_starvation_detected "${log_file}" "${timeout}" "${cpu}"; then
 		pass "${message}"
-		return 0
 	else
-		fail "${message}"
 		log "Log contents:"
 		cat "${log_file}"
-		return 1
+		fail "${message}"
 	fi
 }
 
@@ -182,12 +180,10 @@ assert_boost_detected() {
 
 	if wait_for_boost_detected "${log_file}" "${timeout}"; then
 		pass "${message}"
-		return 0
 	else
-		fail "${message}"
 		log "Log contents:"
 		cat "${log_file}"
-		return 1
+		fail "${message}"
 	fi
 }
 
@@ -217,15 +213,13 @@ assert_log_contains() {
 
 	if [ $found -eq 1 ]; then
 		pass "${message}"
-		return 0
 	else
-		fail "${message}"
 		if [ $negate -eq 1 ]; then
 			log "    Pattern '${pattern}' found in ${log_file} but should not be"
 		else
 			log "    Pattern '${pattern}' not found in ${log_file}"
 		fi
-		return 1
+		fail "${message}"
 	fi
 }
 
@@ -236,14 +230,15 @@ assert_stalld_rejects() {
 	shift
 
 	local log="/tmp/stalld_reject_$$.log"
+	CLEANUP_FILES+=("${log}")
 	timeout 5 ${TEST_ROOT}/../stalld ${BACKEND_FLAG} "$@" > "${log}" 2>&1
 	local ret=$?
 	if [ $ret -ne 0 ] && [ $ret -ne 124 ]; then
 		pass "${message}"
 	else
-		fail "${message}"
 		log "stalld output:"
 		cat "${log}"
+		fail "${message}"
 	fi
 	rm -f "${log}"
 }
@@ -273,10 +268,8 @@ assert_success() {
 
 	if [ $success -eq 1 ]; then
 		pass "${message}"
-		return 0
 	else
 		fail "${message}"
-		return 1
 	fi
 }
 
@@ -289,13 +282,15 @@ pass() {
 	TEST_PASSED=$((TEST_PASSED + 1))
 }
 
-# Record a test failure with a description message.
+# Record a test failure and abort the test immediately.
 #
 # Usage: fail "description"
 fail() {
 	local message=${1:-""}
 	log "✗ FAIL: ${message}"
 	TEST_FAILED=$((TEST_FAILED + 1))
+	end_test
+	exit 1
 }
 
 # Assert functions
@@ -306,12 +301,10 @@ assert_equals() {
 
 	if [ "${expected}" == "${actual}" ]; then
 		pass "${message}"
-		return 0
 	else
-		fail "${message}"
 		log "    Expected: ${expected}"
 		log "    Actual:   ${actual}"
-		return 1
+		fail "${message}"
 	fi
 }
 
@@ -322,11 +315,9 @@ assert_contains() {
 
 	if echo "${haystack}" | grep -q "${needle}"; then
 		pass "${message}"
-		return 0
 	else
-		fail "${message}"
 		log "    String '${needle}' not found"
-		return 1
+		fail "${message}"
 	fi
 }
 
@@ -337,11 +328,9 @@ assert_not_contains() {
 
 	if ! echo "${haystack}" | grep -q "${needle}"; then
 		pass "${message}"
-		return 0
 	else
-		fail "${message}"
 		log "    String '${needle}' found but should not be present"
-		return 1
+		fail "${message}"
 	fi
 }
 
@@ -351,10 +340,8 @@ assert_file_exists() {
 
 	if [ -f "${file}" ]; then
 		pass "${message}"
-		return 0
 	else
 		fail "${message}"
-		return 1
 	fi
 }
 
@@ -364,10 +351,8 @@ assert_file_not_exists() {
 
 	if [ ! -f "${file}" ]; then
 		pass "${message}"
-		return 0
 	else
 		fail "${message}"
-		return 1
 	fi
 }
 
@@ -377,10 +362,8 @@ assert_process_running() {
 
 	if kill -0 ${pid} 2>/dev/null; then
 		pass "${message}"
-		return 0
 	else
 		fail "${message}"
-		return 1
 	fi
 }
 
@@ -390,10 +373,8 @@ assert_process_not_running() {
 
 	if ! kill -0 ${pid} 2>/dev/null; then
 		pass "${message}"
-		return 0
 	else
 		fail "${message}"
-		return 1
 	fi
 }
 
Signature · 1 line
2.54.0
#23 lore raw

PATCH 1/1 23/33] tests: Remove dead code after making fail() fatal

In reply to Wander Lairson Costa

The previous commit making test failures abort immediately rendered several teardown and reporting mechanisms obsolete. Since any failing test now exits the script immediately, end-of-run test summaries reporting the total number of failures will always evaluate to zero during a successful run. These redundant summary banners and failure counts have been removed across all multi-section tests.

This cleanup also drops trailing completion messages and empty log lines that previously cluttered the test output. Unreachable cleanup steps and return statements situated directly after failure calls are now safely discarded. Furthermore, unconditional debug log dumps on success paths have been stripped to keep successful test runs quiet.

Finally, rotting references to specific source code line numbers and redundant backend limitation notes were purged. The relevant backend constraints remain appropriately documented inline where they apply.

Assisted-by Claude Code:claude-opus-4-6[1m] [PAL]
---
 tests/functional/test_affinity.sh                 |  3 ---
 tests/functional/test_backend_selection.sh        |  2 --
 tests/functional/test_boost_duration.sh           |  3 ---
 tests/functional/test_boost_period.sh             |  6 ------
 tests/functional/test_boost_restoration.sh        |  6 ------
 tests/functional/test_boost_runtime.sh            |  3 ---
 tests/functional/test_deadline_boosting.sh        |  6 ------
 tests/functional/test_fifo_boosting.sh            |  6 ------
 tests/functional/test_fifo_priority_starvation.sh | 13 -------------
 tests/functional/test_force_fifo.sh               |  3 ---
 tests/functional/test_idle_detection.sh           |  6 ------
 tests/functional/test_log_only.sh                 |  6 ------
 tests/functional/test_pidfile.sh                  |  3 ---
 tests/functional/test_starvation_detection.sh     |  6 ------
 tests/functional/test_starvation_threshold.sh     |  3 ---
 tests/functional/test_task_merging.sh             |  9 ---------
 16 files changed, 84 deletions(-)
tests/functional/test_affinity.sh +0 -3
index 15a8756..3faa199 100755
@@ -190,7 +190,4 @@ assert_success "CPU affinity persisted over time" test "$affinity_start" = "$aff
 
 stop_stalld
 
-log ""
-log "All affinity tests completed"
-
 end_test
tests/functional/test_backend_selection.sh +0 -2
index d65695b..26654e1 100755
@@ -40,8 +40,6 @@ test_backend_flag() {
 
 	if ! wait_for_stalld_ready "${log_file}" 15; then
 		fail "stalld failed to start (${description})"
-		stop_stalld
-		return 1
 	fi
 
 	assert_log_contains "${log_file}" "${expected_msg}" "${description}"
tests/functional/test_boost_duration.sh +0 -3
index aa46e84..3d6a823 100755
@@ -112,7 +112,4 @@ assert_stalld_rejects "Zero duration rejected with error" -f -v -t ${threshold}
 log "Testing with duration = -5"
 assert_stalld_rejects "Negative duration rejected with error" -f -v -t ${threshold} -d -5
 
-log ""
-log "All boost duration tests completed"
-
 end_test
tests/functional/test_boost_period.sh +0 -6
index 1bb4e12..4b76c6e 100755
@@ -106,10 +106,4 @@ test_section "Test 6: Invalid period value (negative)"
 
 assert_stalld_rejects "Negative period rejected with error" -f -v -t $threshold -p -1000000
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
 end_test
tests/functional/test_boost_restoration.sh +0 -6
index 32a5a02..ddf3940 100755
@@ -192,10 +192,4 @@ fi
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
 end_test
tests/functional/test_boost_runtime.sh +0 -3
index caaeb1d..dcfd7e7 100755
@@ -127,7 +127,4 @@ test_section "Test 7: Invalid runtime value (negative)"
 log "Testing with runtime = -5000"
 assert_stalld_rejects "Negative runtime rejected with error" -f -v -t ${threshold} -r -5000
 
-log ""
-log "All boost runtime tests completed"
-
 end_test
tests/functional/test_deadline_boosting.sh +0 -6
index 50e949c..094c146 100755
@@ -197,10 +197,4 @@ else
     cleanup_scenario "${STARVE_PID0}" "${STARVE_PID1}"
 fi
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
 end_test
tests/functional/test_fifo_boosting.sh +0 -6
index 4da1182..cabc740 100755
@@ -131,10 +131,4 @@ fi
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
 end_test
tests/functional/test_fifo_priority_starvation.sh +0 -13
index a4e1a65..072c951 100755
@@ -204,17 +204,4 @@ fi
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
-if [ -n "${STALLD_TEST_BACKEND}" ] && [ "${STALLD_TEST_BACKEND}" = "queue_track" ]; then
-    log ""
-    log "NOTE: queue_track backend has known limitations with SCHED_FIFO task detection."
-    log "      For reliable FIFO-on-FIFO testing, use the sched_debug backend:"
-    log "      ./test_fifo_priority_starvation.sh -b sched_debug"
-fi
-
 end_test
tests/functional/test_force_fifo.sh +0 -3
index d2a6281..aa66f77 100755
@@ -112,7 +112,4 @@ test_section "Test 5: Single-threaded mode with FIFO (should fail)"
 log "Testing single-threaded mode (-O) with -F (should exit)"
 assert_stalld_rejects "Single-threaded mode rejected FIFO" -f -v -c "${TEST_CPU}" -t ${threshold} -F -O
 
-log ""
-log "All force FIFO tests completed"
-
 end_test
tests/functional/test_idle_detection.sh +0 -6
index 20dfea6..81a9fe1 100755
@@ -117,10 +117,4 @@ else
     cleanup_scenario "${STARVE_PID}"
 fi
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
 end_test
tests/functional/test_log_only.sh +0 -6
index 6a26b93..824ef7a 100755
@@ -66,10 +66,4 @@ assert_log_contains --negate "${LOG_FILE}" "boosted" "stalld did not boost in lo
 # Cleanup
 cleanup_scenario "${STARVGEN_PID}"
 
-echo ""
-echo "Log file contents:"
-echo "=================="
-cat "${LOG_FILE}"
-echo "=================="
-
 end_test
tests/functional/test_pidfile.sh +0 -3
index 75b5359..a2e5eb0 100755
@@ -149,7 +149,4 @@ log "ℹ INFO: Pidfile permissions: $perms"
 
 stop_stalld
 
-log ""
-log "All pidfile tests completed"
-
 end_test
tests/functional/test_starvation_detection.sh +0 -6
index 1d43516..17d7fe0 100755
@@ -193,10 +193,4 @@ assert_log_contains --negate --ignore-case "${STALLD_LOG}" "error\|segfault\|cra
 
 stop_stalld
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Total failures: ${TEST_FAILED}"
-
 end_test
tests/functional/test_starvation_threshold.sh +0 -3
index b0eeb59..6e84966 100755
@@ -115,7 +115,4 @@ assert_stalld_rejects "Zero threshold rejected with error" -f -v -t 0
 log "Testing with threshold = -5"
 assert_stalld_rejects "Negative threshold rejected with error" -f -v -t -5
 
-log ""
-log "All starvation threshold tests completed"
-
 end_test
tests/functional/test_task_merging.sh +0 -9
index 434114b..5e03622 100755
@@ -226,13 +226,4 @@ else
     cleanup_scenario "${STARVE_PID0}" "${STARVE_PID1}"
 fi
 
-#=============================================================================
-# Final Summary
-#=============================================================================
-test_section "Test Summary"
-log "Task merging function: merge_tasks_info() in stalld.c:370-397"
-log "Merge logic: if (PID == PID && ctxsw == ctxsw) preserve timestamp"
-log ""
-log "Total failures: ${TEST_FAILED}"
-
 end_test
Signature · 1 line
2.54.0
#24 lore raw

PATCH 1/1 25/33] tests: Extract wait_for_process_exit helper

In reply to Wander Lairson Costa

The SIGTERM-poll-SIGKILL-poll termination sequence is currently duplicated in both stop_stalld() and cleanup() with identical inline polling loops. This refactors the teardown sequence by extracting the polling logic into a new wait_for_process_exit() helper.

The new helper polls process_alive() at 0.1 second intervals, returning 0 on successful exit or 1 upon timeout. Utilizing this helper eliminates the duplicated logic, removes a redundant sleep in cleanup(), and accelerates test teardown by checking process state more frequently than the previous one second intervals, all while maintaining the existing five second total timeout. Furthermore, stop_stalld() is simplified with an early return guard to reduce nesting. The helper is explicitly exported to ensure availability across the test environment.

---
 tests/helpers/test_helpers.sh | 85 +++++++++++++++++------------------
 1 file changed, 42 insertions(+), 43 deletions(-)
tests/helpers/test_helpers.sh +42 -43
index 7159c19..5187f0f 100755
@@ -360,6 +360,28 @@ process_alive() {
 	kill -0 "$1" 2>/dev/null
 }
 
+# Poll for a process to exit within a timeout.
+# Returns 0 if the process exits, 1 if still alive after timeout.
+#
+# Usage: wait_for_process_exit <pid> <timeout_seconds>
+wait_for_process_exit() {
+	local pid="$1"
+	local timeout="$2"
+	local elapsed=0
+
+	while process_alive "${pid}" && [ ${elapsed} -lt ${timeout} ]; do
+		for i in $(seq 1 10); do
+			sleep 0.1
+			if ! process_alive "${pid}"; then
+				return 0
+			fi
+		done
+		elapsed=$((elapsed + 1))
+	done
+
+	! process_alive "${pid}"
+}
+
 send_signal() {
 	kill -"$1" "$2" 2>/dev/null || true
 }
@@ -537,32 +559,23 @@ start_stalld() {
 # 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 process_alive ${STALLD_PID}; then
-			# Try graceful shutdown first (SIGTERM)
-			send_signal TERM ${STALLD_PID}
-
-			# Poll for graceful exit (up to 5 seconds)
-			local timeout=5
-			local elapsed=0
-			while process_alive ${STALLD_PID} && [ ${elapsed} -lt ${timeout} ]; do
-				sleep 1
-				elapsed=$((elapsed + 1))
-			done
+	if [ -z "${STALLD_PID}" ]; then
+		return
+	fi
+
+	if process_alive ${STALLD_PID}; then
+		# Try graceful shutdown first (SIGTERM)
+		send_signal TERM ${STALLD_PID}
 
+		# Poll for graceful exit (up to 5 seconds)
+		if ! wait_for_process_exit ${STALLD_PID} 5; then
 			# Escalate to SIGKILL if still running
-			if process_alive ${STALLD_PID}; then
-				send_signal KILL ${STALLD_PID}
-				# Poll for forced termination (up to 5 seconds)
-				elapsed=0
-				while process_alive ${STALLD_PID} && [ ${elapsed} -lt ${timeout} ]; do
-					sleep 1
-					elapsed=$((elapsed + 1))
-				done
-			fi
+			send_signal KILL ${STALLD_PID}
+			# Poll for forced termination (up to 5 seconds)
+			wait_for_process_exit ${STALLD_PID} 5
 		fi
-		STALLD_PID=""
 	fi
+	STALLD_PID=""
 }
 
 # Kill any existing stalld processes (cleanup from previous runs)
@@ -607,31 +620,17 @@ cleanup() {
 	# Stop stalld
 	stop_stalld
 
-	# Small delay to let processes terminate
-	sleep 0.2
-
 	# Kill any tracked processes
 	# Use SIGKILL (-9) and ignore EPERM errors (process may have different privileges)
 	for pid in "${CLEANUP_PIDS[@]}"; do
 		if [ -n "${pid}" ] && [ "${pid}" -gt 0 ] 2>/dev/null; then
 			# Check if process exists
-			if process_alive ${pid}; then
-				send_signal TERM ${pid}
-
-				local timeout=5
-				local elapsed=0
-				while process_alive ${pid} && [ ${elapsed} -lt ${timeout} ]; do
-					sleep 1
-					elapsed=$((elapsed + 1))
-				done
-
-				if process_alive ${pid}; then
-					send_signal KILL ${pid}
-					elapsed=0
-					while process_alive ${pid} && [ ${elapsed} -lt ${timeout} ]; do
-						sleep 1
-						elapsed=$((elapsed + 1))
-					done
+			if process_alive "${pid}"; then
+				send_signal TERM "${pid}"
+
+				if ! wait_for_process_exit "${pid}" 5; then
+					send_signal KILL "${pid}"
+					wait_for_process_exit "${pid}" 5
 				fi
 			fi
 		fi
@@ -1323,7 +1322,7 @@ export -f start_test end_test test_section cleanup_scenario find_starved_child
 export -f assert_starvation_detected assert_boost_detected assert_stalld_rejects assert_log_contains assert_success
 export -f pass fail assert_equals assert_contains assert_not_contains
 export -f assert_file_exists assert_file_not_exists
-export -f process_alive send_signal assert_process_running assert_process_not_running
+export -f process_alive wait_for_process_exit send_signal 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 wait_for_starvation_detected wait_for_boost_detected wait_for_n_log_matches
 export -f get_thread_policy get_thread_priority
Signature · 1 line
2.54.0
#25 lore raw

PATCH 1/1 24/33] tests: Introduce and adopt process helpers

In reply to Wander Lairson Costa

Raw kill invocations scattered across the test suite obscure the intent of the code. Using kill with signal zero acts as a liveness check rather than signal delivery, while best-effort process cleanup often requires ignoring errors.

This change introduces two dedicated helper functions to make these patterns self-documenting. The process_alive function wraps the zero signal check and preserves the exit code for use in conditionals and loops. The send_signal function wraps standard signal delivery using named signals like TERM or KILL, operating in a fire-and-forget manner where failure is acceptable. These functions are kept separate to distinguish predicates from cleanup actions, and both are exported via export -f for availability in subshells.

Existing scripts including test_helpers.sh, test_boost_restoration.sh, run_tests.sh, and test_starvation_detection.sh are updated to use the new helpers in place of direct kill invocations.

---
 tests/functional/test_boost_restoration.sh    |  2 +-
 tests/functional/test_starvation_detection.sh |  2 +-
 tests/helpers/test_helpers.sh                 | 60 +++++++++++--------
 tests/run_tests.sh                            |  6 +-
 4 files changed, 39 insertions(+), 31 deletions(-)
tests/functional/test_boost_restoration.sh +1 -1
index ddf3940..62c37b0 100755
@@ -183,7 +183,7 @@ if wait_for_boost_detected "${STALLD_LOG}"; then
     sleep 1
 
     # Verify stalld is still running and didn't crash after task exit
-    assert_success "stalld handled task exit during boost gracefully" kill -0 ${STALLD_PID}
+    assert_success "stalld handled task exit during boost gracefully" process_alive ${STALLD_PID}
 
 else
     log "⚠ WARNING: No boost detected in this test run"
tests/functional/test_starvation_detection.sh +1 -1
index 17d7fe0..9362899 100755
@@ -164,7 +164,7 @@ assert_log_contains --negate "${STALLD_LOG}" \
     "${BUSY_PID}.*starved" \
     "No false positive - progress-making task not reported as starved"
 
-kill ${BUSY_PID} 2>/dev/null
+send_signal TERM ${BUSY_PID}
 wait ${BUSY_PID} 2>/dev/null
 
 stop_stalld
tests/helpers/test_helpers.sh +34 -26

166 lines not on this page · raw message

tests/run_tests.sh +3 -3
index 63d0624..1fbb3f2 100755
@@ -224,7 +224,7 @@ cleanup_runner() {
 		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
+				send_signal KILL ${pid}
 			done
 		fi
 
@@ -250,14 +250,14 @@ kill_existing_stalld_processes() {
 	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
+			send_signal TERM ${pid}
 		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
+				send_signal KILL ${pid}
 			done
 			sleep 0.2
 		fi
Signature · 1 line
2.54.0
#26 lore raw

PATCH 1/1 26/33] tests: Reduce default wait timeouts

In reply to Wander Lairson Costa

Failing tests currently hang for up to thirty seconds before reporting a failure. Reduce the default wait timeouts in the stalld test helpers to cut this failure reporting time in half. These timeouts only impact the failure path since passing tests utilize event-driven waits that return immediately upon matching a pattern.

Decrease the timeout from thirty to fifteen seconds for the starvation and boost detection waits, their assertion wrappers, and the log match helper. Reduce the stalld ready wait from fifteen to five seconds. The new fifteen second limit provides adequate margin because all callers relying on the default use thresholds of three to five seconds, making detection take approximately nine seconds.

Override the timeout explicitly to twenty seconds for the third test in test_boost_duration.sh. This specific caller uses a threshold of ten, which makes the new fifteen second default too tight.

---
 tests/functional/test_boost_duration.sh |  2 +-
 tests/helpers/test_helpers.sh           | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)
tests/functional/test_boost_duration.sh +1 -1
index 3d6a823..821dffe 100755
@@ -74,7 +74,7 @@ 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 actual boosting
-assert_boost_detected "${STALLD_LOG}" "Boost with ${long_duration}s duration"
+assert_boost_detected "${STALLD_LOG}" "Boost with ${long_duration}s duration" 20
 
 # Cleanup
 cleanup_scenario "${STARVE_PID}"
tests/helpers/test_helpers.sh +6 -6
index 5187f0f..af341e0 100755
@@ -159,7 +159,7 @@ find_starved_child() {
 assert_starvation_detected() {
 	local log_file=$1
 	local message=${2:-"Starvation detected"}
-	local timeout=${3:-30}
+	local timeout=${3:-15}
 	local cpu=${4:-}
 
 	if wait_for_starvation_detected "${log_file}" "${timeout}" "${cpu}"; then
@@ -176,7 +176,7 @@ assert_starvation_detected() {
 assert_boost_detected() {
 	local log_file=$1
 	local message=${2:-"Boost detected"}
-	local timeout=${3:-30}
+	local timeout=${3:-15}
 
 	if wait_for_boost_detected "${log_file}" "${timeout}"; then
 		pass "${message}"
@@ -695,7 +695,7 @@ wait_for_log_message() {
 # Usage: wait_for_stalld_ready <log_file> [timeout]
 wait_for_stalld_ready() {
 	local log_file=$1
-	local timeout=${2:-15}
+	local timeout=${2:-5}
 	wait_for_log_message "checking cpu\|waiting tasks\|skipping" "${timeout}" "${log_file}"
 }
 
@@ -704,7 +704,7 @@ wait_for_stalld_ready() {
 # Usage: wait_for_starvation_detected <log_file> [timeout] [cpu]
 wait_for_starvation_detected() {
 	local log_file=$1
-	local timeout=${2:-30}
+	local timeout=${2:-15}
 	local cpu=${3:-}
 	local pattern="starved on CPU"
 	if [ -n "${cpu}" ]; then
@@ -718,7 +718,7 @@ wait_for_starvation_detected() {
 # Usage: wait_for_boost_detected <log_file> [timeout]
 wait_for_boost_detected() {
 	local log_file=$1
-	local timeout=${2:-30}
+	local timeout=${2:-15}
 	wait_for_log_message "boosted pid" "${timeout}" "${log_file}"
 }
 
@@ -729,7 +729,7 @@ wait_for_n_log_matches() {
 	local pattern=$1
 	local count=$2
 	local log_file=$3
-	local timeout=${4:-30}
+	local timeout=${4:-15}
 	local end=$((SECONDS + timeout))
 
 	while [ $SECONDS -lt $end ]; do
Signature · 1 line
2.54.0
#27 lore raw

PATCH 1/1 27/33] tests: Reduce starvation_gen durations

In reply to Wander Lairson Costa

Reduce starvation_gen durations across eight functional test files to eliminate unnecessary wait times. The generator runs for its full duration regardless of whether test assertions have already completed, meaning the previous literal values of 20 and 15 seconds wasted up to 10 seconds per test section. Across the full suite, this accumulated into significant idle time.

With a typical threshold of 5 seconds, the generator only needs to run long enough for detection plus a safety margin. Applying a formula of threshold plus boost duration plus a 4-second margin yields an optimal 12 seconds for standard tests. For detection-only tests lacking a boost, a 10-second duration provides ample margin.

Update seven instances of 20-second durations to 12 seconds and nine instances of 15-second durations to 10 seconds, along with two corresponding log messages. Values already optimized at 12 or 3 seconds, as well as variable-based durations computed directly from thresholds, remain unchanged since they already scale proportionally.

---
 tests/functional/test_boost_duration.sh           | 4 ++--
 tests/functional/test_boost_restoration.sh        | 4 ++--
 tests/functional/test_deadline_boosting.sh        | 8 ++++----
 tests/functional/test_fifo_boosting.sh            | 4 ++--
 tests/functional/test_fifo_priority_starvation.sh | 4 ++--
 tests/functional/test_log_only.sh                 | 4 ++--
 tests/functional/test_starvation_detection.sh     | 2 +-
 tests/functional/test_task_merging.sh             | 6 +++---
 8 files changed, 18 insertions(+), 18 deletions(-)
tests/functional/test_boost_duration.sh +2 -2
index 821dffe..d754977 100755
@@ -92,8 +92,8 @@ log "Starting stalld with ${threshold}s threshold and ${duration}s boost duratio
 start_stalld_with_log "${STALLD_LOG}" -f -v -c "${TEST_CPU}" -a ${STALLD_CPU} -t ${threshold} -d ${duration}
 
 # 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
+log "Creating starvation on CPU ${TEST_CPU} for 10s"
+start_starvation_gen -c "${TEST_CPU}" -p 80 -n 1 -d 10
 
 # Wait for actual boosting
 assert_boost_detected "${STALLD_LOG}" "Boost with ${duration}s boost duration"
tests/functional/test_boost_restoration.sh +2 -2
index 62c37b0..6a71644 100755
@@ -33,7 +33,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${ST
 
 # 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)"
-start_starvation_gen -c ${TEST_CPU} -p 80 -b 1 -n 1 -d 20
+start_starvation_gen -c ${TEST_CPU} -p 80 -b 1 -n 1 -d 12
 
 # Find the starved task
 tracked_pid=$(find_starved_child "${STARVE_PID}")
@@ -120,7 +120,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -t $threshold -c ${TEST_CPU} -a ${ST
 
 # Use -o flag to create SCHED_OTHER blockees
 log "Creating SCHED_OTHER starvation (RT blocker prio 80, SCHED_OTHER blockee)"
-start_starvation_gen -c ${TEST_CPU} -p 80 -o -n 1 -d 20
+start_starvation_gen -c ${TEST_CPU} -p 80 -o -n 1 -d 12
 
 # Find the starved SCHED_OTHER task
 tracked_pid=$(find_starved_child "${STARVE_PID}")
tests/functional/test_deadline_boosting.sh +4 -4
index 094c146..305c641 100755
@@ -64,7 +64,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 10
 
 # Try to find the boosted task PID before it gets boosted
 tracked_pid=$(find_starved_child "${STARVE_PID}")
@@ -106,7 +106,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 12
 
 # Find a starved task before it gets boosted
 tracked_pid=$(find_starved_child "${STARVE_PID}")
@@ -165,12 +165,12 @@ else
 
     # Create starvation on CPU0
     log "Creating starvation on CPU ${CPU0}"
-    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 15
+    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 10
     STARVE_PID0=${STARVE_PID}
 
     # Create starvation on CPU1
     log "Creating starvation on CPU ${CPU1}"
-    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 15
+    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 10
     STARVE_PID1=${STARVE_PID}
 
     # Wait for boosting on both CPUs
tests/functional/test_fifo_boosting.sh +2 -2
index cabc740..57c473d 100755
@@ -50,7 +50,7 @@ rm -f "${STALLD_LOG}"
 
 # Create starvation FIRST
 log "Creating starvation on CPU ${TEST_CPU}"
-start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 10
 tracked_pid=$(find_starved_child "${STARVE_PID}")
 
 log "Starting stalld with -F flag (FIFO boosting)"
@@ -103,7 +103,7 @@ rm -f "${STALLD_LOG}"
 
 # Create starvation FIRST
 log "Creating starvation on CPU ${TEST_CPU}"
-start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 12
 
 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}
tests/functional/test_fifo_priority_starvation.sh +2 -2
index 072c951..66075d4 100755
@@ -58,7 +58,7 @@ threshold=5
 boost_duration=3
 
 log "Creating FIFO-on-FIFO starvation on CPU ${TEST_CPU}"
-start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 1 -d 20
+start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 1 -d 12
 
 # Find the starved task (blockee) PID
 blockee_pid=$(find_starved_child "${STARVE_PID}")
@@ -180,7 +180,7 @@ rm -f "${STALLD_LOG}"
 threshold=5
 
 log "Creating FIFO-on-FIFO starvation"
-start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d 20 -v
+start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d 12 -v
 
 # Extract blocker and blockee PIDs from starvation_gen output
 # The output shows "Blocker TID: <pid>" and "Blockee N TID: <pid>"
tests/functional/test_log_only.sh +2 -2
index 824ef7a..b38db70 100755
@@ -44,10 +44,10 @@ echo "Stalld will run on CPU ${STALLD_CPU}"
 LOG_FILE="/tmp/stalld_test_log_only_$$.log"
 CLEANUP_FILES+=("${LOG_FILE}")
 
-echo "Creating starvation on CPU ${TEST_CPU} (will run for 15 seconds)"
+echo "Creating starvation on CPU ${TEST_CPU} (will run for 10 seconds)"
 
 # Start starvation generator BEFORE stalld to ensure CPU is busy from the start
-start_starvation_gen -c ${TEST_CPU} -p 10 -n 1 -d 15
+start_starvation_gen -c ${TEST_CPU} -p 10 -n 1 -d 10
 STARVGEN_PID=${STARVE_PID}
 
 # Start stalld in log-only mode with verbose output to capture logs
tests/functional/test_starvation_detection.sh +1 -1
index 9362899..33bbfed 100755
@@ -52,7 +52,7 @@ threshold=5
 
 # Create starvation
 log "Creating starvation on CPU ${TEST_CPU}"
-start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 15
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 10
 
 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}
tests/functional/test_task_merging.sh +3 -3
index 5e03622..082f07d 100755
@@ -101,7 +101,7 @@ start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -l -t $threshold -c ${TEST_CPU}
 
 # Create starvation
 log "Creating starvation"
-start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 20
+start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 12
 
 # Wait for starvation detection
 wait_for_starvation_detected "${STALLD_LOG}"
@@ -174,11 +174,11 @@ else
 
     # Create starvation on both CPUs
     log "Creating starvation on CPU ${CPU0}"
-    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 15
+    start_starvation_gen -c ${CPU0} -p 80 -n 1 -d 10
     STARVE_PID0=${STARVE_PID}
 
     log "Creating starvation on CPU ${CPU1}"
-    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 15
+    start_starvation_gen -c ${CPU1} -p 80 -n 1 -d 10
     STARVE_PID1=${STARVE_PID}
 
     # Wait for starvation detection on both CPUs
Signature · 1 line
2.54.0
#28 lore raw

PATCH 1/1 29/33] tests: Drop redundant sleeps in test_pidfile

In reply to Wander Lairson Costa

Remove redundant sleep calls following start_stalld in the pidfile tests. The start_stalld helper already polls for readiness internally before returning. It waits for the pidfile to appear when --pidfile is passed, and uses pgrep retries otherwise. The additional sleeps were dead wait time and unnecessarily slowed down test execution.

---
 tests/functional/test_pidfile.sh | 7 -------
 1 file changed, 7 deletions(-)
tests/functional/test_pidfile.sh +0 -7
index a2e5eb0..b5e1159 100755
@@ -22,9 +22,6 @@ test_section "Test 1: Default behavior (no -P specified)"
 
 start_stalld -l -t 5
 
-# Give stalld time to create pidfile
-sleep 2
-
 # Check common default pidfile locations
 default_found=0
 for pidfile in /var/run/stalld.pid /run/stalld.pid; do
@@ -58,7 +55,6 @@ rm -f "${custom_pidfile}"
 
 log "Starting stalld with custom pidfile: ${custom_pidfile}"
 start_stalld -l -t 5 --pidfile "${custom_pidfile}"
-sleep 2
 
 assert_file_exists "${custom_pidfile}" "Custom pidfile created at ${custom_pidfile}"
 
@@ -87,7 +83,6 @@ rm -f "${tmp_pidfile}"
 
 log "Starting stalld with /tmp pidfile: ${tmp_pidfile}"
 start_stalld -l -t 5 --pidfile "${tmp_pidfile}"
-sleep 2
 
 assert_file_exists "${tmp_pidfile}" "Pidfile created in /tmp directory"
 
@@ -107,7 +102,6 @@ rm -f "${fg_pidfile}"
 
 log "Starting stalld in foreground mode with pidfile: ${fg_pidfile}"
 start_stalld -f -v -l -t 5 --pidfile "${fg_pidfile}"
-sleep 2
 
 if [ -f "${fg_pidfile}" ]; then
     pass "Pidfile created in foreground mode"
@@ -139,7 +133,6 @@ rm -f "${readable_pidfile}"
 
 log "Starting stalld with readable pidfile: ${readable_pidfile}"
 start_stalld -l -t 5 --pidfile "${readable_pidfile}"
-sleep 2
 
 assert_file_exists "${readable_pidfile}" "Pidfile created"
 assert_success "Pidfile is readable" cat "${readable_pidfile}"
Signature · 1 line
2.54.0
#29 lore raw

PATCH 1/1 28/33] tests: Replace init sleeps in test_affinity

In reply to Wander Lairson Costa

Replace start_stalld followed by a fixed two-second sleep with start_stalld_with_log in test_affinity.sh. The helper uses event-driven wait_for_stalld_ready internally, returning as soon as the daemon logs its first monitoring message rather than waiting a fixed interval. Since stalld typically initializes in under half a second, this saves approximately nine seconds across the six modified test sections.

---
 tests/functional/test_affinity.sh | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
tests/functional/test_affinity.sh +12 -12
index 3faa199..43ab912 100755
@@ -54,8 +54,8 @@ fi
 #=============================================================================
 test_section "Test 1: Default behavior (no affinity restriction)"
 
-start_stalld -f -v -l -t 5
-sleep 2
+rm -f "${STALLD_LOG}"
+start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5
 
 default_affinity=$(check_affinity "${STALLD_PID}")
 log "ℹ INFO: Default affinity: $default_affinity"
@@ -69,8 +69,8 @@ stop_stalld
 #=============================================================================
 test_section "Test 2: Single CPU affinity (-a 0)"
 
-start_stalld -f -v -l -t 5 -a 0
-sleep 2
+rm -f "${STALLD_LOG}"
+start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5 -a 0
 
 affinity=$(check_affinity "${STALLD_PID}")
 
@@ -84,8 +84,8 @@ stop_stalld
 test_section "Test 3: Multi-CPU affinity (-a 0,2)"
 
 if [ "$num_cpus" -ge 4 ]; then
-    start_stalld -f -v -l -t 5 -a 0,2
-    sleep 2
+    rm -f "${STALLD_LOG}"
+    start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5 -a 0,2
 
     affinity=$(check_affinity "${STALLD_PID}")
 
@@ -103,8 +103,8 @@ fi
 test_section "Test 4: CPU range affinity (-a 0-2)"
 
 if [ "$num_cpus" -ge 4 ]; then
-    start_stalld -f -v -l -t 5 -a 0-2
-    sleep 2
+    rm -f "${STALLD_LOG}"
+    start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5 -a 0-2
 
     affinity=$(check_affinity "${STALLD_PID}")
 
@@ -123,8 +123,8 @@ test_section "Test 5: Verify stalld threads run on specified CPU"
 
 if [ "$num_cpus" -ge 2 ]; then
     test_cpu=1
-    start_stalld -f -v -l -t 5 -a "$test_cpu"
-    sleep 2
+    rm -f "${STALLD_LOG}"
+    start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5 -a "$test_cpu"
 
     # Check affinity
     affinity=$(check_affinity "${STALLD_PID}")
@@ -174,8 +174,8 @@ assert_stalld_rejects "Invalid CPU affinity rejected with error" -f -v -l -t 5 -
 #=============================================================================
 test_section "Test 8: Verify affinity persists over time"
 
-start_stalld -f -v -l -t 5 -a 0
-sleep 2
+rm -f "${STALLD_LOG}"
+start_stalld_with_log "${STALLD_LOG}" -f -v -l -t 5 -a 0
 
 affinity_start=$(check_affinity "${STALLD_PID}")
 log "ℹ INFO: Initial affinity: $affinity_start"
Signature · 1 line
2.54.0
#30 lore raw

PATCH 1/1 30/33] tests: Remove redundant sleeps after start_stalld

In reply to Wander Lairson Costa

Remove redundant sleep commands from test_foreground.sh and test_logging_destinations.sh. The start_stalld function already polls for readiness before returning, rendering these post-return delays unnecessary.

---
 tests/functional/test_foreground.sh           | 3 ---
 tests/functional/test_logging_destinations.sh | 1 -
 2 files changed, 4 deletions(-)
tests/functional/test_foreground.sh +0 -3
index 792aee0..7dab793 100755
@@ -24,7 +24,6 @@ require_root
 # Test 1: Without -f flag, stalld should daemonize
 test_section "Test 1: stalld daemonizes by default"
 start_stalld -l -t 5
-sleep 2
 
 assert_process_running "${STALLD_PID}" "stalld should be running"
 
@@ -42,7 +41,6 @@ test_section "Test 2: stalld stays in foreground with -f"
 
 # Start stalld in foreground but in background job
 start_stalld -f -l -t 5
-sleep 2
 
 assert_process_running "${STALLD_PID}" "stalld should be running with -f"
 
@@ -55,7 +53,6 @@ stop_stalld
 test_section "Test 3: -v implies foreground mode"
 
 start_stalld -v -l -t 5
-sleep 2
 
 assert_process_running "${STALLD_PID}" "stalld should be running with -v"
 
tests/functional/test_logging_destinations.sh +0 -1
index a25e571..eb06171 100755
@@ -49,7 +49,6 @@ if command -v dmesg >/dev/null 2>&1; then
 	DMESG_BEFORE=$(dmesg | wc -l)
 
 	start_stalld -f -k -l -t 5
-	sleep 2
 
 	if assert_process_running "${STALLD_PID}" "stalld with -k should be running"; then
 		sleep 1
Signature · 1 line
2.54.0
#31 lore raw

PATCH 1/1 31/33] tests: Reduce timing and replace sleeps with event waits

In reply to Wander Lairson Costa

Lower starvation thresholds, reduce starvation_gen durations, and replace fixed sleep delays with process-wait and event-driven synchronization in the functional tests. This reduces test suite runtime without sacrificing reliability.

---
 tests/functional/test_deadline_boosting.sh        | 2 +-
 tests/functional/test_fifo_boosting.sh            | 2 +-
 tests/functional/test_fifo_priority_starvation.sh | 5 +++--
 tests/functional/test_idle_detection.sh           | 2 +-
 tests/functional/test_starvation_detection.sh     | 3 ++-
 tests/functional/test_starvation_threshold.sh     | 5 +----
 tests/functional/test_task_merging.sh             | 9 ++++-----
 7 files changed, 13 insertions(+), 15 deletions(-)
tests/functional/test_deadline_boosting.sh +1 -1
index 305c641..25d0f0e 100755
@@ -30,7 +30,7 @@ log "Starting stalld with ${threshold}s threshold (default DEADLINE boosting)"
 start_stalld_with_log "${STALLD_LOG}" -f -v -g 1 -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
 # Create starvation
-starvation_duration=$((threshold + 8))
+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}
 
tests/functional/test_fifo_boosting.sh +1 -1
index 57c473d..e489950 100755
@@ -23,7 +23,7 @@ test_section "Test 1: FIFO Boost with -F Flag"
 
 threshold=5
 # Create starvation FIRST (before stalld starts)
-starvation_duration=$((threshold + 8))
+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}
 
tests/functional/test_fifo_priority_starvation.sh +3 -2
index 66075d4..028b825 100755
@@ -73,7 +73,8 @@ log "Starting stalld with boosting enabled"
 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))
+wait_for_boost_detected "${STALLD_LOG}"
+sleep ${boost_duration}
 
 ctxt_after=0
 if [ -n "${blockee_pid}" ] && [ -f "/proc/${blockee_pid}/status" ]; then
@@ -104,7 +105,7 @@ rm -f "${STALLD_LOG}"
 threshold=3
 
 # Create long starvation to trigger multiple detection cycles
-starvation_duration=15
+starvation_duration=12
 log "Creating long FIFO-on-FIFO starvation for ${starvation_duration}s"
 start_starvation_gen -c ${TEST_CPU} -p 10 -b 5 -n 2 -d ${starvation_duration}
 
tests/functional/test_idle_detection.sh +1 -1
index 81a9fe1..cafce11 100755
@@ -60,7 +60,7 @@ threshold=3
 log "Starting stalld with idle detection on an idle CPU"
 start_stalld_with_log "${STALLD_LOG}" -f -v -l -t $threshold -c ${TEST_CPU} -a ${STALLD_CPU}
 
-sleep 3
+wait_for_log_message "skipping" 5 "${STALLD_LOG}"
 
 assert_log_contains "${STALLD_LOG}" "skipping" "Idle CPU correctly skipped"
 
tests/functional/test_starvation_detection.sh +2 -1
index 33bbfed..52031c5 100755
@@ -184,7 +184,8 @@ log "Creating short-lived starvation (3s, less than ${threshold}s threshold)"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 1 -d 3
 
 # Wait for task to exit
-sleep 5
+wait "${STARVE_PID}" 2>/dev/null || true
+sleep 1
 
 assert_process_running "${STALLD_PID}" "stalld still running after task exit"
 
tests/functional/test_starvation_threshold.sh +1 -4
index 6e84966..b372a87 100755
@@ -60,14 +60,11 @@ start_starvation_gen -c "${TEST_CPU}" -p 80 -n 2 -d ${starvation_duration}
 log "Starting stalld with ${threshold}s threshold"
 start_stalld_with_log "${STALLD_LOG}" -f -v -N -M -g 1 -c "${TEST_CPU}" -a "${STALLD_CPU}" -t ${threshold}
 
-# Wait for starvation duration + small buffer
-sleep 8
-
 # Wait for starvation generator to fully complete
 wait "${STARVE_PID}" 2>/dev/null || true
 
 # Give stalld time to process and log (if it were to detect)
-sleep 2
+sleep 1
 
 # Check that starvation_gen was NOT detected (duration less than threshold)
 assert_log_contains --negate "${STALLD_LOG}" \
tests/functional/test_task_merging.sh +4 -5
index 082f07d..784f0de 100755
@@ -28,7 +28,7 @@ log "Starting stalld with ${threshold}s threshold (log-only, verbose)"
 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
+starvation_duration=12
 log "Creating starvation for ${starvation_duration}s (multiple detection cycles)"
 start_starvation_gen -c ${TEST_CPU} -p 80 -n 2 -d ${starvation_duration}
 
@@ -51,7 +51,7 @@ fi
 
 # Wait for second detection cycle
 log "Waiting for second detection cycle..."
-sleep 4
+wait_for_n_log_matches "starved" 2 "${STALLD_LOG}"
 
 # Extract second starvation duration
 second_duration=$(grep "starved.*for [0-9]" "${STALLD_LOG}" | tail -1 | grep -oE "for [0-9]+" | awk '{print $2}')
@@ -72,7 +72,7 @@ fi
 
 # Wait for third detection to confirm continued accumulation
 log "Waiting for third detection cycle..."
-sleep 4
+wait_for_n_log_matches "starved" 3 "${STALLD_LOG}"
 
 third_duration=$(grep "starved.*for [0-9]" "${STALLD_LOG}" | tail -1 | grep -oE "for [0-9]+" | awk '{print $2}')
 if [ -z "${third_duration}" ]; then
@@ -182,8 +182,7 @@ else
     STARVE_PID1=${STARVE_PID}
 
     # Wait for starvation detection on both CPUs
-    wait_for_starvation_detected "${STALLD_LOG}"
-    sleep 4
+    wait_for_n_log_matches "starved" 2 "${STALLD_LOG}" 10
 
     # Check CPU0 starvation accumulation
     cpu0_detections=$(grep "starved on CPU ${CPU0}" "${STALLD_LOG}" | wc -l)
Signature · 1 line
2.54.0
#32 lore raw

PATCH 1/1 32/33] tests: Fix async-signal-unsafe handler

In reply to Wander Lairson Costa

Remove the printf call from the starvation_gen signal handler. Calling printf from a signal handler is undefined behavior under POSIX because it is not async-signal-safe. If the signal arrives while another thread holds the internal stdout lock, the process deadlocks. The handler now only sets the volatile running flag.

---
 tests/helpers/starvation_gen.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
tests/helpers/starvation_gen.c +1 -1
index 81ee714..88cedac 100644
@@ -102,7 +102,7 @@ void *blockee_thread(void *arg) {
 }
 
 void signal_handler(int sig) {
-	printf("\nReceived signal %d, exiting...\n", sig);
+	(void)sig;
 	running = 0;
 }
 
Signature · 1 line
2.54.0
#33 lore raw

PATCH 1/1 33/33] bpf: Replace linear task scan with hash map

In reply to Wander Lairson Costa

The BPF queue_track backend previously utilized an O(2048) linear array scan within find_queued_task() on every sched_wakeup event. This design introduced a ~2us latency penalty, which is highly problematic for Telco workloads where it consumed 20% of the stringent 10us real-time latency budget.

Resolve this bottleneck by replacing the per-CPU array with a new BPF hash map, stalld_task_map, keyed by a composite of CPU and PID to enable O(1) task lookups. Consequently, the stalld_cpu_data structure shrinks from approximately 65KB down to 12 bytes. The enqueue_task, dequeue_task, and update_or_add_task routines are rewritten to utilize standard BPF map helpers. In userspace, queue_track.c now iterates through the hash map using bpf_map_get_next_key while applying CPU filtering.

Initialize the legacy pointer in task_running() to avoid use of uninitialized variable.

Verification on a real-time system confirms that thread latency dropped from 5-6us down to 3us. This improvement completely eliminates the overhead, returning performance to the baseline established when stalld is not running.

Assisted-by Claude Code:claude-opus-4-6 [PAL]
---
 bpf/stalld.bpf.c  | 206 ++++++++++++++++------------------------------
 src/queue_track.c |  95 +++++++++++----------
 src/queue_track.h | 107 ++----------------------
 3 files changed, 131 insertions(+), 277 deletions(-)
bpf/stalld.bpf.c +73 -133
index bf55f5b..4df9211 100644
@@ -33,6 +33,14 @@ struct {
 	__type(value, struct stalld_cpu_data);
 } stalld_per_cpu_data SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	/* resized at load time to MAX_QUEUE_TASK * nr_cpus */
+	__uint(max_entries, MAX_QUEUE_TASK);
+	__type(key, struct task_map_key);
+	__type(value, struct queued_task);
+} stalld_task_map SEC(".maps");
+
 #if DEBUG_STALLD
 #define log(msg, ...) bpf_printk("%s: " msg, __func__, ##__VA_ARGS__)
 #else
@@ -82,15 +90,6 @@ struct task_struct___legacy {
  * task_is_rt - Check if a task is a real-time task.
  * @p: A pointer to the kernel's `task_struct` for the task.
  *
- * This function determines if a task belongs to a real-time (RT) scheduling
- * class based on its priority. In the Linux kernel, static priorities from
- * 0 to 99 are reserved for RT tasks (SCHED_FIFO and SCHED_RR), while
- * priorities from 100 to 139 are used for normal tasks (SCHED_NORMAL,
- * SCHED_BATCH, etc.).
- *
- * This check is essential for `stalld` to distinguish between high-priority
- * RT tasks that have strict scheduling deadlines and normal tasks.
- *
  * Return: `true` if the task has a real-time priority (0-99),
  *         `false` otherwise.
  */
@@ -103,13 +102,6 @@ static inline bool task_is_rt(const struct task_struct *p)
  * task_cpu - Get the CPU number that a task is currently running on.
  * @p: A pointer to the kernel's `task_struct` for the task.
  *
- * This function retrieves the CPU identifier where the task is currently
- * scheduled.
- *
- * The CPU number is crucial for `stalld` to associate a task with the
- * correct per-CPU data map, ensuring that task tracking and starvation
- * analysis are performed in the right context.
- *
  * Return: The integer ID of the CPU the task is running on.
  */
 static inline int task_cpu(const struct task_struct *p)
@@ -126,23 +118,6 @@ static inline int task_cpu(const struct task_struct *p)
  * compute_ctxswc - Compute the total context switch count for a task.
  * @p: A pointer to the `task_struct` (process descriptor) of the task.
  *
- * This function calculates the total number of context switches a task
- * has undergone by summing its voluntary and involuntary context switch
- * counts.
- *
- * The `nvcsw` (number of voluntary context switches) increments when a task
- * explicitly yields the CPU (e.g., waiting for I/O, sleeping, or blocking
- * on a lock).
- *
- * The `nivcsw` (number of involuntary context switches) increments when a task
- * is preempted by the scheduler (e.g., its timeslice expires, or a higher
- * priority task becomes runnable).
- *
- * The sum of these two counters provides a comprehensive measure of how many
- * times the task has been context-switched in and out of the CPU. This value
- * is crucial for tools like `stalld` to detect if a task has made progress
- * (i.e., has run at least once) since a previous observation.
- *
  * Return: The total context switch count (nvcsw + nivcsw) for the given task.
  */
 static inline long compute_ctxswc(const struct task_struct *p)
@@ -152,7 +127,8 @@ static inline long compute_ctxswc(const struct task_struct *p)
 
 static inline unsigned int task_running(const struct task_struct *p)
 {
-	const struct task_struct___legacy *lp;
+	const struct task_struct___legacy *lp = (const void *) p;
+
 	const unsigned int state = bpf_core_field_exists(p->__state)
 					? BPF_CORE_READ(p, __state)
 					: BPF_CORE_READ(lp, state);
@@ -178,52 +154,52 @@ static struct stalld_cpu_data *get_cpu_data(int cpu)
 	return NULL;
 }
 
-static int enqueue_task(const struct task_struct *p, struct stalld_cpu_data *cpu_data)
+static int enqueue_task(const struct task_struct *p, int cpu)
 {
-	struct queued_task *task;
-	const long pid = p->pid;
-
-	for_each_task_entry(cpu_data, task) {
-		if (task->pid == 0 || task->pid == pid) {
-			task->ctxswc = compute_ctxswc(p);
-			task->prio = p->prio;
-			task->is_rt = task_is_rt(p);
-			task->tgid = p->tgid;
-
-			/*
-			 * User reads pid to know that there is no data here.
-			 * Update it last.
-			 */
-			barrier();
-			task->pid = pid;
-			log_task(p);
-			return 0;
-		}
-	}
+	struct task_map_key key;
+	struct queued_task task;
 
-	log_task_error(p);
+	/*
+	 * pid 0 (idle/swapper) must not enter the hash map: userspace
+	 * uses pid 0 as "no task" in cpu_starving_vector, so a real
+	 * entry with pid 0 would be silently skipped.
+	 */
+	if (!p->pid)
+		return 0;
+
+	key.cpu = cpu;
+	key.pid = p->pid;
 
+	task.pid = p->pid;
+	task.tgid = p->tgid;
+	task.is_rt = task_is_rt(p);
+	task.prio = p->prio;
+	task.ctxswc = compute_ctxswc(p);
+
+	if (bpf_map_update_elem(&stalld_task_map, &key, &task, BPF_ANY) < 0) {
+		log_task_error(p);
+		return -1;
+	}
+
+	log_task(p);
 	return 0;
 }
 
 /**
  * dequeue_task - Removes a task from a CPU's queue.
- * @p:        Pointer to the task_struct of the task to remove.
- * @cpu_data: Pointer to the per-CPU data structure.
- *
- * This function finds and removes a task from the specified CPU's run queue.
- * It updates the appropriate counter (RT or non-RT) for the queued tasks.
+ * @p:   Pointer to the task_struct of the task to remove.
+ * @cpu: The CPU number to dequeue from.
  *
  * Return: 1 if the task was found and removed, 0 otherwise.
  */
-static int dequeue_task(const struct task_struct *p, struct stalld_cpu_data *cpu_data)
+static int dequeue_task(const struct task_struct *p, int cpu)
 {
-	struct queued_task *task;
-	long pid = p->pid;
+	struct task_map_key key;
 
-	task = find_queued_task(cpu_data, pid);
-	if (task) {
-		task->pid = 0;
+	key.cpu = cpu;
+	key.pid = p->pid;
+
+	if (bpf_map_delete_elem(&stalld_task_map, &key) == 0) {
 		log_task(p);
 		return 1;
 	}
@@ -233,85 +209,55 @@ static int dequeue_task(const struct task_struct *p, struct stalld_cpu_data *cpu
 }
 
 /*
- * update_or_add_task - Manages a task's lifecycle within a per-CPU tracking queue.
- *
- * This function handles the logic for managing individual task entries within
- * stalld's BPF program. It dynamically adds, updates, or removes a task from a
- * specific CPU's tracking array based on its current state. This ensures the
- * array provides an accurate, real-time view of tasks on the run queue.
- *
- * The function's logic is organized into three primary scenarios:
- * 1.  Update: If a task is already tracked and is still in the TASK_RUNNING
- * state, its dynamic properties (context switch count, priority) are
- * refreshed.
- * 2.  Remove: If a tracked task is no longer in the TASK_RUNNING state
- * (e.g., it has gone to sleep or terminated), it is removed from the queue
- * by invalidating its entry (setting pid to 0).
- * 3.  Add: If a new, previously unseen task is encountered and is in the
- * TASK_RUNNING state, it is added to the first available empty slot in
- * the queue.
+ * update_or_add_task - Manages a task's lifecycle within the hash map.
  *
- * Parameters:
- * cpu_data: A pointer to the `stalld_cpu_data` structure for the target CPU.
- * p:        A pointer to the kernel's `task_struct` for the task to be processed.
+ * Three scenarios:
+ * 1. Update: task exists and is TASK_RUNNING -> refresh fields in-place.
+ * 2. Remove: task exists but not TASK_RUNNING -> delete from map.
+ * 3. Add:    task not found and is TASK_RUNNING -> insert into map.
  */
-static void update_or_add_task(struct stalld_cpu_data *cpu_data,
-			       const struct task_struct *p)
+static void update_or_add_task(const struct task_struct *p, int cpu)
 {
+	struct task_map_key key;
 	struct queued_task *task_entry;
 
-	/* Try to find the task first */
-	task_entry = find_queued_task(cpu_data, p->pid);
+	key.cpu = cpu;
+	key.pid = p->pid;
+
+	task_entry = bpf_map_lookup_elem(&stalld_task_map, &key);
 	if (task_entry) {
 		if (task_running(p)) {
-			/* Task found: Update its dynamic fields */
 			task_entry->ctxswc = compute_ctxswc(p);
 			task_entry->prio = p->prio;
 			task_entry->is_rt = task_is_rt(p);
 		} else {
-			/* Task is not running. Remove it. */
 			log_task_prefix("dequeue ", p);
-			task_entry->pid = 0;
+			bpf_map_delete_elem(&stalld_task_map, &key);
 		}
 
 		return;
 	}
 
-	/*
-	 * If we reach here, the task was NOT found, so it's new.
-	 * Check if the new task is in the `TASK_RUNNING` state before adding to queue.
-	 */
 	if (!task_running(p))
 		return;
 
-	/*
-	 * Task not found and is running: find an empty slot to add it
-	 * We iterate through all slots to find the first empty one.
-	 */
-	enqueue_task(p, cpu_data);
+	enqueue_task(p, cpu);
 }
 
 /**
  * __sched_wakeup - Common handler for task wakeup tracepoints.
  * @ctx: A pointer to the tracepoint context.
  *
- * This function serves as the common implementation for handling both
- * `sched_wakeup` and `sched_wakeup_new` tracepoints. It extracts the
- * task_struct from the context, determines its target CPU, and if that
- * CPU is being monitored, enqueues the task for tracking.
- *
- * This centralized approach avoids code duplication and provides a
- * single point of logic for task wakeup events.
- *
  * Return: Always returns 0.
  */
 static int __sched_wakeup(u64 *ctx)
 {
 	const struct task_struct *p = (void *) ctx[0];
-	struct stalld_cpu_data *cpu_data = get_cpu_data(task_cpu(p));
+	int cpu = task_cpu(p);
+	struct stalld_cpu_data *cpu_data = get_cpu_data(cpu);
 
 	if (cpu_data)
-		update_or_add_task(cpu_data, p);
+		update_or_add_task(p, cpu);
 
 	return 0;
 }
@@ -332,10 +278,11 @@ SEC("tp_btf/sched_process_exit")
 int handle__sched_process_exit(u64 *ctx)
 {
 	const struct task_struct *p = (void *) ctx[0];
-	struct stalld_cpu_data *cpu_data = get_cpu_data(task_cpu(p));
+	int cpu = task_cpu(p);
+	struct stalld_cpu_data *cpu_data = get_cpu_data(cpu);
 
 	if (cpu_data)
-		dequeue_task(p, cpu_data);
+		dequeue_task(p, cpu);
 
 	return 0;
 }
@@ -343,7 +290,8 @@ int handle__sched_process_exit(u64 *ctx)
 SEC("tp_btf/sched_switch")
 int handle__sched_switch(u64 *ctx)
 {
-	struct stalld_cpu_data *cpu_data = get_cpu_data(bpf_get_smp_processor_id());
+	int cpu = bpf_get_smp_processor_id();
+	struct stalld_cpu_data *cpu_data = get_cpu_data(cpu);
 	const struct task_struct *prev = (void *) ctx[1];
 	const struct task_struct *next = (void *) ctx[2];
 
@@ -353,9 +301,8 @@ int handle__sched_switch(u64 *ctx)
 
 	cpu_data->nr_rt_running = task_is_rt(next);
 
-	// update the context switch count of the tasks
-	update_or_add_task(cpu_data, next);
-	update_or_add_task(cpu_data, prev);
+	update_or_add_task(next, cpu);
+	update_or_add_task(prev, cpu);
 
 	return 0;
 }
@@ -373,17 +320,15 @@ int handle__sched_migrate_task(u64 *ctx)
 	/*
 	 * Dequeue the task from its original CPU and re-enqueue it on the
 	 * destination CPU. This ensures its run queue state is tracked
-	 * correctly across migrations. If the task was not found on the
-	 * original CPU, there is no need to enqueue it on the new one, as
-	 * it was not being monitored.
+	 * correctly across migrations.
 	 */
 	if (cpu_data) {
 		log("task=%s(%ld) orig=%d dest=%d",
 		    p->comm, p->tgid, orig_cpu, dest_cpu);
-		if (dequeue_task(p, cpu_data)) {
+		if (dequeue_task(p, orig_cpu)) {
 			cpu_data = get_cpu_data(dest_cpu);
 			if (cpu_data)
-				enqueue_task(p, cpu_data);
+				enqueue_task(p, dest_cpu);
 		}
 	}
 
@@ -393,29 +338,24 @@ int handle__sched_migrate_task(u64 *ctx)
 /**
  * iter_task - BPF iterator program for task enumeration
  * @ctx: Iterator context containing the current task
- *
- * This BPF iterator program walks through all tasks in the system and
- * provides visibility into their scheduling state. It's useful for getting
- * a system-wide snapshot of task states, complementing the event-driven
- * tracepoint programs that track dynamic task state changes.
  */
 SEC("iter/task")
 int iter_task(struct bpf_iter__task *ctx)
 {
 	const struct task_struct *p = ctx->task;
-	struct stalld_cpu_data *cpu_data;
+	int cpu;
 
 	if (!p)
 		return 0;
 
-	cpu_data = get_cpu_data(task_cpu(p));
-	if (!cpu_data)
+	cpu = task_cpu(p);
+	if (!get_cpu_data(cpu))
 		return 0;
 
 	log_task(p);
 
 	if (task_running(p))
-		enqueue_task(p, cpu_data);
+		enqueue_task(p, cpu);
 
 	return 0;
 }
src/queue_track.c +53 -42

184 lines not on this page · raw message

src/queue_track.h +5 -102

121 lines not on this page · raw message

Signature · 1 line
2.54.0