mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
143 lines
7.1 KiB
Python
143 lines
7.1 KiB
Python
"""Recorded EV6 8f2e source cutoff keeps only its original unexpired proof."""
|
|
from contextlib import ExitStack
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from openpilot.cereal import log, messaging
|
|
from openpilot.common.prefix import OpenpilotPrefix
|
|
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
|
|
from openpilot.starpilot.aol.tests.test_ev6_physical_authorization import ev6_params
|
|
from openpilot.starpilot.aol.tests.test_ordinary_source_availability import settings
|
|
|
|
SOURCE = 10_020_000_000
|
|
|
|
|
|
@pytest.mark.parametrize('socket_timeout', [False, True])
|
|
@pytest.mark.parametrize('age_ns,published_offset_ns', [
|
|
(30_960_101, 4_945_854),
|
|
(31_244_572, 5_262_889),
|
|
(31_383_633, 5_039_179),
|
|
(30_890_079, 4_798_596),
|
|
(31_008_968, 5_325_976),
|
|
(31_351_205, 5_392_434),
|
|
(31_544_229, 5_502_541),
|
|
(32_161_269, 6_033_539),
|
|
(31_684_749, 5_741_086),
|
|
(31_896_523, 5_808_274),
|
|
(32_254_551, 5_671_607),
|
|
(31_565_476, 5_393_216),
|
|
(30_879_839, 5_223_629),
|
|
])
|
|
def test_ev6_recorded_source_cutoff_retains_only_original_unexpired_output(age_ns, published_offset_ns, socket_timeout):
|
|
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
|
with ExitStack() as cleanup:
|
|
cleanup.callback(case.doCleanups)
|
|
cleanup.enter_context(OpenpilotPrefix())
|
|
cp = ev6_params()
|
|
sd, controls, drive = case._authority_callers(True, cp=cp, genuine_events=True)
|
|
assert sd.CP.carFingerprint == 'KIA_EV6'
|
|
assert sd.CP.safetyConfigs[0].safetyParam == 0x815 and sd.CP.alternativeExperience == 32
|
|
for tick in (0, 1):
|
|
drive(tick, **settings('combined', 10_000_000_000 + tick * 10_000_000, tick + 1))
|
|
original, state, command = drive(2, state_stamp=SOURCE, offset_ns=published_offset_ns, **settings('combined', SOURCE, 3))
|
|
assert original.lateralActive and original.longitudinalActive and command.latActive and command.longActive
|
|
deadline = original.validUntilMonoTime
|
|
assert deadline == SOURCE + published_offset_ns + 30_000_000
|
|
old = original.to_dict()
|
|
timer = sd.state_machine.soft_disable_timer
|
|
sd.car_state_poller = mock.Mock()
|
|
sd.car_state_poller.poll.return_value = []
|
|
axis, state, command = drive(5, timeout=socket_timeout, state_stamp=SOURCE, offset_ns=age_ns - 30_000_000,
|
|
**settings('combined', SOURCE, 3))
|
|
assert sd.aol_axis_publication_deferred
|
|
if socket_timeout:
|
|
assert sd.aol_car_state_log_ns == 0 and not sd.conditional_car_state_valid
|
|
else:
|
|
assert sd.aol_car_state_log_ns == SOURCE and sd.conditional_car_state_valid
|
|
assert axis.to_dict() == old and axis.validUntilMonoTime == deadline
|
|
assert state.enabled and sd.state_machine.soft_disable_timer == timer
|
|
assert log.OnroadEvent.EventName.commIssue not in sd.events.names
|
|
# The consumer stops at the original cutoff; retaining a proof never renews it.
|
|
with mock.patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=deadline + 1):
|
|
expired, _ = controls.state_control()
|
|
assert not expired.latActive and not expired.longActive
|
|
# A second stale loop cannot repeat the deferral even within that deadline.
|
|
next_axis, _, next_command = drive(5, timeout=True, state_stamp=SOURCE, offset_ns=age_ns - 30_000_000 + 100_000,
|
|
**settings('combined', SOURCE, 3))
|
|
assert not sd.aol_axis_publication_deferred
|
|
assert not next_axis.lateralActive and not next_axis.longitudinalActive
|
|
assert not next_command.latActive and not next_command.longActive
|
|
|
|
|
|
@pytest.mark.parametrize('fault', ['can', 'epsTemporary', 'epsPermanent', 'acc', 'brake', 'gas',
|
|
'pandaRx', 'relay', 'nativeInvalid', 'uncalibrated'])
|
|
def test_ev6_unexpired_output_never_defers_a_new_fault(fault):
|
|
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
|
with ExitStack() as cleanup:
|
|
cleanup.callback(case.doCleanups)
|
|
cleanup.enter_context(OpenpilotPrefix())
|
|
sd, _, drive = case._authority_callers(True, cp=ev6_params(), genuine_events=True)
|
|
for tick in (0, 1, 2):
|
|
stamp = 10_000_000_000 + tick * 10_000_000
|
|
axis, _, command = drive(tick, state_stamp=stamp, offset_ns=4_945_854 if tick == 2 else 0,
|
|
**settings('combined', stamp, tick + 1))
|
|
assert axis.lateralActive and axis.longitudinalActive and command.latActive and command.longActive
|
|
state = fixture.car_state()
|
|
changes = {}
|
|
if fault == 'can':
|
|
state.canValid = False
|
|
elif fault == 'epsTemporary':
|
|
state.steerFaultTemporary = True
|
|
elif fault == 'epsPermanent':
|
|
state.steerFaultPermanent = True
|
|
elif fault == 'acc':
|
|
state.accFaulted = True
|
|
elif fault == 'brake':
|
|
changes['brake'] = True
|
|
elif fault == 'gas':
|
|
changes['gas'] = True
|
|
elif fault == 'pandaRx':
|
|
changes['panda_fault'] = True
|
|
elif fault == 'relay':
|
|
changes['relay_fault'] = True
|
|
elif fault == 'nativeInvalid':
|
|
changes['native_valid'] = False
|
|
elif fault == 'uncalibrated':
|
|
# Calibration publishes at 1 Hz; the normal fixture cadence correctly
|
|
# skips a replacement only 30 ms later. Deliver the new envelope itself.
|
|
stamp = SOURCE + 30_960_101
|
|
calibration = messaging.new_message('extrinsicsCalibration', valid=True, logMonoTime=stamp)
|
|
calibration.extrinsicsCalibration.calStatus = 'uncalibrated'
|
|
sd.sm.update_msgs(stamp / 1e9, [calibration.as_reader()])
|
|
if fault in ('can', 'epsTemporary', 'epsPermanent', 'acc'):
|
|
changes['observed_state'] = state
|
|
axis, _, command = drive(5, state_stamp=SOURCE, offset_ns=960_101,
|
|
**settings('combined', SOURCE, 3), **changes)
|
|
if fault == 'uncalibrated':
|
|
assert sd.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.uncalibrated
|
|
assert not sd.aol_axis_publication_deferred
|
|
# The existing fault/pedal rules select the remaining axes; the previous
|
|
# combined proof cannot survive the new restriction.
|
|
assert not (axis.lateralActive and axis.longitudinalActive)
|
|
assert not (command.latActive and command.longActive)
|
|
|
|
|
|
@pytest.mark.parametrize('timing', ['timely', 'late_poll', 'processing_crosses_deadline'])
|
|
def test_ev6_fresh_pair_reconciles_without_extending_the_old_deadline(timing):
|
|
from openpilot.starpilot.aol.tests import test_input_deadline_continuity as continuity
|
|
|
|
def ev6_callers(case, full_op=True, *, stock_angle=False):
|
|
assert full_op and not stock_angle
|
|
sd, controls, drive = case._authority_callers(True, cp=ev6_params(), genuine_events=True)
|
|
assert sd.CP.carFingerprint == 'KIA_EV6'
|
|
assert sd.CP.safetyConfigs[0].safetyParam == 0x815 and sd.CP.alternativeExperience == 32
|
|
for tick in (0, 1):
|
|
stamp = 10_000_000_000 + tick * 10_000_000
|
|
drive(tick, **settings('combined', stamp, tick + 1))
|
|
return sd, controls, drive, None, None
|
|
|
|
# Reuse the existing timing stimulus with the captured EV6 configuration;
|
|
# production event, source, native and consumer checks remain real.
|
|
with mock.patch.object(fixture.IoniqAvailabilityTests, '_ioniq_availability_callers', ev6_callers):
|
|
continuity.test_fresh_source_replaces_expired_output_without_restoring_disengagement('ordinary', timing)
|