Files
StarPilot/openpilot/starpilot/ui/tests/test_native_settings.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

282 lines
15 KiB
Python

"""Replay large-panel input through real widgets with graphics and effects isolated."""
from unittest.mock import patch
from dataclasses import replace
from pathlib import Path
import tempfile
from types import SimpleNamespace as NS
import unittest
from unittest.mock import Mock
import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui.layouts.settings import device, software
from openpilot.selfdrive.ui.layouts.settings.settings import PanelType
from openpilot.starpilot.software.preferences import automatic_downloads
from openpilot.starpilot.ui import runtime_app
from openpilot.starpilot.ui.device_state import DeviceState
from openpilot.starpilot.ui.presentation import Profile
from openpilot.starpilot.ui.preview_home import reference_state
from openpilot.starpilot.ui.onroad_state import OnroadState, SpeedLimitObservation
from openpilot.starpilot.ui.settings_state import Destination, SettingsState
from openpilot.starpilot.ui.shell import ShellInput, ShellMode, ShellSnapshot
from openpilot.starpilot.ui.software_state import SoftwareState
from openpilot.system.ui.lib.application import gui_app, MouseEvent, MousePos
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets import list_view
from openpilot.system.ui.widgets.label import Label
class NativeSettingsTests(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.params = Params(directory.name)
self.params.put('UpdaterState', 'idle', block=True)
self.params.put('UpdaterAvailableBranches', 'SecretGoodStarPilot,Dom', block=True)
self.params.put('GitBranch', 'SecretGoodStarPilot', block=True)
self.offroad = True
self.ui = NS(params=self.params, is_offroad=lambda: self.offroad, is_onroad=lambda: not self.offroad,
prime_state=NS(is_paired=lambda: False), engaged=False, started=False, started_frame=0,
add_offroad_transition_callback=Mock())
self.enterContext(patch.object(device, 'ui_state', self.ui))
self.enterContext(patch.object(software, 'ui_state', self.ui))
self.enterContext(patch.object(runtime_app, 'ui_state', self.ui))
self.enterContext(patch.object(device, 'Params', return_value=self.params))
self.enterContext(patch.object(gui_app, 'font', return_value=rl.Font()))
self.enterContext(patch.object(gui_app, '_mouse_events', []))
self.enterContext(patch.object(gui_app, '_show_touches', False))
self.pushed = self.enterContext(patch.object(gui_app, 'push_widget'))
self.enterContext(patch.object(list_view, 'measure_text_cached', return_value=rl.Vector2(100, 50)))
self.enterContext(patch.object(list_view, 'gui_label'))
self.enterContext(patch.object(list_view, 'HtmlRenderer', side_effect=lambda *a, **kw: Mock(elements=[])))
self.enterContext(patch.object(Label, '_render'))
self.enterContext(patch('openpilot.system.ui.widgets.device', NS(awake=True)))
for name in ('draw_rectangle_rounded', 'draw_circle', 'draw_line', 'draw_text_ex', 'begin_scissor_mode', 'end_scissor_mode'):
self.enterContext(patch.object(rl, name))
self.enterContext(patch.object(rl, 'get_mouse_wheel_move', return_value=0))
self.enterContext(patch.object(rl, 'get_frame_time', return_value=1 / 60))
self.software = software.SoftwareLayout()
self.device = device.DeviceLayout()
self.bounds = rl.Rectangle(550, 25, 1560, 1030)
self.snapshot = ShellSnapshot(ShellMode.SETTINGS, reference_state(), SettingsState(),
OnroadState(False, False, None, None, SpeedLimitObservation()),
device=DeviceState(offroad=False, available_actions=frozenset()),
software=SoftwareState(available_actions=frozenset()))
def event(self, panel, x, y, *, pressed=False, down=False, released=False, t=1):
gui_app._mouse_events = [MouseEvent(MousePos(x, y), 0, pressed, released, down, t)]
panel.render(self.bounds)
gui_app._mouse_events = []
def click(self, panel, item):
panel.render(self.bounds)
rect = item.get_right_item_rect(item.rect)
point = rect.x + rect.width - 100, item.rect.y + 85
self.event(panel, *point, pressed=True, down=True)
self.event(panel, *point, released=True, t=1.1)
def test_desk_buttons_and_scrolling_use_native_widgets(self):
with patch.object(software.subprocess, 'run') as signal:
self.click(self.software, self.software._download_btn)
signal.assert_any_call("pkill -SIGUSR1 -f openpilot.system.updated.updated", shell=True)
self.assertEqual(sum(call.args == ("pkill -SIGUSR1 -f openpilot.system.updated.updated",) for call in signal.call_args_list), 1)
self.software._waiting_for_updater = False
with patch.object(software, 'MultiOptionDialog', return_value='branch chooser'):
self.click(self.software, self.software._branch_btn)
self.pushed.assert_called_once_with('branch chooser')
camera = next(item for item in self.device._scroller._items if item.title == 'Cabin Camera')
with patch.object(device, 'CabinCameraDialog', return_value='camera'):
self.click(self.device, camera)
self.pushed.assert_called_with('camera')
for panel in (self.software, self.device):
with self.subTest(panel=type(panel).__name__):
panel.show_event()
panel.render(self.bounds)
first = next(item for item in panel._scroller._items if item.is_visible)
before = first.rect.y
self.event(panel, 1000, 800, pressed=True, down=True)
self.event(panel, 1000, 770, down=True, t=1.1)
self.event(panel, 1000, 650, down=True, t=1.2)
self.assertLess(first.rect.y, before)
self.assertFalse(panel._scroller.scroll_panel.is_touch_valid())
panel.hide_event()
panel.show_event()
self.assertEqual(panel._scroller.scroll_panel.offset, 0)
def test_restored_controls_and_confirmation_recheck(self):
self.click(self.software, self.software._auto_updates_toggle)
self.assertFalse(automatic_downloads(self.params))
self.assertFalse(self.params.get_bool('DisableUpdates'))
with patch.object(software.Path, 'read_text', return_value='example failure'), patch.object(software, 'ConfirmDialog') as dialog:
self.software._on_error_log()
self.assertEqual(dialog.call_args.args[0], 'example failure')
with patch.object(device, 'ConfirmDialog') as dialog:
self.device._reset_driver_monitoring_prompt()
confirm = dialog.call_args.kwargs['callback']
self.params.put_bool('IsRhdDetected', True, block=True)
self.offroad = False
confirm(DialogResult.CONFIRM)
self.assertTrue(self.params.get_bool('IsRhdDetected'))
self.offroad = True
confirm(DialogResult.CANCEL)
self.assertTrue(self.params.get_bool('IsRhdDetected'))
self.params.put_bool('IsDriverViewEnabled', True, block=True)
confirm(DialogResult.CONFIRM)
self.assertTrue(self.params.get_bool('IsRhdDetected'))
self.params.put_bool('IsDriverViewEnabled', False, block=True)
confirm(DialogResult.CONFIRM)
self.assertFalse(Path(self.params.get_param_path('IsRhdDetected')).exists())
self.assertTrue(self.params.get_bool('OnroadCycleRequested'))
def test_software_effects_recheck_native_offroad_state(self):
with patch.object(software, 'ConfirmDialog') as dialog:
self.software._on_uninstall()
uninstall = dialog.call_args.kwargs['callback']
with patch.object(software, 'MultiOptionDialog') as dialog:
self.software._on_select_branch()
branch = dialog.call_args.kwargs['callback']
self.enterContext(patch.object(self.software._branch_dialog, 'selection', 'Dom', create=True))
self.params.put_bool('UpdateAvailable', True, block=True)
self.offroad = False
with patch.object(software.subprocess, 'run') as signal:
self.software._on_download_update()
self.software._on_install_update()
self.software._on_auto_updates_toggle(False)
uninstall(DialogResult.CONFIRM)
branch(DialogResult.CONFIRM)
signal.assert_not_called()
self.assertFalse(self.params.get_bool('DoReboot'))
self.assertFalse(self.params.get_bool('DoUninstall'))
self.assertFalse(self.params.get('UpdaterTargetBranch'))
self.assertTrue(automatic_downloads(self.params))
def test_native_leaf_input_cannot_also_emit_shell_requests(self):
requests = []
handler = ShellInput(Profile.LARGE, requests.append, native_panels=(Destination.DEVICE, Destination.SOFTWARE))
for destination in (Destination.DEVICE, Destination.SOFTWARE):
snapshot = replace(self.snapshot, selected=destination)
handler.press(1980, 650, 1, snapshot)
handler.move(1985, 650, 1.1, snapshot)
handler.release(1985, 650, 1.2, snapshot)
self.assertEqual(requests, [])
handler.press(250, 160, 2, snapshot)
handler.release(250, 160, 2.1, snapshot)
self.assertEqual(requests[0].source, 'settings')
def test_session_mounts_native_panels_inside_shared_rail(self):
session = runtime_app.StarShellSession.__new__(runtime_app.StarShellSession)
session.slc_actions = None
session.profile = Profile.LARGE
session.view = Mock()
session.favorites = Mock()
session.pip_warning = Mock()
session.pip_renderer = Mock()
session.notice = ''
session.network_layer = None
with patch.object(runtime_app, 'placed_at'), patch.object(software.subprocess, 'run') as signal, \
patch.object(device, 'CabinCameraDialog', return_value='camera'):
for destination, panel in ((Destination.DEVICE, self.device), (Destination.SOFTWARE, self.software)):
with self.subTest(destination=destination):
session.snapshot = Mock(return_value=replace(self.snapshot, selected=destination))
session.settings_layer = Mock(side_effect=lambda dest, rect, panel=panel: panel.render(rect))
session.render(ShellMode.SETTINGS, rl.Rectangle(0, 0, 2160, 1080))
session.settings_layer.assert_called_once()
self.assertEqual(session.view.render.call_args.args[0].selected, Destination.NETWORK)
self.assertEqual(panel.rect.width, 1560)
# Replay the page too: missing parked evidence must not swallow native desk touches.
session.selected = destination
emitted = Mock()
session.input = ShellInput(Profile.LARGE, emitted, native_panels=runtime_app.NATIVE_SETTINGS_PANELS)
page = runtime_app.StarShellPage(session, ShellMode.SETTINGS)
item = (self.software._download_btn if destination == Destination.SOFTWARE else
next(item for item in self.device._scroller._items if item.title == 'Cabin Camera'))
rect = item.get_right_item_rect(item.rect)
point = MousePos(rect.x + rect.width - 100, item.rect.y + 85)
for pressed, released in ((True, False), (False, True)):
gui_app._mouse_events = [MouseEvent(point, 0, pressed, released, pressed, 1)]
page.render(rl.Rectangle(0, 0, 2160, 1080))
gui_app._mouse_events = []
emitted.assert_not_called()
signal.assert_any_call("pkill -SIGUSR1 -f openpilot.system.updated.updated", shell=True)
self.assertEqual(sum(call.args == ("pkill -SIGUSR1 -f openpilot.system.updated.updated",) for call in signal.call_args_list), 1)
self.pushed.assert_called_once_with('camera')
def test_layout_reuses_native_panels_and_preserves_galaxy_entry(self):
toggles = NS(_scroller=NS(_items=[NS(title=str(index)) for index in range(9)]))
panels = {PanelType.DEVICE: NS(instance=self.device), PanelType.SOFTWARE: NS(instance=self.software),
PanelType.DEVELOPER: NS(instance=Mock()), PanelType.NETWORK: NS(instance=Mock()), PanelType.TOGGLES: NS(instance=toggles)}
session = Mock()
session.fonts.profile = Profile.LARGE
def parent_init(layout):
layout._layouts = {runtime_app.MainState.SETTINGS: NS(_panels=panels), runtime_app.MainState.ONROAD: Mock()}
with patch.object(runtime_app.MainLayout, '__init__', parent_init), \
patch.object(runtime_app, 'validate_runtime_fonts'), patch.object(runtime_app, 'validate_runtime_assets'), \
patch.object(runtime_app, 'StarShellSession', return_value=session):
layout = runtime_app.StarMainLayout()
self.assertIs(layout._large_panels[Destination.DEVICE].panel, self.device)
self.assertIs(layout._large_panels[Destination.SOFTWARE].panel, self.software)
self.assertIs(layout._large_panels[Destination.TOGGLES].panel, toggles)
self.assertEqual(toggles._scroller._items[2].title, 'Safe Mode')
self.assertEqual(toggles._scroller._items[7].title, 'Right Hand Driving')
self.assertEqual(self.device._scroller._items[0].title, 'Galaxy')
self.assertNotIn(self.device._power_off_btn, self.device._scroller._items)
self.assertEqual(session.system_power_action, layout._system_power_action)
galaxy = next(item for item in self.device._scroller._items if item.title == 'Galaxy')
galaxy.callback()
session.galaxy_flow.open_large.assert_called_once_with()
def test_system_power_uses_native_confirmation_and_rechecks_before_effects(self):
put_bool = self.params.put_bool
self.enterContext(patch.object(self.params, 'put_bool', side_effect=lambda key, value: put_bool(key, value, block=True)))
layout = runtime_app.StarMainLayout.__new__(runtime_app.StarMainLayout)
layout._current_mode = runtime_app.MainState.SETTINGS
layout.star = runtime_app.StarShellSession.__new__(runtime_app.StarShellSession)
layout.star._mode = ShellMode.SETTINGS
layout.star.selected = Destination.SYSTEM
layout._panel_owner = Mock(return_value=self.device)
self.enterContext(patch.object(layout, '_confirmed_offroad', side_effect=lambda: self.offroad))
for index, key in enumerate(("DoReboot", "DoShutdown")):
with self.subTest(key=key), patch.object(device, 'ConfirmDialog') as dialog:
layout._system_power_action(index)
confirm = dialog.call_args.kwargs['callback']
confirm(DialogResult.CANCEL)
self.assertFalse(self.params.get_bool(key))
self.ui.engaged = True
confirm(DialogResult.CONFIRM)
self.assertFalse(self.params.get_bool(key))
self.ui.engaged = False
layout.star.selected = Destination.STAR
confirm(DialogResult.CONFIRM)
self.assertFalse(self.params.get_bool(key))
layout.star.selected = Destination.SYSTEM
layout.star.display_page = "display"
confirm(DialogResult.CONFIRM)
self.assertFalse(self.params.get_bool(key))
layout.star.display_page = "system"
layout._current_mode = runtime_app.MainState.HOME
confirm(DialogResult.CONFIRM)
self.assertFalse(self.params.get_bool(key))
layout._current_mode = runtime_app.MainState.SETTINGS
if index == 1:
self.offroad = False
confirm(DialogResult.CONFIRM)
self.assertFalse(self.params.get_bool(key))
self.offroad = True
confirm(DialogResult.CONFIRM)
self.assertTrue(self.params.get_bool(key))
with patch.object(device, 'ConfirmDialog') as dialog:
self.offroad = False
layout._system_power_action(1)
layout._system_power_action(-1)
layout._system_power_action(2)
dialog.assert_not_called()
if __name__ == '__main__':
unittest.main()