"""Host source loss and loop-lag lifecycle regression coverage.""" from contextlib import ExitStack from dataclasses import replace from unittest import mock import pytest from openpilot.cereal import log from openpilot.common.prefix import OpenpilotPrefix from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture from openpilot.starpilot.aol.tests.test_input_deadline_continuity import BASE from openpilot.starpilot.aol.tests.test_ordinary_source_availability import settings from openpilot.starpilot.aol.wire import decode_intent, encode_intent @pytest.mark.parametrize('mode', ['long_only', 'combined', 'combined_unarmed']) @pytest.mark.parametrize('prior_lag', [False, True]) def test_host_gap_during_real_loop_lag_remains_ordinary_soft_disable(mode, prior_lag): case=fixture.IoniqAvailabilityTests(methodName='runTest') with ExitStack() as cleanup: cleanup.callback(case.doCleanups) cleanup.enter_context(OpenpilotPrefix()) sd,controls,drive,card,receive=case._ioniq_availability_callers(full_op=True) old,state,command=drive(2,state_stamp=BASE,offset_ns=4_497_535,**settings(mode,BASE,3)) assert state.enabled and old.longitudinalActive and command.longActive deadline=old.validUntilMonoTime if mode=='combined': assert receive(BASE+4_497_535,axis=sd.pm.messages['aolAxisState'],event=sd.pm.messages['onroadEvents']) 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 source,sequence=BASE,3 # Only a numerical input to the REAL Ratekeeper is changed. Real update_events # creates selfdrivedLagging; StateMachine/native/axis decisions are untouched. with mock.patch.object(sd.rk.avg_dt,'get_average',return_value=0.02): assert sd.rk.lagging if prior_lag: source,sequence=BASE+10_000_000,4 axis,state,command=drive(3,state_stamp=source,**settings(mode,source,sequence)) assert state.enabled and state.state==log.SelfdriveState.OpenpilotState.softDisabling assert log.OnroadEvent.EventName.selfdrivedLagging in sd.events.names assert not sd.aol_authority_lost # Corrected contract: fresh, already-engaged ordinary axes continue # under the real SOFT timer. Only the subsequent expired source is OFF. assert str(axis.faultReason) == 'none' and axis.longitudinalActive and command.longActive assert bool(axis.lateralActive) == bool(command.latActive) == (mode != 'long_only') fresh_lag_deadline = axis.validUntilMonoTime if mode=='combined': assert receive(BASE+10_000_000,axis=sd.pm.messages['aolAxisState'],event=sd.pm.messages['onroadEvents']) tick=7 if prior_lag else 5 now=BASE+(tick-2)*10_000_000+5_399_000 axis,state,command=drive(tick,timeout=True,state_stamp=source,offset_ns=5_399_000,**settings(mode,source,sequence)) assert log.OnroadEvent.EventName.selfdrivedLagging in sd.events.names assert not axis.lateralActive and not axis.longitudinalActive assert not command.latActive and not command.longActive assert old.validUntilMonoTime==deadline