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>
4.1 KiB
Scenario stub cleanup (replay_mode helpers + 13 rewires)
Task: AZ-597_scenario_stub_cleanup
Name: Add runner/helpers/replay_mode.py + rewire 13 scenarios off local _resolve_* / _drive_* / _push_* stubs
Description: After AZ-594/595/596 landed the three core harness helpers, sitl_observer, and the fc_proxy_runtime driver, the last unimplemented layer is a grab-bag of local per-scenario stubs that all share the same FDR-replay no-op pattern. Bundle them into one shared runner/helpers/replay_mode.py module so the offline FDR-replay path is end-to-end executable once the SITL replay fixture builder lands.
Complexity: 3 points
Dependencies: AZ-594, AZ-595, AZ-596
Component: Blackbox Tests / Test Infrastructure (epic AZ-262)
Tracker: AZ-597
Epic: AZ-262 (E-BBT)
Problem
Despite the AZ-594/595/596 arc, 13 scenarios still carry local
_resolve_* / _drive_* / _push_* NotImplementedError stubs:
| Stub | Scenarios |
|---|---|
_resolve_frame_sink() |
13 (FT-P-01/02/04/05/07/08/09-AP/09-iNav/10/11, FT-N-01/02/03/04) |
_resolve_fc_inbound_emitter(fc_adapter[, host]) |
3 (FT-P-02/04/10) |
_drive_imu_replay(csv_path) |
2 (FT-P-07, FT-N-02) |
_resolve_frame_period_ms() |
2 (FT-N-03/04) |
_resolve_outage_injection_frames() |
1 (FT-N-03) |
_resolve_gt_per_frame(report) |
1 (FT-N-01) |
_push_single_image_and_observe(...) |
1 (FT-P-03/14) |
These are unreachable today (the b75 sitl_replay_ready gate skips
before they're called) so this cleanup can land safely under the
unit-test regression gate. The value: once the SITL replay fixture
builder ships, scenarios become runnable with no further per-scenario
edits.
Surfaces (runner/helpers/replay_mode.py)
NullFrameSink— implementsFrameSinkprotocol.write_frameis a counter; exposesframes_written: int.NullFcInboundEmitter— implementsFcInboundEmitterprotocol.emitis a counter; exposessamples_emitted: int.DEFAULT_FRAME_PERIOD_MS = 33+default_frame_period_ms() -> int.load_replay_json(filename: str) -> dict | list— reads${E2E_SITL_REPLAY_DIR}/<filename>. RaisesFileNotFoundErrorwhen env var unset OR file missing;ValueErrorwith file pointer on malformed JSON.resolve_replay_subdir(name: str) -> Path— returns${E2E_SITL_REPLAY_DIR}/<name>/. RaisesFileNotFoundErrorwhen env var unset OR directory missing.imu_replay_noop(csv_path: Path) -> None— no-op stand-in for the per-scenario_drive_imu_replay(IMU is pre-baked into the FDR archive in replay mode; the CSV path is preserved as a parameter for diagnostic logging only).
Per-scenario rewire pattern
# Before:
def _resolve_frame_sink():
raise NotImplementedError(...)
# After:
from runner.helpers.replay_mode import NullFrameSink
def _resolve_frame_sink():
return NullFrameSink()
Same shape for the other six helpers. For the two scenarios that need
scenario-specific JSON (_resolve_gt_per_frame,
_push_single_image_and_observe), they call
load_replay_json("gt_per_frame.json") /
load_replay_json("single_image_observation.json") and project the
result into their scenario-local dataclass.
Acceptance Criteria
AC-1: NullFrameSink.write_frame and NullFcInboundEmitter.emit
are pure counters.
AC-2: load_replay_json raises FileNotFoundError (env unset or
file missing) and ValueError (malformed JSON with file pointer).
AC-3: resolve_replay_subdir raises FileNotFoundError (env
unset or subdir missing).
AC-4: default_frame_period_ms() returns 33.
AC-5: All 13 scenarios have local _resolve_* / _drive_* /
_push_* stubs deleted and import from runner.helpers.replay_mode.
AC-6: ≥6 unit tests on replay_mode.py.
AC-7: Full e2e unit-test suite passes (regression gate).
Out of Scope
- The actual SITL replay fixture builder.
- Live MAVLink router / pymavlink plumbing.
Files Touched
e2e/runner/helpers/replay_mode.py(new)e2e/_unit_tests/helpers/test_replay_mode.py(new)e2e/_unit_tests/test_directory_layout.py(register new module)- 13 scenario files under
e2e/tests/{positive,negative}/