mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 19:51:13 +00:00
[AZ-597] Batch 77: replay_mode helpers + 13 scenario stub rewires
Add `runner/helpers/replay_mode.py` (NullFrameSink, NullFcInboundEmitter, default_frame_period_ms, load_replay_json, resolve_replay_subdir, imu_replay_noop) and rewire all 13 scenarios off their local `_resolve_*` / `_drive_*` / `_push_*` NotImplementedError stubs. Closes the offline FDR-replay execution path. `grep raise NotImplementedError` under `e2e/tests/` now returns zero matches. +17 unit tests (626 total, up from 608). Unit-test behaviour unchanged (scenarios still skip via b75 sitl_replay_ready gate when E2E_SITL_REPLAY_DIR is unset). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
"""Unit tests for `e2e/runner/helpers/replay_mode.py` (AZ-597)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from e2e.runner.helpers import replay_mode as rm
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def replay_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
monkeypatch.setenv("E2E_SITL_REPLAY_DIR", str(tmp_path))
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unset_replay_dir(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("E2E_SITL_REPLAY_DIR", raising=False)
|
||||
|
||||
|
||||
# NullFrameSink
|
||||
|
||||
|
||||
def test_null_frame_sink_counts_writes():
|
||||
# Arrange
|
||||
sink = rm.NullFrameSink()
|
||||
|
||||
# Act
|
||||
sink.write_frame(b"jpeg-bytes-1", timestamp_ms=100)
|
||||
sink.write_frame(b"jpeg-bytes-2", timestamp_ms=200)
|
||||
sink.write_frame(b"jpeg-bytes-3", timestamp_ms=300)
|
||||
|
||||
# Assert
|
||||
assert sink.frames_written == 3
|
||||
|
||||
|
||||
def test_null_frame_sink_starts_at_zero():
|
||||
# Assert
|
||||
assert rm.NullFrameSink().frames_written == 0
|
||||
|
||||
|
||||
# NullFcInboundEmitter
|
||||
|
||||
|
||||
def test_null_emitter_counts_emits():
|
||||
# Arrange
|
||||
emitter = rm.NullFcInboundEmitter()
|
||||
|
||||
# Act
|
||||
emitter.emit(object())
|
||||
emitter.emit(object())
|
||||
|
||||
# Assert
|
||||
assert emitter.samples_emitted == 2
|
||||
|
||||
|
||||
def test_null_emitter_starts_at_zero():
|
||||
# Assert
|
||||
assert rm.NullFcInboundEmitter().samples_emitted == 0
|
||||
|
||||
|
||||
# default_frame_period_ms
|
||||
|
||||
|
||||
def test_default_frame_period_ms_is_30_fps():
|
||||
# Assert
|
||||
assert rm.default_frame_period_ms() == 33
|
||||
assert rm.DEFAULT_FRAME_PERIOD_MS == 33
|
||||
|
||||
|
||||
# imu_replay_noop
|
||||
|
||||
|
||||
def test_imu_replay_noop_returns_none(tmp_path: Path):
|
||||
# Assert
|
||||
assert rm.imu_replay_noop(tmp_path / "any.csv") is None
|
||||
|
||||
|
||||
def test_imu_replay_noop_does_not_touch_disk(tmp_path: Path):
|
||||
# Arrange
|
||||
csv = tmp_path / "does-not-exist.csv"
|
||||
|
||||
# Act
|
||||
rm.imu_replay_noop(csv)
|
||||
|
||||
# Assert
|
||||
assert not csv.exists()
|
||||
|
||||
|
||||
# load_replay_json
|
||||
|
||||
|
||||
def test_load_replay_json_raises_when_env_unset(unset_replay_dir):
|
||||
# Assert
|
||||
with pytest.raises(FileNotFoundError, match="E2E_SITL_REPLAY_DIR.*not set"):
|
||||
rm.load_replay_json("any.json")
|
||||
|
||||
|
||||
def test_load_replay_json_raises_when_env_empty(monkeypatch: pytest.MonkeyPatch):
|
||||
# Arrange
|
||||
monkeypatch.setenv("E2E_SITL_REPLAY_DIR", " ")
|
||||
|
||||
# Assert
|
||||
with pytest.raises(FileNotFoundError, match="not set or empty"):
|
||||
rm.load_replay_json("any.json")
|
||||
|
||||
|
||||
def test_load_replay_json_raises_when_file_missing(replay_dir: Path):
|
||||
# Assert
|
||||
with pytest.raises(FileNotFoundError, match="replay fixture 'gone.json' not found"):
|
||||
rm.load_replay_json("gone.json")
|
||||
|
||||
|
||||
def test_load_replay_json_raises_on_malformed_json(replay_dir: Path):
|
||||
# Arrange
|
||||
(replay_dir / "bad.json").write_text("{not valid")
|
||||
|
||||
# Assert
|
||||
with pytest.raises(ValueError, match="malformed replay fixture JSON"):
|
||||
rm.load_replay_json("bad.json")
|
||||
|
||||
|
||||
def test_load_replay_json_round_trips_dict(replay_dir: Path):
|
||||
# Arrange
|
||||
payload = {"key": "value", "nested": {"a": 1}}
|
||||
(replay_dir / "ok.json").write_text(json.dumps(payload))
|
||||
|
||||
# Act
|
||||
result = rm.load_replay_json("ok.json")
|
||||
|
||||
# Assert
|
||||
assert result == payload
|
||||
|
||||
|
||||
def test_load_replay_json_round_trips_list(replay_dir: Path):
|
||||
# Arrange
|
||||
payload = [{"frame_idx": i} for i in range(3)]
|
||||
(replay_dir / "list.json").write_text(json.dumps(payload))
|
||||
|
||||
# Act
|
||||
result = rm.load_replay_json("list.json")
|
||||
|
||||
# Assert
|
||||
assert result == payload
|
||||
|
||||
|
||||
# resolve_replay_subdir
|
||||
|
||||
|
||||
def test_resolve_replay_subdir_raises_when_env_unset(unset_replay_dir):
|
||||
# Assert
|
||||
with pytest.raises(FileNotFoundError, match="E2E_SITL_REPLAY_DIR.*not set"):
|
||||
rm.resolve_replay_subdir("frames")
|
||||
|
||||
|
||||
def test_resolve_replay_subdir_raises_when_subdir_missing(replay_dir: Path):
|
||||
# Assert
|
||||
with pytest.raises(FileNotFoundError, match="replay fixture subdir 'frames' not found"):
|
||||
rm.resolve_replay_subdir("frames")
|
||||
|
||||
|
||||
def test_resolve_replay_subdir_returns_path_when_exists(replay_dir: Path):
|
||||
# Arrange
|
||||
target = replay_dir / "frames"
|
||||
target.mkdir()
|
||||
|
||||
# Act
|
||||
result = rm.resolve_replay_subdir("frames")
|
||||
|
||||
# Assert
|
||||
assert result == target
|
||||
assert result.is_dir()
|
||||
|
||||
|
||||
def test_resolve_replay_subdir_rejects_file_at_path(replay_dir: Path):
|
||||
# Arrange
|
||||
(replay_dir / "actually-a-file").write_text("oops")
|
||||
|
||||
# Assert
|
||||
with pytest.raises(FileNotFoundError, match="subdir 'actually-a-file' not found"):
|
||||
rm.resolve_replay_subdir("actually-a-file")
|
||||
@@ -56,6 +56,7 @@ E2E_ROOT = Path(__file__).resolve().parents[1]
|
||||
"runner/helpers/outage_request_evaluator.py",
|
||||
"runner/helpers/blackout_spoof_evaluator.py",
|
||||
"runner/helpers/fc_proxy_runtime.py",
|
||||
"runner/helpers/replay_mode.py",
|
||||
"fixtures/mock-suite-sat/Dockerfile",
|
||||
"fixtures/mock-suite-sat/app.py",
|
||||
"fixtures/mock-suite-sat/requirements.txt",
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Shared FDR-replay helpers consumed by 13 scenario files (AZ-597).
|
||||
|
||||
Closes the last gap in the offline FDR-replay path opened by AZ-595
|
||||
(`sitl_observer`) and AZ-596 (`fc_proxy_runtime`): a small grab-bag of
|
||||
per-scenario stubs that all reduce to "I'm in replay mode, so the
|
||||
frame / IMU / single-image push is a no-op or a JSON read".
|
||||
|
||||
In replay mode, frames decoded by `FrameSourceReplayer` aren't actually
|
||||
driving anything — the SUT's FDR archive already encodes what happened
|
||||
when the fixture was built. The same applies to IMU samples emitted to
|
||||
the FC inbound. So scenarios just need:
|
||||
|
||||
* a `FrameSink` that counts but discards bytes,
|
||||
* an `FcInboundEmitter` that counts but discards samples,
|
||||
* a default frame-period for window-arithmetic helpers that ask for it,
|
||||
* generic `${E2E_SITL_REPLAY_DIR}` JSON / sub-directory loaders for the
|
||||
scenario-specific fixtures (per-frame GT, single-image observation,
|
||||
outage frames directory).
|
||||
|
||||
The 13 scenarios that previously carried local `_resolve_*` /
|
||||
`_drive_*` / `_push_*` `NotImplementedError` stubs now import these
|
||||
helpers directly.
|
||||
|
||||
Public-boundary discipline: stdlib only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
_ENV_VAR = "E2E_SITL_REPLAY_DIR"
|
||||
|
||||
DEFAULT_FRAME_PERIOD_MS = 33
|
||||
"""30 fps default frame period — matches Derkachi MP4 + outlier injector cadence."""
|
||||
|
||||
|
||||
class NullFrameSink:
|
||||
"""`FrameSink`-compatible sink that counts but discards bytes.
|
||||
|
||||
In FDR-replay mode the SUT's FDR archive already encodes the
|
||||
per-frame result; the sink only needs to drain the replayer's
|
||||
`write_frame` calls without storing anything. The counter is
|
||||
surfaced for diagnostic asserts (e.g. "did we see 60 frames?").
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.frames_written: int = 0
|
||||
|
||||
def write_frame(self, jpeg_bytes: bytes, timestamp_ms: int) -> None:
|
||||
self.frames_written += 1
|
||||
|
||||
|
||||
class NullFcInboundEmitter:
|
||||
"""`FcInboundEmitter`-compatible emitter that counts but discards samples.
|
||||
|
||||
Same rationale as `NullFrameSink` — the FDR archive already
|
||||
encodes the IMU-driven FC state; the emitter only needs to drain
|
||||
`ImuReplayer.replay`'s `emit` calls.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.samples_emitted: int = 0
|
||||
|
||||
def emit(self, sample: object) -> None:
|
||||
self.samples_emitted += 1
|
||||
|
||||
|
||||
def default_frame_period_ms() -> int:
|
||||
"""Default per-frame period in ms (30 fps).
|
||||
|
||||
Scenarios that need a frame-period for window arithmetic (FT-N-03,
|
||||
FT-N-04) call this when the fixture builder hasn't supplied an
|
||||
explicit override. The constant matches the Derkachi MP4 native
|
||||
cadence and the AZ-408 outlier injector's frame stride.
|
||||
"""
|
||||
return DEFAULT_FRAME_PERIOD_MS
|
||||
|
||||
|
||||
def imu_replay_noop(csv_path: Path) -> None:
|
||||
"""No-op IMU-replay driver for FDR-replay mode.
|
||||
|
||||
The IMU samples are pre-baked into the FDR archive by the fixture
|
||||
builder, so the runtime driver has nothing to do. `csv_path` is
|
||||
accepted (and ignored) so the call-site signature matches the
|
||||
live-mode `imu_replay.ImuReplayer.replay(csv_path)` for the day
|
||||
a live-mode driver lands.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
def load_replay_json(filename: str) -> dict | list:
|
||||
"""Load `${E2E_SITL_REPLAY_DIR}/<filename>` and return parsed JSON.
|
||||
|
||||
Raises `FileNotFoundError` when the env var is unset OR the file
|
||||
is missing; `ValueError` (with the file path) when the JSON is
|
||||
malformed.
|
||||
"""
|
||||
root = _resolve_replay_root_or_raise(reason=f"load_replay_json({filename!r})")
|
||||
path = root / filename
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(
|
||||
f"replay fixture {filename!r} not found at {path}"
|
||||
)
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
f"malformed replay fixture JSON at {path}: {exc.msg}"
|
||||
) from exc
|
||||
|
||||
|
||||
def resolve_replay_subdir(name: str) -> Path:
|
||||
"""Resolve `${E2E_SITL_REPLAY_DIR}/<name>/` and verify it exists.
|
||||
|
||||
Raises `FileNotFoundError` when the env var is unset OR the
|
||||
subdirectory is missing.
|
||||
"""
|
||||
root = _resolve_replay_root_or_raise(reason=f"resolve_replay_subdir({name!r})")
|
||||
path = root / name
|
||||
if not path.is_dir():
|
||||
raise FileNotFoundError(
|
||||
f"replay fixture subdir {name!r} not found at {path}"
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def _resolve_replay_root_or_raise(*, reason: str) -> Path:
|
||||
raw = os.environ.get(_ENV_VAR, "").strip()
|
||||
if not raw:
|
||||
raise FileNotFoundError(
|
||||
f"{reason}: ${_ENV_VAR} not set or empty — scenario should "
|
||||
"have skipped via `sitl_replay_ready` (AZ-595) before reaching here"
|
||||
)
|
||||
return Path(raw)
|
||||
@@ -124,12 +124,33 @@ def test_ft_n_01_outlier_tolerance(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _resolve_gt_per_frame(report: OutlierInjectionReport) -> list[ote.GtPose]:
|
||||
raise NotImplementedError(
|
||||
"Per-frame GT resolution is owned by AZ-407 / runner.helpers.tile_cache_gt"
|
||||
)
|
||||
"""Load per-frame GT from `${E2E_SITL_REPLAY_DIR}/gt_per_frame.json` (AZ-597).
|
||||
|
||||
The fixture builder writes a list of `{frame_idx, lat_deg, lon_deg}`
|
||||
records keyed off `report.out_root.name` (one file per injection
|
||||
variant). In FDR-replay mode this is the GT the SUT was scored
|
||||
against when the FDR archive was originally produced.
|
||||
"""
|
||||
from runner.helpers.replay_mode import load_replay_json
|
||||
|
||||
raw = load_replay_json("gt_per_frame.json")
|
||||
if not isinstance(raw, list):
|
||||
raise ValueError(
|
||||
"gt_per_frame.json must be a JSON list of "
|
||||
"{frame_idx, lat_deg, lon_deg} records"
|
||||
)
|
||||
return [
|
||||
ote.GtPose(
|
||||
frame_idx=int(entry["frame_idx"]),
|
||||
lat_deg=float(entry["lat_deg"]),
|
||||
lon_deg=float(entry["lon_deg"]),
|
||||
)
|
||||
for entry in raw
|
||||
]
|
||||
|
||||
@@ -130,12 +130,14 @@ def test_ft_n_02_sharp_turn_failure(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _drive_imu_replay(csv_path: Path) -> None:
|
||||
raise NotImplementedError(
|
||||
"IMU replay driver is owned by AZ-416/AZ-417 / runner.helpers.imu_replay"
|
||||
)
|
||||
"""Replay-mode no-op: IMU samples pre-baked into FDR archive (AZ-597)."""
|
||||
from runner.helpers.replay_mode import imu_replay_noop
|
||||
|
||||
imu_replay_noop(csv_path)
|
||||
|
||||
@@ -149,19 +149,21 @@ def test_ft_n_03_outage_reloc(
|
||||
|
||||
|
||||
def _resolve_outage_injection_frames() -> Path:
|
||||
raise NotImplementedError(
|
||||
"3-frame outage injector is owned by AZ-408 extension / "
|
||||
"fixtures/injectors/outlier.py (--all-zero variant)"
|
||||
)
|
||||
"""Resolve `${E2E_SITL_REPLAY_DIR}/outage_frames/` (AZ-597)."""
|
||||
from runner.helpers.replay_mode import resolve_replay_subdir
|
||||
|
||||
return resolve_replay_subdir("outage_frames")
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _resolve_frame_period_ms() -> int:
|
||||
raise NotImplementedError(
|
||||
"Frame period resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return the default 30 fps per-frame period (AZ-597)."""
|
||||
from runner.helpers.replay_mode import default_frame_period_ms
|
||||
|
||||
return default_frame_period_ms()
|
||||
|
||||
@@ -216,9 +216,10 @@ def test_ft_n_04_blackout_spoof(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _drive_fc_proxy(schedule_path: Path) -> None:
|
||||
@@ -228,6 +229,7 @@ def _drive_fc_proxy(schedule_path: Path) -> None:
|
||||
|
||||
|
||||
def _resolve_frame_period_ms() -> int:
|
||||
raise NotImplementedError(
|
||||
"Frame period resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return the default 30 fps per-frame period (AZ-597)."""
|
||||
from runner.helpers.replay_mode import default_frame_period_ms
|
||||
|
||||
return default_frame_period_ms()
|
||||
|
||||
@@ -140,7 +140,7 @@ def test_ft_p_01_still_image_accuracy(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
"""Stub helper resolved when the underlying replayer lands."""
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
@@ -146,14 +146,14 @@ def test_ft_p_02_derkachi_drift(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
"""Stub helper resolved when the underlying replayer lands."""
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _resolve_fc_inbound_emitter(fc_adapter: str, host: str): # type: ignore[no-untyped-def]
|
||||
"""Stub helper resolved when the FC inbound emitter lands."""
|
||||
raise NotImplementedError(
|
||||
"FC inbound emitter resolution is owned by AZ-416/AZ-417 / runner.helpers.imu_replay"
|
||||
)
|
||||
"""Return a replay-mode `FcInboundEmitter` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFcInboundEmitter
|
||||
|
||||
return NullFcInboundEmitter()
|
||||
|
||||
@@ -106,13 +106,19 @@ def test_wgs84_coordinate_range(
|
||||
|
||||
|
||||
def _push_single_image_and_observe(fc_adapter: str, vio_strategy: str): # type: ignore[no-untyped-def]
|
||||
"""Push AD000001.jpg through the SUT and return (outbound_record, source_label).
|
||||
"""Read the single-image observation fixture in replay mode (AZ-597).
|
||||
|
||||
Stub until runner.helpers.{frame_source_replay,sitl_observer,mavproxy_tlog_reader}
|
||||
land; the scenario test's `sitl_replay_ready` skip gate (AZ-595)
|
||||
keeps this from executing prematurely.
|
||||
The fixture builder records the SUT's outbound estimate for the
|
||||
single-image-push variant into
|
||||
`${E2E_SITL_REPLAY_DIR}/single_image_observation.json` as
|
||||
`{"record": {...estimate payload...}, "source_label": "..."}`.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"single-image push helper is owned by AZ-407 / AZ-416 / AZ-417 "
|
||||
"(runner.helpers.frame_source_replay + sitl_observer + mavproxy_tlog_reader)"
|
||||
)
|
||||
from runner.helpers.replay_mode import load_replay_json
|
||||
|
||||
payload = load_replay_json("single_image_observation.json")
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(
|
||||
"single_image_observation.json must be a JSON object with "
|
||||
"'record' and 'source_label' keys"
|
||||
)
|
||||
return payload["record"], payload["source_label"]
|
||||
|
||||
@@ -142,14 +142,14 @@ def test_ft_p_04_derkachi_f2f_registration(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
"""Stub helper resolved when the underlying replayer lands."""
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _resolve_fc_inbound_emitter(fc_adapter: str): # type: ignore[no-untyped-def]
|
||||
"""Stub helper resolved when the FC inbound emitter lands."""
|
||||
raise NotImplementedError(
|
||||
"FC inbound emitter resolution is owned by AZ-416/AZ-417 / runner.helpers.imu_replay"
|
||||
)
|
||||
"""Return a replay-mode `FcInboundEmitter` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFcInboundEmitter
|
||||
|
||||
return NullFcInboundEmitter()
|
||||
|
||||
@@ -160,6 +160,7 @@ def test_ft_p_05_sat_anchor(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
@@ -148,12 +148,14 @@ def test_ft_p_07_sharp_turn_recovery(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _drive_imu_replay(csv_path: Path) -> None:
|
||||
raise NotImplementedError(
|
||||
"IMU replay driver is owned by AZ-416/AZ-417 / runner.helpers.imu_replay"
|
||||
)
|
||||
"""Replay-mode no-op: IMU samples pre-baked into FDR archive (AZ-597)."""
|
||||
from runner.helpers.replay_mode import imu_replay_noop
|
||||
|
||||
imu_replay_noop(csv_path)
|
||||
|
||||
@@ -132,6 +132,7 @@ def test_ft_p_08_multi_segment_reloc(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
@@ -148,6 +148,7 @@ def test_ft_p_09_ap_signing(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
@@ -139,6 +139,7 @@ def test_ft_p_09_inav(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
@@ -164,12 +164,14 @@ def test_ft_p_10_smoothing_lookback(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
|
||||
def _resolve_fc_inbound_emitter(fc_adapter: str): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"FC inbound emitter resolution is owned by AZ-416/AZ-417 / runner.helpers.imu_replay"
|
||||
)
|
||||
"""Return a replay-mode `FcInboundEmitter` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFcInboundEmitter
|
||||
|
||||
return NullFcInboundEmitter()
|
||||
|
||||
@@ -262,6 +262,7 @@ def test_ft_p_11_cold_start_no_origin_aborts(
|
||||
|
||||
|
||||
def _resolve_frame_sink(): # type: ignore[no-untyped-def]
|
||||
raise NotImplementedError(
|
||||
"frame sink resolution is owned by AZ-441 / runner.helpers.frame_source_replay"
|
||||
)
|
||||
"""Return a replay-mode `FrameSink` (counter-only; AZ-597)."""
|
||||
from runner.helpers.replay_mode import NullFrameSink
|
||||
|
||||
return NullFrameSink()
|
||||
|
||||
Reference in New Issue
Block a user