mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
Test bt_adapter.sh to 98.55%; line 97 is unreachable by construction
Third of the 16 untested fixes/lib libs, and the first that does NOT come
off the allowlist. 68/69 = 98.55%, up from 45/69. The remaining line is
not a missing test:
PS4='+TRACE:${BASH_SOURCE##*/}:${LINENO} ' bash -x
shows bash attributing the whole multi-line dbus-send assignment in
_services_resolved to line 100, and never emitting line 97 at all. Line 97
is the OPENING line of a backslash-continued statement; kcov instruments
it, bash never attributes a statement to it. That is defect (a) in
docs/kcov-under-report.md -- a wrong denominator -- and no test can move it.
continuation_lines() in shell_coverage_lines.py already excludes such lines,
but only detects them by tracking QUOTE state, so a line ending in an
unquoted backslash slips through. Fixing that is deliberately left out of
this commit: it changes the denominator for every measured lib, and if it
regresses one of the libs now gated with no exemption, reverting a mixed
commit would throw away these tests too.
Scope of the problem, measured rather than guessed -- backslash
continuations are confined to the bluetooth family: bt_audio.sh 10,
bt_adapter.sh 7, bt_pairing.sh 3, pacman_hook_stall_summary.sh 2, and ZERO
in the other 12 remaining libs. So this caps bt_audio.sh next and then stops
mattering.
Also replaces the harness's _t_only_path with _t_hide. _t_only_path dropped
PATH to $FAKE_BIN alone, which hid grep, head and sort as well as the tool
under test -- so the "usbreset is not installed" case died in a cascade of
"command not found" and passed for the wrong reason instead of exercising
the fallback. _t_hide rebuilds PATH from symlinks to every real executable
except the named ones; verified directly that command -v usbreset is then
empty while grep and head still resolve.
bt_report.sh (37/37) and bt_pairing.sh (65/65) re-measured at --min 100,
both exit 0, so neither cleared lib regressed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0e9cc0ce95
commit
434cb7587e
@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "Test bt_adapter.sh, and record why its last line is unreachable",
|
||||
"objective": "Cover the third of the sixteen untested fixes/lib libs. Unlike the first two this one does NOT come off the allowlist: it tops out at 98.55% because line 97 is the opening line of a backslash-continued statement, which kcov instruments but bash never attributes a statement to. The deliverable is therefore the tests plus a proven diagnosis, not a cleared entry.",
|
||||
"acceptance_criteria": [
|
||||
"bt_adapter.sh measures 98.55% (68/69) through lib/tests/run_all.sh, with line 97 the only uncovered line.",
|
||||
"The claim that line 97 is unreachable is proven by a PS4 trace showing bash attributing the statement to line 100, not asserted.",
|
||||
"bt_report.sh and bt_pairing.sh still measure 100.00% at --min 100 with exit 0, since both are now gated with no exemption.",
|
||||
"The whole suite passes bare: run_all.sh reports all test files passing.",
|
||||
"Every test file stays under the repo's 250-line cap, and pre-commit passes with no suppression added."
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Removing bt_adapter.sh from meta/shell-coverage-allowlist.txt. It is not at 100%, so it does not meet the bar.",
|
||||
"Changing continuation_lines() in meta/scripts/lib/shell_coverage_lines.py to recognise backslash continuations. That alters the denominator for every measured lib and must land on its own, so a regression can be reverted without losing these tests.",
|
||||
"Reformatting bt_adapter.sh to collapse the multi-line dbus-send call. Production source is not rewritten to please the coverage tool."
|
||||
],
|
||||
"verifier": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh (expects all files passing, 14 + 27 for the two bt_adapter files), plus bash meta/scripts/shell_coverage_jail.sh --subject linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh --measure bt_adapter.sh --min 1 --fail-on-case-error -- \"\" (expects 68/69 = 98.55%, uncovered: 97), plus the same command with --measure bt_report.sh and --measure bt_pairing.sh at --min 100 (expects 37/37 and 65/65, exit 0)."
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
{
|
||||
"intent": "Test linux_configuration/scripts/single_use/fixes/lib/bt_adapter.sh, third of the 16 untested fixes/lib libs. It reaches 98.55% and STAYS on the allowlist: the one remaining line is not reachable by any test, for an instrument reason recorded below.",
|
||||
"scope": [
|
||||
"linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_adapter.sh (new, 14 assertions: check_firmware and _download_firmware)",
|
||||
"linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_adapter_reset.sh (new, 27 assertions: check_adapter_stuck, _reload_btusb, _services_resolved, _full_adapter_reset_and_connect, _verify_audio_sink)",
|
||||
"linux_configuration/scripts/single_use/fixes/lib/tests/bt_harness.sh (_t_only_path replaced by _t_hide)",
|
||||
"Non-goal: removing bt_adapter.sh from meta/shell-coverage-allowlist.txt -- it is at 98.55%, not 100%, so it does not meet the bar",
|
||||
"Non-goal: fixing the continuation-line detector, or reformatting bt_adapter.sh to suit the tool"
|
||||
],
|
||||
"changes": [
|
||||
"Added test_bt_adapter.sh covering check_firmware (no .hcd named in dmesg, a missing file that triggers the download, an already-installed file, and de-duplication of repeated names via sort -u) and _download_firmware (wget succeeding, and the curl fallback when wget fails).",
|
||||
"Added test_bt_adapter_reset.sh covering check_adapter_stuck (responsive adapter, no USB dongle, the usbreset path, the btusb-reload fallback when usbreset is absent, and an unparseable USB ID), _reload_btusb, _services_resolved (boolean true, boolean false, and dbus-send failing), _full_adapter_reset_and_connect, and _verify_audio_sink (pactl absent, card found on the first probe, and never found after five retries).",
|
||||
"Replaced the harness's _t_only_path with _t_hide, which rebuilds PATH from symlinks to every real executable EXCEPT the named ones. _t_only_path dropped PATH to $FAKE_BIN alone, which also hid grep, head and sort -- so a 'usbreset not installed' case died in a cascade of 'command not found' and passed for the wrong reason rather than exercising the fallback. Verified directly: after _t_hide usbreset, command -v usbreset is empty while grep and head still resolve.",
|
||||
"test_bt_adapter_reset.sh sources bt_audio.sh as well: bt_adapter.sh calls _run_as_user and _restart_pipewire_stack, which live in bt_audio.sh, and fix_bluetooth.sh sources both. Sourcing it reproduces the real resolution order instead of stubbing the two helpers.",
|
||||
"sleep is stubbed for _full_adapter_reset_and_connect (34 real seconds) and _verify_audio_sink (5 retries x 3s), with the calls still recorded so the retry-count assertion is on the real loop."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh",
|
||||
"result": "pass",
|
||||
"evidence": "bt_adapter (firmware): 14 passed, 0 failed; bt_adapter (reset/probe): 27 passed, 0 failed. All fixes lib test files passed, so bt_report, both bt_pairing files and the eight 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_adapter.sh --min 1 --fail-on-case-error -- \"\"",
|
||||
"result": "pass",
|
||||
"evidence": "bt_adapter.sh: 68/69 lines = 98.55%, up from 45/69 = 65.22% before _full_adapter_reset_and_connect and _verify_audio_sink were covered. Only line 97 remains uncovered."
|
||||
},
|
||||
{
|
||||
"command": "PS4='+TRACE:${BASH_SOURCE##*/}:${LINENO} ' bash -x, driving _services_resolved directly",
|
||||
"result": "pass",
|
||||
"evidence": "bash attributes the whole multi-line dbus-send assignment to line 100 ('+TRACE:bt_adapter.sh:100 result=...'), and never emits line 97 at all. Line 97 is the OPENING line of a backslash-continued statement: kcov instruments it, bash never attributes a statement to it, so no test can cover it. This is defect (a) in docs/kcov-under-report.md -- a wrong denominator, not a missing test."
|
||||
},
|
||||
{
|
||||
"command": "grep -c '\\\\$' on each of the 22 fixes/lib libs",
|
||||
"result": "pass",
|
||||
"evidence": "Backslash continuations are confined to the bluetooth family: bt_audio.sh 10, bt_adapter.sh 7, bt_pairing.sh 3, pacman_hook_stall_summary.sh 2, and ZERO in the other 12 remaining libs. So this artifact caps bt_adapter.sh and will cap bt_audio.sh, but does not block the rest of the campaign."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"bt_adapter.sh stays on the allowlist at 98.55%. Clearing it needs meta/scripts/lib/shell_coverage_lines.py:continuation_lines to also treat a line ending in an unquoted backslash as a continuation -- it currently tracks quote state only. That change alters the denominator for every measured lib, so it is deliberately NOT bundled here: if it regresses one of the five pacman_hook_stall libs that are already gated with no exemption, reverting a mixed commit would throw away these tests too.",
|
||||
"Two check_firmware cases read the real /usr/lib/firmware/brcm (the lib hardcodes that path). The already-installed case is driven by whatever .hcd is genuinely present and self-skips with a note if the directory is empty; the download case uses BCM99999-nonexistent.hcd, a name no distribution ships. An earlier draft used BCM20702A1-0b05-17cb.hcd, which IS installed on this host, and silently exercised the wrong branch -- caught by the assertion, not by inspection."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit; it adds two test files and changes one harness helper, and touches no production lib and no allowlist entry.",
|
||||
"After rollback, run bash linux_configuration/scripts/single_use/fixes/lib/tests/run_all.sh and confirm the remaining suites pass; no coverage gate changes, since bt_adapter.sh stays exempt either way."
|
||||
]
|
||||
}
|
||||
@ -99,11 +99,43 @@ SUDO_BODY
|
||||
_t_stub sudo "$sudo_body"
|
||||
}
|
||||
|
||||
# _t_only_path — restrict PATH to $FAKE_BIN plus coreutils, so a tool absent
|
||||
# from $FAKE_BIN is genuinely "not installed" as far as has_cmd/command -v is
|
||||
# concerned. Used for the has_cmd false branches.
|
||||
_t_only_path() {
|
||||
PATH="${FAKE_BIN}"
|
||||
# _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
|
||||
}
|
||||
|
||||
|
||||
@ -85,6 +85,27 @@ STUB_PREAMBLE
|
||||
hash -r
|
||||
}
|
||||
|
||||
# _t_stub_writes NAME ARGNUM CONTENT — stub NAME so it writes CONTENT to the
|
||||
# path in its ARGNUM-th argument, then succeeds.
|
||||
#
|
||||
# Exists because downloaders take their destination positionally
|
||||
# (`wget -q "$url" -O "$dest"` puts it in $4), and writing that stub body as a
|
||||
# quoted string means embedding a literal $4 the writer must not expand. The
|
||||
# argument number is passed as data instead, so no caller needs to quote a $.
|
||||
_t_stub_writes() {
|
||||
local name="$1" argnum="$2" content="$3"
|
||||
# The dollar that selects the positional argument is assembled from a
|
||||
# character rather than written literally, so nothing in this file is a
|
||||
# quoted expression that only looks like it should expand.
|
||||
local dollar
|
||||
dollar="$(printf '\044')"
|
||||
local body
|
||||
body="printf %s '${content}' >\"${dollar}{${argnum}}\""
|
||||
body+="
|
||||
exit 0"
|
||||
_t_stub "$name" "$body"
|
||||
}
|
||||
|
||||
# _t_unstub NAME... — remove stubs and drop bash's cached executable paths.
|
||||
#
|
||||
# `rm` alone does NOT hide a stub: bash caches the resolved location of every
|
||||
|
||||
118
linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_adapter.sh
Executable file
118
linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_adapter.sh
Executable file
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for lib/bt_adapter.sh: check_firmware and _download_firmware.
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=bt_harness.sh
|
||||
. "${SCRIPT_DIR}/bt_harness.sh"
|
||||
|
||||
# shellcheck source=../bt_adapter.sh
|
||||
. "${FIXES_DIR}/lib/bt_adapter.sh"
|
||||
|
||||
# _dmesg_out TEXT — stub dmesg to emit TEXT.
|
||||
_dmesg_out() {
|
||||
printf '%s\n' "$1" >"${DEV}/dmesg_out"
|
||||
local body
|
||||
body="$(
|
||||
cat <<'DMESG_BODY'
|
||||
cat "${LIB_TEST_DEV}/dmesg_out"
|
||||
DMESG_BODY
|
||||
)"
|
||||
_t_stub dmesg "$body"
|
||||
}
|
||||
|
||||
# _record_download ARGS... — stand-in for the real downloader, recording only.
|
||||
_record_download() {
|
||||
printf 'download_called %s\n' "$*" >>"${DEV}/fixes"
|
||||
}
|
||||
|
||||
# --- check_firmware ---------------------------------------------------------
|
||||
|
||||
# dmesg mentions no .hcd file at all: nothing is missing.
|
||||
bt_reset
|
||||
_dmesg_out "usb 1-1: new full-speed USB device number 2"
|
||||
out="$(check_firmware 2>&1)"
|
||||
_t_contains "$out" "No missing firmware detected" \
|
||||
"check_firmware: reports nothing missing when dmesg names no .hcd file"
|
||||
_t_eq "" "$(_t_fixes)" "check_firmware: applies no fix when no firmware is missing"
|
||||
|
||||
# dmesg names a firmware file that is NOT present under the absolute
|
||||
# /usr/lib/firmware the lib reads, so the download branch runs. The name is
|
||||
# deliberately one no distribution ships; the "already installed" arm is
|
||||
# covered by the real-path case below.
|
||||
bt_reset
|
||||
_dmesg_out "bluetooth hci0: Direct firmware load for brcm/BCM99999-nonexistent.hcd failed"
|
||||
out="$(
|
||||
# Called indirectly by check_firmware.
|
||||
eval '_download_firmware() { _record_download "$@"; }'
|
||||
check_firmware 2>&1
|
||||
)"
|
||||
_t_contains "$out" "Missing firmware: brcm/BCM99999-nonexistent.hcd" \
|
||||
"check_firmware: warns about a firmware file dmesg reports as missing"
|
||||
_t_contains "$out" "Downloading BCM99999-nonexistent.hcd" \
|
||||
"check_firmware: applies the download fix for missing firmware"
|
||||
_t_contains "$(_t_fixes)" \
|
||||
"download_called https://github.com/winterheart/broadcom-bt-firmware/raw/master/brcm/BCM99999-nonexistent.hcd /usr/lib/firmware/brcm/BCM99999-nonexistent.hcd" \
|
||||
"check_firmware: builds the upstream URL and destination path for the download"
|
||||
|
||||
# A firmware file that IS present under /usr/lib/firmware is skipped. Which
|
||||
# files exist varies by host, so this case is driven by whatever is really
|
||||
# there and self-skips when the directory has none.
|
||||
bt_reset
|
||||
present_fw="$(find /usr/lib/firmware/brcm -name '*.hcd' -printf '%f\n' 2>/dev/null | head -1)"
|
||||
if [[ -n $present_fw ]]; then
|
||||
_dmesg_out "bluetooth hci0: Direct firmware load for brcm/${present_fw} failed"
|
||||
out="$(check_firmware 2>&1)"
|
||||
_t_contains "$out" "Firmware brcm/${present_fw} already installed" \
|
||||
"check_firmware: skips a firmware file that is already installed"
|
||||
_t_eq "" "$(_t_fixes)" \
|
||||
"check_firmware: applies no fix when the firmware is already installed"
|
||||
else
|
||||
_t_pass "check_firmware: SKIP already-installed case (no .hcd under /usr/lib/firmware/brcm)"
|
||||
fi
|
||||
|
||||
# Several missing files in one dmesg dump are each handled, and duplicates
|
||||
# collapse: the lib pipes through `sort -u`.
|
||||
bt_reset
|
||||
_dmesg_out "bluetooth hci0: Direct firmware load for brcm/BCM111.hcd failed
|
||||
bluetooth hci0: Direct firmware load for brcm/BCM222.hcd failed
|
||||
bluetooth hci0: Direct firmware load for brcm/BCM111.hcd failed"
|
||||
out="$(
|
||||
eval '_download_firmware() { _record_download "$@"; }'
|
||||
check_firmware 2>&1
|
||||
)"
|
||||
_t_eq "2" "$(grep -c 'download_called' "${DEV}/fixes")" \
|
||||
"check_firmware: de-duplicates repeated firmware names via sort -u"
|
||||
_t_contains "$(_t_fixes)" "brcm/BCM111.hcd" "check_firmware: handles the first missing file"
|
||||
_t_contains "$(_t_fixes)" "brcm/BCM222.hcd" "check_firmware: handles the second missing file"
|
||||
|
||||
# --- _download_firmware -----------------------------------------------------
|
||||
|
||||
# wget succeeding is enough: curl is never reached.
|
||||
bt_reset
|
||||
dest="${TEST_TMPDIR}/fw/BCM.hcd"
|
||||
rm -rf "${TEST_TMPDIR}/fw"
|
||||
# Real call shapes: wget -q "$url" -O "$dest" and curl -sL "$url" -o "$dest",
|
||||
# so the destination is the 4th argument to each.
|
||||
_t_stub_writes wget 4 "fw-from-wget"
|
||||
_t_stub_writes curl 4 "fw-from-curl"
|
||||
_download_firmware "https://example.invalid/BCM.hcd" "$dest"
|
||||
_t_eq "fw-from-wget" "$(cat "$dest")" \
|
||||
"_download_firmware: fetches with wget and writes the destination file"
|
||||
_t_lacks "$(_t_calls)" "curl " \
|
||||
"_download_firmware: does not fall back to curl when wget succeeds"
|
||||
_t_pass "_download_firmware: creates the destination directory (write above succeeded)"
|
||||
|
||||
# wget failing falls back to curl.
|
||||
bt_reset
|
||||
dest="${TEST_TMPDIR}/fw2/BCM.hcd"
|
||||
rm -rf "${TEST_TMPDIR}/fw2"
|
||||
_t_stub wget 'exit 1'
|
||||
_t_stub_writes curl 4 "fw-from-curl"
|
||||
_download_firmware "https://example.invalid/BCM.hcd" "$dest"
|
||||
_t_eq "fw-from-curl" "$(cat "$dest")" \
|
||||
"_download_firmware: falls back to curl when wget fails"
|
||||
|
||||
echo
|
||||
echo "bt_adapter (firmware): ${PASS} passed, ${FAIL} failed"
|
||||
((FAIL == 0))
|
||||
212
linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_adapter_reset.sh
Executable file
212
linux_configuration/scripts/single_use/fixes/lib/tests/test_bt_adapter_reset.sh
Executable file
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for lib/bt_adapter.sh: check_adapter_stuck, _reload_btusb,
|
||||
# _services_resolved, _full_adapter_reset_and_connect and _verify_audio_sink.
|
||||
# Split from test_bt_adapter.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_adapter.sh
|
||||
. "${FIXES_DIR}/lib/bt_adapter.sh"
|
||||
|
||||
# bt_adapter.sh calls _run_as_user and _restart_pipewire_stack, which are
|
||||
# defined in bt_audio.sh. fix_bluetooth.sh sources both libs, so sourcing
|
||||
# bt_audio.sh here reproduces the real resolution order rather than stubbing
|
||||
# the two helpers out.
|
||||
# shellcheck source=../bt_audio.sh
|
||||
. "${FIXES_DIR}/lib/bt_audio.sh"
|
||||
|
||||
# _btctl_list TEXT — stub bluetoothctl so `_btctl list` emits TEXT.
|
||||
_btctl_list() {
|
||||
printf '%s\n' "$1" >"${DEV}/btctl_out"
|
||||
local body
|
||||
body="$(
|
||||
cat <<'BTCTL_BODY'
|
||||
cat "${LIB_TEST_DEV}/btctl_out"
|
||||
BTCTL_BODY
|
||||
)"
|
||||
_t_stub bluetoothctl "$body"
|
||||
}
|
||||
|
||||
# _lsusb_line TEXT — stub lsusb to emit 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"
|
||||
}
|
||||
|
||||
# _record_reload ARGS... — stand-in for the real btusb reloader.
|
||||
_record_reload() {
|
||||
printf 'reload_called %s\n' "$*" >>"${DEV}/fixes"
|
||||
}
|
||||
|
||||
# --- check_adapter_stuck ----------------------------------------------------
|
||||
|
||||
# A responsive adapter short-circuits before any USB work.
|
||||
bt_reset
|
||||
_btctl_list "Controller AA:BB:CC:11:22:33 kuhy-pc [default]"
|
||||
out="$(check_adapter_stuck 2>&1)"
|
||||
_t_contains "$out" "Adapter is responsive" \
|
||||
"check_adapter_stuck: reports a responsive adapter"
|
||||
_t_eq "" "$(_t_fixes)" "check_adapter_stuck: applies no fix when the adapter responds"
|
||||
|
||||
# Unresponsive adapter, and lsusb sees no bluetooth dongle at all.
|
||||
bt_reset
|
||||
_btctl_list "No default controller available"
|
||||
_t_stub lsusb 'echo "Bus 001 Device 002: ID 046d:c077 Logitech, Inc. Mouse"'
|
||||
out="$(check_adapter_stuck 2>&1)"
|
||||
_t_contains "$out" "not responding" \
|
||||
"check_adapter_stuck: warns when bluetoothctl sees no controller"
|
||||
_t_contains "$out" "No USB Bluetooth adapter found" \
|
||||
"check_adapter_stuck: reports a missing dongle when lsusb has no bluetooth line"
|
||||
|
||||
# Unresponsive adapter with a bluetooth dongle present and usbreset available:
|
||||
# the USB reset path runs.
|
||||
bt_reset
|
||||
_btctl_list "No default controller available"
|
||||
_lsusb_line "Bus 001 Device 004: ID 0a12:0001 Cambridge Silicon Radio Bluetooth dongle"
|
||||
out="$(check_adapter_stuck 2>&1)"
|
||||
_t_contains "$out" "USB-resetting Bluetooth adapter (0a12:0001)" \
|
||||
"check_adapter_stuck: USB-resets the adapter when usbreset is available"
|
||||
_t_contains "$(_t_calls)" "usbreset 0a12:0001" \
|
||||
"check_adapter_stuck: passes the parsed USB ID to usbreset"
|
||||
|
||||
# Same, but usbreset is NOT installed: the btusb reload fallback runs. This
|
||||
# host really has /usr/bin/usbreset, and a prepended stub dir cannot hide a
|
||||
# real binary, so _t_hide rebuilds PATH without it while keeping grep/head
|
||||
# (which this very branch uses) reachable.
|
||||
bt_reset
|
||||
_btctl_list "No default controller available"
|
||||
_lsusb_line "Bus 001 Device 004: ID 0a12:0001 Cambridge Silicon Radio Bluetooth dongle"
|
||||
_t_unstub usbreset
|
||||
_t_hide usbreset
|
||||
out="$(
|
||||
eval '_reload_btusb() { _record_reload "$@"; }'
|
||||
check_adapter_stuck 2>&1
|
||||
)"
|
||||
_t_full_path
|
||||
_t_contains "$out" "Falling back to btusb module reload" \
|
||||
"check_adapter_stuck: falls back to a btusb reload when usbreset is absent"
|
||||
_t_contains "$(_t_fixes)" "reload_called" \
|
||||
"check_adapter_stuck: runs the btusb reload fix when usbreset is absent"
|
||||
|
||||
# A bluetooth line whose ID does not parse leaves usb_id empty, which also
|
||||
# takes the btusb reload fallback.
|
||||
bt_reset
|
||||
_btctl_list "No default controller available"
|
||||
_lsusb_line "Bus 001 Device 004: Bluetooth dongle with no parseable id"
|
||||
out="$(
|
||||
eval '_reload_btusb() { _record_reload "$@"; }'
|
||||
check_adapter_stuck 2>&1
|
||||
)"
|
||||
_t_contains "$(_t_fixes)" "reload_called" \
|
||||
"check_adapter_stuck: falls back to a btusb reload when the USB ID does not parse"
|
||||
|
||||
# --- _reload_btusb ----------------------------------------------------------
|
||||
|
||||
bt_reset
|
||||
_reload_btusb
|
||||
_t_contains "$(_t_calls)" "modprobe -r btusb" "_reload_btusb: unloads the btusb module"
|
||||
_t_contains "$(_t_calls)" "modprobe btusb" "_reload_btusb: loads btusb again"
|
||||
|
||||
# --- _services_resolved -----------------------------------------------------
|
||||
|
||||
bt_reset
|
||||
_t_stub dbus-send 'echo " variant boolean true"'
|
||||
if _services_resolved "AA:BB:CC:DD:EE:FF"; then
|
||||
_t_pass "_services_resolved: true when the property reads boolean true"
|
||||
else
|
||||
_t_fail "_services_resolved: expected success on boolean true"
|
||||
fi
|
||||
_t_contains "$(_t_calls)" "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF" \
|
||||
"_services_resolved: builds the dbus object path from the MAC"
|
||||
|
||||
bt_reset
|
||||
_t_stub dbus-send 'echo " variant boolean false"'
|
||||
if _services_resolved "AA:BB:CC:DD:EE:FF"; then
|
||||
_t_fail "_services_resolved: expected failure on boolean false"
|
||||
else
|
||||
_t_pass "_services_resolved: false when the property reads boolean false"
|
||||
fi
|
||||
|
||||
bt_reset
|
||||
_t_stub dbus-send 'exit 1'
|
||||
if _services_resolved "AA:BB:CC:DD:EE:FF"; then
|
||||
_t_fail "_services_resolved: expected failure when dbus-send fails"
|
||||
else
|
||||
_t_pass "_services_resolved: false when dbus-send itself fails"
|
||||
fi
|
||||
|
||||
# --- _full_adapter_reset_and_connect ----------------------------------------
|
||||
#
|
||||
# The real body sleeps 1 + 2 + 5 + 3 + 3 + 20 = 34 seconds. `sleep` is stubbed
|
||||
# to return instantly so the suite stays fast; the calls are still recorded,
|
||||
# so the ordering assertions below are on the real sequence.
|
||||
|
||||
bt_reset
|
||||
_t_stub sleep 'exit 0'
|
||||
_t_stub bluetoothctl 'cat >/dev/null; exit 0'
|
||||
out="$(_full_adapter_reset_and_connect "AA:BB:CC:DD:EE:FF" 2>&1)"
|
||||
calls="$(_t_calls)"
|
||||
|
||||
_t_contains "$out" "Performing full adapter reset" \
|
||||
"_full_adapter_reset_and_connect: announces the reset"
|
||||
_t_contains "$calls" "modprobe -r btusb" \
|
||||
"_full_adapter_reset_and_connect: unloads btusb"
|
||||
_t_contains "$calls" "modprobe btusb" \
|
||||
"_full_adapter_reset_and_connect: reloads btusb"
|
||||
_t_contains "$calls" "systemctl restart bluetooth.service" \
|
||||
"_full_adapter_reset_and_connect: restarts bluetooth.service"
|
||||
_t_contains "$out" "Reconnecting to AA:BB:CC:DD:EE:FF" \
|
||||
"_full_adapter_reset_and_connect: announces the reconnect"
|
||||
_t_contains "$calls" "systemctl --user restart pipewire pipewire-pulse wireplumber" \
|
||||
"_full_adapter_reset_and_connect: restarts the pipewire stack via _run_as_user"
|
||||
|
||||
# --- _verify_audio_sink -----------------------------------------------------
|
||||
|
||||
# pactl absent: the function returns success without probing anything. This
|
||||
# host really has pactl, so _t_hide removes it from PATH outright.
|
||||
bt_reset
|
||||
_t_unstub pactl
|
||||
_t_hide pactl
|
||||
if _verify_audio_sink "AA:BB:CC:DD:EE:FF"; then
|
||||
_t_pass "_verify_audio_sink: returns success when pactl is not installed"
|
||||
else
|
||||
_t_fail "_verify_audio_sink: expected success when pactl is absent"
|
||||
fi
|
||||
_t_full_path
|
||||
|
||||
# The card shows up on the first probe.
|
||||
bt_reset
|
||||
_t_stub sleep 'exit 0'
|
||||
_t_stub pactl 'echo "42 bluez_card.AA_BB_CC_DD_EE_FF module-bluez5-device.c"'
|
||||
out="$(_verify_audio_sink "AA:BB:CC:DD:EE:FF" 2>&1)"
|
||||
_t_contains "$out" "Bluetooth audio card detected" \
|
||||
"_verify_audio_sink: reports the card once PipeWire lists it"
|
||||
_t_eq "0" "$(_t_calls | grep -c '^sleep')" \
|
||||
"_verify_audio_sink: does not sleep when the card is present on the first probe"
|
||||
|
||||
# The card never appears: five probes, then a warning and a non-zero return.
|
||||
bt_reset
|
||||
_t_stub sleep 'exit 0'
|
||||
_t_stub pactl 'echo "42 alsa_card.pci-0000_00_1f.3 module-alsa-card.c"'
|
||||
if out="$(_verify_audio_sink "AA:BB:CC:DD:EE:FF" 2>&1)"; then
|
||||
_t_fail "_verify_audio_sink: expected a non-zero return when the card never appears"
|
||||
else
|
||||
_t_pass "_verify_audio_sink: returns non-zero when the card never appears"
|
||||
fi
|
||||
_t_contains "$out" "not found in PipeWire" \
|
||||
"_verify_audio_sink: warns when the card never appears"
|
||||
_t_eq "5" "$(_t_calls | grep -c '^sleep')" \
|
||||
"_verify_audio_sink: retries five times before giving up"
|
||||
|
||||
echo
|
||||
echo "bt_adapter (reset/probe): ${PASS} passed, ${FAIL} failed"
|
||||
((FAIL == 0))
|
||||
Loading…
Reference in New Issue
Block a user