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

118 lines
6.1 KiB
Python

"""Synthetic envelopes exercise the actual Card bridge, not native admission."""
import ast
import inspect
import textwrap
import unittest
from types import SimpleNamespace
from unittest.mock import patch
from openpilot.selfdrive.car import card
from openpilot.starpilot.vehicle_startup import VehicleStartupOwner
class Envelope(dict):
def __init__(self):
super().__init__(carControl=SimpleNamespace(enabled=1), pandaStates=[object(), object()])
self.seen = dict.fromkeys(self, True)
self.valid = dict.fromkeys(self, True)
self.alive = dict.fromkeys(self, True)
self.logMonoTime = {'carControl': 9_950_000_000, 'pandaStates': 29_950_000_000}
self.recv_time = dict.fromkeys(self, 9.95)
class TestVehicleStateContext(unittest.TestCase):
def fixture(self):
calls = []
holder = VehicleStartupOwner()
holder.owner = SimpleNamespace(after_state=lambda **context: calls.append(context))
instance = SimpleNamespace(vehicle_startup=holder, sm=Envelope(), CI=object())
return instance, calls
def invoke(self, instance, clocks=(10_000_000_000, 30_000_000_000)):
state = object()
with patch.object(card, 'clock_pair_ns', return_value=clocks):
card.Car.update_vehicle_state_context(instance, state)
return state
def test_separated_clocks_and_exact_owner_forwarding(self):
instance, calls = self.fixture()
state = self.invoke(instance)
self.assertEqual(len(calls), 1)
result = calls[0]
self.assertIs(result['state'], state)
self.assertIs(result['ci'], instance.CI)
self.assertIs(result['pandas'], instance.sm['pandaStates'])
self.assertIs(result['control_enabled'], True)
self.assertIs(result['control_current'], True)
self.assertIs(result['panda_current'], True)
self.assertEqual(result['now_ns'], 30_000_000_000)
self.assertEqual(result['panda_log_ns'], 29_950_000_000)
self.assertEqual(result['panda_recv_ns'], 29_950_000_000)
def test_stale_future_absent_and_invalid_evidence(self):
for service, age in (('carControl', 150_000_001), ('pandaStates', 300_000_001)):
for evidence in ('producer_stale', 'producer_future', 'producer_absent',
'receipt_stale', 'receipt_future', 'receipt_absent', 'seen', 'valid', 'alive'):
with self.subTest(service=service, evidence=evidence):
instance, calls = self.fixture()
producer_now = 10_000_000_000 if service == 'carControl' else 30_000_000_000
if evidence.startswith('producer'):
instance.sm.logMonoTime[service] = {'producer_stale': producer_now - age,
'producer_future': producer_now + 1, 'producer_absent': 0}[evidence]
elif evidence.startswith('receipt'):
instance.sm.recv_time[service] = {'receipt_stale': (10_000_000_000 - age) / 1e9,
'receipt_future': 10.001, 'receipt_absent': 0}[evidence]
else:
getattr(instance.sm, evidence)[service] = False
self.invoke(instance)
result = calls[0]
self.assertIs(result['control_current' if service == 'carControl' else 'panda_current'], False)
if service == 'pandaStates':
self.assertEqual(result['panda_recv_ns'], 0)
instance, calls = self.fixture()
self.invoke(instance, None)
self.assertIs(calls[0]['control_current'], False)
self.assertIs(calls[0]['panda_current'], False)
self.assertEqual(calls[0]['now_ns'], 0)
def test_no_holder_or_owner_performs_no_clock_or_envelope_work(self):
empty = card.Car.__new__(card.Car)
owned = card.Car.__new__(card.Car)
owned.vehicle_startup = VehicleStartupOwner()
for instance in (empty, owned):
with patch.object(card, 'clock_pair_ns', side_effect=AssertionError('unexpected clock read')):
card.Car.update_vehicle_state_context(instance, object())
VehicleStartupOwner().after_state(unused=object())
def test_state_update_source_order_obligation(self):
# The full Card fixture requires transport/hardware. Bind the actual caller
# order explicitly; envelope tests above execute the imported bridge.
tree = ast.parse(textwrap.dedent(inspect.getsource(card.Car.state_update)))
calls = []
for node in ast.walk(tree):
if isinstance(node, ast.Call):
calls.append((node.lineno, ast.unparse(node.func)))
def position(name):
return min(line for line, function in calls if function == name)
self.assertLess(position('self.CI.update'), position('self.sm.update'))
self.assertLess(position('self.sm.update'), position('self.update_vehicle_state_context'))
self.assertLess(position('self.update_vehicle_state_context'), position('self.update_cruise_speed'))
helper = ast.parse(textwrap.dedent(inspect.getsource(card.Car.update_cruise_speed)))
helper_calls = {ast.unparse(node.func): node for node in ast.walk(helper) if isinstance(node, ast.Call)}
update = helper_calls['self.v_cruise_helper.update_v_cruise']
initialize = helper_calls['self.v_cruise_helper.initialize_v_cruise']
self.assertLess(update.lineno, initialize.lineno)
self.assertEqual(ast.unparse(update.args[0]), 'CS')
self.assertEqual(ast.unparse(initialize.args[0]), 'self.CS_prev')
self.assertEqual(ast.unparse(initialize.keywords[0].value), 'self.vehicle_startup.consume_cruise_resume()')
enable_edge = next(node for node in ast.walk(helper) if isinstance(node, ast.If))
self.assertEqual(ast.unparse(enable_edge.test), "self.sm['carControl'].enabled and (not self.CC_prev.enabled)")
self.assertIn(initialize, list(ast.walk(enable_edge)))
step = inspect.getsource(card.Car.step)
step_tree = ast.parse(textwrap.dedent(step))
state_update = next(node for node in ast.walk(step_tree)
if isinstance(node, ast.Call) and ast.unparse(node.func) == 'self.state_update')
self.assertEqual([(kw.arg, ast.unparse(kw.value)) for kw in state_update.keywords], [('defer_radar', 'True')])
self.assertLess(step.index('self.state_update('), step.index('self.state_publish('))
self.assertLess(step.index('self.state_publish('), step.index('self.CS_prev = CS'))