Files
StarPilot/openpilot/starpilot/ui/tests/test_lead_info.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

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: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
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: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

217 lines
10 KiB
Python

"""C4 lead text uses strict saved choices and the existing lead freshness boundary."""
import os
from pathlib import Path
import tempfile
from types import SimpleNamespace as NS
import unittest
from unittest.mock import Mock, patch
import numpy as np
import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui.mici.onroad import model_renderer as native
from openpilot.starpilot import saved_document
from openpilot.starpilot.ui.appearance_owner import AppearanceOwner
from openpilot.starpilot.ui.appearance_preferences import LeadInfoMode, onroad_appearance, read_lead_info
from openpilot.starpilot.ui.feature_settings_state import FeatureSettingsRequest
from openpilot.starpilot.ui.presentation import Profile
class LeadInfoTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.params = Params(temporary.name)
storage = tempfile.TemporaryDirectory()
self.addCleanup(storage.cleanup)
self.storage = Path(storage.name)
self.parked = True
self.owner = AppearanceOwner(self.params, lambda: self.parked)
def raw(self, key):
path = Path(self.params.get_param_path(key))
return path.read_bytes() if path.exists() else None
def row(self):
return next(row for row in self.owner.snapshot(Profile.COMPACT).rows if row.key == "LeadInfo")
def choose(self, value):
row = self.row()
return self.owner.apply(FeatureSettingsRequest(row.key, row.source, value, related_source=row.related_source,
dependencies=row.dependencies))
def test_defaults_original_saved_combinations_and_marker_prerequisite(self):
self.assertEqual(self.params.get_default_value("LeadInfo"), False)
self.assertEqual(self.params.get_default_value("LeadInfoMode"), 2)
self.assertEqual(read_lead_info(self.params).mode, LeadInfoMode.OFF)
self.assertEqual(self.row().value, "Off")
self.assertFalse(self.row().available)
self.assertEqual(onroad_appearance(self.params).lead_info_mode, LeadInfoMode.OFF)
self.params.put_bool("HideLeadMarker", False, block=True)
self.assertTrue(self.row().available)
for flag, mode, expected in ((b"0", b"2", LeadInfoMode.OFF), (b"1", b"1", LeadInfoMode.DISTANCE),
(b"1", b"2", LeadInfoMode.SPEED), (b"1", b"0", LeadInfoMode.SPEED)):
with self.subTest(flag=flag, mode=mode):
Path(self.params.get_param_path("LeadInfo")).write_bytes(flag)
Path(self.params.get_param_path("LeadInfoMode")).write_bytes(mode)
self.assertEqual(read_lead_info(self.params).mode, expected)
self.assertEqual(onroad_appearance(self.params).lead_info_mode, expected)
self.params.put_bool("HideLeadMarker", True, block=True)
self.assertFalse(self.row().available)
self.assertEqual(onroad_appearance(self.params).lead_info_mode, LeadInfoMode.OFF)
def test_manager_materializes_off_without_replacing_saved_choice(self):
from openpilot.system.manager import manager
class AfterDefaults(Exception):
pass
def initialize_defaults():
with patch.object(manager, "Params", return_value=self.params), \
patch.object(manager, "prepare_manager_start"), patch.object(manager, "starpilot_storage_root", return_value=self.storage), \
patch.object(manager, "save_bootlog"), \
patch.object(manager, "get_build_metadata", return_value=NS(release_channel=False)), \
patch.object(manager.Paths, "shm_path", side_effect=AfterDefaults):
with self.assertRaises(AfterDefaults):
manager.manager_init()
initialize_defaults()
self.assertEqual((self.raw("LeadInfo"), self.raw("LeadInfoMode")), (b"0", b"2"))
self.params.put_bool("HideLeadMarker", False, block=True)
self.assertEqual(onroad_appearance(self.params).lead_info_mode, LeadInfoMode.OFF)
self.assertTrue(self.choose("Distance"))
initialize_defaults()
self.assertEqual((self.raw("LeadInfo"), self.raw("LeadInfoMode")), (b"1", b"1"))
def test_ordered_save_repair_parked_and_stale_sources(self):
self.params.put_bool("HideLeadMarker", False, block=True)
self.parked = False
self.assertFalse(self.choose("Distance"))
self.assertIsNone(self.raw("LeadInfo"))
self.parked = True
old = self.row()
self.assertTrue(self.choose("Distance"))
self.assertEqual((self.raw("LeadInfoMode"), self.raw("LeadInfo")), (b"1", b"1"))
self.assertFalse(self.owner.apply(FeatureSettingsRequest(old.key, old.source, "Speed",
related_source=old.related_source,
dependencies=old.dependencies)))
self.assertTrue(self.choose("Speed"))
self.assertEqual((self.raw("LeadInfoMode"), self.raw("LeadInfo")), (b"2", b"1"))
self.assertTrue(self.choose("Off"))
self.assertEqual((self.raw("LeadInfo"), self.raw("LeadInfoMode")), (b"0", b"0"))
self.params.put_bool("HideLeadMarker", True, block=True)
self.assertFalse(self.choose("Distance"))
def test_invalid_and_unreadable_bytes_never_enable(self):
self.params.put_bool("HideLeadMarker", False, block=True)
mode = Path(self.params.get_param_path("LeadInfoMode"))
mode.write_bytes(b"broken")
row = self.row()
self.assertEqual((row.value, row.repair_value), ("Invalid saved choice", "Off"))
self.assertEqual(onroad_appearance(self.params).lead_info_mode, LeadInfoMode.OFF)
self.assertFalse(self.choose("Distance"))
self.assertTrue(self.choose("Off"))
self.assertEqual((self.raw("LeadInfo"), mode.read_bytes()), (b"0", b"0"))
mode.unlink()
mode.mkdir()
self.assertFalse(self.row().available)
self.assertFalse(self.choose("Speed"))
def test_partial_enable_failure_remains_off_and_requires_readback(self):
self.params.put_bool("HideLeadMarker", False, block=True)
actual_fsync = os.fsync
calls = 0
def revoke_after_first(fd):
nonlocal calls
actual_fsync(fd)
calls += 1
if calls == 2:
self.parked = False
with patch.object(saved_document.os, "fsync", side_effect=revoke_after_first):
self.assertFalse(self.choose("Distance"))
self.assertEqual((self.raw("LeadInfoMode"), self.raw("LeadInfo")), (b"1", None))
self.assertEqual(onroad_appearance(self.params).lead_info_mode, LeadInfoMode.OFF)
self.parked = True
self.assertTrue(self.choose("Distance"))
self.assertEqual(onroad_appearance(self.params).lead_info_mode, LeadInfoMode.DISTANCE)
def test_finite_metric_and_imperial_text(self):
lead = NS(present=True, dRel=20.0, vLead=12.5)
format_info = native.ModelRenderer._format_lead_info
self.assertEqual(format_info(lead, 1, True), "20 m")
self.assertEqual(format_info(lead, 1, False), "66 ft")
self.assertEqual(format_info(lead, 2, True), "45 km/h")
self.assertEqual(format_info(lead, 2, False), "28 mph")
for invalid in (None, float("nan"), float("inf"), "bad"):
self.assertEqual(format_info(NS(present=True, dRel=invalid, vLead=12.5), 1, True), "")
self.assertEqual(format_info(NS(present=True, dRel=20.0, vLead=invalid), 2, True), "")
self.assertEqual(format_info(NS(present=False, dRel=20, vLead=12.5), 1, True), "")
self.assertEqual(format_info(lead, 1, None), "")
self.assertEqual(format_info(lead, 0, True), "")
def test_native_text_is_centered_with_original_outline(self):
rect = rl.Rectangle(10, 20, 400, 240)
with patch.object(native.gui_app, "font", return_value=object()), \
patch.object(native, "measure_text_cached", return_value=NS(x=80.0)), \
patch.object(native.rl, "draw_text_ex") as draw:
native.ModelRenderer._draw_lead_info(rect, "20 m")
self.assertEqual(draw.call_count, 5)
self.assertEqual([(call.args[2].x, call.args[2].y) for call in draw.call_args_list],
[(169.0, 41.0), (171.0, 41.0), (169.0, 43.0), (171.0, 43.0), (170.0, 42.0)])
self.assertEqual([call.args[-1] for call in draw.call_args_list], [rl.BLACK] * 4 + [rl.WHITE])
def test_native_text_needs_current_primary_bar_and_context_resets(self):
renderer = native.ModelRenderer.__new__(native.ModelRenderer)
renderer._lead_indicator_enabled = False
renderer._lead_info_mode = 0
renderer._lead_info_metric = None
renderer._torque_filter = Mock()
renderer._path = native.ModelPoints(raw_points=np.array([[20.0, 0.0, 0.0]], dtype=np.float32))
renderer._path_offset_z = 0.0
renderer._transform_dirty = False
renderer._update_model = Mock()
renderer._update_leads = Mock(side_effect=lambda *_: setattr(renderer, "_lead_vehicles", [
NS(bar=np.ones((4, 2), dtype=np.float32), info=NS(present=True, dRel=20.0, vLead=10.0)), native.LeadVehicle()]))
renderer._draw_lead_indicator = Mock()
renderer._draw_lead_info = Mock()
renderer._draw_lane_lines = Mock()
renderer._draw_path = Mock()
renderer._experimental_mode = False
rect = rl.Rectangle(0, 0, 476, 240)
lead = NS(present=True, dRel=20.0, yRel=0.0, vRel=-1.0, vLead=12.5)
messages = {"carOutput": NS(actuatorsOutput=NS(torque=0.0)), "extrinsicsCalibration": NS(height=[]),
"selfdriveState": NS(experimentalMode=False), "modelV2": NS(),
"radarState": NS(leadOne=lead, leadTwo=NS(present=False))}
class SubMaster:
recv_frame = {"extrinsicsCalibration": 2, "modelV2": 2}
updated = {"carParams": False, "modelV2": False, "radarState": False}
valid = {"radarState": True}
def __getitem__(self, key):
return messages[key]
sm = SubMaster()
ui = NS(sm=sm, started_frame=1, status=native.UIStatus.DISENGAGED)
with patch.object(native, "ui_state", ui), patch.object(native.ModelRenderer, "render", autospec=True,
side_effect=lambda self, _rect: self._render(_rect)):
renderer.render_with_lead(rect, True, 1, True)
renderer._draw_lead_info.assert_called_once_with(rect, "20 m")
self.assertEqual((renderer._lead_info_mode, renderer._lead_info_metric), (0, None))
renderer._draw_lead_info.reset_mock()
sm.recv_frame["modelV2"] = 0
renderer.render_with_lead(rect, True, 1, True)
renderer._draw_lead_info.assert_not_called()
sm.recv_frame["modelV2"] = 2
renderer._path.raw_points = np.empty((0, 3), dtype=np.float32)
renderer.render_with_lead(rect, True, 1, True)
renderer._draw_lead_info.assert_not_called()
if __name__ == "__main__":
unittest.main()