mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
79 lines
3.3 KiB
Python
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()
|