mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
160 lines
7.4 KiB
Python
160 lines
7.4 KiB
Python
"""Drive-scoped stopped duration and native display-source regression checks."""
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from opendbc.car.structs import car as car_schema
|
|
|
|
from openpilot.starpilot.ui.onroad_customization import STOPPED_TIMER_WIDGET, default_document
|
|
from openpilot.starpilot.ui.onroad_stopped_timer import (ENGAGED, EXPERIMENTAL, TRAFFIC, StoppedTimer,
|
|
duration_color, duration_text)
|
|
from openpilot.starpilot.ui.runtime_snapshot import RuntimeSnapshotAdapter
|
|
from openpilot.starpilot.ui.shell import ShellMode
|
|
from openpilot.starpilot.ui.tests.test_runtime_snapshot import NOW, ui_fake
|
|
|
|
|
|
SECOND = 1_000_000_000
|
|
|
|
|
|
class StoppedTimerTests(unittest.TestCase):
|
|
def test_native_timer_follows_its_screen_profile(self):
|
|
for profile in ("large", "compact"):
|
|
ui = ui_fake()
|
|
car = ui.sm.messages['carState']
|
|
car.canValid, car.canTimeout, car.standstill = True, False, True
|
|
car.gearShifter = car_schema.CarState.GearShifter.drive
|
|
document = default_document()
|
|
document['layouts'][profile][STOPPED_TIMER_WIDGET]['enabled'] = True
|
|
for screen in ("large", "compact"):
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
with patch('openpilot.starpilot.ui.runtime_snapshot.read_customization', return_value=document):
|
|
for second in (0, 61):
|
|
now = NOW + second * SECOND
|
|
for name in ('deviceState', 'pandaStates', 'carState'):
|
|
ui.sm.logMonoTime[name] = now
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=now, stopped_timer_profile=screen).onroad
|
|
self.assertEqual(state.stopped_duration_s, 61 if screen == profile else None)
|
|
|
|
def test_projection_timer_enablement_overrides_native_choice(self):
|
|
for native_enabled, projection_enabled in ((False, True), (True, False)):
|
|
with self.subTest(native_enabled=native_enabled, projection_enabled=projection_enabled):
|
|
ui = ui_fake()
|
|
car = ui.sm.messages['carState']
|
|
car.canValid, car.canTimeout, car.standstill = True, False, True
|
|
car.gearShifter = car_schema.CarState.GearShifter.drive
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
document = default_document()
|
|
document['layouts']['compact'][STOPPED_TIMER_WIDGET]['enabled'] = native_enabled
|
|
with patch('openpilot.starpilot.ui.runtime_snapshot.read_customization', return_value=document):
|
|
for second in (0, 61):
|
|
now = NOW + second * SECOND
|
|
for name in ('deviceState', 'pandaStates', 'carState'):
|
|
ui.sm.logMonoTime[name] = now
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=now, stopped_timer_enabled=projection_enabled).onroad
|
|
self.assertEqual(state.stopped_duration_s, 61 if projection_enabled else None)
|
|
self.assertEqual(document['layouts']['compact'][STOPPED_TIMER_WIDGET]['enabled'], native_enabled)
|
|
|
|
def test_first_drive_minute_then_elapsed_and_color_transitions(self):
|
|
timer = StoppedTimer()
|
|
def step(second):
|
|
return timer.step(now_ns=second * SECOND, drive_key=(1, 1), car_fresh=True,
|
|
standstill=True, reverse=False, enabled=True)
|
|
self.assertIsNone(step(1))
|
|
self.assertIsNone(step(59))
|
|
self.assertEqual(step(61), 60)
|
|
self.assertEqual(duration_text(60), ("1 minute", "0 seconds"))
|
|
self.assertEqual(duration_text(121), ("2 minutes", "1 second"))
|
|
self.assertEqual(duration_color(0), ENGAGED)
|
|
self.assertEqual(duration_color(60), ENGAGED)
|
|
self.assertEqual(duration_color(150), EXPERIMENTAL)
|
|
self.assertEqual(duration_color(300), TRAFFIC)
|
|
|
|
def test_movement_reverse_source_loss_disabled_and_new_drive_reset(self):
|
|
timer = StoppedTimer()
|
|
def step(second, *, drive=(1, 1), fresh=True, standstill=True, reverse=False, enabled=True):
|
|
return timer.step(now_ns=second * SECOND, drive_key=drive, car_fresh=fresh,
|
|
standstill=standstill, reverse=reverse, enabled=enabled)
|
|
self.assertIsNone(step(1))
|
|
self.assertEqual(step(62), 61)
|
|
self.assertIsNone(step(63, standstill=False))
|
|
self.assertIsNone(step(64))
|
|
self.assertEqual(step(65), 1)
|
|
self.assertIsNone(step(66, reverse=True))
|
|
self.assertIsNone(step(67))
|
|
self.assertEqual(step(68), 1)
|
|
self.assertIsNone(step(69, fresh=False))
|
|
self.assertIsNone(step(70))
|
|
self.assertEqual(step(71), 1)
|
|
self.assertIsNone(step(72, enabled=False))
|
|
self.assertIsNone(step(73))
|
|
self.assertEqual(step(74), 1)
|
|
self.assertIsNone(step(75, drive=None))
|
|
self.assertIsNone(step(76, drive=(2, 2)))
|
|
self.assertIsNone(step(135, drive=(2, 2)))
|
|
self.assertEqual(step(136, drive=(2, 2)), 60)
|
|
self.assertEqual(step(137, drive=(2, 2)), 61)
|
|
self.assertIsNone(step(10, drive=(2, 2)), "a regressed monotonic clock clears the interval")
|
|
|
|
def test_runtime_requires_post_start_fresh_valid_car_and_clears_on_loss(self):
|
|
ui = ui_fake()
|
|
car = ui.sm.messages["carState"]
|
|
car.canValid = True
|
|
car.canTimeout = False
|
|
car.standstill = True
|
|
car.gearShifter = car_schema.CarState.GearShifter.drive
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
timer_on = default_document()
|
|
timer_on["layouts"]["compact"][STOPPED_TIMER_WIDGET]["enabled"] = True
|
|
def refresh(now_ns, *, car_age_ns=0):
|
|
for name in ("deviceState", "pandaStates", "carState"):
|
|
ui.sm.logMonoTime[name] = now_ns - (car_age_ns if name == "carState" else 0)
|
|
with patch("openpilot.starpilot.ui.runtime_snapshot.read_customization", return_value=timer_on):
|
|
return adapter.build(ShellMode.ONROAD, now_ns=now_ns).onroad.stopped_duration_s
|
|
self.assertIsNone(refresh(NOW))
|
|
self.assertIsNone(refresh(NOW + 59 * SECOND))
|
|
self.assertEqual(refresh(NOW + 61 * SECOND), 61)
|
|
self.assertIsNone(refresh(NOW + 62 * SECOND, car_age_ns=201_000_000))
|
|
self.assertIsNone(refresh(NOW + 63 * SECOND))
|
|
self.assertEqual(refresh(NOW + 64 * SECOND), 1)
|
|
car.gearShifter = car_schema.CarState.GearShifter.reverse
|
|
self.assertIsNone(refresh(NOW + 65 * SECOND))
|
|
car.gearShifter = car_schema.CarState.GearShifter.drive
|
|
self.assertIsNone(refresh(NOW + 66 * SECOND))
|
|
ui.started_frame = 4
|
|
self.assertIsNone(refresh(NOW + 67 * SECOND), "old carState receive cannot seed the next drive")
|
|
ui.sm.recv_frame["carState"] = 5
|
|
self.assertIsNone(refresh(NOW + 68 * SECOND))
|
|
self.assertIsNone(refresh(NOW + 126 * SECOND))
|
|
self.assertEqual(refresh(NOW + 127 * SECOND), 59)
|
|
self.assertEqual(refresh(NOW + 129 * SECOND), 61)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
def test_replay_seek_during_standstill_does_not_invent_duration():
|
|
from unittest.mock import Mock
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
ui = ui_fake()
|
|
ui.replay_clock = Mock()
|
|
car = ui.sm.messages['carState']
|
|
car.canValid, car.canTimeout, car.standstill = True, False, True
|
|
car.gearShifter = car_schema.CarState.GearShifter.drive
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
def step(second, epoch=1):
|
|
now = NOW + second * SECOND
|
|
ui.replay_sample = ui.sm.replay_sample = ClockSample(now, None, now, epoch=epoch, paused=True, valid=True)
|
|
for name in ('deviceState', 'carState'):
|
|
ui.sm.logMonoTime[name] = now
|
|
return adapter.build(ShellMode.ONROAD, stopped_timer_enabled=True).onroad.stopped_duration_s
|
|
assert step(0) is None
|
|
assert step(61) is None
|
|
car.standstill = False
|
|
assert step(62) is None
|
|
car.standstill = True
|
|
assert step(63) is None
|
|
assert step(65) == 2
|
|
assert step(100, epoch=2) is None
|
|
assert step(161, epoch=2) is None
|