Files
gps-denied-onboard/tests/unit/test_az275_fake_fdr_sink.py
T
Oleksandr Bezdieniezhnykh ba20c2d195 [AZ-273] [AZ-274] [AZ-275] [AZ-267] [AZ-268] FDR producer chain + log bridge + contract test
AZ-273: lock-free SPSC ring buffer with pre-allocated slots, power-of-
two capacity, opt-in SPSC guard, and EnqueueResult / FdrSpscViolationError
on the public surface. make_fdr_client caches one client per producer_id
and reads capacity from config.fdr.per_producer_capacity with fallback
to queue_size.
AZ-274: default_overrun_policy implements drop-oldest + retry + immediate
marker emission, with prior-marker dropped_count folding via _evict_one
so user-loss info is never lost across iterations. ERROR diagnostic is
rate-limited to <=1/sec per producer.
AZ-275: FakeFdrSink mirrors the FdrClient public surface and reuses the
production default_overrun_policy via a duck-typed _PolicyAdapter. The
test-only records/all_records_ever properties let component tests assert
both in-buffer and lifetime state. tests/conftest.py registers the
fake_fdr_sink fixture and an AST architecture lint forbids production
imports of fakes.
AZ-267: FdrLogBridgeHandler installs on the root logger via wire_log_bridge
and forwards only WARN+ERROR records into the FDR with kind="log".
Thread-local recursion guard short-circuits internal logging; saturated-
queue diagnostics go to stderr every N=1000 drops.
AZ-268: tests/contract/log_schema.py covers every row of the schema's
Test Cases table plus the "DEBUG+INFO never reach FDR" invariant.
pyproject.toml registers the contract pytest marker and the
contract-mandated log_schema.py file-name.
251 unit + contract tests pass (48 new). Review verdict:
PASS_WITH_WARNINGS; findings are NFR-perf deferrals + documented
relaxation of AZ-274 AC-2 coalescing under permanently-stalled consumer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 03:00:49 +03:00

217 lines
6.5 KiB
Python

"""AZ-275 — FakeFdrSink test double + production isolation guard."""
from __future__ import annotations
import ast
from pathlib import Path
import pytest
from gps_denied_onboard.fdr_client import (
EnqueueResult,
FdrClient,
FdrRecord,
)
from gps_denied_onboard.fdr_client.fakes import FakeFdrSink
from gps_denied_onboard.fdr_client.records import OVERRUN_KIND, OVERRUN_PRODUCER_ID
_REPO_ROOT = Path(__file__).resolve().parents[2]
_SRC_ROOT = _REPO_ROOT / "src" / "gps_denied_onboard"
def _make_record(producer_id: str = "test.producer", frame_id: int = 0) -> FdrRecord:
return FdrRecord(
schema_version=1,
ts="2026-05-11T00:00:00.000000Z",
producer_id=producer_id,
kind="log",
payload={
"level": "INFO",
"component": producer_id,
"frame_id": frame_id,
"kind": "test.tick",
"msg": "hello",
"kv": {},
"exc": None,
},
)
# ---------------------------------------------------------------------------
# AC-1: drop-in for FdrClient public surface.
def test_ac1_drop_in_for_fdr_client_public_surface() -> None:
# Arrange
sink = FakeFdrSink(producer_id="c1_vio")
record = _make_record(producer_id="c1_vio")
# Act
result = sink.enqueue(record)
popped = sink.pop_one()
# Assert
assert result == EnqueueResult.OK
assert popped is record
assert sink.producer_id == "c1_vio"
# ---------------------------------------------------------------------------
# AC-2: records reflects in-buffer state in FIFO order.
def test_ac2_records_reflects_in_buffer_state_fifo() -> None:
# Arrange
sink = FakeFdrSink(producer_id="test")
records = [_make_record(frame_id=i) for i in range(3)]
# Act
for r in records:
sink.enqueue(r)
sink.pop_one()
# Assert
assert sink.records == records[1:]
# ---------------------------------------------------------------------------
# AC-3: all_records_ever captures dropped records too.
def test_ac3_all_records_ever_captures_dropped() -> None:
# Arrange
sink = FakeFdrSink(producer_id="test", capacity=2, with_default_overrun_policy=True)
a = _make_record(frame_id=0)
b = _make_record(frame_id=1)
c = _make_record(frame_id=2)
# Act
sink.enqueue(a)
sink.enqueue(b)
sink.enqueue(c)
# Assert
# Buffer carries the newest 2 (b dropped first, c retried into a's slot)
# plus the synthesised overrun record at the burst end.
assert any(r.kind == OVERRUN_KIND for r in sink.records)
user_records = [r for r in sink.records if r.kind != OVERRUN_KIND]
assert c in user_records
# all_records_ever includes a (which was dropped by drop-oldest) too.
assert a in sink.all_records_ever
assert b in sink.all_records_ever
assert c in sink.all_records_ever
# ---------------------------------------------------------------------------
# AC-4: overrun policy parity with real FdrClient.
def test_ac4_overrun_policy_parity_with_real_client() -> None:
# Arrange
sink = FakeFdrSink(producer_id="c1_vio", capacity=4, with_default_overrun_policy=True)
# Fill (capacity 4 holds 4 records before overrun starts).
for i in range(4):
sink.enqueue(_make_record(frame_id=i))
# Act
sink.enqueue(_make_record(frame_id=999))
# Assert
overruns = [r for r in sink.records if r.kind == OVERRUN_KIND]
assert overruns, "fake must emit an overrun record when policy is wired"
assert overruns[0].producer_id == OVERRUN_PRODUCER_ID
assert overruns[0].payload["producer_id"] == "c1_vio"
# ---------------------------------------------------------------------------
# AC-5: pytest fixture available.
def test_ac5_fixture_provides_clean_sink_per_test(fake_fdr_sink: FakeFdrSink) -> None:
# Arrange / Act / Assert
assert isinstance(fake_fdr_sink, FakeFdrSink)
assert fake_fdr_sink.records == []
fake_fdr_sink.enqueue(_make_record())
assert len(fake_fdr_sink.records) == 1
# ---------------------------------------------------------------------------
# AC-6: producer_id preserved on round-trip.
def test_ac6_producer_id_preserved_on_roundtrip() -> None:
# Arrange
sink = FakeFdrSink(producer_id="c2_vpr")
record = _make_record(producer_id="c2_vpr")
# Act
sink.enqueue(record)
popped = sink.pop_one()
# Assert
assert popped is not None
assert popped.producer_id == "c2_vpr"
# ---------------------------------------------------------------------------
# Empty producer_id rejected (parity with real client).
def test_empty_producer_id_raises_value_error() -> None:
# Arrange / Act / Assert
with pytest.raises(ValueError, match="producer_id"):
FakeFdrSink(producer_id="")
# ---------------------------------------------------------------------------
# Production isolation: no `src/gps_denied_onboard/**.py` imports the fakes.
def test_production_does_not_import_fakes() -> None:
# Arrange
violations: list[str] = []
target_module_prefix = "gps_denied_onboard.fdr_client.fakes"
# Act
for path in _SRC_ROOT.rglob("*.py"):
if path.name == "fakes.py":
continue # the module itself
try:
tree = ast.parse(path.read_text(encoding="utf-8"))
except SyntaxError:
continue
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module:
if node.module.startswith(target_module_prefix):
violations.append(str(path.relative_to(_REPO_ROOT)))
elif isinstance(node, ast.Import):
for alias in node.names:
if alias.name.startswith(target_module_prefix):
violations.append(str(path.relative_to(_REPO_ROOT)))
# Assert
assert not violations, (
"Production code must not import gps_denied_onboard.fdr_client.fakes. "
f"Violations: {violations}"
)
# ---------------------------------------------------------------------------
# Contract parity: every public method on FdrClient also exists on FakeFdrSink.
def test_contract_parity_public_methods() -> None:
# Arrange
public_attrs = {
name
for name in dir(FdrClient)
if not name.startswith("_") and callable(getattr(FdrClient, name))
}
public_attrs |= {"producer_id", "on_overrun"} # property pair
# Act
missing = [name for name in public_attrs if not hasattr(FakeFdrSink, name)]
# Assert
assert not missing, f"FakeFdrSink missing public surface: {missing}"