Decision spike for the firmware/test/ host harness (split of bf-4ne). Records why the ESP-IDF --target linux / Unity-host path was rejected: csi.c is blocked by esp_wifi.h and provision.c by driver/uart.h, and firmware/main builds as one component whose REQUIRES line names esp_wifi/bt/driver — none of which have a linux build. Falls back to a plain gcc harness under firmware/test/ that does not link main/*.c. Reason must be carried into bf-4ne's test-runner header comment. Co-Authored-By: Claude <noreply@anthropic.com>
6.7 KiB
Firmware Host-Based Test Approach — Decision Record
Bead: bf-21t (decision spike; parent harness bead: bf-4ne)
Date: 2026-07-03
Decision: gcc host harness (plain gcc-compiled test runner under firmware/test/,
not the ESP-IDF --target linux / Unity-host path).
This is a decision spike only. It records why the ESP-IDF linux target was rejected so that the harness build-out (bf-4ne) does not have to re-derive it. The chosen approach and the reason below must be carried into bf-4ne's test-runner header comment.
TL;DR
firmware/main/*.c cannot be compiled for the ESP-IDF linux target because the
core files pull in hardware-radio / peripheral drivers that have no host build:
csi.c→esp_wifi.h(CSI callback + promiscuous-mode API) — no host build.provision.c→driver/uart.h(UART provisioning window) — no host build.
The firmware is built as a single main component, so the whole component must
link for any one file to be host-tested via idf.py --target linux. It doesn't, so
the linux target is impractical. Fall back to a gcc harness that does not link
firmware/main/*.c directly — it tests pure-logic extractions and binary-format
contracts instead.
Investigation
ESP-IDF was not installed on the investigation host (IDF_PATH unset, no idf.py),
so this is reasoned from the source plus the documented ESP-IDF linux-target
component matrix (IDF 5.2.x, the version pinned for this project). gcc 14.2 is
available, which the fallback path requires.
What the ESP-IDF linux target provides
Host builds exist for the "hostable" components:
nvs_flash/nvs (file-backed host emulation — the canonical IDF host-test
example), esp_log, esp_timer, esp_event, freertos (POSIX/SMP-on-host
port — so freertos/task.h, queue.h, event_groups.h, semphr.h resolve),
cJSON (pure C), and partial esp_netif/mbedtls.
What it does not provide (hardware/peripheral-only, target-arch):
esp_wifi (depends on the radio MAC/baseband binary blob), the entire
driver/* namespace (uart, gpio, temperature_sensor, …), esp_bt/BLE,
esp_http_server/esp_http_client/esp_websocket_client, esp_ota_ops,
mdns, lwip (as these files use it).
Per-file verdict for the three target files
| File | Non-hostable include | Specific symbols used | linux target |
|---|---|---|---|
nvs_migration.c |
— (only nvs, esp_log, spaxel.h) |
nvs_open/get_str/set_str/commit/erase_key, nvs_handle_t |
✅ feasible in isolation |
csi.c |
esp_wifi.h |
wifi_csi_info_t, wifi_csi_config_t, esp_wifi_set_csi_config, esp_wifi_set_csi_rx_cb, esp_wifi_set_csi, esp_wifi_set_promiscuous |
❌ blocked |
provision.c |
driver/uart.h |
uart_param_config, uart_driver_install, uart_write_bytes, uart_read_bytes, uart_driver_delete, UART_NUM_0, uart_config_t, UART_DATA_8_BITS |
❌ blocked |
Why the lone feasible file doesn't rescue the approach
nvs_migration.c is the one file whose dependency set (nvs + esp_log +
freertos, transitively via spaxel.h) is entirely hostable — NVS has a real
file-backed host build. So in isolation it could be run through the IDF linux
target. But:
- The firmware compiles
mainas one component —firmware/main/CMakeLists.txt'sidf_component_register(...)lists every*.cas SRCS and declaresREQUIRES esp_wifi esp_netif nvs_flash esp_http_client esp_timer bt driver log esp_http_server mbedtls app_update json freertos(verified verbatim in the tree). ThatREQUIRESline itself namesesp_wifi,bt, anddriver— three components with nolinuxbuild — so themaincomponent cannot even be configured for the host target, regardless of which translation unit you care about. The IDF host-test model builds whole components, not cherry-picked TUs;csi.c,wifi.c,websocket.c,ble.c,provision.c,led.c,ntp.call drag in unhostable drivers. - Making
mainlinkable for the host would mean wrapping every driver include in host/target#ifdefguards or splitting hardware code into a separate component — i.e. refactoring production firmware purely to satisfy host tests, which risks the on-target build and defeats the purpose. - Even if it worked, you would then run two different test mechanisms (IDF linux for nvs, gcc for csi/prov). A single coherent harness is simpler and is what the parent bead (bf-4ne) asks for.
Conclusion: the ESP-IDF linux target is rejected as the unified harness approach.
Decision: gcc host harness under firmware/test/
A plain gcc-compiled test runner that:
- Does not
#includeor linkfirmware/main/*.cdirectly (they can't compile without the ESP-IDF target toolchain). - Instead tests:
- Pure-logic extractions — small, dependency-free pieces of behavior copied
into separately-compilable test units (e.g. the Welford amplitude-variance in
csi.c, the JSON→NVS-key mapping inprovision.c, the migration-step logic innvs_migration.c), or refactored into*_logic.cfiles with zeroesp_includes that both the firmware and the harness can compile. - Binary-format contracts — the 24-byte CSI frame layout and the provisioning JSON schema, validated by re-implementing a reference encoder/decoder in the test.
- Pure-logic extractions — small, dependency-free pieces of behavior copied
into separately-compilable test units (e.g. the Welford amplitude-variance in
- Uses assert-style macros (or Unity host) — either is fine; the key constraint is "compiles with plain gcc, no ESP-IDF toolchain."
- Is wired so adding new
test_*.cfiles later is trivial (convention-based or a singleSOURCESlist), per bf-4ne.
What this does NOT cover (accepted trade-off)
The gcc harness cannot exercise the actual esp_wifi/uart/nvs call sites —
those remain validated on-target (and via the Go-side spaxel-sim acceptance
suite, which already drives the full node→mothership contract host-free). The
firmware host tests are a logic-and-format safety net, not a hardware test.
Carry-forward into child 2 (bf-4ne — the harness build-out)
bf-4ne must, in its test-runner header comment, state:
- Chosen approach: gcc host harness.
- Concrete reason:
csi.cis blocked byesp_wifi.handprovision.cbydriver/uart.h; neither has an ESP-IDFlinux-target build, sofirmware/main/*.ccannot be linked directly and the Unity/idf.py --target linuxpath was rejected. (Evennvs_migration.c, the lone hostable file, is blocked becausemainbuilds as one component.) - The single documented run command for the harness.
This doc (docs/notes/firmware-host-test-approach.md) is the durable reference
for that comment.