/* * test_can_watchdog.c — Unit tests for CAN bus watchdog and error recovery (Issue #694). * * Build (host, no hardware): * gcc -I include -I test/stubs -DTEST_HOST \ * -o /tmp/test_can_watchdog src/can_driver.c test/test_can_watchdog.c * * All timing driven through now_ms argument passed to can_driver_watchdog_tick(). * ESR state injected via can_driver_inject_esr(). */ /* Include implementation directly (pulls stm32 stub via -I test/stubs) */ #include "../src/can_driver.c" /* ---- Test framework ---- */ #include #include static int g_pass = 0; static int g_fail = 0; #define ASSERT(cond, msg) \ do { \ if (cond) { g_pass++; } \ else { g_fail++; printf("FAIL [%s:%d] %s\n", __FILE__, __LINE__, msg); } \ } while (0) /* HAL_GetTick stub */ uint32_t HAL_GetTick(void) { return 0u; } /* Reset driver state between tests */ static void _reset(void) { can_driver_inject_esr(0u); can_driver_init(); } /* ---- Tests ---- */ static void test_nominal_no_errors(void) { _reset(); can_error_state_t st = can_driver_watchdog_tick(0u); ASSERT(st == CAN_ERR_NOMINAL, "clean ESR: state is NOMINAL"); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.restart_count == 0u, "nominal: restart_count 0"); ASSERT(wd.busoff_count == 0u, "nominal: busoff_count 0"); } static void test_errwarn_detection(void) { _reset(); can_driver_inject_esr(CAN_ESR_EWGF); can_error_state_t st = can_driver_watchdog_tick(1000u); ASSERT(st == CAN_ERR_WARNING, "EWGF set: state is WARNING"); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.errwarn_count == 1u, "errwarn_count incremented"); } static void test_errwarn_cleared_returns_nominal(void) { _reset(); /* Enter warning state */ can_driver_inject_esr(CAN_ESR_EWGF); can_driver_watchdog_tick(100u); /* Clear ESR - should de-escalate */ can_driver_inject_esr(0u); can_error_state_t st = can_driver_watchdog_tick(200u); ASSERT(st == CAN_ERR_NOMINAL, "ESR cleared: de-escalate to NOMINAL"); } static void test_errpassive_detection(void) { _reset(); can_driver_inject_esr(CAN_ESR_EPVF); can_error_state_t st = can_driver_watchdog_tick(500u); ASSERT(st == CAN_ERR_ERROR_PASSIVE, "EPVF set: state is ERROR_PASSIVE"); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.errpassive_count == 1u, "errpassive_count incremented"); } static void test_busoff_detection(void) { _reset(); can_driver_inject_esr(CAN_ESR_BOFF); can_error_state_t st = can_driver_watchdog_tick(1000u); ASSERT(st == CAN_ERR_BUS_OFF, "BOFF set: state is BUS_OFF"); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.busoff_count == 1u, "busoff_count incremented"); ASSERT(wd.busoff_ms == 1000u, "busoff_ms recorded"); } static void test_busoff_sets_stats_bus_off(void) { _reset(); can_driver_inject_esr(CAN_ESR_BOFF); can_driver_watchdog_tick(500u); can_stats_t cs; can_driver_get_stats(&cs); ASSERT(cs.bus_off == 1u, "bus-off: stats.bus_off = 1"); } static void test_busoff_no_restart_before_timeout(void) { _reset(); can_driver_inject_esr(CAN_ESR_BOFF); can_driver_watchdog_tick(0u); /* Call again just before CAN_WDOG_RESTART_MS */ can_error_state_t st = can_driver_watchdog_tick(CAN_WDOG_RESTART_MS - 1u); ASSERT(st == CAN_ERR_BUS_OFF, "before restart timeout: still BUS_OFF"); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.restart_count == 0u, "no restart yet"); } static void test_busoff_auto_restart_after_timeout(void) { _reset(); /* Inject bus-off at t=0 */ can_driver_inject_esr(CAN_ESR_BOFF); can_driver_watchdog_tick(0u); /* After restart timeout, HAL_CAN_Stop/Start clears s_test_esr internally */ can_error_state_t st = can_driver_watchdog_tick(CAN_WDOG_RESTART_MS); ASSERT(st == CAN_ERR_NOMINAL, "after restart timeout: state is NOMINAL"); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.restart_count == 1u, "restart_count incremented to 1"); ASSERT(wd.busoff_ms == 0u, "busoff_ms cleared after restart"); } static void test_busoff_stats_cleared_after_restart(void) { _reset(); can_driver_inject_esr(CAN_ESR_BOFF); can_driver_watchdog_tick(0u); can_driver_watchdog_tick(CAN_WDOG_RESTART_MS); can_stats_t cs; can_driver_get_stats(&cs); ASSERT(cs.bus_off == 0u, "after restart: stats.bus_off cleared"); } static void test_busoff_count_only_increments_once_per_event(void) { _reset(); can_driver_inject_esr(CAN_ESR_BOFF); /* Call tick multiple times while still in bus-off */ can_driver_watchdog_tick(0u); can_driver_watchdog_tick(10u); can_driver_watchdog_tick(50u); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.busoff_count == 1u, "repeated BOFF ticks: count stays 1"); } static void test_multiple_restart_cycles(void) { _reset(); /* Cycle 1 */ can_driver_inject_esr(CAN_ESR_BOFF); can_driver_watchdog_tick(0u); can_driver_watchdog_tick(CAN_WDOG_RESTART_MS); /* restart */ /* Cycle 2: inject bus-off again */ can_driver_inject_esr(CAN_ESR_BOFF); can_driver_watchdog_tick(CAN_WDOG_RESTART_MS + 1u); can_driver_watchdog_tick(CAN_WDOG_RESTART_MS * 2u + 1u); /* restart */ can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.restart_count == 2u, "two restart cycles: restart_count = 2"); ASSERT(wd.busoff_count == 2u, "two restart cycles: busoff_count = 2"); } static void test_tec_rec_extracted(void) { _reset(); /* Inject ESR with TEC=0xAB (bits 23:16) and REC=0xCD (bits 31:24) */ uint32_t fake_esr = (0xABu << 16) | (0xCDu << 24); can_driver_inject_esr(fake_esr); can_driver_watchdog_tick(100u); can_wdog_t wd; can_driver_get_wdog(&wd); ASSERT(wd.tec == 0xABu, "TEC extracted from ESR correctly"); ASSERT(wd.rec == 0xCDu, "REC extracted from ESR correctly"); } /* ---- main ---- */ int main(void) { printf("=== test_can_watchdog ===\n"); test_nominal_no_errors(); test_errwarn_detection(); test_errwarn_cleared_returns_nominal(); test_errpassive_detection(); test_busoff_detection(); test_busoff_sets_stats_bus_off(); test_busoff_no_restart_before_timeout(); test_busoff_auto_restart_after_timeout(); test_busoff_stats_cleared_after_restart(); test_busoff_count_only_increments_once_per_event(); test_multiple_restart_cycles(); test_tec_rec_extracted(); printf("\nResults: %d passed, %d failed\n", g_pass, g_fail); return g_fail ? 1 : 0; }