mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
110 lines
5.5 KiB
Python
110 lines
5.5 KiB
Python
"""Parent availability/attempt policy only; no fit or native proof is invented."""
|
|
from dataclasses import replace
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from openpilot.starpilot.flm import source_plant_runtime as runtime
|
|
from openpilot.starpilot.flm.original_source_capture import OriginalSourceCapture, MAX_CAPTURE_SPAN_NS
|
|
from openpilot.starpilot.flm.source_plant_fit import FIRST_SLOT_NS, BLOCK_NS, PURGE_NS
|
|
from openpilot.starpilot.flm.tests.test_fullrate_pipeline import snapshots
|
|
|
|
SECOND = 1_000_000_000
|
|
START = 200 * SECOND
|
|
|
|
|
|
def parent_fixture(age_ns, *, pose_hz=20, with_goals=True, last_attempt=0):
|
|
now = START + age_ns
|
|
control, output, _ = snapshots(now - 1_000_000)
|
|
# Existing ordinary-car evidence contract: no native ACK is manufactured.
|
|
control = replace(control, native_required=False, native_allowed=False, native_identity='',
|
|
native_observed_ns=0, native_expires_ns=0)
|
|
records = []
|
|
for tick in range(age_ns // 10_000_000 + 1):
|
|
elapsed = tick * 10_000_000
|
|
goal = (0.3 if ((elapsed - 39 * SECOND) // (2 * SECOND)) % 2 == 0 else -0.3) if with_goals and elapsed >= 39 * SECOND else 0.
|
|
records.append(SimpleNamespace(control_ns=START + elapsed, frame_sequence=tick + 1,
|
|
desired_physical_accel=goal))
|
|
capture = SimpleNamespace(epoch=0, epoch_start_ns=START, epoch_cutoff_ns=START + MAX_CAPTURE_SPAN_NS,
|
|
rows=tuple(SimpleNamespace(mono_ns=stamp) for stamp in range(START, now + 1, SECOND // pose_hz)))
|
|
exported = []
|
|
|
|
def original_source_packet(collector, windows, observed_ns):
|
|
exported.append((windows, observed_ns))
|
|
# A parent-selection fixture must never fabricate a qualified child packet.
|
|
raise ValueError('parent-only export boundary reached')
|
|
|
|
adapter = SimpleNamespace(envelope=object(), source_plant_submitted_epoch=-1, identity=control.identity,
|
|
baseline_revision=control.baseline_revision, snapshot_readiness=None,
|
|
replay=SimpleNamespace(records=tuple(records), checkpoints=((START, object()),), _frame_context=lambda frame: True),
|
|
original_source_packet=original_source_packet)
|
|
collector = SimpleNamespace(adapter=adapter, original_source_capture=capture, ready=[(control, output)],
|
|
last_fit=last_attempt, snapshot_attempts=0)
|
|
assert runtime._current_fit_pair_reason(collector, now) is None
|
|
return collector, now, exported
|
|
|
|
|
|
def test_predeclared_constants_and_plant_design_remain_separate():
|
|
assert runtime.MIN_NORMAL_JOB_AGE_NS == 60 * SECOND
|
|
assert runtime.NORMAL_JOB_ATTEMPT_SPACING_NS == 80 * SECOND
|
|
assert MAX_CAPTURE_SPAN_NS == 100 * SECOND
|
|
assert FIRST_SLOT_NS + 7 * (BLOCK_NS + PURGE_NS) + BLOCK_NS == 38_800_000_000
|
|
|
|
|
|
@pytest.mark.parametrize('age_ns,attempt', [(60 * SECOND - 1, False), (60 * SECOND, True)])
|
|
def test_complete_parent_geometry_first_attempt_boundary(age_ns, attempt):
|
|
collector, now, exported = parent_fixture(age_ns)
|
|
assert runtime.snapshot(collector, now) is None
|
|
assert bool(exported) is attempt
|
|
assert collector.snapshot_attempts == int(attempt)
|
|
assert collector.last_fit == (now if attempt else 0)
|
|
if attempt:
|
|
assert exported == [(((START, now - age_ns % 10_000_000),), now)]
|
|
assert collector.adapter.snapshot_readiness['reason'] == 'plant_snapshot_unavailable'
|
|
else:
|
|
assert collector.adapter.snapshot_readiness['reason'] == 'plant_fixed_cohort_still_collecting'
|
|
|
|
|
|
@pytest.mark.parametrize('pose_hz,goals', [(20, False), (10, True)])
|
|
def test_incomplete_geometry_never_consumes_attempt_or_cooldown(pose_hz, goals):
|
|
collector, now, exported = parent_fixture(60 * SECOND, pose_hz=pose_hz, with_goals=goals)
|
|
assert runtime.snapshot(collector, now) is None
|
|
assert exported == []
|
|
assert collector.snapshot_attempts == collector.last_fit == 0
|
|
assert collector.adapter.snapshot_readiness['reason'] == 'plant_later_original_goal_geometry'
|
|
assert collector.adapter.snapshot_readiness['candidateBundles'] < 8
|
|
|
|
|
|
@pytest.mark.parametrize('elapsed,attempt', [(80 * SECOND - 1, False), (80 * SECOND, True)])
|
|
def test_actual_attempt_spacing_boundary(elapsed, attempt):
|
|
collector, now, exported = parent_fixture(90 * SECOND)
|
|
collector.last_fit = now - elapsed
|
|
previous = collector.last_fit
|
|
assert runtime.snapshot(collector, now) is None
|
|
assert bool(exported) is attempt
|
|
assert collector.snapshot_attempts == int(attempt)
|
|
assert collector.last_fit == (now if attempt else previous)
|
|
|
|
|
|
def test_submitted_epoch_still_forbids_another_attempt():
|
|
collector, now, exported = parent_fixture(90 * SECOND)
|
|
collector.adapter.source_plant_submitted_epoch = collector.original_source_capture.epoch
|
|
assert runtime.snapshot(collector, now) is None
|
|
assert not exported and collector.snapshot_attempts == 0
|
|
|
|
|
|
def test_real_capture_keeps_fixed_hundred_second_epoch_and_no_reselection():
|
|
capture = OriginalSourceCapture()
|
|
receipt = {'epoch_id': 'first', 'start_ns': START, 'cutoff_ns': START + 100 * SECOND,
|
|
'identity': '1' * 64, 'source_epoch': '2' * 64, 'baseline_revision': 'baseline'}
|
|
capture.begin_epoch(receipt)
|
|
ordinal = capture.epoch
|
|
with pytest.raises(ValueError, match='cannot reselect'):
|
|
capture.begin_epoch({**receipt, 'epoch_id': 'overlap', 'start_ns': START + 60 * SECOND,
|
|
'cutoff_ns': START + 160 * SECOND})
|
|
assert capture.epoch == ordinal and capture.epoch_cutoff_ns == START + 100 * SECOND
|
|
capture.begin_epoch({**receipt, 'epoch_id': 'second', 'start_ns': START + 100 * SECOND,
|
|
'cutoff_ns': START + 200 * SECOND})
|
|
assert capture.epoch == ordinal + 1
|
|
assert capture.epoch_cutoff_ns - capture.epoch_start_ns == 100 * SECOND
|