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>
9.3 KiB
FakeFdrSink for Component-Level Tests
Task: AZ-275_fake_fdr_sink
Name: FakeFdrSink
Description: An in-process, in-memory test double for FdrClient that conforms to the fdr_client_protocol contract's public surface and lets component-level tests assert on every record their code emits to the FDR. Drop-in replacement for FdrClient everywhere it is injected; no writer thread, no segment files, no real ring buffer — just a list-of-records the test inspects.
Complexity: 2 points
Dependencies: AZ-272_fdr_record_schema, AZ-273_fdr_client_ringbuf
Component: shared.fdr_client (cross-cutting; epic AZ-247 / E-CC-FDR-CLIENT)
Tracker: AZ-275
Epic: AZ-247 (E-CC-FDR-CLIENT)
Document Dependencies
_docs/02_document/contracts/shared_fdr_client/fdr_client_protocol.md— the public surface this fake conforms to._docs/02_document/contracts/shared_fdr_client/fdr_record_schema.md— the record envelope this fake stores in memory.
Problem
Component-level tests (every component under tests/unit/components/<name>/ and tests/integration/<name>/) must assert on what their code writes to the FDR. Without a fake:
- Tests would have to spin up the C13 writer thread + a tmp segment file just to read records back — slow, brittle, cross-component coupling.
- Tests would all reach into
FdrClient's private buffer state, freezing internal layout into every test and blocking future implementation changes.
A simple, contract-conforming FakeFdrSink lets each component's test assert on records via a stable public API — and crucially, the same API every other component test uses, so test infrastructure does not fork per component.
Outcome
- Tests inject
FakeFdrSink(producer_id="c1_vio")wherever production code expects anFdrClient. The component code is unchanged; the test readssink.recordsafter exercising the component. - Every assertion the contract test of
fdr_client_protocolmakes against a realFdrClientALSO holds againstFakeFdrSink— except the lock-free / allocation-free / SPSC-guard NFRs (those are real-buffer concerns and are explicitly out of scope for the fake). - Tests can opt in to drop-oldest semantics (
FakeFdrSink(capacity=N, with_default_overrun_policy=True)) when verifying overrun behaviour, or leave it disabled and rely on unbounded list mode for general assertions.
Scope
Included
FakeFdrSink(producer_id: str, capacity: int | None = None, with_default_overrun_policy: bool = False)constructor implementing theFdrClientpublic surface fromfdr_client_protocol.md:enqueue,pop_one,drain,flush,producer_id,on_overrungetter/setter.
- An
FakeFdrSink.records: list[FdrRecord]property returning the records currently in-buffer in FIFO order. Tests use this directly for assertions. - An
FakeFdrSink.all_records_ever: list[FdrRecord]property returning every record ever enqueued, INCLUDING records dropped by the overrun policy when it is active. Lets tests assert on what the producer TRIED to send vs. what the buffer KEPT. - Behaviour parity with
FdrClientfor the contract-relevant subset:- Returns
EnqueueResult.OVERRUNwhencapacityis set and the buffer is full. - Invokes
on_overrunexactly once per overrun event when wired. - Stamps
producer_idcorrectly per the protocol (does NOT mutaterecord.producer_id).
- Returns
- A pytest fixture (
fake_fdr_sink) undertests/conftest.pythat constructs a default-configuration sink and yields it to tests.
Excluded
- The lock-free SPSC ring buffer, allocation-free hot path, and SPSC guards — owned by AZ-273 (this is a fake; real concurrency primitives are explicitly NOT replicated).
- The drop-oldest closure itself — owned by AZ-274; the fake imports and reuses it when the user opts in via
with_default_overrun_policy=True. - The
FdrRecordschema — owned by AZ-272. - The C13 writer thread, segment files, etc. — owned by E-C13 (AZ-248).
- A "fake C13 writer" that drains the sink — out of scope. Tests that need the drained side use
pop_one/draindirectly on the fake.
Acceptance Criteria
AC-1: Drop-in for FdrClient public surface
Given any production code that takes an FdrClient parameter (e.g. Vio(fdr=fdr_client, ...))
When the test passes a FakeFdrSink instead
Then the production code's calls (enqueue, flush) work identically; no AttributeError, no signature mismatch
AC-2: records reflects in-buffer state
Given a FakeFdrSink with no capacity limit
When the producer enqueues 3 records, then the test calls pop_one() once
Then sink.records returns the 2 remaining records in FIFO order
AC-3: all_records_ever captures dropped records
Given a FakeFdrSink(capacity=2, with_default_overrun_policy=True) filled to capacity
When the producer enqueues a 3rd record (drop-oldest fires)
Then sink.records has 2 entries (newest 2) AND sink.all_records_ever has 3 entries (all of them, including the dropped one)
AC-4: Overrun policy parity with real FdrClient
Given a FakeFdrSink(capacity=4, with_default_overrun_policy=True)
When the test reproduces AC-1 from AZ-274 (overflow + canonical overrun record)
Then the same assertion that holds against real FdrClient holds against FakeFdrSink — same overrun record shape, same coalescing across bursts
AC-5: pytest fixture available
Given a test file imports the standard project conftest
When the test signature is def test_x(fake_fdr_sink): ...
Then pytest injects a default-configuration FakeFdrSink and yields it; teardown clears the sink
AC-6: producer_id is preserved
Given FakeFdrSink(producer_id="c2_vpr") and an enqueued record carrying producer_id="c2_vpr"
When the test inspects sink.records[0]
Then records[0].producer_id == "c2_vpr" (the fake does NOT rewrite producer_id)
Non-Functional Requirements
Performance
enqueuep99 ≤ 100 µs on Tier-2 (developer machines + CI). The fake is not in the production critical path; the budget exists only to keep tests fast (10k assertions in a long fixture should add < 1 s).
Reliability
- The fake is single-threaded only. Concurrent
enqueue/pop_oneis undefined behaviour and not tested. Documented in the docstring.
Compatibility
- The fake's public surface mirrors the
fdr_client_protocol.mdcontract version it conforms to. The fake's docstring records the contract version. Bumping the protocol contract major version requires bumping the fake's surface in lock-step.
Unit Tests
| AC Ref | What to Test | Required Outcome |
|---|---|---|
| AC-1 | Inject FakeFdrSink into a stub component that expects FdrClient |
No AttributeError; calls succeed |
| AC-2 | 3 enqueues + 1 pop on unbounded sink | len(sink.records) == 2 in FIFO order |
| AC-3 | Capacity-2 sink with overrun policy + 3 enqueues | len(sink.records) == 2, len(sink.all_records_ever) == 3 |
| AC-4 | Re-run AZ-274 AC-1 + AC-2 against the fake | Same overrun record shape; same coalescing |
| AC-5 | A trivial test using fake_fdr_sink fixture |
Fixture provides a clean sink per test |
| AC-6 | Construct sink + enqueue with explicit producer_id | producer_id preserved on the popped record |
Constraints
- Public surface is fixed by
fdr_client_protocol.mdv1.0.0. The fake is allowed to expose ADDITIONAL test-only attributes (records,all_records_ever) — these are documented as fake-only and never appear on the realFdrClient(so production code accidentally using them fails the type checker). - The fake lives at
src/gps_denied_onboard/fdr_client/fakes.py— a separate module from the production code so production imports never pick it up. Tests importfrom gps_denied_onboard.fdr_client.fakes import FakeFdrSink. - The fake reuses
default_overrun_policyfrom AZ-274 verbatim; it does NOT re-implement the policy.
Risks & Mitigation
Risk 1: Fake drift from real client
- Risk: Engineers add a method to
FdrClientand forget to mirror it onFakeFdrSink; tests pass against the fake but production fails. - Mitigation: A contract test (
tests/contract/fdr_client_fake_parity.py) iterates over every public method onFdrClientand asserts the same method exists onFakeFdrSinkwith a compatible signature. Failure mode is loud and immediate.
Risk 2: Tests reach into _records private state, freezing implementation
- Risk: A test does
sink._buffer[3]instead ofsink.records[3]; later refactor breaks the test. - Mitigation:
recordsandall_records_everare the documented public access; pyright/mypy mark_bufferas private with_prefix; code review catches private-state access.
Runtime Completeness
- Named capability:
FakeFdrSinktest double — it is NOT a runtime capability; it is test infrastructure. Production code MUST NOT import fromfakes.py(verified by import-linter rule in the project'spyproject.toml). - Production code that must exist: import-linter rule preventing
src/gps_denied_onboard/**/*.py(excludingtests/) from importinggps_denied_onboard.fdr_client.fakes. Otherwise none — this PBI's deliverable is test infrastructure. - Allowed external stubs: this IS the stub. It is allowed in tests only.
- Unacceptable substitutes: production code wiring
FakeFdrSinkinstead ofFdrClient(would silently disable real FDR writes); per-test ad-hoc fakes that drift from the contract.