Wren Linux

wren-linux / tools / test-radios.sh

#!/bin/bash
# Checks bin/radios (Wi-Fi, Bluetooth and microphone on/off) with a pretend
# rfkill, wpctl and bluetoothctl and a pretend /sys. Changes nothing. Run by
# test.sh, or:  bash tools/test-radios.sh
# shellcheck disable=SC2016  # the inner bash expands its own variables
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
R=$HERE/bin/radios

T=$(mktemp -d) || { echo "  FAIL  couldn't make a temp folder (is /tmp full?)"; exit 1; }
trap 'rm -rf "$T"' EXIT
fails=0
check() { # DESCRIPTION CONDITION-COMMAND...
    local what=$1
    shift
    if "$@" >/dev/null 2>&1; then printf '  ok    %s\n' "$what"; else printf '  FAIL  %s\n' "$what"; fails=$((fails + 1)); fi
}
not() { ! "$@"; }

mkdir -p "$T/fake" "$T/sys/class/net/wlan0/wireless" "$T/sys/class/bluetooth/hci0" "$T/rf"
# rfkill keeps "SOFT HARD" per type in a file; list prints it, block/unblock change the soft part
cat >"$T/fake/rfkill" <<FAKE
#!/bin/bash
f="$T/rf/\${@: -1}"
case \$1 in
    block) read -r s h <"$T/rf/\$2"; echo "blocked \$h" >"$T/rf/\$2" ;;
    unblock) read -r s h <"$T/rf/\$2"; echo "unblocked \$h" >"$T/rf/\$2" ;;
    -n) [ -f "\$f" ] && cat "\$f" ;;
esac
FAKE
# wpctl: the microphone is "Volume: 1.00" or "Volume: 1.00 [MUTED]"
cat >"$T/fake/wpctl" <<FAKE
#!/bin/bash
[ -f "$T/mic" ] || exit 1
case \$1 in
    get-volume) echo "Volume: 1.00\$([ "\$(cat "$T/mic")" = muted ] && echo ' [MUTED]')" ;;
    # (with $T/sink-only there is no microphone, and wpctl answers for @DEFAULT_AUDIO_SOURCE@ with a
    # speaker sink's volume: a Dummy Output and nothing else, tried for real on a laptop)
    inspect) echo "  * media.class = \"\$([ -e "$T/sink-only" ] && echo Audio/Sink || echo Audio/Source)\"" ;;
    # (with $T/mic-stuck it takes the command and changes nothing, as a source PipeWire
    # isn't ready with can)
    set-mute) [ -e "$T/mic-stuck" ] && exit 0
        if [ "\$3" = 1 ]; then echo muted >"$T/mic"; else echo open >"$T/mic"; fi ;;
esac
FAKE
cat >"$T/fake/bluetoothctl" <<FAKE
#!/bin/bash
echo "bluetoothctl \$*" >>"$T/calls"
case "\$1 \$2" in
    "power on") echo yes >"$T/powered" ;;
    "power off") echo no >"$T/powered" ;;
    show*) echo "Controller AA:BB"; echo "	Powered: \$(cat "$T/powered")" ;;
esac
FAKE
chmod +x "$T/fake/"*
echo "unblocked unblocked" >"$T/rf/wifi"; echo "unblocked unblocked" >"$T/rf/bluetooth"; echo open >"$T/mic"; echo yes >"$T/powered"
r() { env PATH="$T/fake:/usr/bin:/bin" WREN_SYS="$T/sys" XDG_RUNTIME_DIR="$T/run" bash "$R" "$@"; }

check "list shows each radio that exists, with its state" test "$(r list | paste -sd,)" = "wifi on,bluetooth on,mic on"
check "status says on" test "$(r wifi status)" = on
check "no word means status" test "$(r mic)" = on

r wifi off
check "wifi off blocks the radio" test "$(r wifi status)" = off
check "list shows it off" grep -qx 'wifi off' <(r list)
r wifi toggle
check "toggle turns it back on" test "$(r wifi status)" = on
r wifi on
check "on when it is on stays on" test "$(r wifi status)" = on

: >"$T/calls"
r bluetooth off
check "bluetooth off powers the adapter down and blocks the radio" test "$(r bluetooth status)" = off -a -n "$(grep 'power off' "$T/calls")"
r bluetooth on
check "bluetooth on unblocks it and powers the adapter up" test "$(r bluetooth status)" = on -a -n "$(grep 'power on' "$T/calls")"

r mic off
check "mic off mutes the default microphone" test "$(r mic status)" = off
check "...and nothing else changed" test "$(r wifi status)" = on
r mic toggle
check "mic toggle unmutes it" test "$(r mic status)" = on
touch "$T/mic-stuck"
r mic off 2>"$T/err"; rc=$?
check "a mute that doesn't take: mic off fails with status 1" test "$rc" -eq 1
check "...and says so, not that it is off" grep -q "didn't switch off" "$T/err"
check "...and it really is still on" test "$(r mic status)" = on
r mic toggle 2>/dev/null; rc=$?
check "...toggle fails the same way" test "$rc" -eq 1
rm -f "$T/mic-stuck"
r mic off; rc=$?
check "a mute that takes: status 0" test "$rc" -eq 0
r mic on

echo "unblocked blocked" >"$T/rf/wifi"
check "a hardware switch is reported as blocked, not as off" test "$(r wifi status)" = blocked
echo "blocked blocked" >"$T/rf/wifi"
check "...also when the software one is on too" test "$(r wifi status)" = blocked
echo "unblocked unblocked" >"$T/rf/wifi"

# bluez starts the adapter powered down: free of rfkill, but still off
echo no >"$T/powered"
check "an unblocked but powered-down Bluetooth adapter counts as off" test "$(r bluetooth status)" = off
r bluetooth on
check "switching it on powers it up" test "$(r bluetooth status)" = on

# an adapter but no bluez (the Bluetooth extra not installed): no switch for it
mkdir -p "$T/nobz"
for c in rfkill wpctl; do ln -sf "$T/fake/$c" "$T/nobz/$c"; done
for c in grep cat tr seq sleep; do ln -sf "$(command -v "$c")" "$T/nobz/$c"; done
rnobz() { env PATH="$T/nobz" WREN_SYS="$T/sys" XDG_RUNTIME_DIR="$T/run" "$BASH" "$R" "$@"; }
echo "unblocked unblocked" >"$T/rf/bluetooth"
check "an adapter without bluez: Bluetooth isn't listed (the menu shows no switch)" bash -c '! grep -q bluetooth <<<"$1"' _ "$(rnobz list)"
rnobz bluetooth on 2>"$T/err"; rc=$?
check "...and switching it on fails, saying how to install it" bash -c '[ "$1" -eq 1 ] && grep -q "wrenctl install bluetooth" "$2"' _ "$rc" "$T/err"
check "the extra's --remove blocks the radio before bluez goes" python3 -I -c 'import sys; s = open(sys.argv[1]).read(); r = s.index("--remove ]"); b = s.index("rfkill block bluetooth", r); p = s.index("apt_purge bluez", r); assert b < p' "$HERE/extras/bluetooth.sh"

# what a desktop does not have is not listed, and asking for it fails
# No sound hardware: PipeWire has a Dummy Output (a sink) and no source, and wpctl then answers for the
# default microphone with that sink's volume. That is no microphone: not "mic ON" in the bar
touch "$T/sink-only"
check "only a speaker sink, no microphone: it isn't listed as one" not grep -q '^mic' <<<"$(r list)"
check "...its status says there is none, not on" test "$(r mic status 2>&1 | head -1)" = "radios: no mic on this computer."
rm -f "$T/sink-only"
rm -rf "$T/sys/class/bluetooth"; rm -f "$T/mic"
check "without Bluetooth or a microphone, only Wi-Fi is listed" test "$(r list)" = "wifi on"
r bluetooth on 2>"$T/err"; rc=$?
check "asking for Bluetooth there fails with status 1" test "$rc" -eq 1
check "...and says so" grep -q 'no bluetooth on this computer' "$T/err"
rm -rf "$T/sys/class/net"
check "with no Wi-Fi adapter nothing is listed" test -z "$(r list)"

# --wait: the microphone shows up a moment later (sound is still starting at login)
echo open >"$T/mic"
(sleep 1.5; echo open >"$T/mic") &
rm -f "$T/mic"
r mic off --wait
check "mic off --wait waits for the microphone, then mutes it" test "$(cat "$T/mic")" = muted
wait

# radios switches Wi-Fi and Bluetooth with rfkill, which Debian doesn't install by
# itself: the scripts that put radios in place for those must install it
for f in parts/wren-menu.sh extras/bluetooth.sh; do
    check "$f installs rfkill (without it the menu has no Wi-Fi or Bluetooth)" \
        grep -qE '^[[:space:]]*apt_install .*\<rfkill\>' "$HERE/$f"
done

check "a wrong word is refused with status 2" bash -c 'env PATH="$1/fake:/usr/bin:/bin" bash "$2" nonsense; [ $? -eq 2 ]' _ "$T" "$R"
check "so is a wrong action" bash -c 'echo open >"$1/mic"; env PATH="$1/fake:/usr/bin:/bin" WREN_SYS="$1/sys" bash "$2" mic sideways; [ $? -eq 2 ]' _ "$T" "$R"

[ "$fails" -eq 0 ]