ADR-002: Add a Host-Side pytest-bdd Acceptance Tier via the Mocked-HAL Test Double¶
| Field | Value |
|---|---|
| Date | 2026-09-29 |
| Status | Accepted |
| Deciders | TODO |
| Supersedes | — |
| Superseded by | — |
Context and Problem Statement¶
A host-side build of src/app.cpp against a mocked HAL (test/mocks/hal_mock.hpp) is tested
with Catch2, while on-target/HIL testing stays deferred. The .feature files in
test/bdd/features/ hold the scenarios derived from the acceptance criteria of the capabilities,
but nothing executes them by itself: there is no running process for pytest-bdd to poll over
HTTP (unlike service), and no end-user binary to invoke (unlike tool). Without an acceptance
tier of its own, this repository's .feature files would stay pure documentation, with nothing to
notice if the implementation drifted away from what they describe.
The obvious candidate for a host-side harness is src/app_c_api.h/app_c_api.cpp — the extern
"C" bridge that vendor/cubemx/Core/Src/main.c calls on real hardware. It looks attractive because
it exercises the exact entry points production code calls, not a re-implementation of them.
But app_c_api.cpp wires a concrete TargetHal (see its own TODO comment). That class exists
to grow real calls into CubeMX-generated HAL functions (e.g. HAL_GPIO_WritePin), which need
CubeMX/vendor headers only available when cross-compiling for the target MCU. The
moment TargetHal gets its first real implementation — the intended next step for this firmware,
not an edge case — app_c_api.cpp stops compiling on the host at all. A harness built on top of it
would work only in the initial, not-yet-implemented scaffold state and break as soon as the
project does what it was scaffolded to do next.
Decision Drivers¶
- Reuse, not replace, the existing host-side mocked-HAL build — no second Hal double, no separate build configuration.
- Must stay host-compilable indefinitely, regardless of how much real hardware logic
TargetHalgrows — ruling out anything that linksapp_c_api.cpp. - Must not imply this substitutes for on-target/HIL testing — that stays deferred.
Considered Options¶
- No acceptance tier beyond the existing Catch2
app_test.cpp. - A thin, subprocess-drivable harness executable (
test/bdd/harness/main.cpp) that linksapp_c_api.cppand callsapp_init()/app_poll()throughapp_c_api.h, exercisingTargetHal's production wiring directly. - A thin, subprocess-drivable harness executable (
test/bdd/harness/main.cpp) that wiresApptoHalMockdirectly — the same test doubleapp_test.cppalready uses — built alongsideapp_test, driven by pytest-bdd via subprocess exactly like thelibrary/toolacceptance tiers. - Introduce on-target/HIL testing with pytest-bdd now.
Decision Outcome¶
Chosen option: 3. test/bdd/harness/main.cpp wires App to HalMock — identical to
app_test.cpp's setup — calls app.init()/app.poll(), prints an observable result, and exits
0. test/CMakeLists.txt compiles it alongside app_test, linking app plus the same
test/mocks/hal_mock.hpp header app_test uses. pytest-bdd drives the built bdd_harness binary via
subprocess, the same pattern as the library/tool acceptance tiers.
Trigger to revisit: the introduction of on-target/HIL testing — this host-side harness is additive and does not replace it.
Positive Consequences¶
- Closes the same spec/test drift gap already closed for
service/tool/library, now here too. - No second Hal double: reuses the same
HalMockapp_test.cppalready uses, rather than introducing a third Hal implementation. - Stays host-compilable indefinitely:
HalMockhas no reason to ever call real hardware, unlikeTargetHal, so this harness cannot be broken by this firmware's own intended evolution. - Structurally cannot affect the ARM cross-build under
firmware/:test/is never referenced byfirmware/CMakeLists.txt.
Negative Consequences / Risks¶
- Adds pytest-bdd (Python) as a devbox dependency, the same cost
service/tool/libraryalready carry for their own acceptance tiers. - Does not verify the
app_c_api.hextern "C" bridge orTargetHal's real hardware calls — onlyApp's logic against a mock, exactly like the Catch2 tier it sits alongside. That bridge is exercised by the ARM cross-build (make firmware-build) instead, the only place CubeMX's real HAL headers are available. - Limitations: this harness only proves hardware-independent logic against the mocked HAL —
real timing, interrupt latency, and actual peripheral behaviour remain unverified by it, exactly
as for the Catch2 tier. Not every scenario in
test/bdd/features/maps sensibly onto the mocked HAL either; such scenarios stay documentation without a test binding until a genuine on-target/HIL tier exists.
Pros and Cons of the Options¶
Option 1 — No acceptance tier beyond Catch2¶
- Pro: Simplest option; no new devbox dependency.
- Con: Leaves the spec/test drift gap open here, inconsistent with how
service/tool/libraryhandle it.
Option 2 — Host-side app_c_api harness, subprocess-driven¶
- Pro: Would exercise the exact production entry points
app_c_api.hexposes, not justApp's logic in isolation. - Con: Only works while
TargetHalis still the empty scaffold placeholder (see Context) — breaks on the host the momentTargetHalgets its real CubeMX HAL implementation.
Option 3 — Host-side HalMock harness, subprocess-driven (chosen)¶
- Pro: Reuses the existing mocked-HAL host build and test double unchanged; stays
host-compilable no matter how
TargetHalevolves; consistent with the other project types' acceptance-tier pattern. - Con: Still only a host-side smoke test — see Limitations above — and does not itself prove
the
app_c_apibridge builds (the ARM cross-build already covers that).
Option 4 — On-target/HIL testing now¶
- Pro: Would close the timing/interrupt/peripheral gap this ADR's harness cannot.
- Con: Contradicts the deferral of on-target/HIL testing without a concrete trigger having been hit; disproportionate investment at this stage.
Notes¶
This ADR concerns the host-side build only. It complements the Catch2 tier — the two stay
structurally identical (same App, same HalMock), differing only in how they are driven: Catch2 macros
in-process for app_test, pytest-bdd over a subprocess for bdd_harness.