Files
StarPilot/openpilot/starpilot/conditional_mode/tests/test_effective_status.py
T
2026-10-20 12:14:47 -05:00

120 lines
6.4 KiB
Python

"""Actual custom Event acknowledgment and historical alert compatibility."""
from types import SimpleNamespace as NS
from dataclasses import replace
import time
import unittest
from unittest.mock import patch
from openpilot.cereal import messaging
from openpilot.starpilot.conditional_mode.consumer import ConsumerResult, ModeConsumer
from openpilot.starpilot.conditional_mode.effective_status import LIFETIME_NS, observation, publish_ack
from openpilot.starpilot.conditional_mode.policy import ModeChoice
from openpilot.starpilot.conditional_mode.status import ModeObservation
NOW = 100_000_000_000
SESSION = 'a' * 32
PLANNER = 'b' * 32
def proposal() -> ModeObservation:
return ModeObservation(PLANNER, 7, NOW - 2_000_000, NOW + 30_000_000, NOW - 1_000_000_000,
NOW - 20_000_000, NOW - 5_000_000, 3, 'c' * 64, ModeChoice.CEM,
True, 'proposed', 'cem_speed', 3)
class TestEffectiveStatus(unittest.TestCase):
def test_actual_selfdrived_publish_joins_emitted_state_and_clears_fallback(self):
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD, State
sent = {}
daemon = SelfdriveD.__new__(SelfdriveD)
daemon.enabled = daemon.active = daemon.experimental_mode = True
daemon.personality = 'standard'
self.enterContext(patch.object(daemon, "state_machine", NS(state=State.enabled), create=True))
daemon.events = Events()
daemon.events_prev = []
daemon.AM = AlertManager()
from openpilot.starpilot.aol.runtime import AxisDecision
daemon.aol_axis_decision = AxisDecision()
daemon.aol_replay = False
daemon.conditional_replay = True
daemon.conditional_ack_session = SESSION
daemon.conditional_ack_sequence = 0
sm = type('SM', (dict,), {'frame': 1})()
self.enterContext(patch.object(daemon, 'sm', sm, create=True))
current = time.monotonic_ns()
sm['deviceState'] = NS(startedMonoTime=current - 1_000_000_000)
self.enterContext(patch.object(daemon, 'pm', NS(send=lambda service, event: sent.__setitem__(service, event.to_bytes())), create=True))
actual_proposal = replace(proposal(), observed_ns=current - 1_000_000, expires_ns=current + 30_000_000,
drive_id=current - 1_000_000_000, model_ns=current - 20_000_000,
car_state_ns=current - 5_000_000)
daemon.conditional_result = ConsumerResult(True, True, 'proposed')
daemon.conditional_consumer = ModeConsumer()
daemon.conditional_consumer.last = actual_proposal
daemon.publish_selfdriveState(NS(vEgo=12.0))
state = messaging.log_from_bytes(sent['selfdriveState'])
ack = messaging.log_from_bytes(sent['starpilotSelfdriveState'])
value = observation(ack.starpilotSelfdriveState, ack.logMonoTime)
self.assertIsNotNone(value)
self.assertEqual(value.selfdrive_state_ns, state.logMonoTime)
self.assertEqual(value.effective_experimental, state.selfdriveState.experimentalMode)
self.assertEqual(value.reason, 'cem_speed')
daemon.conditional_result = ConsumerResult(False, False, 'unavailable')
daemon.experimental_mode = False
daemon.publish_selfdriveState(NS(vEgo=12.0))
fallback = messaging.log_from_bytes(sent['starpilotSelfdriveState'])
value = observation(fallback.starpilotSelfdriveState, fallback.logMonoTime)
self.assertIsNotNone(value)
self.assertFalse(value.accepted)
self.assertIsNone(value.reason)
def test_serialized_accepted_receipt_binds_actual_mode_and_source_frame(self):
event = publish_ack(session=SESSION, sequence=4, observed_ns=NOW, selfdrive_state_ns=NOW - 1_000_000,
drive_id=NOW - 1_000_000_000, effective_experimental=True,
result=ConsumerResult(True, True, 'proposed'), accepted_proposal=proposal())
event.starpilotSelfdriveState.alertText1 = 'Keep hands on wheel'
event.starpilotSelfdriveState.alertStatus = 'userPrompt'
event.starpilotSelfdriveState.alertSound = 'prompt'
decoded = messaging.log_from_bytes(event.to_bytes())
self.assertEqual(decoded.starpilotSelfdriveState.alertText1, 'Keep hands on wheel')
self.assertEqual(str(decoded.starpilotSelfdriveState.alertStatus), 'userPrompt')
self.assertEqual(str(decoded.starpilotSelfdriveState.alertSound), 'prompt')
value = observation(decoded.starpilotSelfdriveState, NOW)
self.assertIsNotNone(value)
self.assertTrue(value.accepted)
self.assertEqual(value.choice, ModeChoice.CEM)
self.assertEqual(value.selfdrive_state_ns, NOW - 1_000_000)
self.assertEqual(value.planner_session, PLANNER)
self.assertEqual(value.reason, 'cem_speed')
self.assertIsNone(observation(decoded.starpilotSelfdriveState, NOW + LIFETIME_NS + 1))
def test_fallback_clears_prior_planner_reason_and_malformed_receipts(self):
event = publish_ack(session=SESSION, sequence=5, observed_ns=NOW, selfdrive_state_ns=NOW - 1_000_000,
drive_id=NOW - 1_000_000_000, effective_experimental=False,
result=ConsumerResult(False, False, 'unavailable'), accepted_proposal=proposal())
raw = event.to_bytes()
decoded = messaging.log_from_bytes(raw).starpilotSelfdriveState
value = observation(decoded, NOW)
self.assertIsNotNone(value)
self.assertFalse(value.accepted)
self.assertIsNone(value.reason)
self.assertEqual(value.planner_sequence, 0)
self.assertEqual(value.choice, ModeChoice.STOCK)
for field, bad in (('version', 2), ('sessionId', ''), ('sequence', 0),
('sourceSelfdriveStateMonoTime', NOW + 1), ('reason', 'stale reason')):
changed = messaging.log_from_bytes(raw).as_builder()
setattr(changed.starpilotSelfdriveState.conditionalModeAck, field, bad)
with self.subTest(field=field):
self.assertIsNone(observation(messaging.log_from_bytes(changed.to_bytes()).starpilotSelfdriveState, NOW))
def test_accepted_flag_cannot_be_derived_from_cached_proposal(self):
wrong_mode = publish_ack(session=SESSION, sequence=6, observed_ns=NOW, selfdrive_state_ns=NOW - 1_000_000,
drive_id=NOW - 1_000_000_000, effective_experimental=False,
result=ConsumerResult(False, True, 'proposed'), accepted_proposal=proposal())
self.assertFalse(observation(messaging.log_from_bytes(wrong_mode.to_bytes()).starpilotSelfdriveState, NOW).accepted)