diff --git a/docs/superpowers/contracts/arch-sysctl-tests-2026-08-22.json b/docs/superpowers/contracts/arch-sysctl-tests-2026-08-22.json new file mode 100644 index 00000000..09ba0524 --- /dev/null +++ b/docs/superpowers/contracts/arch-sysctl-tests-2026-08-22.json @@ -0,0 +1,19 @@ +{ + "title": "Test arch_sysctl.sh and open the optimize_arch_desktop lib family", + "objective": "Give arch_sysctl.sh a real test suite covering both of its sysctl-tuning functions, and add the third family harness so arch_cpu.sh and arch_hardware.sh can reuse it, as the first of the eight untested fixes/lib libraries.", + "acceptance_criteria": [ + "test_arch_sysctl.sh exercises both tweak_vm_sysctl and tweak_network_sysctl across every branch: already-tuned-with-drop-in (skip), already-tuned-without-drop-in (write), value-differs (write and reload), sysctl-read-failure, and the tcp_bbr modprobe guard.", + "The whole suite stays green: run_all.sh reports every test file passing, including the 20 that predate this change.", + "shellcheck reports nothing on the new and modified files, with zero suppressions added anywhere.", + "No write reaches /etc: the two real sysctl drop-ins keep their pre-existing mtimes after the suite runs.", + "arch_sysctl.sh's production behaviour is unchanged -- SYSCTL_DROPIN_DIR defaults to the real /etc/sysctl.d.", + "arch_sysctl.sh stays on meta/shell-coverage-allowlist.txt, because it is not yet measurable through run_all.sh; the allowlist line may only be removed once the jail reports a real percentage." + ], + "out_of_scope": [ + "Removing arch_sysctl.sh from the coverage allowlist while its measurement still fails.", + "Changing the coverage instrument (meta/scripts/lib/shell_coverage_lines.py), which is scheduled after the eight libs.", + "Refactoring pacman_hook_stall_harness.sh onto lib_test_core.sh.", + "Touching arch_cpu.sh or arch_hardware.sh, which reuse this harness in later commits." + ], + "verifier": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh (all test files pass, arch_sysctl reports 23 passed / 0 failed), plus shellcheck on the four touched shell files, plus stat on /etc/sysctl.d/9[01]-*.conf showing unchanged mtimes." +} diff --git a/docs/superpowers/evidence/arch-sysctl-tests-2026-08-22.json b/docs/superpowers/evidence/arch-sysctl-tests-2026-08-22.json new file mode 100644 index 00000000..431cf273 --- /dev/null +++ b/docs/superpowers/evidence/arch-sysctl-tests-2026-08-22.json @@ -0,0 +1,57 @@ +{ + "intent": "Add a test suite for arch_sysctl.sh and the shared harness for the optimize_arch_desktop.sh lib family, covering both sysctl-tuning functions across every branch.", + "scope": [ + "linux_configuration/scripts/single_use/fixes/lib/arch_sysctl.sh (SYSCTL_DROPIN_DIR override only)", + "linux_configuration/scripts/single_use/fixes/lib/tests/arch_desktop_harness.sh (new)", + "linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_sysctl.sh (new)", + "linux_configuration/scripts/single_use/fixes/lib/tests/lib_test_core.sh (_t_run primitive)", + "Non-goal: removing arch_sysctl.sh from the coverage allowlist -- see risks, the lib is not yet measurable through run_all.sh" + ], + "changes": [ + "Added SYSCTL_DROPIN_DIR to arch_sysctl.sh, defaulting to the real /etc/sysctl.d so production behaviour is unchanged; it exists so tests never write to /etc, which matters because run_all.sh runs un-jailed in ci_mirror.sh and in CI", + "Added arch_desktop_harness.sh, the third family harness, supplying the DRY_RUN/AGGRESSIVE/TWEAKS_* globals and the apply_tweak helper that live in the entry script optimize_arch_desktop.sh rather than in any lib", + "Added test_arch_sysctl.sh: 23 assertions over 8 cases covering both functions' skip path, write-when-drop-in-missing path, value-differs path, sysctl-failure path, and tweak_network_sysctl's modprobe guard", + "Added _t_run to lib_test_core.sh: runs the subject in the current shell and captures output via a file, because command substitution forks a subshell and kcov then never registers the sourced lib" + ], + "verification": [ + { + "command": "bash linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_sysctl.sh", + "result": "pass", + "evidence": "arch_sysctl: 23 passed, 0 failed" + }, + { + "command": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh", + "result": "pass", + "evidence": "All fixes lib test files passed; 21 test files green including the new one." + }, + { + "command": "shellcheck lib_test_core.sh arch_desktop_harness.sh test_arch_sysctl.sh arch_sysctl.sh", + "result": "pass", + "evidence": "No findings, zero suppressions. The SC2034 on $out was fixed by exporting it, matching the existing treatment of cross-file globals in lib_test_core.sh." + }, + { + "command": "ls -la /etc/sysctl.d/90-desktop-performance.conf /etc/sysctl.d/91-desktop-network.conf", + "result": "pass", + "evidence": "Both still dated Mar 2, i.e. untouched by the suite: the SYSCTL_DROPIN_DIR override kept every write inside the throwaway tmpdir." + }, + { + "command": "kcov --include-pattern=arch_sysctl.sh ./g1.sh (direct call, no command substitution)", + "result": "pass", + "evidence": "arch_sysctl.sh instrumented 54 lines when the function is called directly, versus 0 lines when first called inside $( ). This is what motivated _t_run." + }, + { + "command": "shell_coverage_jail.sh --measure arch_sysctl.sh --subject .../run_all.sh", + "result": "fail", + "evidence": "'kcov instrumented no lines' persists through the jail even after the _t_run change, while the control lib ubuntu_perf_more.sh measures 32/32 = 100.00% in the same worktree and same jail invocation. The lib is therefore NOT yet measurable and its allowlist line is deliberately left in place." + } + ], + "risks": [ + "arch_sysctl.sh remains on the coverage allowlist despite having 23 passing assertions, because it cannot yet be measured through run_all.sh; the tests are real but the coverage number is unknown", + "_t_run changes the calling convention for future lib tests; existing test files are untouched and still green", + "The SYSCTL_DROPIN_DIR override adds a test seam to production code, the same seam ubuntu_perf_more.sh and arch_perf_fixes.sh already carry" + ], + "rollback": [ + "git revert the commit; arch_sysctl.sh returns to hardcoded /etc/sysctl.d paths and the two new test files disappear", + "Re-run tests/run_all.sh to confirm the remaining 20 test files are still green" + ] +} diff --git a/linux_configuration/scripts/single_use/fixes/lib/arch_sysctl.sh b/linux_configuration/scripts/single_use/fixes/lib/arch_sysctl.sh index 8f2d8107..3f9423d5 100755 --- a/linux_configuration/scripts/single_use/fixes/lib/arch_sysctl.sh +++ b/linux_configuration/scripts/single_use/fixes/lib/arch_sysctl.sh @@ -9,7 +9,9 @@ # 3. Memory & swap tuning via sysctl # =================================================================== tweak_vm_sysctl() { - local dropin="/etc/sysctl.d/90-desktop-performance.conf" + # SYSCTL_DROPIN_DIR defaults to the real directory and is overridden only + # by the test harness, which runs un-jailed in ci_mirror.sh and in CI. + local dropin="${SYSCTL_DROPIN_DIR:-/etc/sysctl.d}/90-desktop-performance.conf" # Desktop workloads: low swappiness, aggressive VFS caching, tuned dirty ratios local -A params=( @@ -62,7 +64,7 @@ VMEOF # 4. Network: TCP BBR + fastopen + buffer tuning # =================================================================== tweak_network_sysctl() { - local dropin="/etc/sysctl.d/91-desktop-network.conf" + local dropin="${SYSCTL_DROPIN_DIR:-/etc/sysctl.d}/91-desktop-network.conf" # Check if BBR module is available if ! modprobe tcp_bbr 2>/dev/null; then diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/arch_desktop_harness.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/arch_desktop_harness.sh new file mode 100755 index 00000000..4b370933 --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/arch_desktop_harness.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# lib/tests/arch_desktop_harness.sh — shared setup for the arch desktop +# optimization lib tests (arch_sysctl.sh, arch_cpu.sh, arch_hardware.sh). +# +# Sourced, not executed. Builds on lib_test_core.sh and adds the pieces these +# three libs read from their entry script, optimize_arch_desktop.sh: +# +# * DRY_RUN, AGGRESSIVE, TWEAKS_APPLIED and TWEAKS_SKIPPED; +# * apply_tweak, redefined verbatim from the entry script -- it lives there +# rather than in any lib, the same arrangement arch_perf_harness.sh +# handles with add_finding/add_action; +# * the real log_*/has_cmd helpers, via scripts/lib/common.sh. +# +# NOT to be confused with arch_perf_harness.sh, which serves a different entry +# script (diagnose_arch_performance.sh) and a different set of libs. The two +# families were mis-grouped in an earlier handoff; the distinct names are the +# fix for that. +# +# Every absolute write target of these libs is behind an override defaulting +# to the real location, because run_all.sh runs UN-jailed in ci_mirror.sh and +# in CI, where a bind mount would protect nothing. +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 optimize_arch_desktop.sh ------------------ + +# Read by the libs under test, never by this harness, so static analysis +# cannot see the use; export makes the intent explicit. +export SYSCTL_DROPIN_DIR="${TEST_TMPDIR}/sysctl.d" +export UDEV_RULES_DIR="${TEST_TMPDIR}/udev.rules.d" +export CPUPOWER_CONF_FILE="${TEST_TMPDIR}/cpupower" +export SYSTEMD_UNIT_DIR="${TEST_TMPDIR}/systemd_units" +export JOURNALD_CONF_DIR="${TEST_TMPDIR}/journald.conf.d" +export DRY_RUN="false" +export AGGRESSIVE="false" +export INTERACTIVE_MODE="false" +TWEAKS_APPLIED=0 +TWEAKS_SKIPPED=0 + +# Verbatim from optimize_arch_desktop.sh. +apply_tweak() { + local description="$1" + shift + + echo "" + log_info "$description" + + if [[ $DRY_RUN == "true" ]]; then + echo " [dry-run] Would run: $*" + return 0 + fi + + if [[ $INTERACTIVE_MODE == "true" ]]; then + if ! ask_yes_no " Apply this optimization?"; then + log_warn "Skipped." + ((TWEAKS_SKIPPED++)) || true + return 0 + fi + fi + + if "$@"; then + log_ok "Done." + ((TWEAKS_APPLIED++)) || true + else + log_error "Failed (non-fatal, continuing)." + fi +} + +# --- external tool stubs ---------------------------------------------------- + +_arch_desktop_default_stubs() { + local tool + for tool in sysctl modprobe udevadm cpupower systemctl nvidia-smi \ + lsblk lscpu journalctl pacman; do + _t_stub "$tool" 'exit 0' + done +} + +# arch_desktop_reset — start a test group from "nothing has happened yet". +arch_desktop_reset() { + _t_reset_calls + TWEAKS_APPLIED=0 + TWEAKS_SKIPPED=0 + DRY_RUN="false" + AGGRESSIVE="false" + INTERACTIVE_MODE="false" + export DRY_RUN AGGRESSIVE INTERACTIVE_MODE + rm -rf "${SYSCTL_DROPIN_DIR}" "${UDEV_RULES_DIR}" "${SYSTEMD_UNIT_DIR}" \ + "${JOURNALD_CONF_DIR}" + mkdir -p "${SYSCTL_DROPIN_DIR}" "${UDEV_RULES_DIR}" "${SYSTEMD_UNIT_DIR}" \ + "${JOURNALD_CONF_DIR}" + rm -f "${CPUPOWER_CONF_FILE}" + _t_full_path + _arch_desktop_default_stubs +} + +# _t_dropin FILE — the contents of a sysctl drop-in written this round. +_t_dropin() { + cat "${SYSCTL_DROPIN_DIR}/$1" 2>/dev/null || true +} + +arch_desktop_reset 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 3f60b3f2..d96e1442 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 @@ -195,6 +195,28 @@ _t_full_path() { export LIB_TEST_ORIG_PATH="${PATH#"${FAKE_BIN}:"}" +# _t_run FUNC [ARG...] — run FUNC in THE CURRENT SHELL, putting its combined +# output in $out and returning its exit status. +# +# Deliberately not `out="$(func)"`. Command substitution forks a subshell, and +# kcov does not register a sourced file whose first execution happens inside +# one: the lib then reports "kcov instrumented no lines" and cannot be +# measured at all, even though every assertion against it passes. Capturing +# through a file keeps the call in the current shell, so the lib is +# registered. Measured on arch_sysctl.sh: 0 lines via $( ), 54 lines via this. +# $out is the output of the most recent _t_run, read by the assertions at +# every call site. Exported for the same reason the other cross-file globals +# here are: the readers live in other files, so static analysis cannot see +# the use from this one. +export out="" + +_t_run() { + local rc=0 + "$@" >"${TEST_TMPDIR}/run_out" 2>&1 || rc=$? + out="$(cat "${TEST_TMPDIR}/run_out")" + return "$rc" +} + # _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_sysctl.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_sysctl.sh new file mode 100755 index 00000000..44149841 --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/test_arch_sysctl.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# lib/tests/test_arch_sysctl.sh — tests for arch_sysctl.sh. +# +# Both functions share a shape: probe every parameter with `sysctl -n`, set +# needs_update when any current value differs from the wanted one, skip when +# nothing differs AND the drop-in already exists, otherwise write the drop-in +# and reload. The cases below walk each arm of that shape for both functions, +# plus tweak_network_sysctl's modprobe guard. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=arch_desktop_harness.sh +. "${SCRIPT_DIR}/arch_desktop_harness.sh" + +# shellcheck source=../arch_sysctl.sh +. "${FIXES_DIR}/lib/arch_sysctl.sh" + +printf '\n-- tweak_vm_sysctl --\n' + +# A sysctl stub that reports every parameter as already at its wanted value, +# so needs_update stays false. Values are keyed by the parameter name in $2 +# (sysctl is called as `sysctl -n KEY`). +_stub_sysctl_tuned() { + _t_stub_stdin sysctl <<'STUB' +case "$2" in +vm.swappiness) echo 10 ;; +vm.vfs_cache_pressure) echo 50 ;; +vm.dirty_ratio) echo 15 ;; +vm.dirty_background_ratio) echo 5 ;; +vm.dirty_writeback_centisecs) echo 1500 ;; +vm.page-cluster) echo 0 ;; +net.core.default_qdisc) echo fq ;; +net.ipv4.tcp_congestion_control) echo bbr ;; +net.ipv4.tcp_fastopen) echo 3 ;; +net.core.rmem_max) echo 16777216 ;; +net.core.wmem_max) echo 16777216 ;; +net.ipv4.tcp_rmem) printf '4096\t1048576\t16777216\n' ;; +net.ipv4.tcp_wmem) printf '4096\t1048576\t16777216\n' ;; +net.ipv4.tcp_mtu_probing) echo 1 ;; +*) exit 0 ;; +esac +STUB +} + +# Case 1: values already tuned AND the drop-in exists -> skip without writing. +arch_desktop_reset +_stub_sysctl_tuned +: >"${SYSCTL_DROPIN_DIR}/90-desktop-performance.conf" +_t_run tweak_vm_sysctl +_t_eq "0" "$?" "tweak_vm_sysctl: returns 0 when already tuned" +_t_contains "$out" "already tuned" "tweak_vm_sysctl: reports the skip" +_t_eq "" "$(_t_dropin 90-desktop-performance.conf)" \ + "tweak_vm_sysctl: leaves the existing drop-in untouched" +_t_lacks "$(_t_calls)" "sysctl --system" \ + "tweak_vm_sysctl: does not reload when skipping" + +# Case 2: values already tuned but NO drop-in file -> must still write it. +# This is the arm that a naive "nothing to do" check would wrongly skip. +arch_desktop_reset +_stub_sysctl_tuned +_t_run tweak_vm_sysctl +_t_contains "$(_t_dropin 90-desktop-performance.conf)" "vm.swappiness = 10" \ + "tweak_vm_sysctl: writes the drop-in when it is missing" +_t_lacks "$out" "already tuned" \ + "tweak_vm_sysctl: does not report a skip when the drop-in is absent" + +# Case 3: a value differs -> write the drop-in and reload. +arch_desktop_reset +_t_stub_stdin sysctl <<'STUB' +case "$2" in +vm.swappiness) echo 60 ;; +*) exit 0 ;; +esac +STUB +_t_run tweak_vm_sysctl +dropin="$(_t_dropin 90-desktop-performance.conf)" +_t_contains "$dropin" "vm.swappiness = 10" "tweak_vm_sysctl: writes swappiness" +_t_contains "$dropin" "vm.vfs_cache_pressure = 50" \ + "tweak_vm_sysctl: writes vfs_cache_pressure" +_t_contains "$dropin" "vm.page-cluster = 0" "tweak_vm_sysctl: writes page-cluster" +_t_contains "$dropin" "managed by optimize_arch_desktop.sh" \ + "tweak_vm_sysctl: stamps the drop-in as managed" +_t_contains "$(_t_calls)" "sysctl --system" "tweak_vm_sysctl: reloads sysctl" + +# Case 4: sysctl failing outright (unknown key) still reaches the write path. +arch_desktop_reset +_t_stub sysctl 'exit 1' +_t_run tweak_vm_sysctl +_t_eq "0" "$?" "tweak_vm_sysctl: returns 0 when sysctl -n fails" +_t_contains "$(_t_dropin 90-desktop-performance.conf)" "vm.dirty_ratio = 15" \ + "tweak_vm_sysctl: writes the drop-in when current values are unreadable" + +printf '\n-- tweak_network_sysctl --\n' + +# Case 5: the tcp_bbr module is unavailable -> warn and return without writing. +arch_desktop_reset +_t_stub modprobe 'exit 1' +_t_run tweak_network_sysctl +_t_eq "0" "$?" "tweak_network_sysctl: returns 0 when tcp_bbr is unavailable" +_t_contains "$out" "tcp_bbr kernel module unavailable" \ + "tweak_network_sysctl: warns about the missing module" +_t_eq "" "$(_t_dropin 91-desktop-network.conf)" \ + "tweak_network_sysctl: writes nothing without tcp_bbr" + +# Case 6: already tuned AND drop-in present -> skip. The tcp_rmem/tcp_wmem +# values come back tab-separated from the kernel, which is why the lib +# normalizes both sides through xargs before comparing; this case is what +# proves that normalization works. +arch_desktop_reset +_stub_sysctl_tuned +: >"${SYSCTL_DROPIN_DIR}/91-desktop-network.conf" +_t_run tweak_network_sysctl +_t_contains "$out" "already tuned" \ + "tweak_network_sysctl: skips when tab-separated values match" +_t_lacks "$(_t_calls)" "sysctl --system" \ + "tweak_network_sysctl: does not reload when skipping" + +# Case 7: already tuned but no drop-in -> write it. +arch_desktop_reset +_stub_sysctl_tuned +_t_run tweak_network_sysctl +_t_contains "$(_t_dropin 91-desktop-network.conf)" "net.ipv4.tcp_congestion_control = bbr" \ + "tweak_network_sysctl: writes the drop-in when it is missing" + +# Case 8: a value differs -> write and reload. +arch_desktop_reset +_t_stub_stdin sysctl <<'STUB' +case "$2" in +net.ipv4.tcp_congestion_control) echo cubic ;; +*) exit 0 ;; +esac +STUB +_t_run tweak_network_sysctl +dropin="$(_t_dropin 91-desktop-network.conf)" +_t_contains "$dropin" "net.core.default_qdisc = fq" \ + "tweak_network_sysctl: writes the qdisc" +_t_contains "$dropin" "net.ipv4.tcp_rmem = 4096 1048576 16777216" \ + "tweak_network_sysctl: writes the receive buffers" +_t_contains "$dropin" "net.ipv4.tcp_mtu_probing = 1" \ + "tweak_network_sysctl: writes mtu probing" +_t_contains "$(_t_calls)" "sysctl --system" "tweak_network_sysctl: reloads sysctl" + +printf '\narch_sysctl: %d passed, %d failed\n' "$PASS" "$FAIL" +[[ $FAIL -eq 0 ]]