diff --git a/docs/superpowers/contracts/unity-handler-off-allowlist-2026-08-22.json b/docs/superpowers/contracts/unity-handler-off-allowlist-2026-08-22.json new file mode 100644 index 00000000..1daaebf0 --- /dev/null +++ b/docs/superpowers/contracts/unity-handler-off-allowlist-2026-08-22.json @@ -0,0 +1,19 @@ +{ + "title": "Take unity_handler.sh off the shell-coverage allowlist at a measured 100%", + "objective": "Cover unity_handler.sh to 100% through its directory's runner and remove it from meta/shell-coverage-allowlist.txt -- the campaign's first removal this session -- while fixing the bash hash-cache defect that had made 'missing tool' assertions in the same directory vacuous.", + "acceptance_criteria": [ + "unity_handler.sh measures 100.00% through tests/run_all.sh, the subject is_covered() builds, with --fail-on-case-error passing.", + "check_shell_coverage.sh passes for unity_handler.sh AFTER the allowlist entry is deleted -- the real gate, not just the measurement.", + "meta/shell-coverage-allowlist.txt drops from 105 to 104 entries and no entry is ever added.", + "Every 'tool is missing' assertion genuinely hides the tool: removal must clear bash's command hash table, or the assertion tests the opposite of its name.", + "The two suites committed earlier in the same directory are corrected for the same defect, and their assertion counts are re-verified rather than assumed.", + "The other two libs' measured figures are unchanged by the harness edit.", + "All three suites pass with 0 failures; shellcheck clean with no suppressions; all files within the 250-line cap." + ], + "out_of_scope": [ + "clean_audio_filters.sh and transcribe_venv.sh, the two libs in this directory still without suites.", + "Auditing every other suite in the repo for the same bare-`rm` unstub defect; it is recorded as a risk for a later sweep.", + "Any change to unity_handler.sh itself." + ], + "verifier": "meta/scripts/shell_coverage_jail.sh --subject .../testsAndMisc-bash/lib/tests/run_all.sh --measure unity_handler.sh --min 100 --fail-on-case-error (expects 32/32 = 100.00%, exit 0), then bash meta/scripts/check_shell_coverage.sh on that path with the allowlist entry removed (expects exit 0), plus .../tests/run_all.sh reporting 15 + 18 + 18 assertions and 0 failures." +} diff --git a/docs/superpowers/evidence/unity-handler-off-allowlist-2026-08-22.json b/docs/superpowers/evidence/unity-handler-off-allowlist-2026-08-22.json new file mode 100644 index 00000000..c48e1713 --- /dev/null +++ b/docs/superpowers/evidence/unity-handler-off-allowlist-2026-08-22.json @@ -0,0 +1,51 @@ +{ + "intent": "Cover unity_handler.sh to a measured 100% and take it OFF the shell-coverage allowlist -- the first entry removed this session, 105 -> 104. Fix the bash hash-cache bug found doing it, which had silently weakened the two suites committed just before.", + "scope": [ + ".../testsAndMisc-bash/lib/tests/test_unity_handler.sh: new, 18 assertions across all 4 functions", + ".../tests/transcribe_harness.sh: new _t_unstub helper; basename added to the restricted PATH", + ".../tests/test_transcribe_deps.sh and test_transcribe_pkgmgr.sh: switched to _t_unstub", + "meta/shell-coverage-allowlist.txt: unity_handler.sh removed", + "Non-goal: no change to any subject; no latent bug surfaced in unity_handler.sh" + ], + "changes": [ + "HARNESS BUG, found by an assertion that would not go green: bash caches executable locations in a hash table, so `command -v foo` keeps SUCCEEDING after foo is deleted from the stub dir. Every 'tool is missing' test therefore ran as a 'tool is present' test -- asserting the opposite of its own name while looking plausible. Reproduced minimally: after `rm`, STILL FOUND; after `hash -r`, gone.", + "The fix is a _t_unstub helper that removes the stub and clears the table, applied to all three suites in the directory. The two committed an hour earlier were affected and are corrected here; test_transcribe_pkgmgr.sh goes 15 -> 18 passing assertions as a result, so three of its checks had been vacuous.", + "`basename` was added to the restricted PATH because the SUBJECT calls it, unlike bash/env which the stubs' shebangs need. The suite avoids needing `tail` by taking the last line with a parameter expansion instead: every binary in that PATH is another thing a shebang can trip over.", + "The generated .desktop file is asserted on its CONTENT (Exec and MimeType lines), and xdg-mime registration on the BASENAME it is given -- a full path there would silently fail to register." + ], + "verification": [ + { + "command": "shell_coverage_jail.sh --subject .../tests/run_all.sh --measure unity_handler.sh --min 100 --fail-on-case-error", + "result": "pass", + "evidence": "unity_handler.sh: 32/32 lines = 100.00%, exit 0 at --min 100. Measured through run_all.sh, the subject is_covered() builds." + }, + { + "command": "bash meta/scripts/check_shell_coverage.sh .../unity_handler.sh (AFTER removing it from the allowlist)", + "result": "pass", + "evidence": "'every checked shell library meets the coverage bar', exit 0. The lib passes the REAL gate with no exemption -- properly cleared, not merely measured." + }, + { + "command": ".../lib/tests/run_all.sh", + "result": "pass", + "evidence": "15 + 18 + 18 = 51 assertions, 0 failures, 'All transcribe lib test files passed.'" + }, + { + "command": "minimal repro of the hash cache: stub a command, run it, rm it, then command -v", + "result": "pass", + "evidence": "'STILL FOUND (hash cache)' before `hash -r`, 'gone after hash -r'. This is what makes _t_unstub necessary rather than cosmetic." + }, + { + "command": "re-measure the other two libs after the harness change", + "result": "pass", + "evidence": "transcribe_deps.sh 48/52 = 92.31% and transcribe_pkgmgr.sh 49/50 = 98.00%, both unchanged. Both stay on the allowlist." + } + ], + "risks": [ + "Any other suite in the repo that removes a stub with a bare `rm` has the same latent defect: its 'missing tool' branch is not being tested. The features/lib suites use a jail and mostly add rather than remove stubs, but this is worth a sweep before the gate is armed.", + "The restricted PATH must list every binary the subjects use. A missing one surfaces as 'command not found' mid-suite rather than as a clear assertion failure." + ], + "rollback": [ + "git revert the commit; unity_handler.sh returns to the allowlist and the three suites return to bare `rm` unstubbing.", + "After rollback, confirm the allowlist is back to 105 entries so the gate's view is unchanged." + ] +} diff --git a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_deps.sh b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_deps.sh index 1e514699..fb5633f9 100755 --- a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_deps.sh +++ b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_deps.sh @@ -38,7 +38,7 @@ _t_has "$present_out" 'System deps present' "install_system_deps reports a satis _t_lacks "$(_t_calls)" 'pacman' "nothing is installed when ffmpeg and espeak-ng are both present" # --- offline with a missing dep exits 5 ------------------------------------- -rm -f "$TEST_TMPDIR/bin/ffmpeg" +_t_unstub ffmpeg offline_rc=0 ( OFFLINE=1 @@ -47,7 +47,7 @@ offline_rc=0 _t_eq "5" "$offline_rc" "a missing dep in offline mode exits 5" # --- no sudo means the install is skipped, not failed ----------------------- -rm -f "$TEST_TMPDIR/bin/sudo" +_t_unstub sudo _t_stub pacman no_sudo_out="$(install_system_deps 2>&1)" _t_has "$no_sudo_out" 'sudo not found' "a missing sudo skips the install instead of failing" @@ -63,7 +63,7 @@ _t_has "$pac_calls" 'ffmpeg' "the pacman branch installs the missing ffmpeg" _t_lacks "$pac_calls" 'espeak-ng' "the pacman branch omits espeak-ng, which is already present" # --- the apt branch uses Debian package names and refreshes first ----------- -rm -f "$TEST_TMPDIR/bin/pacman" +_t_unstub pacman _t_stub apt-get _t_reset_calls install_system_deps >/dev/null 2>&1 @@ -72,28 +72,28 @@ _t_has "$apt_calls" 'apt-get update -y' "the apt branch refreshes the package li _t_has "$apt_calls" 'python3-venv' "the apt branch asks for python3-venv, not python-virtualenv" # --- the dnf branch --------------------------------------------------------- -rm -f "$TEST_TMPDIR/bin/apt-get" +_t_unstub apt-get _t_stub dnf _t_reset_calls install_system_deps >/dev/null 2>&1 _t_has "$(_t_calls)" 'dnf install -y python3-venv' "the dnf branch installs python3-venv" # --- the yum branch --------------------------------------------------------- -rm -f "$TEST_TMPDIR/bin/dnf" +_t_unstub dnf _t_stub yum _t_reset_calls install_system_deps >/dev/null 2>&1 _t_has "$(_t_calls)" 'yum install -y python3-venv' "the yum branch installs python3-venv" # --- the zypper branch uses its own versioned python names ------------------ -rm -f "$TEST_TMPDIR/bin/yum" +_t_unstub yum _t_stub zypper _t_reset_calls install_system_deps >/dev/null 2>&1 _t_has "$(_t_calls)" 'python311-virtualenv' "the zypper branch asks for python311-virtualenv" # --- an unknown package manager warns rather than guessing ------------------ -rm -f "$TEST_TMPDIR/bin/zypper" +_t_unstub zypper unknown_out="$(install_system_deps 2>&1)" _t_has "$unknown_out" 'Unknown package manager' "an unrecognised manager warns instead of installing" diff --git a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_pkgmgr.sh b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_pkgmgr.sh index ef9da1c9..9bd1e7f0 100755 --- a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_pkgmgr.sh +++ b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_transcribe_pkgmgr.sh @@ -76,7 +76,9 @@ _t_has "$no_sudo_out" 'sudo not found' "ensure_cuda_runtime reports a missing su # sudo is a pass-through so the manager stub below records the real argv. printf '#!/usr/bin/env bash\nexec "$@"\n' >"$TEST_TMPDIR/bin/sudo" chmod +x "$TEST_TMPDIR/bin/sudo" -rm -f "$TEST_TMPDIR/bin/apt-get" "$TEST_TMPDIR/bin/dnf" "$TEST_TMPDIR/bin/yum" +_t_unstub apt-get +_t_unstub dnf +_t_unstub yum _t_reset_calls pac_rc=0 (ensure_cuda_runtime) >/dev/null 2>&1 || pac_rc=$? @@ -90,27 +92,28 @@ _t_reset_calls _t_has "$(_t_calls)" 'apt-get install -y nvidia-cuda-toolkit' "the apt branch installs the CUDA toolkit" # --- ensure_cuda_runtime: dnf and yum share one branch ---------------------- -rm -f "$TEST_TMPDIR/bin/apt-get" +_t_unstub apt-get _t_stub dnf _t_reset_calls (ensure_cuda_runtime) >/dev/null 2>&1 || true _t_has "$(_t_calls)" 'dnf install -y cuda cudnn' "the dnf branch installs cuda and cudnn" -rm -f "$TEST_TMPDIR/bin/dnf" +_t_unstub dnf _t_stub yum _t_reset_calls (ensure_cuda_runtime) >/dev/null 2>&1 || true _t_has "$(_t_calls)" 'yum install -y cuda cudnn' "the shared dnf|yum branch also drives yum" # --- ensure_cuda_runtime: the zypper branch --------------------------------- -rm -f "$TEST_TMPDIR/bin/yum" "$TEST_TMPDIR/bin/pacman" +_t_unstub yum +_t_unstub pacman _t_stub zypper _t_reset_calls (ensure_cuda_runtime) >/dev/null 2>&1 || true _t_has "$(_t_calls)" 'zypper install -y cuda cudnn' "the zypper branch installs cuda and cudnn" # --- ensure_cuda_runtime: an unknown manager warns -------------------------- -rm -f "$TEST_TMPDIR/bin/zypper" +_t_unstub zypper unknown_out="$( (ensure_cuda_runtime) 2>&1)" || true _t_has "$unknown_out" 'cannot install CUDA automatically' "an unknown package manager warns rather than guessing" diff --git a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_unity_handler.sh b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_unity_handler.sh new file mode 100755 index 00000000..3c16b2e2 --- /dev/null +++ b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/test_unity_handler.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# Tests for unity_handler.sh — the unityhub:// URL handler registration. +# +# No jail: the only write goes to $DESKTOP_DIR, pointed at a throwaway dir, +# and every external (update-desktop-database, xdg-mime, xdg-open) is +# intercepted by the harness's PATH stub dir. +# +# The generated .desktop file is asserted on its CONTENT: the MimeType and +# Exec lines are what make the handler work, and a file that exists with the +# wrong Exec is exactly the bug a presence check would pass. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./transcribe_harness.sh +. "$SCRIPT_DIR/transcribe_harness.sh" + +_t_setup_env +trap _t_teardown EXIT + +# Globals the entry script defines above its source line. +DESKTOP_DIR="$TEST_TMPDIR/applications" +RUN_TEST=false +mkdir -p "$DESKTOP_DIR" + +log_info() { printf '[INFO] %s\n' "$*"; } +log_ok() { printf '[OK] %s\n' "$*"; } +log_warn() { printf '[WARN] %s\n' "$*"; } +log_error() { printf '[ERR] %s\n' "$*"; } + +# shellcheck source=../unity_handler.sh +. "$TRANSCRIBE_LIB_DIR/unity_handler.sh" + +# --- create_handler_desktop ------------------------------------------------- +create_out="$(create_handler_desktop '/opt/unityhub/unityhub %u')" +desktop_file="${create_out##*$'\n'}" +_t_eq "$DESKTOP_DIR/unityhub-url-handler.desktop" "$desktop_file" \ + "create_handler_desktop returns the path it wrote" +desktop_body="$(cat "$desktop_file")" +_t_has "$desktop_body" 'Exec=/opt/unityhub/unityhub %u' \ + "the Exec line carries the command it was given" +_t_has "$desktop_body" 'x-scheme-handler/unityhub;x-scheme-handler/unity;' \ + "both URL schemes are declared in MimeType" +_t_has "$desktop_body" 'NoDisplay=true' "the entry is hidden from menus" + +# --- register_mime_handler: both tools present ------------------------------ +_t_stub update-desktop-database +_t_stub xdg-mime +_t_reset_calls +register_mime_handler "$desktop_file" >/dev/null +mime_calls="$(_t_calls)" +_t_has "$mime_calls" 'update-desktop-database' "the desktop database is refreshed" +_t_has "$mime_calls" 'default unityhub-url-handler.desktop x-scheme-handler/unityhub' \ + "xdg-mime registers the unityhub scheme by BASENAME, not full path" +_t_has "$mime_calls" 'unityhub-url-handler.desktop x-scheme-handler/unity' \ + "xdg-mime also registers the bare unity scheme" + +# --- register_mime_handler: no update-desktop-database is survivable -------- +_t_unstub update-desktop-database +no_db_out="$(register_mime_handler "$desktop_file" 2>&1)" +_t_has "$no_db_out" 'update-desktop-database not found' \ + "a missing update-desktop-database is reported" +_t_has "$no_db_out" 'MIME handler registered' "registration still completes without it" + +# --- register_mime_handler: no xdg-mime is FATAL ---------------------------- +# Without xdg-mime nothing is registered, so this must fail rather than +# report success -- the distinction the else branch exists to make. +_t_unstub xdg-mime +no_mime_rc=0 +register_mime_handler "$desktop_file" >/dev/null 2>&1 || no_mime_rc=$? +_t_eq "1" "$no_mime_rc" "a missing xdg-mime returns non-zero rather than claiming success" + +# --- verify_registration: the handler matches ------------------------------- +cat >"$TEST_TMPDIR/bin/xdg-mime" <<'XDGMIME' +#!/usr/bin/env bash +printf 'unityhub-url-handler.desktop\n' +XDGMIME +chmod +x "$TEST_TMPDIR/bin/xdg-mime" +match_out="$(verify_registration "$desktop_file" 2>&1)" +_t_has "$match_out" 'correctly set to unityhub-url-handler.desktop' \ + "verify_registration confirms a correctly registered handler" + +# --- verify_registration: a foreign handler warns --------------------------- +cat >"$TEST_TMPDIR/bin/xdg-mime" <<'XDGMIME' +#!/usr/bin/env bash +printf 'some-other-app.desktop\n' +XDGMIME +chmod +x "$TEST_TMPDIR/bin/xdg-mime" +wrong_out="$(verify_registration "$desktop_file" 2>&1)" +_t_has "$wrong_out" 'not set to' "verify_registration warns when another app owns the scheme" +_t_has "$wrong_out" 'some-other-app.desktop' "the warning names the handler actually registered" + +# --- verify_registration: nothing registered at all ------------------------- +cat >"$TEST_TMPDIR/bin/xdg-mime" <<'XDGMIME' +#!/usr/bin/env bash +exit 1 +XDGMIME +chmod +x "$TEST_TMPDIR/bin/xdg-mime" +none_out="$(verify_registration "$desktop_file" 2>&1)" +_t_has "$none_out" '' "an unset scheme is reported as rather than blank" + +# --- maybe_test_open: disabled prints the manual command -------------------- +manual_out="$(maybe_test_open 2>&1)" +_t_has "$manual_out" 'test manually with' "RUN_TEST=false explains how to test by hand" + +# --- maybe_test_open: enabled invokes xdg-open ------------------------------ +_t_stub xdg-open +_t_reset_calls +opened_out="$( + RUN_TEST=true + maybe_test_open 2>&1 +)" +_t_has "$(_t_calls)" 'unityhub://v1/editor-signin' "RUN_TEST=true opens the test link" +_t_has "$opened_out" 'Test link invoked' "the run reports that the link was invoked" + +# --- maybe_test_open: enabled but xdg-open missing -------------------------- +_t_unstub xdg-open +no_open_out="$( + RUN_TEST=true + maybe_test_open 2>&1 +)" +_t_has "$no_open_out" 'xdg-open not found' "a missing xdg-open warns instead of failing" + +_t_report "test_unity_handler.sh" diff --git a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/transcribe_harness.sh b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/transcribe_harness.sh index 2c498f7a..d7ae5447 100755 --- a/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/transcribe_harness.sh +++ b/linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/tests/transcribe_harness.sh @@ -70,7 +70,7 @@ _t_setup_env() { # shebang, and env resolves it through the RESTRICTED PATH. Without it each # stub dies with "env: 'bash': No such file or directory" and the branch it # was standing in for is silently never taken. - for tool in bash cat chmod cp env grep ln mkdir mktemp printf rm sed; do + for tool in basename bash cat chmod cp env grep ln mkdir mktemp printf rm sed; do if command -v "$tool" >/dev/null 2>&1; then ln -sf "$(command -v "$tool")" "$TEST_TMPDIR/coreutils/$tool" fi @@ -94,6 +94,16 @@ _t_stub_failing() { chmod +x "$TEST_TMPDIR/bin/$1" } +# Remove a stub AND forget it. bash caches executable locations in a hash +# table, so `command -v foo` keeps succeeding after foo is deleted until the +# table is cleared -- which silently turns every "tool is missing" test into a +# "tool is present" test that asserts the opposite of its own name. Verified: +# without the `hash -r` the removed stub is STILL FOUND. +_t_unstub() { + rm -f "$TEST_TMPDIR/bin/$1" + hash -r +} + _t_calls() { if [[ -f "$TEST_TMPDIR/calls.log" ]]; then cat "$TEST_TMPDIR/calls.log" diff --git a/meta/shell-coverage-allowlist.txt b/meta/shell-coverage-allowlist.txt index 19c951e3..56975067 100644 --- a/meta/shell-coverage-allowlist.txt +++ b/meta/shell-coverage-allowlist.txt @@ -101,7 +101,6 @@ linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/clean_audio_fi linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/transcribe_deps.sh linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/transcribe_pkgmgr.sh linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/transcribe_venv.sh -linux_configuration/scripts/single_use/misc/testsAndMisc-bash/lib/unity_handler.sh meta/lib/lint_full_tools.sh meta/lib/lint_output.sh meta/scripts/lib/disk_cleanup_common.sh