Files
StarPilot/openpilot/starpilot/ui/tests/test_runtime_layouts.py
T
2026-10-18 01:40:04 -04:00

206 lines
10 KiB
Python

"""Exercise actual opt-in layout constructors with external owners isolated."""
import atexit
from contextlib import ExitStack
import tempfile
from types import SimpleNamespace as NS
import unittest
from unittest.mock import Mock, patch
from openpilot.common.params import Params
_param_dir = tempfile.TemporaryDirectory(prefix="starpilot-ui-layout-test-")
atexit.register(_param_dir.cleanup)
_original_params_init = Params.__init__
with patch.object(Params, "__init__", lambda self, d="": _original_params_init(self, _param_dir.name)):
from openpilot.starpilot.ui import runtime_app
class TestOptInLayouts(unittest.TestCase):
def test_compact_device_has_one_display_entry_and_reuses_panel(self):
from openpilot.starpilot.ui.display_compact import DisplayCompact
from openpilot.starpilot.ui import replay_settings
from openpilot.selfdrive.ui.mici.layouts.settings.device import device_layout
layout = runtime_app.StarMiciMainLayout.__new__(runtime_app.StarMiciMainLayout)
layout.star = self._session()
layout._compact_panels = {}
panel, entry, replay = Mock(), Mock(), Mock()
with patch.object(device_layout, "DeviceLayoutMici", return_value=panel) as create, \
patch.object(DisplayCompact, "entry_button", return_value=entry), \
patch.object(replay_settings, "replay_entry", return_value=replay), \
patch.object(runtime_app.gui_app, "push_widget") as push:
layout._open_compact_destination(runtime_app.Destination.DEVICE)
layout._open_compact_destination(runtime_app.Destination.DEVICE)
create.assert_called_once()
panel._scroller.add_widgets.assert_called_once_with([entry, replay])
self.assertEqual(push.call_count, 2)
push.assert_called_with(panel)
def _session(self):
session = Mock()
session.profile = runtime_app.Profile.COMPACT
return session
def test_large_constructor_routes_into_existing_layout_state_and_closes_camera(self):
session = self._session()
session.fonts.profile = runtime_app.Profile.LARGE
camera = Mock()
def parent_init(layout):
from openpilot.selfdrive.ui.layouts.settings.settings import PanelType
settings = Mock()
settings._panels = {panel: NS(instance=Mock()) for panel in PanelType}
settings._panels[PanelType.TOGGLES].instance._scroller._items = []
layout._layouts = {runtime_app.MainState.HOME: Mock(), runtime_app.MainState.SETTINGS: settings,
runtime_app.MainState.ONROAD: camera}
layout._current_mode = runtime_app.MainState.HOME
with patch.object(runtime_app, "validate_runtime_fonts"), patch.object(runtime_app, "validate_runtime_assets"), \
patch.object(runtime_app.MainLayout, "__init__", parent_init), \
patch.object(runtime_app, "button_item"), \
patch.object(runtime_app, "text_item"), \
patch.object(runtime_app, "StarShellSession", return_value=session):
layout = runtime_app.StarMainLayout()
navigate = session.set_navigation.call_args.kwargs
navigate["on_settings"]()
self.assertEqual(layout._current_mode, runtime_app.MainState.SETTINGS)
session.cancel.assert_called_once()
with patch.object(runtime_app.ui_state, "is_body", True), \
patch.object(runtime_app.MainLayout, "_render_main_content") as body_render:
layout._set_current_layout(runtime_app.MainState.HOME)
layout._render_main_content()
body_render.assert_called_once()
layout._rect = Mock()
session.snapshot.return_value = NS(home=NS(paired=False))
with patch.object(runtime_app.ui_state, "is_body", False), patch.object(layout.page, "render") as custom_render:
layout._render_main_content()
custom_render.assert_called_once_with(layout._rect)
session.cancel.reset_mock()
with patch.object(runtime_app.MainLayout, "_on_body_changed") as body_changed:
layout._on_body_changed()
session.cancel.assert_called_once()
body_changed.assert_called_once()
self.assertIs(layout._native_onroad, camera)
layout.close()
session.close.assert_called_once()
camera.close.assert_called_once()
def test_compact_constructor_replaces_scroller_pages_and_keeps_camera_owner(self):
session = self._session()
camera = Mock()
scroller = NS(items=[Mock(), Mock(), Mock(), Mock()], set_scrolling_enabled=Mock())
def parent_init(layout):
layout._car_onroad_layout = camera
layout._home_layout = Mock()
layout._scroller = scroller
with patch.object(runtime_app, "validate_runtime_fonts"), patch.object(runtime_app, "validate_runtime_assets"), \
patch.object(runtime_app.MiciMainLayout, "__init__", parent_init), \
patch.object(runtime_app.MiciMainLayout, "_on_body_changed"), \
patch.object(runtime_app, "StarShellSession", return_value=session):
layout = runtime_app.StarMiciMainLayout()
self.assertIs(scroller.items[1], layout._home_layout)
self.assertIs(scroller.items[2], layout._car_onroad_layout)
self.assertIs(layout._native_onroad, camera)
self.assertIsInstance(layout._settings_layout, runtime_app.StarCompactSettings)
self.assertFalse(hasattr(layout._home_layout, "fallback"))
navigate = session.set_navigation.call_args.kwargs
with patch.object(runtime_app.gui_app, "push_widget") as push_widget:
navigate["on_settings"]()
push_widget.assert_called_once_with(layout._settings_layout)
layout.close()
session.close.assert_called_once()
camera.close.assert_called_once()
def test_compact_settings_are_lazy_and_pairing_keeps_native_owner(self):
from openpilot.selfdrive.ui.mici.layouts import main
for layout_type in (main.MiciMainLayout, runtime_app.StarMiciMainLayout):
with self.subTest(layout=layout_type.__name__), ExitStack() as stack:
def scroller_init(layout, **kwargs):
layout._scroller = Mock()
layout._scroller.items = [Mock(), Mock(), Mock(), Mock()]
stack.enter_context(patch.object(main.Scroller, "__init__", scroller_init))
for owner in ("MiciHomeLayout", "MiciOffroadAlerts", "AugmentedRoadView", "BodyLayout", "OnboardingWindow"):
stack.enter_context(patch.object(main, owner))
settings_factory = stack.enter_context(patch.object(main, "SettingsLayout"))
stack.enter_context(patch.object(main.messaging, "PubMaster"))
stack.enter_context(patch.object(main, "gui_app"))
stack.enter_context(patch.object(main, "device"))
stack.enter_context(patch.object(main, "ui_state"))
stack.enter_context(patch.object(main.MiciMainLayout, "_on_body_changed"))
stack.enter_context(patch.object(runtime_app, "validate_runtime_fonts"))
stack.enter_context(patch.object(runtime_app, "validate_runtime_assets"))
stack.enter_context(patch.object(runtime_app, "StarShellSession", return_value=self._session()))
layout = layout_type()
settings_factory.assert_not_called()
pairing = layout._alerts_layout.set_pairing_callback.call_args.args[0]
pairing()
settings_factory.assert_called_once_with()
native_settings = settings_factory.return_value
native_settings.set_rect.assert_called_once()
native_settings.show_pairing.assert_called_once_with()
pairing()
settings_factory.assert_called_once_with()
if layout_type is main.MiciMainLayout:
self.assertIs(layout._settings_layout, native_settings)
with patch.object(main.gui_app, "push_widget") as push:
layout._home_layout.set_callbacks.call_args.kwargs["on_settings"]()
push.assert_called_once_with(native_settings)
else:
self.assertIsInstance(layout._settings_layout, runtime_app.StarCompactSettings)
self.assertIsNot(layout._settings_layout, native_settings)
def test_slc_publisher_reader_rejects_stale_transport_or_missing_started_evidence(self):
from openpilot.starpilot.ui.tests.test_runtime_snapshot import NOW, BOOT_OFFSET_NS, ui_fake
clocks = patch("openpilot.starpilot.ui.runtime_snapshot._clock_pair", return_value=(NOW, NOW + BOOT_OFFSET_NS, BOOT_OFFSET_NS))
clocks.start()
self.addCleanup(clocks.stop)
ui = ui_fake()
ui.CP = NS(openpilotLongitudinalControl=True, pcmCruise=False)
ui.sm.messages["carState"].canTimeout = False
ui.sm.messages["carState"].canValid = True
ui.sm.messages["carControl"].longActive = True
ui.sm.messages["selfdriveState"].enabled = True
ui.sm.put("controlsState", NS(longControlState="pid"))
session = runtime_app.StarShellSession.__new__(runtime_app.StarShellSession)
session.adapter = runtime_app.RuntimeSnapshotAdapter(ui)
self.assertIs(session._live_slc_message(NOW), ui.sm["slcState"])
ui.sm.logMonoTime["slcState"] = NOW - 200_000_000
self.assertIsNone(session._live_slc_message(NOW))
ui.sm.logMonoTime["slcState"] = NOW
ui.sm.valid["slcState"] = False
self.assertIsNone(session._live_slc_message(NOW))
ui.sm.valid["slcState"] = True
ui.sm["pandaStates"][0].ignitionLine = False
self.assertIsNone(session._live_slc_message(NOW))
ui.sm["pandaStates"][0].ignitionLine = True
ui.sm["carControl"].longActive = False
self.assertIs(session._live_slc_message(NOW), ui.sm["slcState"])
# Fresh evidence is required even when a choice cannot command a cruise target.
ui.sm["carState"].canValid = False
self.assertIsNone(session._live_slc_message(NOW))
ui.sm["carState"].canValid = True
ui.sm["carState"].canTimeout = True
self.assertIsNone(session._live_slc_message(NOW))
ui.sm["carState"].canTimeout = False
ui.sm.recv_frame["slcState"] = 0
self.assertIsNone(session._live_slc_message(NOW))
def test_slc_action_publisher_is_singleton_for_process(self):
runtime_app._slc_action_publisher.cache_clear()
try:
with patch.object(runtime_app.messaging, "PubMaster") as owner:
first = runtime_app._slc_action_publisher()
self.assertIs(first, runtime_app._slc_action_publisher())
owner.assert_called_once_with(["slcAction"])
finally:
runtime_app._slc_action_publisher.cache_clear()
if __name__ == "__main__":
unittest.main()