Files
Panama/tests/quickshell/bluetooth-discovery-contract
T

87 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Bluetooth discovery holds the radio, so who turns it on and off is a contract
# rather than a detail.
#
# The pins below started life against modules/quicksettings/BluetoothList.qml,
# which computed the desired state and wrote BlueZ itself. That was correct
# until a second surface -- the settings page, through Connectivity.qml -- began
# doing the same thing from its own visibility flag, at which point the last
# writer decided for both: closing the settings page stopped discovery under an
# open quick-settings panel, which then sat on "Searching…" over a radio that
# had stopped.
#
# The fix moved the BlueZ write into Connectivity.qml behind named holds, so the
# same five guarantees are checked there, plus a sixth that keeps them there:
#
# 1. the desired state is calculated from state, not toggled;
# 2. a redundant start is not issued;
# 3. discovery this desktop did not start is never stopped;
# 4. merely constructing an inactive picker does not write BlueZ state;
# 5. an adapter appearing does not start discovery nobody asked for;
# 6. a surface releases its hold when it is destroyed.
#
# Read-only. It changes no Bluetooth state.
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
bluetooth="$repo_dir/config/dot/quickshell/modules/quicksettings/BluetoothList.qml"
service="$repo_dir/config/dot/quickshell/services/Connectivity.qml"
for path in "$bluetooth" "$service"; do
[[ -r "$path" ]] || {
printf 'bluetooth discovery contract: missing %s\n' "$path" >&2
exit 1
}
done
rg -q 'const shouldDiscover = root\.discoveryWanted' "$service" || {
printf 'bluetooth discovery contract: desired state is not calculated idempotently\n' >&2
exit 1
}
rg -q 'shouldDiscover && !root\.adapter\.discovering' "$service" || {
printf 'bluetooth discovery contract: redundant BlueZ starts are not guarded\n' >&2
exit 1
}
rg -q '!shouldDiscover && root\.discoveryOwned && root\.adapter\.discovering' "$service" || {
printf 'bluetooth discovery contract: the service may stop discovery it does not own\n' >&2
exit 1
}
# The holds themselves. Without both halves the refcount is decorative and the
# two surfaces are back to overwriting each other.
rg -q 'function acquireDiscovery' "$service" || {
printf 'bluetooth discovery contract: nothing can take a hold on discovery\n' >&2
exit 1
}
rg -q 'function releaseDiscovery' "$service" || {
printf 'bluetooth discovery contract: a hold on discovery cannot be released\n' >&2
exit 1
}
rg -q 'discoveryWanted: root\.discoveryHolders\.length > 0' "$service" || {
printf 'bluetooth discovery contract: discovery does not follow the outstanding holds\n' >&2
exit 1
}
# 6. One writer. A picker that still wrote adapter.discovering would reopen the
# exact fight the holds exist to settle, and it would do it silently.
! rg -q 'discovering *=' "$bluetooth" || {
printf 'bluetooth discovery contract: the picker writes BlueZ discovery directly, behind the holds\n' >&2
exit 1
}
! rg -q 'Component\.onCompleted: root\.(syncDiscovery|acquireDiscovery)' "$bluetooth" || {
printf 'bluetooth discovery contract: inactive construction still takes a discovery hold\n' >&2
exit 1
}
rg -Uq 'onActiveChanged:[^{\n]*\{[^}]*Connectivity\.acquireDiscovery' "$bluetooth" || {
printf 'bluetooth discovery contract: discovery is not gated by an open panel\n' >&2
exit 1
}
rg -q 'Component\.onDestruction: Connectivity\.releaseDiscovery' "$bluetooth" || {
printf 'bluetooth discovery contract: the hold is not released on destruction\n' >&2
exit 1
}
printf 'bluetooth discovery contract: PASS\n'