From 0e9cc0ce950804eaede6b774a02e2659ab3d0aa4 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sat, 22 Aug 2026 16:47:40 +0200 Subject: [PATCH] Test bt_pairing.sh to 100%: allowlist 97 -> 96 Second of the 16 untested fixes/lib libs. 65/65 = 100.00% through run_all.sh, twice, the second at --min 100 (exit 0), then cleared from the allowlist and confirmed by the real gate with no exemption. Two findings worth keeping: - udevadm was NOT in the harness's stub list, so `udevadm control --reload-rules` was reaching the REAL binary during the suite. A prepended stub dir cannot hide a tool the host actually has. Caught by asserting on the recorded call rather than by reading the code, which is the argument for asserting on calls at all. - _disable_usb_autosuspend writes /etc/udev/rules.d/50-bluetooth-no- autosuspend.rules. Binding /etc in the jail would have covered it, and was rejected: run_all.sh runs UN-jailed in ci_mirror.sh and in the shell-tests workflow, as its own header says, so a bind mount protects the coverage run and leaves every bare run writing to a real root-owned file. Instead the lib gained a UDEV_RULES_DIR override, defaulting to the real path -- the same env-override seam pacman_hook_stall_harness.sh uses for PACMAN_LOG/CACHE_DIR/PACMAN_LOCK. Proof the suite is safe where it runs un-jailed: after a bare run_all.sh, the real rule file still has its 2026-02-21 mtime (it is a genuine artifact of a production run six months ago) and the probed power/control values are unchanged. The jail cannot give that check. fix_usb_autosuspend globs the real /sys/bus/usb/devices/*/ with no override, so its branches are driven through stubbed lsusb output: ffff:ffff matches nothing on any machine and reaches the not-found arm, while 046d:c077 (control=on) and 046d:0825 (control=auto) are read-only probes, each guarded so the case self-skips with a note if the host state changes rather than silently asserting nothing. Split across two test files to stay under the 250-line cap; run_all.sh globs test_*.sh, so splitting costs nothing. Co-Authored-By: Claude Opus 5 (1M context) --- .../bt-pairing-coverage-2026-08-22.json | 17 ++ .../bt-pairing-coverage-2026-08-22.json | 53 ++++++ .../single_use/fixes/lib/bt_pairing.sh | 11 +- .../single_use/fixes/lib/tests/bt_harness.sh | 3 +- .../fixes/lib/tests/test_bt_pairing.sh | 98 ++++++++++ .../fixes/lib/tests/test_bt_pairing_usb.sh | 173 ++++++++++++++++++ meta/shell-coverage-allowlist.txt | 1 - 7 files changed, 350 insertions(+), 6 deletions(-) create mode 100644 docs/superpowers/contracts/bt-pairing-coverage-2026-08-22.json create mode 100644 docs/superpowers/evidence/bt-pairing-coverage-2026-08-22.json create mode 100755 linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing.sh create mode 100755 linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing_usb.sh diff --git a/docs/superpowers/contracts/bt-pairing-coverage-2026-08-22.json b/docs/superpowers/contracts/bt-pairing-coverage-2026-08-22.json new file mode 100644 index 00000000..cfe427a2 --- /dev/null +++ b/docs/superpowers/contracts/bt-pairing-coverage-2026-08-22.json @@ -0,0 +1,17 @@ +{ + "title": "Test bt_pairing.sh to 100% and clear it from the shell-coverage allowlist", + "objective": "Cover the second of the sixteen untested linux_configuration/scripts/single_use/fixes/lib libs. bt_pairing.sh is the first in the family whose code writes outside its own process -- a sysfs power/control file and a udev rule under /etc -- so the contract is to reach 100% without ever letting the suite touch either, given that lib/tests/run_all.sh runs UN-jailed in ci_mirror.sh and in CI.", + "acceptance_criteria": [ + "bt_pairing.sh measures 100.00% through lib/tests/run_all.sh, twice, the second run at --min 100 with exit 0.", + "check_shell_coverage.sh exits 0 for bt_pairing.sh with its allowlist line removed, i.e. with no exemption.", + "A bare (un-jailed) run of run_all.sh leaves /etc/udev/rules.d/50-bluetooth-no-autosuspend.rules with its original 2026-02-21 mtime, and leaves the power/control values of the probed USB devices unchanged.", + "Every test file stays under the repo's 250-line cap.", + "pre-commit passes on all changed files with no suppression added -- no # shellcheck disable, no noqa." + ], + "out_of_scope": [ + "Bind-mounting /etc in the jail to make the udev write safe. Rejected: run_all.sh runs un-jailed in ci_mirror.sh and CI, so a bind mount would protect only the coverage run.", + "Refactoring pacman_hook_stall_harness.sh onto the new lib_test_core.sh. Its six libs are already gated at 100% with no exemption.", + "The remaining fourteen untested fixes/lib libs, which land in later commits." + ], + "verifier": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh (expects all files passing, 11 + 20 for the two bt_pairing files), plus bash meta/scripts/shell_coverage_jail.sh --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure bt_pairing.sh --min 100 --fail-on-case-error -- \"\" (expects 65/65 = 100.00% and exit 0), plus bash meta/scripts/check_shell_coverage.sh linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh (expects exit 0), plus stat -c '%y' /etc/udev/rules.d/50-bluetooth-no-autosuspend.rules (expects 2026-02-21)." +} diff --git a/docs/superpowers/evidence/bt-pairing-coverage-2026-08-22.json b/docs/superpowers/evidence/bt-pairing-coverage-2026-08-22.json new file mode 100644 index 00000000..4a5b9e91 --- /dev/null +++ b/docs/superpowers/evidence/bt-pairing-coverage-2026-08-22.json @@ -0,0 +1,53 @@ +{ + "intent": "Test linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh to 100% line coverage and take it off the shell-coverage allowlist, second of the 16 untested fixes/lib libs.", + "scope": [ + "linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing.sh and test_bt_pairing_usb.sh (new, 11 + 20 = 31 assertions; split across two files to stay under the 250-line cap, which run_all.sh's test_*.sh glob makes free)", + "linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh (one line: UDEV_RULES_DIR override)", + "linux_configuration/scripts/single_use/fixes/lib/tests/bt_harness.sh (stub udevadm, which was reaching the real binary)", + "meta/shell-coverage-allowlist.txt (one line removed)", + "Non-goal: bind-mounting /etc to make the udev write safe -- rejected, see risks" + ], + "changes": [ + "Added test_bt_pairing.sh covering remove_stale_pairing (empty TARGET_MAC, paired-but-not-connected, paired-and-connected, not-paired, and device-unknown) and restart_bluetooth (both arms of the FIXES_APPLIED guard); added test_bt_pairing_usb.sh covering fix_usb_autosuspend (no bluetooth device, unparseable USB ID, no matching sysfs path, control already 'on', and control 'auto') and _disable_usb_autosuspend directly, including both arms of its idempotence guard and the different-vendor rewrite.", + "Added a UDEV_RULES_DIR override to bt_pairing.sh:_disable_usb_autosuspend so the test suite can redirect the udev rule write out of /etc. Production default is unchanged: ${UDEV_RULES_DIR:-/etc/udev/rules.d}.", + "Added udevadm to the harness's default stub list. It was NOT stubbed before, so `udevadm control --reload-rules` was reaching the real binary on the host -- the 'a prepended stub dir cannot hide a real binary' trap, caught by an assertion on the recorded call rather than by inspection.", + "fix_usb_autosuspend globs the real /sys/bus/usb/devices/*/ with no env override, so each branch is driven through the stubbed lsusb output instead: ffff:ffff matches no device on any machine and exercises the not-found arm, while 046d:c077 (control=on) and 046d:0825 (control=auto) are read-only probes of devices present on this host, each guarded so it self-skips if that stops holding.", + "Removed linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh from meta/shell-coverage-allowlist.txt." + ], + "verification": [ + { + "command": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh", + "result": "pass", + "evidence": "test_bt_pairing.sh: 11 passed, 0 failed; test_bt_pairing_usb.sh: 20 passed, 0 failed; test_bt_report.sh: 24 passed, 0 failed. All fixes lib test files passed, so the eight pre-existing pacman_hook_stall suites stay green." + }, + { + "command": "bash meta/scripts/shell_coverage_jail.sh --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure bt_pairing.sh --min 1 --fail-on-case-error -- \"\"", + "result": "pass", + "evidence": "bt_pairing.sh: 65/65 lines = 100.00%, up from 55/65 = 84.62% before _disable_usb_autosuspend's body (lines 112-126) was covered. No 'outside kcov's line set' lines reported." + }, + { + "command": "bash meta/scripts/shell_coverage_jail.sh --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure bt_pairing.sh --min 100 --fail-on-case-error -- \"\"", + "result": "pass", + "evidence": "bt_pairing.sh: 65/65 lines = 100.00%, exit code 0. Second matching run at the --min 100 bar, re-run after the two-file split to confirm the split did not change the line set." + }, + { + "command": "bash meta/scripts/check_shell_coverage.sh linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh", + "result": "pass", + "evidence": "'check_shell_coverage.sh: every checked shell library meets the coverage bar', exit 0, with the allowlist line already removed so the lib had no exemption." + }, + { + "command": "stat -c '%y' /etc/udev/rules.d/50-bluetooth-no-autosuspend.rules", + "result": "pass", + "evidence": "2026-02-21 21:51:36, unchanged after a bare (un-jailed) run of run_all.sh -- the file is a real artifact of a past production run, six months old. /sys/bus/usb/devices/1-10 and 1-11 power/control still read 'auto' and 'on'. This is the check that proves the suite is safe where it actually runs un-jailed; the jail cannot give it." + } + ], + "risks": [ + "Bind-mounting /etc was considered and rejected. lib/tests/run_all.sh runs UN-jailed in ci_mirror.sh (the pre-push hook) and in the shell-tests workflow, as its own header states, so a --bind would have made only the coverage run safe and left every bare run writing to the real root-owned udev rule. The UDEV_RULES_DIR override follows the precedent pacman_hook_stall_harness.sh set with PACMAN_LOG/CACHE_DIR/PACMAN_LOCK.", + "Two fix_usb_autosuspend cases read real sysfs devices (046d:c077 and 046d:0825) because the /sys path is hardcoded in the lib. Both are read-only and each is wrapped in a guard that turns the case into a pass with an explicit SKIP note if the host's power/control value changes, so the suite cannot silently start asserting nothing.", + "The coverage runs were made in a detached worktree, because a second Claude session is writing to this repo concurrently and the jail aborts on any working-tree delta during a run." + ], + "rollback": [ + "git revert the commit; that restores the bt_pairing.sh allowlist line, removes the UDEV_RULES_DIR override and deletes the test file together.", + "After rollback, run bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh and confirm the remaining suites pass, then bash meta/scripts/check_shell_coverage.sh to confirm the gate is green with bt_pairing.sh exempt again." + ] +} diff --git a/linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh b/linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh index 6791893b..4ab53e81 100755 --- a/linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh +++ b/linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh @@ -114,14 +114,17 @@ _disable_usb_autosuspend() { local product="$3" # Immediate fix - echo "on" > "$power_control" + echo "on" >"$power_control" - # Persistent udev rule - local rule_file="/etc/udev/rules.d/50-bluetooth-no-autosuspend.rules" + # Persistent udev rule. UDEV_RULES_DIR defaults to the real directory and + # is only overridden by the test suite, which cannot be allowed to write + # to /etc: lib/tests/run_all.sh runs UN-jailed in ci_mirror.sh and in CI, + # so a bind mount would protect the coverage run and nothing else. + local rule_file="${UDEV_RULES_DIR:-/etc/udev/rules.d}/50-bluetooth-no-autosuspend.rules" local rule="ACTION==\"add\", SUBSYSTEM==\"usb\", ATTR{idVendor}==\"$vendor\", ATTR{idProduct}==\"$product\", ATTR{power/control}=\"on\"" if [[ ! -f $rule_file ]] || ! grep -qF "$vendor" "$rule_file" 2>/dev/null; then - echo "$rule" > "$rule_file" + echo "$rule" >"$rule_file" udevadm control --reload-rules 2>/dev/null || true log_info "Created persistent udev rule: $rule_file" fi 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 dc2c9adf..c27d08ee 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 @@ -78,7 +78,8 @@ apply_fix() { _bt_default_stubs() { local tool for tool in dmesg wget curl lsusb modprobe dbus-send pactl wpctl \ - bluetoothctl rfkill lsmod usbreset systemctl journalctl sudo id; do + bluetoothctl rfkill lsmod usbreset systemctl journalctl sudo id \ + udevadm; do _t_stub "$tool" 'exit 0' done # `id -u ` feeds _run_as_user's XDG_RUNTIME_DIR path. diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing.sh new file mode 100755 index 00000000..7044efec --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# Tests for lib/bt_pairing.sh: remove_stale_pairing, restart_bluetooth and +# fix_usb_autosuspend. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=bt_harness.sh +. "${SCRIPT_DIR}/bt_harness.sh" + +# shellcheck source=../bt_pairing.sh +. "${FIXES_DIR}/lib/bt_pairing.sh" + +# _btctl_info BODY — stub bluetoothctl so `_btctl info ` returns BODY. +_btctl_info() { + local body="$1" + local stub + stub="$( + cat <<'BTCTL_HEAD' +read -r cmd rest +if [[ "$cmd" == "info" ]]; then + cat "${LIB_TEST_DEV}/btctl_info" +fi +exit 0 +BTCTL_HEAD + )" + printf '%s\n' "$body" >"${DEV}/btctl_info" + _t_stub bluetoothctl "$stub" +} + +# --- remove_stale_pairing --------------------------------------------------- + +bt_reset +TARGET_MAC="" +out="$(remove_stale_pairing)" +_t_eq "" "$out" "remove_stale_pairing: no output when TARGET_MAC is empty" +_t_eq "" "$(_t_fixes)" "remove_stale_pairing: applies no fix when TARGET_MAC is empty" + +# Paired but not connected — the stale-pairing case the function exists for. +bt_reset +TARGET_MAC="AA:BB:CC:DD:EE:FF" +_btctl_info "Device AA:BB:CC:DD:EE:FF JBL + Paired: yes + Connected: no" +out="$(remove_stale_pairing 2>&1)" +_t_contains "$out" "stale pairing" \ + "remove_stale_pairing: warns when the device is paired but not connected" +_t_contains "$out" "Removing stale pairing for AA:BB:CC:DD:EE:FF" \ + "remove_stale_pairing: applies the removal fix for a stale pairing" + +# Paired AND connected — healthy, nothing to do. +bt_reset +TARGET_MAC="AA:BB:CC:DD:EE:FF" +_btctl_info "Device AA:BB:CC:DD:EE:FF JBL + Paired: yes + Connected: yes" +out="$(remove_stale_pairing 2>&1)" +_t_contains "$out" "paired and connected" \ + "remove_stale_pairing: reports a healthy device as paired and connected" +_t_lacks "$out" "Removing stale pairing" \ + "remove_stale_pairing: applies no fix when the device is connected" + +# Known to the adapter but not paired at all. +bt_reset +TARGET_MAC="AA:BB:CC:DD:EE:FF" +_btctl_info "Device AA:BB:CC:DD:EE:FF JBL + Paired: no + Connected: no" +out="$(remove_stale_pairing 2>&1)" +_t_contains "$out" "not currently paired" \ + "remove_stale_pairing: reports an unpaired device as not paired" +_t_lacks "$out" "Removing stale pairing" \ + "remove_stale_pairing: applies no fix when the device is not paired" + +# Device entirely unknown to bluetoothctl. +bt_reset +TARGET_MAC="11:22:33:44:55:66" +_btctl_info "No default controller available" +out="$(remove_stale_pairing 2>&1)" +_t_contains "$out" "Fresh pairing needed" \ + "remove_stale_pairing: asks for fresh pairing when the device is unknown" + +# --- restart_bluetooth ------------------------------------------------------ + +bt_reset +FIXES_APPLIED=0 +out="$(restart_bluetooth 2>&1)" +_t_contains "$out" "skipping service restart" \ + "restart_bluetooth: skips the restart when no fixes were applied" + +bt_reset +FIXES_APPLIED=2 +out="$(restart_bluetooth 2>&1)" +_t_contains "$out" "Restarting bluetooth.service" \ + "restart_bluetooth: restarts the service when fixes were applied" + +echo +echo "bt_pairing (stale pairing + restart): ${PASS} passed, ${FAIL} failed" +((FAIL == 0)) diff --git a/linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing_usb.sh b/linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing_usb.sh new file mode 100755 index 00000000..4840751f --- /dev/null +++ b/linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_pairing_usb.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +# Tests for lib/bt_pairing.sh: fix_usb_autosuspend and +# _disable_usb_autosuspend, split from test_bt_pairing.sh for the 250-line cap. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=bt_harness.sh +. "${SCRIPT_DIR}/bt_harness.sh" + +# shellcheck source=../bt_pairing.sh +. "${FIXES_DIR}/lib/bt_pairing.sh" + +# --- fix_usb_autosuspend ---------------------------------------------------- +# +# This function globs the REAL /sys/bus/usb/devices/*/ -- the path is +# hardcoded, with no env override to point elsewhere. Rather than bind-mount +# or write anything, each case drives the branch it wants through the stubbed +# `lsusb` output: +# +# * a vendor:product of ffff:ffff matches no device on any machine, so the +# loop runs to exhaustion and takes the "could not find sysfs path" arm; +# * a vendor:product that IS present on this host reaches the power/control +# read, and _disable_usb_autosuspend is stubbed IN A SUBSHELL for those +# cases so the udev rule and the sysfs write never happen. +# +# A top-level redefinition would shadow the real function for every later +# assertion, so each override is confined to its own subshell. + +# _lsusb_line TEXT — stub lsusb to emit a single line of TEXT. +_lsusb_line() { + printf '%s\n' "$1" >"${DEV}/lsusb_out" + local body + body="$( + cat <<'LSUSB_BODY' +cat "${LIB_TEST_DEV}/lsusb_out" +LSUSB_BODY + )" + _t_stub lsusb "$body" +} + +# No bluetooth device in lsusb at all: the function returns before any lookup. +bt_reset +_t_stub lsusb 'echo "Bus 001 Device 002: ID 046d:c077 Logitech, Inc. Mouse"' +out="$(fix_usb_autosuspend 2>&1)" +_t_contains "$out" "Checking USB autosuspend" \ + "fix_usb_autosuspend: announces the check" +_t_eq "" "$(_t_fixes)" \ + "fix_usb_autosuspend: applies no fix when lsusb lists no bluetooth device" + +# A bluetooth line with no parseable ID: the usb_id guard returns early. +bt_reset +_lsusb_line "Bus 001 Device 003: Bluetooth adapter with no id field" +out="$(fix_usb_autosuspend 2>&1)" +_t_eq "" "$(_t_fixes)" \ + "fix_usb_autosuspend: applies no fix when the bluetooth line has no ID" + +# A well-formed ID that matches no real device: the sysfs scan finds nothing. +bt_reset +_lsusb_line "Bus 001 Device 004: ID ffff:ffff Bluetooth Device" +out="$(fix_usb_autosuspend 2>&1)" +_t_contains "$out" "Could not find sysfs path" \ + "fix_usb_autosuspend: warns when no sysfs device matches the USB ID" +_t_eq "" "$(_t_fixes)" \ + "fix_usb_autosuspend: applies no fix when the sysfs path is not found" + +# A device whose power/control is already "on" needs no change. 046d:c077 is +# present on this host with control=on; skipped if that ever stops holding. +bt_reset +if grep -qx "on" /sys/bus/usb/devices/1-11/power/control 2>/dev/null; then + _lsusb_line "Bus 001 Device 005: ID 046d:c077 Bluetooth Device" + out="$(fix_usb_autosuspend 2>&1)" + _t_contains "$out" "already disabled" \ + "fix_usb_autosuspend: reports autosuspend already disabled when control is on" + _t_eq "" "$(_t_fixes)" \ + "fix_usb_autosuspend: applies no fix when control is already on" +else + _t_pass "fix_usb_autosuspend: SKIP already-on case (1-11 control is not 'on' here)" +fi + +# _record_disable ARGS... — stand-in for the real writer, recording only. +_record_disable() { + printf 'disable_called %s\n' "$*" >>"${DEV}/fixes" +} + +# A device whose power/control is "auto" gets the fix. _disable_usb_autosuspend +# is replaced INSIDE the subshell so this case proves the arguments handed to +# the writer without letting the real writer touch /sys or /etc; the real +# writer gets its own direct tests further down. The redefinition must stay in +# the subshell, since a top-level one would shadow the real function for every +# later assertion in this file. +bt_reset +if grep -qx "auto" /sys/bus/usb/devices/1-10/power/control 2>/dev/null; then + _lsusb_line "Bus 001 Device 006: ID 046d:0825 Bluetooth Device" + # _record_disable is defined at top level (so it is plainly a called + # function) but only BOUND to the writer's name inside the subshell. + out="$( + eval '_disable_usb_autosuspend() { _record_disable "$@"; }' + fix_usb_autosuspend 2>&1 + )" + _t_contains "$out" "USB autosuspend is enabled" \ + "fix_usb_autosuspend: warns when autosuspend is enabled" + _t_contains "$out" "Disabling USB autosuspend" \ + "fix_usb_autosuspend: applies the disable fix when control is auto" + _t_contains "$(_t_fixes)" "disable_called /sys/bus/usb/devices/1-10/power/control 046d 0825" \ + "fix_usb_autosuspend: passes the control path, vendor and product to the writer" +else + _t_pass "fix_usb_autosuspend: SKIP auto case (1-10 control is not 'auto' here)" +fi + +# --- _disable_usb_autosuspend ---------------------------------------------- +# +# Called directly, with the REAL body running. Both of its writes are +# redirected out of the way rather than stubbed: +# +# * the sysfs power/control file is already a parameter, so a tmpdir file +# covers it; +# * the udev rule path is the UDEV_RULES_DIR override the lib exposes for +# exactly this. A bind mount was rejected: run_all.sh runs UN-jailed in +# ci_mirror.sh and in CI, so binding /etc would make the coverage run +# safe and leave every bare run writing to the real file. +# +# udevadm is already stubbed by _bt_default_stubs. + +bt_reset +control_file="${TEST_TMPDIR}/power_control" +rules_dir="${TEST_TMPDIR}/udev_rules" +rule_file="${rules_dir}/50-bluetooth-no-autosuspend.rules" +rm -rf "$rules_dir" +mkdir -p "$rules_dir" +echo "auto" >"$control_file" + +out="$(UDEV_RULES_DIR="$rules_dir" \ + _disable_usb_autosuspend "$control_file" "046d" "0825" 2>&1)" + +_t_eq "on" "$(cat "$control_file")" \ + "_disable_usb_autosuspend: writes 'on' to the sysfs power/control file" +_t_contains "$out" "Created persistent udev rule" \ + "_disable_usb_autosuspend: reports creating the udev rule" +_t_contains "$(cat "$rule_file")" 'ATTR{idVendor}=="046d"' \ + "_disable_usb_autosuspend: writes the vendor into the udev rule" +_t_contains "$(cat "$rule_file")" 'ATTR{idProduct}=="0825"' \ + "_disable_usb_autosuspend: writes the product into the udev rule" +_t_contains "$(cat "$rule_file")" 'ATTR{power/control}="on"' \ + "_disable_usb_autosuspend: pins power/control to on in the udev rule" +_t_contains "$(_t_calls)" "udevadm control --reload-rules" \ + "_disable_usb_autosuspend: reloads udev rules after writing" + +# Second call with the rule already present for this vendor: the guard must +# skip the rewrite, so the existing file survives untouched. +bt_reset +printf '%s\n' "# pre-existing rule for 046d" >"$rule_file" +echo "auto" >"$control_file" +out="$(UDEV_RULES_DIR="$rules_dir" \ + _disable_usb_autosuspend "$control_file" "046d" "0825" 2>&1)" +_t_eq "# pre-existing rule for 046d" "$(cat "$rule_file")" \ + "_disable_usb_autosuspend: leaves an existing rule for the same vendor alone" +_t_lacks "$out" "Created persistent udev rule" \ + "_disable_usb_autosuspend: does not claim to create a rule that already existed" +_t_eq "on" "$(cat "$control_file")" \ + "_disable_usb_autosuspend: still writes power/control on the idempotent path" + +# A rule file that exists but covers a DIFFERENT vendor must be rewritten. +bt_reset +printf '%s\n' "# rule for some other vendor 1234" >"$rule_file" +echo "auto" >"$control_file" +out="$(UDEV_RULES_DIR="$rules_dir" \ + _disable_usb_autosuspend "$control_file" "046d" "0825" 2>&1)" +_t_contains "$(cat "$rule_file")" 'ATTR{idVendor}=="046d"' \ + "_disable_usb_autosuspend: rewrites a rule file that covers a different vendor" + +echo +echo "bt_pairing (usb autosuspend): ${PASS} passed, ${FAIL} failed" +((FAIL == 0)) diff --git a/meta/shell-coverage-allowlist.txt b/meta/shell-coverage-allowlist.txt index 776ba1b8..10308ea8 100644 --- a/meta/shell-coverage-allowlist.txt +++ b/meta/shell-coverage-allowlist.txt @@ -81,7 +81,6 @@ linux_configuration/scripts/single_use/fixes/lib/arch_perf_report.sh linux_configuration/scripts/single_use/fixes/lib/arch_sysctl.sh linux_configuration/scripts/single_use/fixes/lib/bt_adapter.sh linux_configuration/scripts/single_use/fixes/lib/bt_audio.sh -linux_configuration/scripts/single_use/fixes/lib/bt_pairing.sh linux_configuration/scripts/single_use/fixes/lib/hosts_guard_migrate.sh linux_configuration/scripts/single_use/fixes/lib/hosts_guard_rollback.sh linux_configuration/scripts/single_use/fixes/lib/nvidia_config.sh