test(firmware): broaden native-test coverage + gcov instrumentation

Extract the pre-first-image retry from normal_operation() into a
templated bootstrap_loop() helper in operation.h so the loop body
becomes unit-testable. Add four tests against it: two that verify the
X-Panel-Id header is sent on every poll and matches the compile-time
PANEL_ID (a silent server-side mis-routing risk if dropped), and two
that exercise the loop's exit-on-deep-sleep vs. iterate-while-204
behaviour.

Wire --coverage into env:native-test (compile + link via a post-script)
so `gcovr -r . --filter src/` produces a real number, and ignore the
stray *.gcov files gcovr drops at the repo root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 16:25:00 -04:00
parent 013e49d859
commit e9f2ec0629
7 changed files with 118 additions and 5 deletions
+4 -4
View File
@@ -292,13 +292,13 @@ static void normal_operation(const String& mac) {
// we've received our first image. While in the pre-image window it
// returns instead, so we keep WiFi up and retry on a short interval —
// way faster end-to-end than waiting through a deep-sleep + reconnect
// for every "no image yet" poll.
while (true) {
// for every "no image yet" poll. The loop body is extracted into
// bootstrap_loop() (operation.h) so it's unit-testable.
bootstrap_loop([&]() {
HTTPClient http;
http.begin(client, url);
normal_operation_impl(mac, http, url, prefs);
delay(BOOTSTRAP_RETRY_INTERVAL_MS);
}
});
}
// ── Setup ─────────────────────────────────────────────────────────────────────
+20
View File
@@ -356,3 +356,23 @@ void normal_operation_impl(const String& mac, HTTP& http, const String& url, Pre
esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BTN_RESET, 0);
esp_deep_sleep_start();
}
// ── Bootstrap-stay-awake loop ────────────────────────────────────────────────
// Wraps normal_operation_impl in a retry loop for the pre-first-image window.
// Once an image arrives, impl calls esp_deep_sleep_start() and never returns —
// in production that's literal silicon-level halt; in unit tests the mock just
// sets g_deep_sleep_started and returns, so we check that flag to break out.
// Caller passes a callable that runs one poll iteration (instantiates a fresh
// HTTPClient bound to the WiFiClient, calls normal_operation_impl, etc.).
template<typename PollOnce>
inline void bootstrap_loop(PollOnce poll_once) {
while (true) {
poll_once();
#ifdef UNIT_TEST
// Production: esp_deep_sleep_start never returns. Tests: it sets the
// flag and returns, so without this guard the loop spins forever.
if (g_deep_sleep_started) return;
#endif
delay(BOOTSTRAP_RETRY_INTERVAL_MS);
}
}