mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-30 19:33:45 +08:00
177 lines
5.7 KiB
Python
177 lines
5.7 KiB
Python
import json
|
|
from collections import Counter
|
|
from types import SimpleNamespace
|
|
|
|
import pyray as rl
|
|
import pytest
|
|
|
|
from openpilot.common.params import UnknownKeyName
|
|
from openpilot.selfdrive.ui.onroad.starpilot import navigation_card
|
|
|
|
|
|
class FakeParams:
|
|
def __init__(self, values):
|
|
self.values = values
|
|
self.reads = Counter()
|
|
|
|
def get(self, key):
|
|
self.reads[key] += 1
|
|
return self.values.get(key)
|
|
|
|
def get_bool(self, key):
|
|
return bool(self.get(key))
|
|
|
|
def put_bool(self, key, value):
|
|
self.values[key] = value
|
|
|
|
def remove(self, key):
|
|
self.values.pop(key, None)
|
|
|
|
|
|
@pytest.fixture
|
|
def card_state(monkeypatch):
|
|
params = FakeParams({"NavigationUI": True, "NavDestination": {"name": "Home"}})
|
|
nav_state = {"valid": True, "maneuverPrimaryText": "Main Street", "maneuverDistance": 1500}
|
|
memory = FakeParams({"NavInstructionState": json.dumps(nav_state), "NavInstructionCollapsed": False})
|
|
ui = SimpleNamespace(ui_params=params, params_memory=memory, is_metric=True)
|
|
clock = [0.0]
|
|
monkeypatch.setattr(navigation_card, "ui_state", ui)
|
|
monkeypatch.setattr(navigation_card.time, "monotonic", lambda: clock[0])
|
|
monkeypatch.setattr(navigation_card.gui_app, "font", lambda _: None)
|
|
card = navigation_card.NavigationCardRenderer()
|
|
return card, ui, clock
|
|
|
|
|
|
def test_navigation_state_reads_are_bounded_at_sixty_fps(card_state):
|
|
card, ui, clock = card_state
|
|
for frame in range(60):
|
|
clock[0] = frame / 60
|
|
card._update_state()
|
|
assert card._valid
|
|
|
|
assert 8 <= ui.params_memory.reads["NavInstructionState"] <= 10
|
|
assert ui.params_memory.reads["NavInstructionCollapsed"] == ui.params_memory.reads["NavInstructionState"]
|
|
|
|
|
|
def test_render_does_not_repeat_state_update(card_state, monkeypatch):
|
|
card, ui, _ = card_state
|
|
card._update_state()
|
|
reads = ui.ui_params.reads.copy()
|
|
monkeypatch.setattr(card, "_render_default", lambda _: None)
|
|
|
|
card._render(rl.Rectangle(0, 0, 100, 100))
|
|
|
|
assert ui.ui_params.reads == reads
|
|
|
|
|
|
def test_metric_and_destination_changes_bypass_refresh_interval(card_state):
|
|
card, ui, clock = card_state
|
|
card._update_state()
|
|
assert card._distance == "1.5 km"
|
|
|
|
clock[0] = 0.01
|
|
ui.is_metric = False
|
|
card._update_state()
|
|
assert card._distance == "0.9 mi"
|
|
|
|
ui.ui_params.values["NavDestination"] = {"name": "Work"}
|
|
ui.params_memory.values["NavInstructionState"] = {"valid": True, "maneuverPrimaryText": "Second Street"}
|
|
card._update_state()
|
|
assert card._primary_text == "Second Street"
|
|
assert ui.params_memory.reads["NavInstructionState"] == 3
|
|
|
|
|
|
@pytest.mark.parametrize("key, value", [("NavigationUI", False), ("NavDestination", None)])
|
|
def test_disabled_navigation_clears_card_and_reenables_immediately(card_state, key, value):
|
|
card, ui, clock = card_state
|
|
card._update_state()
|
|
card._interactive_rect = rl.Rectangle(0, 0, 100, 100)
|
|
previous = ui.ui_params.values[key]
|
|
ui.ui_params.values[key] = value
|
|
clock[0] = 0.01
|
|
card._update_state()
|
|
assert not card._valid
|
|
assert card._hit_rect.width == 0
|
|
assert ui.params_memory.reads["NavInstructionState"] == 1
|
|
|
|
ui.ui_params.values[key] = previous
|
|
card._update_state()
|
|
assert card._valid
|
|
assert ui.params_memory.reads["NavInstructionState"] == 2
|
|
|
|
|
|
@pytest.mark.parametrize("state", [None, "{invalid json", {"valid": False}, {"valid": True, "maneuverPrimaryText": ""}])
|
|
def test_invalid_state_clears_card_at_next_refresh(card_state, state):
|
|
card, ui, clock = card_state
|
|
card._update_state()
|
|
card._interactive_rect = rl.Rectangle(0, 0, 100, 100)
|
|
ui.params_memory.values["NavInstructionState"] = state
|
|
clock[0] = 0.1
|
|
card._update_state()
|
|
|
|
assert not card._valid
|
|
assert card._hit_rect.width == 0
|
|
|
|
|
|
@pytest.mark.parametrize("supported", [False, True])
|
|
def test_collapse_changes_immediately_and_forces_refresh(card_state, monkeypatch, supported):
|
|
card, ui, _ = card_state
|
|
card._update_state()
|
|
if not supported:
|
|
def unsupported(*_):
|
|
raise UnknownKeyName("NavInstructionCollapsed")
|
|
monkeypatch.setattr(ui.params_memory, "put_bool", unsupported)
|
|
|
|
card._toggle_collapsed()
|
|
assert card._collapsed
|
|
card._update_state()
|
|
assert card._collapsed
|
|
assert ui.params_memory.reads["NavInstructionState"] == 2
|
|
|
|
card._toggle_collapsed()
|
|
assert not card._collapsed
|
|
|
|
|
|
def test_cancel_navigation_clears_card_immediately(card_state):
|
|
card, ui, _ = card_state
|
|
card._update_state()
|
|
card._interactive_rect = rl.Rectangle(0, 0, 100, 100)
|
|
card._cancel_navigation()
|
|
|
|
assert not card._valid
|
|
assert card._hit_rect.width == 0
|
|
assert "NavDestination" not in ui.ui_params.values
|
|
assert "NavInstructionState" not in ui.params_memory.values
|
|
assert "NavInstructionCollapsed" not in ui.params_memory.values
|
|
card._update_state()
|
|
assert not card._valid
|
|
|
|
|
|
@pytest.mark.parametrize("maneuver, modifier, expected", [
|
|
("turn", "left", "direction_turn_left.png"),
|
|
("turn", "uturn", "direction_uturn.png"),
|
|
("missing", "right", navigation_card.FALLBACK_ICON),
|
|
])
|
|
def test_icon_resolution_and_texture_load_are_cached(card_state, monkeypatch, maneuver, modifier, expected):
|
|
card, _, _ = card_state
|
|
checks = []
|
|
loads = []
|
|
texture = object()
|
|
|
|
def exists(path):
|
|
checks.append(path.name)
|
|
return not path.name.startswith("direction_missing")
|
|
|
|
monkeypatch.setattr(navigation_card.Path, "exists", exists)
|
|
monkeypatch.setattr(rl, "load_image", lambda path: loads.append(path))
|
|
monkeypatch.setattr(rl, "load_texture_from_image", lambda _: texture)
|
|
monkeypatch.setattr(rl, "set_texture_filter", lambda *_: None)
|
|
monkeypatch.setattr(rl, "set_texture_wrap", lambda *_: None)
|
|
monkeypatch.setattr(rl, "unload_image", lambda _: None)
|
|
|
|
for _ in range(60):
|
|
assert card._get_icon(maneuver, modifier) is texture
|
|
|
|
assert len(checks) == 1
|
|
assert loads == [str(navigation_card.ASSETS_PATH / expected)]
|