test(firmware): event_log boot recovery — partial fill and post-wrap
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.
This commit is contained in:
@@ -27,6 +27,11 @@
|
|||||||
g_head = 0;
|
g_head = 0;
|
||||||
g_cnt = 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
|
#endif
|
||||||
|
|
||||||
static const size_t SLOTS = 32;
|
static const size_t SLOTS = 32;
|
||||||
@@ -69,6 +74,7 @@ void event_log_init() {
|
|||||||
Serial.println("[evlog] NVS begin failed");
|
Serial.println("[evlog] NVS begin failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// Scan all 32 slots; locate the one with the largest seq.
|
// 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).
|
// Empty log: every slot tag == 0 (not a valid EventLogTag, which starts at 1).
|
||||||
uint32_t max_seq = 0;
|
uint32_t max_seq = 0;
|
||||||
@@ -91,9 +97,6 @@ void event_log_init() {
|
|||||||
g_head = 0;
|
g_head = 0;
|
||||||
g_cnt = 0;
|
g_cnt = 0;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
// nothing
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void event_log_write(EventLogTag tag, uint16_t data0, uint16_t data1) {
|
void event_log_write(EventLogTag tag, uint16_t data0, uint16_t data1) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ struct EventLogEntry {
|
|||||||
static_assert(sizeof(EventLogEntry) == 32, "EventLogEntry must be 32 bytes");
|
static_assert(sizeof(EventLogEntry) == 32, "EventLogEntry must be 32 bytes");
|
||||||
|
|
||||||
// NVS-backed 32-slot ring buffer. Safe to call before NTP sync.
|
// 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_init();
|
||||||
void event_log_write(EventLogTag tag, uint16_t data0 = 0, uint16_t data1 = 0);
|
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);
|
size_t event_log_read_recent(EventLogEntry* out, size_t max_entries);
|
||||||
|
|||||||
@@ -74,6 +74,58 @@ void test_path_hash_distinguishes_real_api_paths() {
|
|||||||
TEST_ASSERT_NOT_EQUAL(h2, h3);
|
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() {
|
int main() {
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
RUN_TEST(test_entry_is_32_bytes);
|
RUN_TEST(test_entry_is_32_bytes);
|
||||||
@@ -83,5 +135,7 @@ int main() {
|
|||||||
RUN_TEST(test_empty_log_read_returns_zero);
|
RUN_TEST(test_empty_log_read_returns_zero);
|
||||||
RUN_TEST(test_read_recent_truncates_to_max_entries);
|
RUN_TEST(test_read_recent_truncates_to_max_entries);
|
||||||
RUN_TEST(test_path_hash_distinguishes_real_api_paths);
|
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();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user