From ffcf99f1024fa02237b56e3578a91d0a5112f947 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sat, 22 Aug 2026 18:06:57 +0200 Subject: [PATCH] Test arch_perf_report.sh to 98.33% and fix its dead journal-size check Fifth of the 16 untested fixes/lib libs and the first of the arch-performance group, which gets its own harness here. 38 assertions across two files. The lib STAYS on the allowlist at 59/60 = 98.33%, measured twice. The second dead branch this campaign has found, fixed here. Lines 36 and 65 matched on: ([0-9]+\.?[0-9]*)\ (G|M) a REQUIRED space before the unit. journalctl actually prints "Archived and active journals take up 305.5M in the file system." with no separator -- verified against the real binary on this machine. So check_journal_size could never report a large journal and apply_safe_fixes could never vacuum one: both were unreachable. The space is now optional, and tests cover the spaced and unspaced forms so neither can rot. NOTE: this makes a previously-dead DESTRUCTIVE action live. With --apply-safe-fixes and a journal over a gigabyte, the entry script will now really run `journalctl --vacuum-size=300M`. That is the flag's documented purpose and it stays behind the flag, but it has never actually executed before today. Line 20, the one uncovered line, is `} >>"$REPORT_FILE"` -- the closing brace of a redirected group. A minimal probe writes both inner lines to the file while bash attributes no statement to the brace, and the report assertions independently prove the group runs, since they read the text it writes. Same defect (a) class as bt_audio's `} |`. That form now caps four libs: bt_audio.sh, arch_perf_report.sh, and arch_perf_probes.sh and nvidia_config.sh which are still untested. So the continuation_lines() fix is worth landing after all -- as its own commit, next, so a denominator change can be reverted without taking these tests with it. _t_hide and _t_full_path move from bt_harness.sh into lib_test_core.sh: they are generic PATH plumbing and the arch harness needs them for the "nvidia-smi is not installed" branch. All twelve existing suites re-run green after the move. One trap worth recording: apply_safe_fixes appends to the ACTIONS array, so it must be called directly rather than inside "$(...)" -- a command substitution runs it in a subshell where the appends die with it, and the assertions read an empty array. Co-Authored-By: Claude Opus 5 (1M context) --- .../arch-perf-report-coverage-2026-08-22.json | 18 +++ .../arch-perf-report-coverage-2026-08-22.json | 55 +++++++++ .../single_use/fixes/lib/arch_perf_report.sh | 8 +- .../fixes/lib/tests/arch_perf_harness.sh | 96 +++++++++++++++ .../single_use/fixes/lib/tests/bt_harness.sh | 48 -------- .../fixes/lib/tests/lib_test_core.sh | 48 ++++++++ .../fixes/lib/tests/test_arch_perf_report.sh | 116 ++++++++++++++++++ .../lib/tests/test_arch_perf_report_fixes.sh | 111 +++++++++++++++++ 8 files changed, 450 insertions(+), 50 deletions(-) create mode 100644 docs/superpowers/contracts/arch-perf-report-coverage-2026-08-22.json create mode 100644 docs/superpowers/evidence/arch-perf-report-coverage-2026-08-22.json create mode 100755 linux_configuration/scripts/single_use/fixes/lib/tests/arch_perf_harness.sh create mode 100755 linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report.sh create mode 100755 linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report_fixes.sh diff --git a/docs/superpowers/contracts/arch-perf-report-coverage-2026-08-22.json b/docs/superpowers/contracts/arch-perf-report-coverage-2026-08-22.json new file mode 100644 index 00000000..9fe492e4 --- /dev/null +++ b/docs/superpowers/contracts/arch-perf-report-coverage-2026-08-22.json @@ -0,0 +1,18 @@ +{ + "title": "Test arch_perf_report.sh, open the arch-performance family, and fix the dead journal-size check", + "objective": "Cover the fifth of the sixteen untested fixes/lib libs and stand up the harness the arch-performance group will share. Writing the tests exposed a second dead branch -- the journal size regex demanded a space before the unit that journalctl never prints -- so this lands that fix alongside the coverage. The lib stops at 98.33%: its last line is a redirected closing brace no test can reach.", + "acceptance_criteria": [ + "arch_perf_report.sh measures 98.33% (59/60) through lib/tests/run_all.sh, twice, with line 20 the only uncovered line both times.", + "The old journal regex is shown NOT to match journalctl's real output before the fix, and the fixed one shown to match both the spaced and unspaced forms.", + "Line 20 is shown to be a closing brace of a redirected group that executes, not a missing test.", + "meta/shell-coverage-allowlist.txt is UNCHANGED -- arch_perf_report.sh does not meet the 100% bar.", + "The four bluetooth suites and the eight pacman_hook_stall suites still pass after _t_hide and _t_full_path move into lib_test_core.sh.", + "Every file stays under the repo's 250-line cap, and pre-commit passes with no suppression added." + ], + "out_of_scope": [ + "Clearing arch_perf_report.sh, bt_audio.sh or bt_adapter.sh from the allowlist. All three are capped by defect (a) in docs/kcov-under-report.md.", + "Changing continuation_lines() in meta/scripts/lib/shell_coverage_lines.py. That is the next commit, kept separate so a denominator change can be reverted without losing these tests.", + "The remaining eleven untested fixes/lib libs." + ], + "verifier": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh (expects all files passing, 17 + 21 for the two arch_perf_report files), plus bash meta/scripts/shell_coverage_jail.sh --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure arch_perf_report.sh --min 1 --fail-on-case-error -- \"\" (expects 59/60 = 98.33%, uncovered 20), plus bash meta/scripts/check_shell_coverage.sh on the cleared libs (expects exit 0)." +} diff --git a/docs/superpowers/evidence/arch-perf-report-coverage-2026-08-22.json b/docs/superpowers/evidence/arch-perf-report-coverage-2026-08-22.json new file mode 100644 index 00000000..31b32f0c --- /dev/null +++ b/docs/superpowers/evidence/arch-perf-report-coverage-2026-08-22.json @@ -0,0 +1,55 @@ +{ + "intent": "Test linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh, fifth of the 16 untested fixes/lib libs and the first of the arch-performance group, and fix a second production bug the tests exposed: the journal size check could never fire. The lib reaches 98.33% and STAYS on the allowlist, capped by one closing-brace line.", + "scope": [ + "linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh (two regexes: the journal size match)", + "linux_configuration/scripts/single_use/fixes/lib/tests/arch_perf_harness.sh (new family harness)", + "linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report.sh (new, 17 assertions)", + "linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report_fixes.sh (new, 21 assertions)", + "linux_configuration/scripts/single_use/fixes/lib/tests/lib_test_core.sh and bt_harness.sh (_t_hide/_t_full_path moved into the core)", + "meta/shell-coverage-allowlist.txt is deliberately UNCHANGED: arch_perf_report.sh does not meet the 100% bar" + ], + "changes": [ + "Fixed the journal size regexes in arch_perf_report.sh, lines 36 and 65. Both required a SPACE before the unit -- ([0-9]+\\.?[0-9]*)\\ (G|M) -- but journalctl prints 'Archived and active journals take up 305.5M in the file system.' with no separator, verified on this machine. check_journal_size could therefore never report a large journal, and apply_safe_fixes could never vacuum one: both were dead code. The space is now optional (\\ ?), and tests cover the spaced and unspaced forms.", + "Added arch_perf_harness.sh: sources lib_test_core.sh and the real scripts/lib/common.sh, points REPORT_FILE into the throwaway tmpdir, and redefines add_finding, add_action and run_and_log verbatim from diagnose_arch_performance.sh, which owns them rather than any lib.", + "Added test_arch_perf_report.sh covering check_gpu_state (nvidia-smi absent, an idle GPU pinned in P0, a busy P0 GPU that must NOT be flagged, an idle GPU in a low-power state, and nvidia-smi returning nothing) and check_journal_size (gigabyte, megabyte, space-separated, and journalctl failing).", + "Added test_arch_perf_report_fixes.sh covering apply_safe_fixes (disabled by the flag, nothing to do, enabling a disabled fstrim.timer, disabling a conflicting tlp.service, and vacuuming a multi-gigabyte journal) and print_summary (clean run, findings listed and counted, actions section).", + "Moved _t_hide and _t_full_path from bt_harness.sh into lib_test_core.sh. They are generic PATH plumbing, not bluetooth-specific, and the arch harness needs them for the 'nvidia-smi is not installed' branch.", + "apply_safe_fixes is called directly rather than inside a command substitution: it appends to the ACTIONS array, and a substitution runs it in a subshell where those appends die with it, leaving the assertions reading an empty array." + ], + "verification": [ + { + "command": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh", + "result": "pass", + "evidence": "arch_perf_report (probes): 17 passed; arch_perf_report (fixes/summary): 21 passed; both 0 failed. All fixes lib test files passed, so the four bluetooth libs and the eight pacman_hook_stall suites stay green after _t_hide moved into the core." + }, + { + "command": "bash meta/scripts/shell_coverage_jail.sh --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure arch_perf_report.sh --min 1 --fail-on-case-error -- \"\"", + "result": "pass", + "evidence": "arch_perf_report.sh: 59/60 lines = 98.33%, uncovered: 20. Run twice with the identical line set." + }, + { + "command": "bash -c 'line=\"...take up 4.2G in the file system.\"; [[ $line =~ ([0-9]+\\.?[0-9]*)\\ (G|M) ]]'", + "result": "pass", + "evidence": "Reproduced the bug before fixing: NO MATCH against the old regex, so no journal size could ever be flagged. With the optional space the same line matches and yields 4.2G. journalctl on this host prints '305.5M' with no space, confirming the real format the old regex excluded." + }, + { + "command": "Read uncovered line 20 and probe it minimally", + "result": "pass", + "evidence": "Line 20 is '} >>\"$REPORT_FILE\"', the closing brace of a redirected group. A minimal probe ({ echo a; echo b; } >>file inside a function) writes both lines to the file while bash attributes no statement to the closing brace. The report assertions independently prove the group runs, since they read the very text it writes. Same defect (a) class as bt_audio's '} |' lines." + }, + { + "command": "grep -lE '^[[:space:]]*\\}[[:space:]]*>' over all 22 fixes/lib libs", + "result": "pass", + "evidence": "The redirected-closing-brace form appears in arch_perf_report.sh, arch_perf_probes.sh and nvidia_config.sh. Together with bt_audio.sh's '} |' that is four libs capped by one detector gap, which is why the continuation_lines() fix is now worth landing as its own commit." + } + ], + "risks": [ + "The journal regex fix makes a previously-dead DESTRUCTIVE action live. apply_safe_fixes will now actually run 'journalctl --vacuum-size=300M' when the entry script is invoked with --apply-safe-fixes and the journal exceeds a gigabyte, deleting roughly 3.9G of logs on a 4.2G journal. That is the documented intent of the flag, and the vacuum still only runs behind it, but it has never actually executed before this commit.", + "arch_perf_report.sh stays on the allowlist at 98.33%, capped by line 20. Clearing it needs continuation_lines() in meta/scripts/lib/shell_coverage_lines.py to exclude a closing brace followed by a redirection or pipe, which lands separately so a regression can be reverted without losing these tests.", + "The GPU tests stub nvidia-smi rather than reading the real card, so they assert on the lib's parsing of a known nvidia-smi contract, not on this host's actual GPU state." + ], + "rollback": [ + "git revert the commit; it adds a harness and two test files, moves two helpers between harness files, and changes two regexes in one lib. No allowlist entry is touched.", + "After rollback, run bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh and confirm the remaining suites pass; the coverage gate is unaffected since arch_perf_report.sh stays exempt either way." + ] +} diff --git a/linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh b/linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh index 7a8da896..9160b8b7 100755 --- a/linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh +++ b/linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh @@ -33,7 +33,10 @@ check_journal_size() { journal_line=$(journalctl --disk-usage 2>/dev/null || true) echo "Journal usage: ${journal_line:-unknown}" >>"$REPORT_FILE" - if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ (G|M) ]]; then + # The space before the unit is OPTIONAL: journalctl prints "305.5M in + # the file system", with no separator, so requiring one made this check + # dead code that could never report a large journal. + if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ ?(G|M) ]]; then local value unit value="${BASH_REMATCH[1]}" unit="${BASH_REMATCH[2]}" @@ -62,7 +65,8 @@ apply_safe_fixes() { local journal_line journal_line=$(journalctl --disk-usage 2>/dev/null || true) - if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ G ]]; then + # Optional space, as in check_journal_size above. + if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ ?G ]]; then journalctl --vacuum-size=300M add_action "Vacuumed systemd journal to 300M." fi diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/arch_perf_harness.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/arch_perf_harness.sh new file mode 100755 index 00000000..63db81e2 --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/arch_perf_harness.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# lib/tests/arch_perf_harness.sh — shared setup for the arch performance lib +# tests (arch_perf_report.sh, arch_perf_probes.sh). +# +# Sourced, not executed. Builds on lib_test_core.sh and adds the pieces these +# libs read from their entry script, diagnose_arch_performance.sh: +# +# * REPORT_FILE, FINDINGS, ACTIONS and the APPLY_SAFE_FIXES flag; +# * add_finding, add_action and run_and_log, redefined verbatim from the +# entry script -- they live there rather than in any lib, the same +# arrangement pacman_hook_stall_harness.sh handles with log_size; +# * the real log_*/has_cmd helpers, via scripts/lib/common.sh. +# +# REPORT_FILE points into the throwaway tmpdir, so the probes' report writes +# never leave the sandbox. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FIXES_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +REPO_ROOT="$(cd "${FIXES_DIR}/../../../.." && pwd)" + +# shellcheck source=lib_test_core.sh +. "${SCRIPT_DIR}/lib_test_core.sh" + +# shellcheck source=../../../../lib/common.sh +. "${REPO_ROOT}/linux_configuration/scripts/lib/common.sh" + +# --- globals and helpers owned by diagnose_arch_performance.sh -------------- + +# These four are read by the libs under test, never by this harness, so +# static analysis cannot see the use; export makes the intent explicit. +export REPORT_FILE="${TEST_TMPDIR}/report.log" +export APPLY_SAFE_FIXES="false" +export INSTALL_TOOLS="false" +declare -a FINDINGS=() +declare -a ACTIONS=() + +# Verbatim from diagnose_arch_performance.sh. +add_finding() { + FINDINGS+=("$1") + log_warn "$1" +} + +add_action() { + ACTIONS+=("$1") + log_info "$1" +} + +run_and_log() { + local header="$1" + shift + { + echo + echo "=== $header ===" + "$@" 2>&1 || true + } >>"$REPORT_FILE" +} + +# --- external tool stubs ---------------------------------------------------- + +_arch_default_stubs() { + local tool + for tool in nvidia-smi lspci journalctl systemctl sysctl lsblk sensors \ + free uptime ps top swapon findmnt pacman systemd-analyze; do + _t_stub "$tool" 'exit 0' + done +} + +# arch_reset — start a test group from "nothing has happened yet". +arch_reset() { + _t_reset_calls + FINDINGS=() + ACTIONS=() + APPLY_SAFE_FIXES="false" + INSTALL_TOOLS="false" + export APPLY_SAFE_FIXES INSTALL_TOOLS + : >"$REPORT_FILE" + _t_full_path + _arch_default_stubs +} + +# _t_report — the report file's contents this round. +_t_report() { + cat "$REPORT_FILE" 2>/dev/null || true +} + +# _t_findings / _t_actions — the recorded findings and actions, one per line. +_t_findings() { + printf '%s\n' "${FINDINGS[@]:-}" +} + +_t_actions() { + printf '%s\n' "${ACTIONS[@]:-}" +} + +arch_reset diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/bt_harness.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/bt_harness.sh index 568a0f46..12538ea5 100755 --- a/linux_configuration/scripts/single_use/fixes/lib/tests/bt_harness.sh +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/bt_harness.sh @@ -99,54 +99,6 @@ SUDO_BODY _t_stub sudo "$sudo_body" } -# _t_hide TOOL... — make TOOL genuinely unfindable, so has_cmd/command -v -# report it absent. -# -# A prepended stub dir cannot do this: every tool these libs probe for -# (usbreset, pactl, wpctl) really exists on an Arch desktop, so PATH has to -# stop resolving it. Dropping PATH to $FAKE_BIN alone is NOT the answer -# either -- that also hides grep, head, awk and sort, which these libs use -# on the very branch under test, turning a "not installed" case into a -# cascade of "command not found". -# -# Instead a shim dir is built containing a symlink to every real executable -# on PATH EXCEPT the hidden ones, and PATH is pointed at it. -_t_hide() { - local hide_dir="${TEST_TMPDIR}/hidden_path" - rm -rf "$hide_dir" - mkdir -p "$hide_dir" - - local -A hidden=() - local tool - for tool in "$@"; do - hidden["$tool"]=1 - done - - local dir entry name - local IFS=':' - for dir in ${LIB_TEST_ORIG_PATH}; do - [[ -d "$dir" ]] || continue - for entry in "$dir"/*; do - name="${entry##*/}" - [[ -n "${hidden[$name]:-}" ]] && continue - [[ -x "$entry" && ! -d "$entry" ]] || continue - [[ -e "${hide_dir}/${name}" ]] && continue - ln -s "$entry" "${hide_dir}/${name}" 2>/dev/null || true - done - done - - PATH="${FAKE_BIN}:${hide_dir}" - hash -r -} - -# _t_full_path — restore the prepended-stub PATH set up by lib_test_core.sh. -_t_full_path() { - PATH="${FAKE_BIN}:${LIB_TEST_ORIG_PATH}" - hash -r -} - -export LIB_TEST_ORIG_PATH="${PATH#"${FAKE_BIN}:"}" - # bt_reset — start a test group from "nothing has happened yet". bt_reset() { _t_reset_calls diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/lib_test_core.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/lib_test_core.sh index c41649d8..1858e98c 100755 --- a/linux_configuration/scripts/single_use/fixes/lib/tests/lib_test_core.sh +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/lib_test_core.sh @@ -134,6 +134,54 @@ _t_unstub() { hash -r } +# _t_hide TOOL... — make TOOL genuinely unfindable, so has_cmd/command -v +# report it absent. +# +# A prepended stub dir cannot do this: every tool these libs probe for +# (usbreset, pactl, wpctl) really exists on an Arch desktop, so PATH has to +# stop resolving it. Dropping PATH to $FAKE_BIN alone is NOT the answer +# either -- that also hides grep, head, awk and sort, which these libs use +# on the very branch under test, turning a "not installed" case into a +# cascade of "command not found". +# +# Instead a shim dir is built containing a symlink to every real executable +# on PATH EXCEPT the hidden ones, and PATH is pointed at it. +_t_hide() { + local hide_dir="${TEST_TMPDIR}/hidden_path" + rm -rf "$hide_dir" + mkdir -p "$hide_dir" + + local -A hidden=() + local tool + for tool in "$@"; do + hidden["$tool"]=1 + done + + local dir entry name + local IFS=':' + for dir in ${LIB_TEST_ORIG_PATH}; do + [[ -d "$dir" ]] || continue + for entry in "$dir"/*; do + name="${entry##*/}" + [[ -n "${hidden[$name]:-}" ]] && continue + [[ -x "$entry" && ! -d "$entry" ]] || continue + [[ -e "${hide_dir}/${name}" ]] && continue + ln -s "$entry" "${hide_dir}/${name}" 2>/dev/null || true + done + done + + PATH="${FAKE_BIN}:${hide_dir}" + hash -r +} + +# _t_full_path — restore the prepended-stub PATH set up by lib_test_core.sh. +_t_full_path() { + PATH="${FAKE_BIN}:${LIB_TEST_ORIG_PATH}" + hash -r +} + +export LIB_TEST_ORIG_PATH="${PATH#"${FAKE_BIN}:"}" + # _t_calls — print everything the stubs recorded this round. _t_calls() { cat "${DEV}/calls" 2>/dev/null || true diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report.sh new file mode 100755 index 00000000..64649320 --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# Tests for lib/arch_perf_report.sh: check_gpu_state, check_journal_size, +# apply_safe_fixes and print_summary. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=arch_perf_harness.sh +. "${SCRIPT_DIR}/arch_perf_harness.sh" + +# shellcheck source=../arch_perf_report.sh +. "${FIXES_DIR}/lib/arch_perf_report.sh" + +# --- check_gpu_state -------------------------------------------------------- + +# No nvidia-smi: falls back to listing PCI display devices. The host may have +# a real nvidia-smi, so _t_hide takes it off PATH outright. +arch_reset +_t_unstub nvidia-smi +_t_hide nvidia-smi +_t_stub lspci 'echo "01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104"' +check_gpu_state +_t_full_path +_t_contains "$(_t_report)" "=== PCI VGA Devices ===" \ + "check_gpu_state: logs PCI display devices when nvidia-smi is absent" +_t_eq "" "$(_t_findings)" "check_gpu_state: records no finding without nvidia-smi" + +# An idle GPU pinned in P0 is the condition the check exists for. +arch_reset +_t_stub_stdin nvidia-smi <<'STUB_BODY' +case "$1" in +--query-gpu=pstate) echo P0 ;; +--query-gpu=utilization.gpu) echo 2 ;; +--query-gpu=power.draw) echo 31.5 ;; +*) echo "full nvidia-smi output" ;; +esac +STUB_BODY +check_gpu_state +_t_contains "$(_t_report)" "NVIDIA pstate: P0" \ + "check_gpu_state: records the reported pstate" +_t_contains "$(_t_report)" "NVIDIA util: 2%" \ + "check_gpu_state: records the reported utilisation" +_t_contains "$(_t_report)" "NVIDIA power: 31.5W" \ + "check_gpu_state: records the reported power draw" +_t_contains "$(_t_findings)" "P0 high-performance state while mostly idle" \ + "check_gpu_state: flags a GPU idling in P0" +_t_contains "$(_t_actions)" "prefer iGPU mode" \ + "check_gpu_state: recommends the iGPU for desktop workloads" + +# A busy GPU in P0 is normal and must NOT be flagged. +arch_reset +_t_stub_stdin nvidia-smi <<'STUB_BODY' +case "$1" in +--query-gpu=pstate) echo P0 ;; +--query-gpu=utilization.gpu) echo 87 ;; +--query-gpu=power.draw) echo 210 ;; +*) echo "out" ;; +esac +STUB_BODY +check_gpu_state +_t_eq "" "$(_t_findings)" "check_gpu_state: does not flag a P0 GPU that is actually busy" + +# An idle GPU in a low-power state is also fine. +arch_reset +_t_stub_stdin nvidia-smi <<'STUB_BODY' +case "$1" in +--query-gpu=pstate) echo P8 ;; +--query-gpu=utilization.gpu) echo 0 ;; +--query-gpu=power.draw) echo 9 ;; +*) echo "out" ;; +esac +STUB_BODY +check_gpu_state +_t_eq "" "$(_t_findings)" "check_gpu_state: does not flag an idle GPU in a low-power state" + +# nvidia-smi present but answering nothing: the report records "unknown". +arch_reset +_t_stub nvidia-smi 'exit 0' +check_gpu_state +_t_contains "$(_t_report)" "NVIDIA pstate: unknown" \ + "check_gpu_state: records 'unknown' when nvidia-smi returns nothing" + +# --- check_journal_size ----------------------------------------------------- + +arch_reset +_t_stub journalctl 'echo "Archived and active journals take up 4.2G in the file system."' +check_journal_size +_t_contains "$(_t_report)" "Journal usage:" "check_journal_size: records journal usage" +_t_contains "$(_t_findings)" "journal is large (4.2G)" \ + "check_journal_size: flags a multi-gigabyte journal" + +# The real journalctl format, with NO space before the unit -- this is what +# the machine actually prints, and requiring a space made the check dead. +arch_reset +_t_stub journalctl 'echo "Archived and active journals take up 240.0M in the file system."' +check_journal_size +_t_eq "" "$(_t_findings)" "check_journal_size: does not flag a journal measured in megabytes" +_t_contains "$(_t_report)" "240.0M" \ + "check_journal_size: records the megabyte figure verbatim" + +# A space-separated figure must keep working too. +arch_reset +_t_stub journalctl 'echo "Archived and active journals take up 6.1 G in the file system."' +check_journal_size +_t_contains "$(_t_findings)" "journal is large (6.1G)" \ + "check_journal_size: still flags a space-separated gigabyte figure" + +arch_reset +_t_stub journalctl 'exit 1' +check_journal_size +_t_contains "$(_t_report)" "Journal usage: unknown" \ + "check_journal_size: records 'unknown' when journalctl fails" +_t_eq "" "$(_t_findings)" "check_journal_size: records no finding when journalctl fails" + +echo +echo "arch_perf_report (probes): ${PASS} passed, ${FAIL} failed" +((FAIL == 0)) diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report_fixes.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report_fixes.sh new file mode 100755 index 00000000..f953835b --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_perf_report_fixes.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Tests for lib/arch_perf_report.sh: apply_safe_fixes and print_summary. +# Split from test_arch_perf_report.sh for the 250-line cap. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=arch_perf_harness.sh +. "${SCRIPT_DIR}/arch_perf_harness.sh" + +# shellcheck source=../arch_perf_report.sh +. "${FIXES_DIR}/lib/arch_perf_report.sh" + +# --- apply_safe_fixes ------------------------------------------------------- + +# Off by default: the flag guards every mutation. +arch_reset +APPLY_SAFE_FIXES="false" +apply_safe_fixes >"${TEST_TMPDIR}/out" 2>&1 +out="$(cat "${TEST_TMPDIR}/out")" +_t_eq "" "$out" "apply_safe_fixes: does nothing unless APPLY_SAFE_FIXES is true" +_t_eq "" "$(_t_calls)" "apply_safe_fixes: runs no command when disabled" + +# fstrim.timer already enabled, no tlp/ppd conflict, small journal: nothing +# to do, but the pass still announces itself. +arch_reset +APPLY_SAFE_FIXES="true" +_t_stub systemctl 'exit 0' +_t_stub journalctl 'echo "Archived and active journals take up 120.0M in the file system."' +apply_safe_fixes >"${TEST_TMPDIR}/out" 2>&1 +out="$(cat "${TEST_TMPDIR}/out")" +_t_contains "$out" "Applying safe fixes" "apply_safe_fixes: announces the pass" +_t_lacks "$(_t_calls)" "systemctl enable --now fstrim.timer" \ + "apply_safe_fixes: does not re-enable an already-enabled fstrim.timer" +_t_lacks "$(_t_calls)" "journalctl --vacuum-size" \ + "apply_safe_fixes: does not vacuum a journal measured in megabytes" + +# fstrim.timer disabled: it gets enabled. +arch_reset +APPLY_SAFE_FIXES="true" +_t_stub_stdin systemctl <<'STUB_BODY' +if [[ "$1" == "is-enabled" && "$2" == "fstrim.timer" ]]; then + exit 1 +fi +exit 0 +STUB_BODY +_t_stub journalctl 'echo "120.0M"' +apply_safe_fixes >"${TEST_TMPDIR}/out" 2>&1 +out="$(cat "${TEST_TMPDIR}/out")" +_t_contains "$(_t_calls)" "systemctl enable --now fstrim.timer" \ + "apply_safe_fixes: enables a disabled fstrim.timer" +_t_contains "$(_t_actions)" "Enabled and started fstrim.timer" \ + "apply_safe_fixes: records enabling fstrim.timer as an action" + +# tlp and power-profiles-daemon both enabled: tlp is disabled to break the +# conflict. +arch_reset +APPLY_SAFE_FIXES="true" +_t_stub systemctl 'exit 0' +_t_stub journalctl 'echo "120.0M"' +apply_safe_fixes >"${TEST_TMPDIR}/out" 2>&1 +out="$(cat "${TEST_TMPDIR}/out")" +_t_contains "$(_t_calls)" "systemctl disable --now tlp.service" \ + "apply_safe_fixes: disables tlp.service when it conflicts with ppd" +_t_contains "$(_t_actions)" "Disabled tlp.service" \ + "apply_safe_fixes: records disabling tlp.service as an action" + +# A multi-gigabyte journal gets vacuumed. This only fires because the size +# regex now tolerates journalctl's spaceless "4.2G" output. +arch_reset +APPLY_SAFE_FIXES="true" +_t_stub systemctl 'exit 0' +_t_stub journalctl 'echo "Archived and active journals take up 4.2G in the file system."' +apply_safe_fixes >"${TEST_TMPDIR}/out" 2>&1 +out="$(cat "${TEST_TMPDIR}/out")" +_t_contains "$(_t_calls)" "journalctl --vacuum-size=300M" \ + "apply_safe_fixes: vacuums a multi-gigabyte journal" +_t_contains "$(_t_actions)" "Vacuumed systemd journal to 300M" \ + "apply_safe_fixes: records the vacuum as an action" + +# --- print_summary ---------------------------------------------------------- + +arch_reset +out="$(print_summary 2>&1)" +_t_contains "$out" "Arch Performance Diagnostics" "print_summary: prints the banner" +_t_contains "$out" "Report: ${REPORT_FILE}" "print_summary: names the report file" +_t_contains "$out" "No high-confidence bottlenecks detected" \ + "print_summary: reports a clean run when there are no findings" +_t_contains "$out" "systemd-analyze blame" \ + "print_summary: suggests the deep-analysis commands" + +arch_reset +add_finding "first problem" >/dev/null +add_finding "second problem" >/dev/null +out="$(print_summary 2>&1)" +_t_contains "$out" "Likely issues found (2)" \ + "print_summary: counts the findings it lists" +_t_contains "$out" "- first problem" "print_summary: lists the first finding" +_t_contains "$out" "- second problem" "print_summary: lists the second finding" +_t_lacks "$out" "No high-confidence bottlenecks" \ + "print_summary: does not claim a clean run when findings exist" + +arch_reset +add_action "do the thing" >/dev/null +out="$(print_summary 2>&1)" +_t_contains "$out" "Actions/recommendations:" \ + "print_summary: prints the actions section when actions exist" +_t_contains "$out" "- do the thing" "print_summary: lists the recorded action" + +echo +echo "arch_perf_report (fixes/summary): ${PASS} passed, ${FAIL} failed" +((FAIL == 0))