feat(planning): consolidate recorded-route localization and spatial scene
Preserve the completed teach-and-repeat laboratory stage: reference preparation, cascaded acquisition, local tracking and recovery, recording lifecycle, replay qualification, and persistent Rerun scene controls. Document the open grid-picking regression and Rerun upgrade contract. No autonomous driving or loop-closure optimization is claimed.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""The archive fault adapter omits receipts without changing surviving clocks."""
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def adapter_module():
|
||||
path = Path(__file__).resolve().parents[1] / "scripts/planning_archive_source.py"
|
||||
spec = importlib.util.spec_from_file_location("archive_fault_probe", path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_fault_preserves_survivor_identity_payload_and_receipt_offsets(monkeypatch):
|
||||
module = adapter_module()
|
||||
original = [
|
||||
SimpleNamespace(
|
||||
received_monotonic_ns=int((100 + t) * 1e9),
|
||||
received_at_epoch_ns=int((1000 + t) * 1e9),
|
||||
topic="fixture/lio_pcl" if i % 2 else "fixture/lio_pose",
|
||||
sequence=i + 1,
|
||||
payload=bytes([i]),
|
||||
)
|
||||
for i, t in enumerate([0, 1, 2, 3, 3.9, 4, 5])
|
||||
]
|
||||
monkeypatch.setattr(module, "iter_replay_messages", lambda _: iter(original))
|
||||
clock = [int(500e9)]
|
||||
|
||||
def wait(delay):
|
||||
clock[0] += int(delay * 1e9)
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(module, "time", SimpleNamespace(monotonic_ns=lambda: clock[0]))
|
||||
source = module.ReceiptQueueArchiveSource(Path("unused"), "B", 10, drop_interval_s=(2, 4))
|
||||
source.stop = SimpleNamespace(wait=wait)
|
||||
source.started = clock[0]
|
||||
published = []
|
||||
source.ingress = SimpleNamespace(
|
||||
publish=lambda **kw: published.append(kw) or True,
|
||||
end_session=lambda session: None,
|
||||
)
|
||||
source.publish()
|
||||
kept = [original[i] for i in [0, 1, 5, 6]]
|
||||
assert source.error is None
|
||||
assert [x["sequence"] for x in source.dropped_receipts] == [3, 4, 5]
|
||||
for out, entry in zip(published, kept, strict=True):
|
||||
assert out["source_sequence"] == entry.sequence
|
||||
assert out["payload"] is entry.payload
|
||||
assert out["captured_at_epoch_ns"] == entry.received_at_epoch_ns
|
||||
assert out["received_monotonic_ns"] == source.started + entry.received_monotonic_ns - int(
|
||||
100e9
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interval", [(2, 2), (4, 2), (0, 2), (1, 11), (1, float("nan"))])
|
||||
def test_invalid_fault_interval_is_rejected(interval):
|
||||
with pytest.raises(ValueError, match="fault interval"):
|
||||
adapter_module().ReceiptQueueArchiveSource(
|
||||
Path("unused"), "B", 10, drop_interval_s=interval
|
||||
)
|
||||
Reference in New Issue
Block a user