mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
80fbb4f6db
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>
118 lines
6.1 KiB
Python
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'))
|