Files
StarPilot/openpilot/starpilot/ui/tests/test_bluetooth_status.py
T

79 lines
3.3 KiB
Python

"""The compact Bluetooth glyph follows a fresh adapter state: off, on, or a device connected."""
from concurrent.futures import Future
import json
import subprocess
import unittest
from unittest.mock import patch
from openpilot.starpilot.ui.bluetooth_status import BluetoothStatusSource, read_bluetooth_state
class FakeExecutor:
def __init__(self):
self.future = Future()
self.calls = 0
self.closed = False
def submit(self, _fn):
self.calls += 1
return self.future
def shutdown(self, *, wait, cancel_futures):
self.closed = True
class TestBluetoothStatus(unittest.TestCase):
def test_bluez_objects_give_off_on_or_connected(self):
def objects(powered, connected, powered_type="b"):
data = {"/org/bluez/hci0": {"org.bluez.Adapter1": {"Powered": {"type": powered_type, "data": powered}}},
"/org/bluez/hci0/dev_AA": {"org.bluez.Device1": {"Connected": {"type": "b", "data": connected}}}}
return json.dumps({"type": "a{oa{sa{sv}}}", "data": [data]})
for output, expected in ((objects(True, True), "connected"), (objects(True, False), "on"), (objects(False, True), "off"),
(objects("true", True, "s"), "off"), ("not json", "off"), ('{"data": []}', "off")):
with self.subTest(output=output), patch("openpilot.starpilot.ui.bluetooth_status.subprocess.run",
return_value=subprocess.CompletedProcess([], 0, output, "")) as run:
self.assertEqual(read_bluetooth_state(), expected)
self.assertEqual(run.call_args.kwargs["timeout"], 1.5)
with patch("openpilot.starpilot.ui.bluetooth_status.subprocess.run", return_value=subprocess.CompletedProcess([], 1, "", "")):
self.assertEqual(read_bluetooth_state(), "off")
with patch("openpilot.starpilot.ui.bluetooth_status.subprocess.run",
side_effect=subprocess.TimeoutExpired(["busctl"], 1.5)):
self.assertEqual(read_bluetooth_state(), "off")
def test_pending_failure_and_stale_result_never_show_icon(self):
clock = [10_000_000_000]
source = BluetoothStatusSource(clock=lambda: clock[0])
source.executor.shutdown(wait=False, cancel_futures=True)
executor = FakeExecutor()
self.enterContext(patch.object(source, "executor", executor))
try:
self.assertEqual(source.snapshot(), "off") # schedules work without blocking the render
self.assertEqual(executor.calls, 1)
executor.future.set_result(("connected", clock[0]))
self.assertEqual(source.snapshot(), "connected")
clock[0] += source.MAX_AGE_NS + 1
self.assertEqual(source.snapshot(), "off")
self.assertEqual(executor.calls, 2)
source.close()
self.assertEqual(source.snapshot(), "off")
self.assertTrue(executor.closed)
finally:
source.close()
def test_worker_rejects_failed_query(self):
source = BluetoothStatusSource(request=lambda: (_ for _ in ()).throw(OSError("no adapter")), clock=lambda: 3)
try:
self.assertEqual(source._poll(), ("off", 3))
finally:
source.close()
unknown = BluetoothStatusSource(request=lambda: True, clock=lambda: 3) # ty: ignore[invalid-argument-type] # unexpected reply type
try:
self.assertEqual(unknown._poll(), ("off", 3))
finally:
unknown.close()
if __name__ == "__main__":
unittest.main()