Files
StarPilot/openpilot/starpilot/ui/tests/test_onroad_stopped_timer.py
T

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