From 9eb1e19651557963b2714668a2caf767ff9b6859 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Thu, 23 Apr 2026 13:18:08 -0700 Subject: [PATCH] =?UTF-8?q?test(firmware):=20event=5Flog=20boot=20recovery?= =?UTF-8?q?=20=E2=80=94=20partial=20fill=20and=20post-wrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exercises the slot-scan logic in event_log_init(): after a simulated reboot (RAM state cleared, NVS slots preserved) the module must resume with the correct head/cnt so newest-first read order is unchanged and subsequent writes continue the seq monotonically. Adds native-only event_log_test_simulate_reboot() helper. Lifts the slot-scan loop out of the #ifdef ARDUINO guard so the native stub exercises the same recovery path as production; the platform-specific NVS setup remains guarded. --- firmware/lib/event_log/event_log.cpp | 9 ++-- firmware/lib/event_log/event_log.h | 1 + .../test/test_event_log/test_event_log.cpp | 54 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/firmware/lib/event_log/event_log.cpp b/firmware/lib/event_log/event_log.cpp index 3766935..95fd630 100644 --- a/firmware/lib/event_log/event_log.cpp +++ b/firmware/lib/event_log/event_log.cpp @@ -27,6 +27,11 @@ g_head = 0; g_cnt = 0; } + extern "C" void event_log_test_simulate_reboot() { + // Simulate device reboot: clear in-RAM state, keep persistent slots. + g_head = 0; + g_cnt = 0; + } #endif static const size_t SLOTS = 32; @@ -69,6 +74,7 @@ void event_log_init() { Serial.println("[evlog] NVS begin failed"); return; } +#endif // Scan all 32 slots; locate the one with the largest seq. // Empty log: every slot tag == 0 (not a valid EventLogTag, which starts at 1). uint32_t max_seq = 0; @@ -91,9 +97,6 @@ void event_log_init() { g_head = 0; g_cnt = 0; } -#else - // nothing -#endif } void event_log_write(EventLogTag tag, uint16_t data0, uint16_t data1) { diff --git a/firmware/lib/event_log/event_log.h b/firmware/lib/event_log/event_log.h index d4ff60f..0782743 100644 --- a/firmware/lib/event_log/event_log.h +++ b/firmware/lib/event_log/event_log.h @@ -33,6 +33,7 @@ struct EventLogEntry { static_assert(sizeof(EventLogEntry) == 32, "EventLogEntry must be 32 bytes"); // NVS-backed 32-slot ring buffer. Safe to call before NTP sync. +// Call exactly once from application setup, before any task writes events. void event_log_init(); void event_log_write(EventLogTag tag, uint16_t data0 = 0, uint16_t data1 = 0); size_t event_log_read_recent(EventLogEntry* out, size_t max_entries); diff --git a/firmware/test/test_event_log/test_event_log.cpp b/firmware/test/test_event_log/test_event_log.cpp index aceb47f..5742b2e 100644 --- a/firmware/test/test_event_log/test_event_log.cpp +++ b/firmware/test/test_event_log/test_event_log.cpp @@ -74,6 +74,58 @@ void test_path_hash_distinguishes_real_api_paths() { TEST_ASSERT_NOT_EQUAL(h2, h3); } +extern "C" void event_log_test_simulate_reboot(); + +void test_boot_recovery_after_partial_fill() { + // Phase 1: write 5 entries before "reboot" + event_log_init(); + for (uint16_t i = 0; i < 5; i++) event_log_write(EVT_HTTP_OK, i, 0); + + // Phase 2: simulate reboot (clear RAM state, keep slots), re-init, verify + event_log_test_simulate_reboot(); + event_log_init(); + + // All 5 original entries should still be readable, newest first + EventLogEntry buf[8]; + size_t n = event_log_read_recent(buf, 8); + TEST_ASSERT_EQUAL(5, n); + TEST_ASSERT_EQUAL(4, buf[0].data0); // newest + TEST_ASSERT_EQUAL(0, buf[4].data0); // oldest + + // Phase 3: write one more — seq must continue (not restart at 0), + // so the new entry is the newest and slot index 5 holds it + event_log_write(EVT_HTTP_OK, 99, 0); + n = event_log_read_recent(buf, 8); + TEST_ASSERT_EQUAL(6, n); + TEST_ASSERT_EQUAL(99, buf[0].data0); + TEST_ASSERT_EQUAL(4, buf[1].data0); +} + +void test_boot_recovery_after_wrap() { + // Phase 1: write 40 entries (wraps the 32-slot ring once; oldest 8 dropped) + event_log_init(); + for (uint16_t i = 0; i < 40; i++) event_log_write(EVT_HTTP_OK, i, 0); + + // Phase 2: simulate reboot, re-init + event_log_test_simulate_reboot(); + event_log_init(); + + // Still 32 entries visible, newest=39, oldest=8 + EventLogEntry buf[32]; + size_t n = event_log_read_recent(buf, 32); + TEST_ASSERT_EQUAL(32, n); + TEST_ASSERT_EQUAL(39, buf[0].data0); + TEST_ASSERT_EQUAL(8, buf[31].data0); + + // Phase 3: one more write — newest becomes 100, head advances past + // wherever the max-seq slot was, oldest drops to data0=9 + event_log_write(EVT_HTTP_OK, 100, 0); + n = event_log_read_recent(buf, 32); + TEST_ASSERT_EQUAL(32, n); + TEST_ASSERT_EQUAL(100, buf[0].data0); + TEST_ASSERT_EQUAL(9, buf[31].data0); +} + int main() { UNITY_BEGIN(); RUN_TEST(test_entry_is_32_bytes); @@ -83,5 +135,7 @@ int main() { RUN_TEST(test_empty_log_read_returns_zero); RUN_TEST(test_read_recent_truncates_to_max_entries); RUN_TEST(test_path_hash_distinguishes_real_api_paths); + RUN_TEST(test_boot_recovery_after_partial_fill); + RUN_TEST(test_boot_recovery_after_wrap); return UNITY_END(); }