111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_RUNTIME_PATH = (
|
|
Path(__file__).resolve().parents[1]
|
|
/ "experiments"
|
|
/ "perception"
|
|
/ "worker"
|
|
/ "e15_shadow_runtime.py"
|
|
)
|
|
_SPEC = importlib.util.spec_from_file_location("e15_shadow_runtime_test", _RUNTIME_PATH)
|
|
assert _SPEC is not None and _SPEC.loader is not None
|
|
_RUNTIME = importlib.util.module_from_spec(_SPEC)
|
|
sys.modules[_SPEC.name] = _RUNTIME
|
|
_SPEC.loader.exec_module(_RUNTIME)
|
|
CameraFragmentMetadata = _RUNTIME.CameraFragmentMetadata
|
|
IncrementalMediaBuffer = _RUNTIME.IncrementalMediaBuffer
|
|
PersistentFmp4Decoder = _RUNTIME.PersistentFmp4Decoder
|
|
ShadowRuntimeError = _RUNTIME.ShadowRuntimeError
|
|
|
|
|
|
def _metadata(sequence: int) -> CameraFragmentMetadata:
|
|
return CameraFragmentMetadata(
|
|
ingress_sequence=sequence,
|
|
source_sequence=sequence,
|
|
captured_at_epoch_ns=sequence * 100_000_000,
|
|
worker_received_monotonic=0.0,
|
|
)
|
|
|
|
|
|
def test_incremental_media_buffer_is_bounded_and_fail_closed() -> None:
|
|
source = IncrementalMediaBuffer(1024)
|
|
source.append(b"a" * 800)
|
|
with pytest.raises(ShadowRuntimeError, match="hard bound"):
|
|
source.append(b"b" * 300)
|
|
snapshot = source.snapshot()
|
|
assert snapshot["maximum_depth_bytes"] == 800
|
|
assert snapshot["failed"] is True
|
|
with pytest.raises(ShadowRuntimeError, match="source failed"):
|
|
source.read(100)
|
|
|
|
|
|
def test_persistent_decoder_counts_latest_wins_camera_fragment_gaps() -> None:
|
|
decoder = PersistentFmp4Decoder(on_frame=lambda _frame: None)
|
|
decoder.start()
|
|
decoder.feed_init(b"not-a-real-init")
|
|
decoder.feed_segment(_metadata(1), b"first")
|
|
decoder.feed_segment(_metadata(3), b"third")
|
|
assert decoder.snapshot()["source_sequence_gaps"] == 1
|
|
decoder.finish_input()
|
|
with pytest.raises(ShadowRuntimeError, match="decoder failed"):
|
|
decoder.join()
|
|
|
|
|
|
def test_persistent_decoder_rejects_non_increasing_camera_sequence() -> None:
|
|
decoder = PersistentFmp4Decoder(on_frame=lambda _frame: None)
|
|
decoder.start()
|
|
decoder.feed_init(b"not-a-real-init")
|
|
decoder.feed_segment(_metadata(2), b"second")
|
|
with pytest.raises(ShadowRuntimeError, match="not increasing"):
|
|
decoder.feed_segment(_metadata(2), b"duplicate")
|
|
decoder.finish_input()
|
|
with pytest.raises(ShadowRuntimeError, match="decoder failed"):
|
|
decoder.join()
|
|
|
|
|
|
def test_persistent_decoder_streams_real_fmp4_epoch() -> None:
|
|
pytest.importorskip("av")
|
|
repository = Path(__file__).resolve().parents[1]
|
|
epoch = (
|
|
repository
|
|
/ ".runtime"
|
|
/ "mission-core"
|
|
/ "evidence"
|
|
/ "sessions"
|
|
/ "20260720T065719Z_viewer_live"
|
|
/ "media"
|
|
/ "sensor.camera.right"
|
|
/ "epoch-1"
|
|
)
|
|
if not epoch.is_dir():
|
|
pytest.skip("canonical RAVNOVES00 camera epoch is unavailable")
|
|
rows = [json.loads(line) for line in (epoch / "index.jsonl").read_text().splitlines()[:10]]
|
|
decoded = []
|
|
decoder = PersistentFmp4Decoder(on_frame=decoded.append)
|
|
decoder.start()
|
|
decoder.feed_init((epoch / "init.mp4").read_bytes())
|
|
for row in rows:
|
|
decoder.feed_segment(
|
|
CameraFragmentMetadata(
|
|
ingress_sequence=int(row["sequence"]),
|
|
source_sequence=int(row["sequence"]),
|
|
captured_at_epoch_ns=int(row["host_epoch_ns"]),
|
|
worker_received_monotonic=0.0,
|
|
),
|
|
(epoch / row["path"]).read_bytes(),
|
|
)
|
|
decoder.finish_input()
|
|
decoder.join()
|
|
|
|
assert len(decoded) == 10
|
|
assert [frame.metadata.source_sequence for frame in decoded] == list(range(1, 11))
|
|
assert all(frame.image.shape == (600, 800, 3) for frame in decoded)
|
|
assert decoder.snapshot()["media"]["depth_bytes"] == 0
|