Files
StarPilot/openpilot/starpilot/flm/tests/test_learner_unavailable_reason.py
T
Dom 487dfc4bf4 FLM: preserve fresh evidence across paired queue drains
Merge bounded control/output FIFO heads using original publisher headers. Preserve original age, native, source, overflow and malformed-input checks; retain producer order after joins.

Validated with actual pinned cloud dependencies and fresh-head regression tests.
2026-10-27 17:01:24 -05:00

90 lines
4.1 KiB
Python

"""Existing status publications must name settings blocks accurately."""
from openpilot.starpilot.flm.tests._drain_fixture import separate_drain_fixture
from dataclasses import replace
from types import SimpleNamespace
from unittest import mock
import pytest
from openpilot.starpilot.flm import steerlearnerd
from openpilot.starpilot.flm.runtime_adapter_registry import SourceState
NOW = 1_000_000_000
def publication(source, evidence=None, adapter=None, sources=None):
sources = [source] if sources is None else sources
sm = mock.Mock(updated={'deviceMotion': False}, logMonoTime={'deviceMotion': 0})
ticks = iter(NOW + index * 250_000_000 for index in range(len(sources)))
current_time = [NOW]
def update(timeout=None):
if timeout == 0:
return
current_time[0] = next(ticks)
sm.update.side_effect = update
pm, fitter = mock.Mock(), mock.Mock()
control_socket, applied_socket = object(), object()
factory = mock.Mock(return_value=adapter)
source_reader = mock.Mock(side_effect=sources)
clock = mock.Mock(side_effect=lambda: current_time[0])
def drain(socket, callback, receive, sink, now):
if socket is control_socket and evidence is not None:
event = SimpleNamespace(which=lambda: 'starpilotLateralState',
starpilotLateralState=SimpleNamespace(learningEvidence=SimpleNamespace(version=1)))
callback(event, now, now)
with mock.patch.object(steerlearnerd, 'drain_pair', separate_drain_fixture(drain)), mock.patch.object(steerlearnerd, 'read_control', return_value=evidence):
with pytest.raises(StopIteration):
steerlearnerd.run(SimpleNamespace(), sm, control_socket, applied_socket, pm, factory, mock.Mock(),
source_reader, lambda item: (('feedforward', 'low', 'steady'),),
lambda item: ('feedforward', 'low', 'steady'), mock.Mock(), mock.Mock(), fitter,
mock.Mock(), clock=clock)
fitter.close.assert_called_once_with('learner_parent_exit')
factory_calls = factory.call_count
message = pm.send.call_args.args[1]
assert message.valid
assert str(message.autoSteeringLearning.learning.status) == 'unavailable'
assert message.autoSteeringLearning.learning.sourceReceipt.manual == sources[-1].manual
return str(message.autoSteeringLearning.learning.reason), factory_calls
def source():
return SourceState(True, False, '0', 'a' * 64, NOW, NOW + 500_000_000, 'b' * 64, (0., 0., 0.))
@pytest.mark.parametrize('change,reason', (({'enabled': False}, 'learning_disabled'),
({'manual': True}, 'manual_profile_override'),
({'observed_ns': NOW - 600_000_000, 'expires_ns': NOW - 100_000_000},
'settings_receipt_expired')))
def test_settings_block_is_reported_without_claiming_an_unsupported_controller(change, reason):
actual, factory_calls = publication(replace(source(), **change))
assert actual == reason
assert factory_calls == 0
def test_no_controller_evidence_is_distinct_from_adapter_rejection():
reason, factory_calls = publication(source())
assert reason == 'no_current_controller_evidence'
assert factory_calls == 0
def test_cleared_manual_block_does_not_leave_a_stale_status_reason():
resumed = replace(source(), source_epoch='c' * 64)
reason, factory_calls = publication(resumed, sources=[replace(source(), manual=True), resumed, resumed])
assert reason == 'no_current_controller_evidence'
assert factory_calls == 0
def test_actual_adapter_unavailable_is_reserved_for_a_real_factory_refusal():
evidence = SimpleNamespace(source_epoch='a' * 64, source_receipt_ns=NOW, control_ns=NOW,
identity='c' * 64, generation=1, domain='normalized_torque', stage_revision='stage',
response_revision='response', controller_mode='starpilot', policy='ioniq6',
baseline_revision='baseline', controller_revision='controller', turn_assist=False)
reason, factory_calls = publication(source(), evidence)
assert reason == 'actual_adapter_unavailable'
assert factory_calls == 1