Files
StarPilot/openpilot/starpilot/conditional_mode/tests/test_settings_clock_race.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

128 lines
5.8 KiB
Python

"""Actual CEM host/consumer/caller; controlled settings-worker interleaving."""
from dataclasses import replace
from pathlib import Path
import unittest
from unittest.mock import patch
from opendbc.car.honda.interface import CarInterface
from opendbc.car.honda.values import CAR
from openpilot.cereal import messaging
from openpilot.selfdrive.selfdrived import selfdrived as module
from openpilot.starpilot.conditional_mode.policy import ManualIntent
from openpilot.starpilot.conditional_mode.runtime_settings import DOCUMENT_KEY, REFRESH_NS
from openpilot.starpilot.conditional_mode.tests import test_status_consumer as fixture
from openpilot.starpilot.conditional_mode.tests.test_projection import BOOT, MONO, owner_context
class TestSettingsClockRace(unittest.TestCase):
def setUp(self):
self.source = fixture.TestModeTransport(methodName='runTest')
self.source.setUp()
self.addCleanup(self.source.doCleanups)
self.enterContext(patch.object(module, 'REPLAY', False))
self.drive = module.SelfdriveD.__new__(module.SelfdriveD)
self.drive.CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH)
self.drive.CP.openpilotLongitudinalControl = True
self.drive.params = self.source.params
self.drive.conditional_replay = True
self.drive.conditional_settings = self.source.owner
self.drive.conditional_consumer = self.source.consumer
self.drive.conditional_car_state_valid = self.drive.initialized = self.drive.enabled = True
self.drive.aol_replay = False
self.scene = {'speed': 10.0, 'horizon': 40.0, 'lead_present': False}
for tick in range(1, 31):
now = MONO + tick * 50_000_000
event, _, _ = self.source.produce(now, intent=ManualIntent.NONE, scene=self.scene,
context=owner_context(now - 2_000_000))
self.source.consume(event, now)
self.assertTrue(self.source.host.projector.stop_detector.committed)
def call(self, during_clock=None, before_verdict=None, *, can_valid=True):
now = MONO + 31 * 50_000_000
event, proposal, sm = self.source.produce(now, intent=ManualIntent.NONE, scene=self.scene,
context=owner_context(now - 2_000_000))
assert proposal.decision is not None
self.assertEqual(proposal.decision.reason.value, 'cem_stop')
device = messaging.new_message('deviceState', valid=True)
device.deviceState.started = True
device.deviceState.startedMonoTime = self.source.drive_id
sm.payloads['deviceState'] = messaging.log_from_bytes(device.to_bytes()).deviceState
sm.payloads['slcState'] = messaging.log_from_bytes(event.to_bytes()).slcState
for service in ('deviceState', 'slcState'):
sm.logMonoTime[service] = now
sm.recv_time[service] = now / 1e9
sm.seen[service] = sm.alive[service] = sm.valid[service] = True
self.drive.sm, self.drive.aol_car_state_log_ns = sm, now - 2_000_000
cs = sm['carState'].as_builder()
cs.canValid = can_valid
verdict = self.source.owner.verdict
def clocks():
if during_clock is not None:
during_clock(now)
return now, BOOT + now - MONO, 1000
def interleaved(snapshot, **kwargs):
if before_verdict is not None:
before_verdict(now)
return verdict(snapshot, **kwargs)
with patch.object(module, 'paired_clocks_ns', side_effect=clocks), \
patch.object(self.source.owner, 'verdict', side_effect=interleaved):
self.drive.update_conditional_mode(cs)
self.assertTrue(self.source.host.projector.stop_detector.committed)
return now
def reverify(self, now):
old = self.source.owner.current
self.assertIsNotNone(old)
assert old is not None
new = self.source.owner.refresh(now + 1_000_000, force=True)
self.assertEqual(new.revision, old.revision)
self.assertEqual(new.observed_mono_ns, old.observed_mono_ns)
self.assertGreater(new.verified_mono_ns, now)
def test_same_revision_worker_after_cycle_clock_preserves_actual_cem(self):
self.call(during_clock=self.reverify)
self.assertTrue(self.drive.conditional_result.accepted)
self.assertTrue(self.drive.experimental_mode)
def test_same_revision_worker_before_verdict_preserves_actual_cem(self):
self.call(before_verdict=self.reverify)
self.assertTrue(self.drive.conditional_result.accepted)
self.assertTrue(self.drive.experimental_mode)
def test_changed_source_rejects_captured_revision(self):
def change(now):
self.source.params.put_bool('ExperimentalMode', True, block=True)
self.source.owner.refresh(now + 1_000_000, force=True)
self.call(during_clock=change)
self.assertFalse(self.drive.conditional_result.accepted)
self.assertFalse(self.drive.experimental_mode)
def test_corrupt_source_rejects_captured_revision(self):
def change(now):
Path(self.source.params.get_param_path(DOCUMENT_KEY)).write_bytes(b'malformed')
self.source.owner.refresh(now + 1_000_000, force=True)
self.call(during_clock=change)
self.assertFalse(self.drive.conditional_result.accepted)
self.assertFalse(self.drive.experimental_mode)
def test_current_vehicle_fault_remains_rejected_during_reverification(self):
self.call(during_clock=self.reverify, can_valid=False)
self.assertFalse(self.drive.conditional_result.accepted)
self.assertFalse(self.drive.experimental_mode)
def test_captured_verification_cannot_borrow_newer_expiry(self):
old = self.source.owner.current
self.assertIsNotNone(old)
assert old is not None
now = old.verified_mono_ns + REFRESH_NS + 1
self.source.owner.refresh(now + 1_000_000, force=True)
self.assertFalse(self.source.owner.affirm(old, now_mono_ns=now))
def test_snapshot_source_fields_cannot_be_forged(self):
old = self.source.owner.current
self.assertIsNotNone(old)
assert old is not None
self.assertFalse(self.source.owner.affirm(replace(old, experimental_raw=b'1'), now_mono_ns=old.verified_mono_ns))