Files
gps-denied-onboard/_docs/02_tasks/done/AZ-597_scenario_stub_cleanup.md
T
Oleksandr Bezdieniezhnykh f49d803252 [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>
2026-05-17 09:52:05 +03:00

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 — implements FrameSink protocol. write_frame is a counter; exposes frames_written: int.
  • NullFcInboundEmitter — implements FcInboundEmitter protocol. emit is a counter; exposes samples_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>. Raises FileNotFoundError when env var unset OR file missing; ValueError with file pointer on malformed JSON.
  • resolve_replay_subdir(name: str) -> Path — returns ${E2E_SITL_REPLAY_DIR}/<name>/. Raises FileNotFoundError when 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}/