mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 21:01:13 +00:00
[AZ-621] Phase C: build_pre_constructed seeds c7_inference
Third subtask of AZ-618. Extends airborne_bootstrap.build_pre_constructed additively with c7_inference (GPU InferenceRuntime). Wraps the existing inference_factory.build_inference_runtime so a BUILD_TENSORRT_RUNTIME / BUILD_PYTORCH_FP16_RUNTIME mismatch surfaces a clear operator-facing AirborneBootstrapError naming BOTH airborne C7 flags plus the consuming component slug, rather than bubbling up RuntimeNotAvailableError with no context. New public const C7_AIRBORNE_BUILD_FLAGS pairs each airborne runtime with its gating env flag (onnx_trt_ep deliberately omitted — research only). Tests stub at the factory boundary; real GPU/TensorRT load remains Tier-2 only (consolidated at AZ-624). AZ-619 and AZ-620 test files extended with a _stub_c7_inference_builder autouse fixture mirroring the AZ-620 pattern for _build_c6_*. 18/18 runtime_root unit tests pass. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -67,6 +67,19 @@ def _stub_c6_builders(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_c7_inference_builder(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub the AZ-621 Phase C C7 inference builder so the AZ-619
|
||||
# tests stay focused on Phase A. Without this the bare Config() below
|
||||
# would hit KeyError inside inference_factory's
|
||||
# config.components["c7_inference"] lookup, AND the airborne BUILD_*
|
||||
# flags are typically unset in the test env. The sentinel is opaque
|
||||
# on purpose — AZ-619 assertions never inspect it.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap, "_build_c7_inference", lambda _config: object()
|
||||
)
|
||||
|
||||
|
||||
def test_ac_619_1_default_config_seeds_c13_fdr_and_clock() -> None:
|
||||
# Arrange
|
||||
config = Config()
|
||||
|
||||
@@ -60,6 +60,20 @@ def _isolated_fdr_cache() -> Iterator[None]:
|
||||
fdr_client_module._reset_for_tests()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_c7_inference_builder(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub the AZ-621 Phase C C7 inference builder so AZ-620
|
||||
# tests stay focused on the Phase B contract. Without this the
|
||||
# configs used below would hit KeyError inside inference_factory's
|
||||
# config.components["c7_inference"] lookup. The sentinel is opaque
|
||||
# on purpose — AZ-620 assertions never inspect it.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"_build_c7_inference",
|
||||
lambda _config: MagicMock(name="InferenceRuntime"),
|
||||
)
|
||||
|
||||
|
||||
def test_ac_620_1_adds_c6_descriptor_index_and_c6_tile_store(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
"""AZ-621 — Phase C of AZ-618: ``build_pre_constructed`` seeds c7_inference.
|
||||
|
||||
Verifies the contract at
|
||||
``_docs/02_tasks/todo/AZ-621_pre_constructed_phase_c_c7_inference.md``:
|
||||
|
||||
* AC-621.1: with a default airborne ``Config`` (and one of the airborne
|
||||
C7 ``BUILD_*`` flags ON in spirit — here stubbed at the factory
|
||||
boundary), ``build_pre_constructed(config)`` additionally contains
|
||||
``c7_inference`` (InferenceRuntime instance) on top of AZ-619 + AZ-620.
|
||||
* AC-621.2: when BOTH ``BUILD_TENSORRT_RUNTIME=OFF`` AND
|
||||
``BUILD_PYTORCH_FP16_RUNTIME=OFF`` AND a config selects a C2 / C3
|
||||
strategy that needs c7 (here: ``c3_matcher.strategy="disk_lightglue"``),
|
||||
``build_pre_constructed`` raises ``AirborneBootstrapError`` whose
|
||||
message names both airborne flags AND the consuming component slug.
|
||||
|
||||
AC-621.3 (this file exists with the above tests) is satisfied by the
|
||||
existence of this module.
|
||||
|
||||
The tests stub the heavy ``build_inference_runtime`` factory at the
|
||||
airborne_bootstrap import boundary — exactly mirroring the AZ-620
|
||||
pattern for the C6 factories. The factory itself has its own
|
||||
component-level unit tests under ``tests/unit/c7_inference/`` for the
|
||||
real engine-load paths; real GPU model load is Tier-2 only (consolidated
|
||||
under AZ-624 per the umbrella's Tier-2 testing strategy).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from gps_denied_onboard.config import Config
|
||||
from gps_denied_onboard.fdr_client import client as fdr_client_module
|
||||
from gps_denied_onboard.runtime_root import airborne_bootstrap
|
||||
from gps_denied_onboard.runtime_root.airborne_bootstrap import (
|
||||
C7_AIRBORNE_BUILD_FLAGS,
|
||||
AirborneBootstrapError,
|
||||
build_pre_constructed,
|
||||
)
|
||||
from gps_denied_onboard.runtime_root.errors import RuntimeNotAvailableError
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _C3MatcherBlock:
|
||||
"""Minimal c3_matcher config block — only the field the test needs."""
|
||||
|
||||
strategy: str = "disk_lightglue"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolated_fdr_cache() -> Iterator[None]:
|
||||
# Arrange: every test starts with an empty FdrClient cache so the
|
||||
# bootstrap's make_fdr_client call doesn't accidentally pick up a
|
||||
# stale instance from a prior test in the same process.
|
||||
fdr_client_module._reset_for_tests()
|
||||
yield
|
||||
fdr_client_module._reset_for_tests()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_c6_builders(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub the AZ-620 Phase B C6 builders so AZ-621 stays
|
||||
# focused on the Phase C contract. The configs used below either
|
||||
# omit c6 entirely or use bare Config()-derived blocks.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"_build_c6_descriptor_index",
|
||||
lambda _config: MagicMock(name="DescriptorIndex"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"_build_c6_tile_store",
|
||||
lambda _config: MagicMock(name="TileStore"),
|
||||
)
|
||||
|
||||
|
||||
def test_ac_621_1_adds_c7_inference(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub build_inference_runtime to a sentinel so we can
|
||||
# assert wiring without standing up real GPU/TensorRT/PyTorch.
|
||||
inference_runtime_sentinel = MagicMock(name="InferenceRuntime")
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"build_inference_runtime",
|
||||
lambda _config: inference_runtime_sentinel,
|
||||
)
|
||||
config = Config()
|
||||
|
||||
# Act
|
||||
pre_constructed = build_pre_constructed(config)
|
||||
|
||||
# Assert: AZ-621 key is present and references the exact factory
|
||||
# return. AZ-619 + AZ-620 keys are still present (additivity contract).
|
||||
assert "c7_inference" in pre_constructed
|
||||
assert pre_constructed["c7_inference"] is inference_runtime_sentinel
|
||||
assert {"c13_fdr", "clock", "c6_descriptor_index", "c6_tile_store"}.issubset(
|
||||
pre_constructed.keys()
|
||||
)
|
||||
|
||||
|
||||
def test_ac_621_2_both_build_flags_off_with_configured_consumer_raises_named_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# Arrange: simulate both BUILD_TENSORRT_RUNTIME=OFF and
|
||||
# BUILD_PYTORCH_FP16_RUNTIME=OFF by making build_inference_runtime
|
||||
# raise RuntimeNotAvailableError the same way inference_factory does
|
||||
# in production when neither flag is ON.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"build_inference_runtime",
|
||||
_raise_no_c7_runtime_available,
|
||||
)
|
||||
config = Config.with_blocks(
|
||||
c3_matcher=_C3MatcherBlock(strategy="disk_lightglue")
|
||||
)
|
||||
|
||||
# Act + Assert
|
||||
with pytest.raises(AirborneBootstrapError) as excinfo:
|
||||
build_pre_constructed(config)
|
||||
|
||||
message = str(excinfo.value)
|
||||
assert "c7_inference" in message
|
||||
# Both airborne C7 BUILD_* flags must be named in the message so the
|
||||
# operator sees the production-default AND the Tier-0 fallback flag.
|
||||
for _runtime, flag in C7_AIRBORNE_BUILD_FLAGS:
|
||||
assert flag in message, f"{flag} missing from error: {message!r}"
|
||||
# Consuming component slug is named.
|
||||
assert "c3_matcher" in message
|
||||
# The original factory error is preserved as the cause chain so the
|
||||
# operator can still see the upstream reason.
|
||||
assert isinstance(excinfo.value.__cause__, RuntimeNotAvailableError)
|
||||
|
||||
|
||||
def test_ac_621_2_no_configured_consumer_still_raises_with_full_set(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Defence-in-depth: even with no consumer configured, AZ-621 still
|
||||
fails loudly when no airborne C7 runtime is buildable.
|
||||
|
||||
Mirrors the AZ-620 defence-in-depth test. ``build_pre_constructed``
|
||||
is the airborne contract's single seam — silently returning a dict
|
||||
without ``c7_inference`` would let a later AZ-622..AZ-624 phase or a
|
||||
downstream caller misread the state. The error message lists the
|
||||
FULL theoretical consumer set so the operator still gets actionable
|
||||
information.
|
||||
"""
|
||||
# Arrange
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"build_inference_runtime",
|
||||
_raise_no_c7_runtime_available,
|
||||
)
|
||||
config = Config() # empty components
|
||||
|
||||
# Act + Assert
|
||||
with pytest.raises(AirborneBootstrapError) as excinfo:
|
||||
build_pre_constructed(config)
|
||||
|
||||
message = str(excinfo.value)
|
||||
assert "c7_inference" in message
|
||||
for _runtime, flag in C7_AIRBORNE_BUILD_FLAGS:
|
||||
assert flag in message
|
||||
# When no consumer is configured, ALL theoretical consumers of
|
||||
# c7_inference are surfaced — per AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS
|
||||
# these are c2_vpr, c3_matcher, c3_5_adhop.
|
||||
for slug in ("c2_vpr", "c3_matcher", "c3_5_adhop"):
|
||||
assert slug in message, f"{slug} missing from error: {message!r}"
|
||||
|
||||
|
||||
def _raise_no_c7_runtime_available(_config: Config) -> None:
|
||||
# Mirrors the message that inference_factory.build_inference_runtime
|
||||
# raises when the configured runtime's BUILD_* flag is OFF — except
|
||||
# phrased to cover the "both flags off, nothing left" scenario the
|
||||
# bootstrap is supposed to wrap into an AirborneBootstrapError.
|
||||
raise RuntimeNotAvailableError(
|
||||
"InferenceRuntime runtime 'tensorrt' requires "
|
||||
"BUILD_TENSORRT_RUNTIME=ON in this binary; the flag is OFF."
|
||||
)
|
||||
Reference in New Issue
Block a user