Files
StarPilot/openpilot/starpilot/aol/tests/test_gm_axis_selection_refresh.py
T
2026-10-27 10:13:15 -05:00

107 lines
5.2 KiB
Python

"""A GM proof expiring during Controls preparation must consume queued evidence."""
from contextlib import ExitStack
from unittest.mock import patch
import pytest
from opendbc.car.gm.tests.test_ascm_intercept import params
from opendbc.car.gm.tests.test_bolt_pedal import params as bolt_params
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from opendbc.car.gm.values import CAR
from openpilot.cereal import messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
def configuration(profile):
if profile.startswith('ascm'):
return params(CAR.CHEVROLET_VOLT_ASCM, sascm=True, alpha=profile.endswith('long'))
if profile.startswith('sdgm'):
return sdgm_params(alpha=profile.endswith('long'))
candidate = CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL if profile == 'bolt-acc-pedal' else CAR.CHEVROLET_BOLT_CC_2018_2021
return bolt_params(candidate, pedal=True, camera=True)
@pytest.mark.parametrize('profile', ['ascm-stock', 'ascm-long', 'sdgm-stock', 'sdgm-long', 'bolt-cc-pedal', 'bolt-acc-pedal'])
@pytest.mark.parametrize('arrival', ['healthy', 'empty', 'temporary_fault', 'withdrawn', 'native_invalid',
'native_expired', 'can_invalid', 'longitudinal'])
def test_gm_axis_expiry_during_qualification_uses_only_fresh_queued_permission(profile, arrival):
cp = configuration(profile)
case = fixture.IoniqAvailabilityTests(methodName='runTest')
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
cleanup.enter_context(OpenpilotPrefix())
_, controls, step = case._authority_callers(False, cp=cp)
controls.lane_centering_host = None
_, _, initial = step(0, native_request_long=False, long_allowed=False)
assert initial.latActive and not initial.longActive
base = int(controls.sm.logMonoTime['aolAxisState'])
deadline = int(controls.sm['aolAxisState'].validUntilMonoTime)
clock = [deadline - 10_000_000]
fresh = deadline + (210_000_000 if arrival == 'native_expired' else 10_000_000)
queued = []
for service in ('carState', 'aolAxisState', 'selfdriveState'):
event = messaging.new_message(service, valid=True, logMonoTime=fresh)
setattr(event, service, controls.sm[service])
if service == 'carState':
event.carState.steerFaultTemporary = arrival == 'temporary_fault'
event.carState.canValid = arrival != 'can_invalid'
elif service == 'aolAxisState':
axis = event.aolAxisState
axis.sequence += 1
axis.sourceCarStateMonoTime = axis.observedMonoTime = fresh
axis.validUntilMonoTime = fresh + 30_000_000
axis.lateralActive = axis.desiredLateral = arrival != 'withdrawn'
axis.longitudinalActive = axis.desiredLongitudinal = arrival == 'longitudinal'
queued.append(event.as_reader())
def qualify(active):
# Route6a: proof is fresh on entry, expired by the axis-selection gate.
clock[0] = fresh
return active
def receive(_timeout):
if arrival != 'empty':
controls.sm.update_msgs(fresh / 1e9, queued)
if arrival == 'native_invalid':
controls.sm.valid['aolSafetyWire'] = False
cleanup.enter_context(patch.dict('os.environ', {'REPLAY': '0'}))
cleanup.enter_context(patch('time.monotonic_ns', side_effect=lambda: clock[0]))
cleanup.enter_context(patch.object(controls.longitudinal_inputs, 'qualify_active', side_effect=qualify))
receive_mock = cleanup.enter_context(patch.object(controls.sm, 'update', side_effect=receive))
command, _ = controls.state_control()
receive_mock.assert_called_once_with(0)
assert command.latActive == (arrival in ('healthy', 'longitudinal'))
assert not command.longActive
# Neither the original nor the received proof acquires a longer lease.
assert deadline == base + 30_000_000
expected_deadline = deadline if arrival == 'empty' else fresh + 30_000_000
assert controls.sm['aolAxisState'].validUntilMonoTime == expected_deadline
@pytest.mark.parametrize('profile', ['ascm-long', 'sdgm-long', 'bolt-cc-pedal', 'bolt-acc-pedal'])
def test_expired_combined_gm_permission_still_withdraws_both_axes_without_refresh(profile):
case = fixture.IoniqAvailabilityTests(methodName='runTest')
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
cleanup.enter_context(OpenpilotPrefix())
_, controls, step = case._authority_callers(True, cp=configuration(profile))
controls.lane_centering_host = None
_, _, initial = step(0)
assert initial.latActive and initial.longActive
deadline = controls.sm['aolAxisState'].validUntilMonoTime
clock = [deadline - 10_000_000]
def qualify(active):
clock[0] = deadline + 10_000_000
return active
cleanup.enter_context(patch.dict('os.environ', {'REPLAY': '0'}))
cleanup.enter_context(patch('time.monotonic_ns', side_effect=lambda: clock[0]))
cleanup.enter_context(patch.object(controls.longitudinal_inputs, 'qualify_active', side_effect=qualify))
receive = cleanup.enter_context(patch.object(controls.sm, 'update'))
command, _ = controls.state_control()
receive.assert_not_called()
assert not command.latActive and not command.longActive