LCOV - code coverage report
Current view: top level - tests/unit - test_safety_manager.c (source / functions) Hit Total Coverage
Test: coverage.clean.info Lines: 254 254 100.0 %
Date: 2026-05-13 07:33:50 Functions: 23 23 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file test_safety_manager.c
       3             :  * @brief Unit tests for the Safety Manager (ASIL-D).
       4             :  *
       5             :  * @reqs SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012
       6             :  * @arch SWA-001
       7             :  */
       8             : #include "../unit_test_framework.h"
       9             : #include "../../src/safety_manager.h"
      10             : 
      11             : TestStats g_test_stats = {0, 0};
      12             : 
      13          22 : static SafetyInputs make_driving(void)
      14             : {
      15          22 :     SafetyInputs in = {0};
      16          22 :     in.engine_running       = true;
      17          22 :     in.brake_pedal_pressed  = false;
      18          22 :     in.vehicle_speed_kmh    = 50.0f;
      19          22 :     in.grade_percent        = 0.0f;
      20          22 :     in.current_state        = EPB_STATE_RELEASED;
      21          22 :     return in;
      22             : }
      23             : 
      24          12 : static SafetyInputs make_standstill(void)
      25             : {
      26          12 :     SafetyInputs in = make_driving();
      27          12 :     in.vehicle_speed_kmh = 0.0f;
      28          12 :     in.brake_pedal_pressed = true;
      29          12 :     return in;
      30             : }
      31             : 
      32           4 : static void step_n(SafetyInputs* in, int n)
      33             : {
      34          84 :     for (int i = 0; i < n; ++i) {
      35          80 :         safety_mgr_step_50ms(in);
      36             :     }
      37           4 : }
      38             : 
      39           2 : static void test_init(void)
      40             : {
      41           2 :     TEST_BEGIN("init -> IDLE, no apply request");
      42           2 :     ASSERT_EQ(safety_mgr_init(), EPB_OK);
      43           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
      44           2 :     ASSERT_TRUE(!safety_mgr_apply_requested());
      45           2 :     TEST_END();
      46           2 : }
      47             : 
      48           2 : static void test_null_input(void)
      49             : {
      50           2 :     TEST_BEGIN("NULL input is a no-op");
      51           2 :     (void)safety_mgr_init();
      52           2 :     safety_mgr_step_50ms(NULL);
      53           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
      54           2 :     TEST_END();
      55           2 : }
      56             : 
      57             : /* ---- Auto-Apply (SWE-007 + SWE-008) ---- */
      58             : 
      59           2 : static void test_auto_apply_armed_on_engine_off(void)
      60             : {
      61           2 :     TEST_BEGIN("SWE-007: engine off + standstill -> AUTO_APPLY_ARMED");
      62           2 :     (void)safety_mgr_init();
      63           2 :     SafetyInputs in = make_driving();
      64           2 :     in.engine_running    = false;
      65           2 :     in.vehicle_speed_kmh = 0.0f;
      66           2 :     safety_mgr_step_50ms(&in);
      67           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_ARMED);
      68           2 :     ASSERT_TRUE(!safety_mgr_apply_requested());
      69           2 :     TEST_END();
      70           2 : }
      71             : 
      72           2 : static void test_auto_apply_triggers_after_2s(void)
      73             : {
      74           2 :     TEST_BEGIN("SWE-008: after ~2s -> TRIGGERED + apply request");
      75           2 :     (void)safety_mgr_init();
      76           2 :     SafetyInputs in = make_driving();
      77           2 :     in.engine_running    = false;
      78           2 :     in.vehicle_speed_kmh = 0.0f;
      79             :     /* Halfway: still armed, no request */
      80           2 :     step_n(&in, 20);
      81           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_ARMED);
      82           2 :     ASSERT_TRUE(!safety_mgr_apply_requested());
      83             :     /* Run up to TRIGGERED within at most 25 more steps (~2 s leeway) */
      84          42 :     for (int i = 0; i < 25; ++i) {
      85          42 :         safety_mgr_step_50ms(&in);
      86          42 :         if (safety_mgr_get_state() == SAFETY_AUTO_APPLY_TRIGGERED) break;
      87             :     }
      88           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_TRIGGERED);
      89           2 :     ASSERT_TRUE(safety_mgr_apply_requested());
      90           2 :     TEST_END();
      91           2 : }
      92             : 
      93           2 : static void test_auto_apply_aborts_on_engine_on(void)
      94             : {
      95           2 :     TEST_BEGIN("Auto-Apply aborts when engine comes back on");
      96           2 :     (void)safety_mgr_init();
      97           2 :     SafetyInputs in = make_driving();
      98           2 :     in.engine_running    = false;
      99           2 :     in.vehicle_speed_kmh = 0.0f;
     100           2 :     step_n(&in, 20);
     101           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_ARMED);
     102           2 :     in.engine_running = true;
     103           2 :     safety_mgr_step_50ms(&in);
     104           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     105           2 :     TEST_END();
     106           2 : }
     107             : 
     108           2 : static void test_auto_apply_returns_idle_when_applied(void)
     109             : {
     110           2 :     TEST_BEGIN("AUTO_APPLY_TRIGGERED -> IDLE when Apply Controller is done");
     111           2 :     (void)safety_mgr_init();
     112           2 :     SafetyInputs in = make_driving();
     113           2 :     in.engine_running    = false;
     114           2 :     in.vehicle_speed_kmh = 0.0f;
     115             :     /* Drive to TRIGGERED */
     116          82 :     for (int i = 0; i < 50; ++i) {
     117          82 :         safety_mgr_step_50ms(&in);
     118          82 :         if (safety_mgr_get_state() == SAFETY_AUTO_APPLY_TRIGGERED) break;
     119             :     }
     120           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_TRIGGERED);
     121           2 :     in.current_state = EPB_STATE_APPLIED;
     122           2 :     safety_mgr_step_50ms(&in);
     123           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     124           2 :     ASSERT_TRUE(!safety_mgr_apply_requested());
     125           2 :     TEST_END();
     126           2 : }
     127             : 
     128             : /* ---- Hill-Hold (SWE-009 + SWE-010) ---- */
     129             : 
     130           2 : static void test_hillhold_arms_on_grade_brake_standstill(void)
     131             : {
     132           2 :     TEST_BEGIN("SWE-009: grade >5% + standstill + brake -> HILL_HOLD_ARMED");
     133           2 :     (void)safety_mgr_init();
     134           2 :     SafetyInputs in = make_standstill();
     135           2 :     in.grade_percent = 8.0f;
     136           2 :     safety_mgr_step_50ms(&in);
     137           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ARMED);
     138           2 :     ASSERT_TRUE(!safety_mgr_apply_requested());
     139           2 :     TEST_END();
     140           2 : }
     141             : 
     142           2 : static void test_hillhold_negative_grade_also_triggers(void)
     143             : {
     144           2 :     TEST_BEGIN("Hill-hold also on negative grade (downhill)");
     145           2 :     (void)safety_mgr_init();
     146           2 :     SafetyInputs in = make_standstill();
     147           2 :     in.grade_percent = -7.0f;
     148           2 :     safety_mgr_step_50ms(&in);
     149           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ARMED);
     150           2 :     TEST_END();
     151           2 : }
     152             : 
     153           2 : static void test_hillhold_below_threshold_no_arm(void)
     154             : {
     155           2 :     TEST_BEGIN("Grade < 5% -> no hill-hold");
     156           2 :     (void)safety_mgr_init();
     157           2 :     SafetyInputs in = make_standstill();
     158           2 :     in.grade_percent = 4.0f;
     159           2 :     safety_mgr_step_50ms(&in);
     160           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     161           2 :     TEST_END();
     162           2 : }
     163             : 
     164           2 : static void test_hillhold_active_on_brake_release(void)
     165             : {
     166           2 :     TEST_BEGIN("SWE-010: brake released -> HILL_HOLD_ACTIVE + apply request");
     167           2 :     (void)safety_mgr_init();
     168           2 :     SafetyInputs in = make_standstill();
     169           2 :     in.grade_percent = 8.0f;
     170           2 :     safety_mgr_step_50ms(&in);  /* -> HILL_HOLD_ARMED */
     171           2 :     in.brake_pedal_pressed = false;
     172           2 :     safety_mgr_step_50ms(&in);
     173           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ACTIVE);
     174           2 :     ASSERT_TRUE(safety_mgr_apply_requested());
     175           2 :     TEST_END();
     176           2 : }
     177             : 
     178           2 : static void test_hillhold_active_ends_on_vehicle_rolling(void)
     179             : {
     180           2 :     TEST_BEGIN("HILL_HOLD_ACTIVE ends when vehicle rolls (v > 2 km/h)");
     181           2 :     (void)safety_mgr_init();
     182           2 :     SafetyInputs in = make_standstill();
     183           2 :     in.grade_percent = 8.0f;
     184           2 :     safety_mgr_step_50ms(&in);
     185           2 :     in.brake_pedal_pressed = false;
     186           2 :     safety_mgr_step_50ms(&in);
     187           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ACTIVE);
     188           2 :     in.vehicle_speed_kmh = 3.0f;
     189           2 :     safety_mgr_step_50ms(&in);
     190           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     191           2 :     TEST_END();
     192           2 : }
     193             : 
     194           2 : static void test_hillhold_armed_to_idle_if_grade_drops(void)
     195             : {
     196           2 :     TEST_BEGIN("HILL_HOLD_ARMED -> IDLE when grade < 5%");
     197           2 :     (void)safety_mgr_init();
     198           2 :     SafetyInputs in = make_standstill();
     199           2 :     in.grade_percent = 8.0f;
     200           2 :     safety_mgr_step_50ms(&in);
     201           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ARMED);
     202           2 :     in.grade_percent = 1.0f;
     203           2 :     safety_mgr_step_50ms(&in);
     204           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     205           2 :     TEST_END();
     206           2 : }
     207             : 
     208             : /* ---- Mutually exclusive: never in both modes at once ---- */
     209             : 
     210             : /* ---- Drive-Away-Assist (SWE-011 + SWE-012) ---- */
     211             : 
     212          10 : static SafetyInputs make_applied_at_rest(void)
     213             : {
     214          10 :     SafetyInputs in = {0};
     215          10 :     in.engine_running       = true;
     216          10 :     in.brake_pedal_pressed  = false;
     217          10 :     in.vehicle_speed_kmh    = 0.0f;
     218          10 :     in.grade_percent        = 0.0f;
     219          10 :     in.current_state        = EPB_STATE_APPLIED;
     220          10 :     in.gas_pedal_percent    = 0.0f;
     221          10 :     in.gear_in_drive        = false;
     222          10 :     in.door_closed          = true;
     223          10 :     in.seatbelt_fastened    = true;
     224          10 :     return in;
     225             : }
     226             : 
     227           2 : static void test_drive_away_armed_on_intent(void)
     228             : {
     229           2 :     TEST_BEGIN("SWE-011 + SWE-012: intent + safety -> DRIVE_AWAY + release request");
     230           2 :     (void)safety_mgr_init();
     231           2 :     SafetyInputs in = make_applied_at_rest();
     232           2 :     in.gas_pedal_percent = 25.0f;
     233           2 :     in.gear_in_drive     = true;
     234           2 :     safety_mgr_step_50ms(&in);
     235           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_DRIVE_AWAY);
     236           2 :     ASSERT_TRUE(safety_mgr_release_requested());
     237           2 :     TEST_END();
     238           2 : }
     239             : 
     240           2 : static void test_drive_away_blocked_without_safety(void)
     241             : {
     242           2 :     TEST_BEGIN("SWE-012: door open blocks drive-away");
     243           2 :     (void)safety_mgr_init();
     244           2 :     SafetyInputs in = make_applied_at_rest();
     245           2 :     in.gas_pedal_percent = 25.0f;
     246           2 :     in.gear_in_drive     = true;
     247           2 :     in.door_closed       = false;  /* door open */
     248           2 :     safety_mgr_step_50ms(&in);
     249           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     250           2 :     ASSERT_TRUE(!safety_mgr_release_requested());
     251           2 :     TEST_END();
     252           2 : }
     253             : 
     254           2 : static void test_drive_away_blocked_without_seatbelt(void)
     255             : {
     256           2 :     TEST_BEGIN("SWE-012: seatbelt not fastened blocks drive-away");
     257           2 :     (void)safety_mgr_init();
     258           2 :     SafetyInputs in = make_applied_at_rest();
     259           2 :     in.gas_pedal_percent = 25.0f;
     260           2 :     in.gear_in_drive     = true;
     261           2 :     in.seatbelt_fastened = false;
     262           2 :     safety_mgr_step_50ms(&in);
     263           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     264           2 :     TEST_END();
     265           2 : }
     266             : 
     267           2 : static void test_drive_away_blocked_below_gas_threshold(void)
     268             : {
     269           2 :     TEST_BEGIN("SWE-011: throttle < 10% does not trigger drive-away");
     270           2 :     (void)safety_mgr_init();
     271           2 :     SafetyInputs in = make_applied_at_rest();
     272           2 :     in.gas_pedal_percent = 5.0f;
     273           2 :     in.gear_in_drive     = true;
     274           2 :     safety_mgr_step_50ms(&in);
     275           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     276           2 :     TEST_END();
     277           2 : }
     278             : 
     279           2 : static void test_drive_away_ends_when_released(void)
     280             : {
     281           2 :     TEST_BEGIN("DRIVE_AWAY -> IDLE when Apply Controller has released");
     282           2 :     (void)safety_mgr_init();
     283           2 :     SafetyInputs in = make_applied_at_rest();
     284           2 :     in.gas_pedal_percent = 25.0f;
     285           2 :     in.gear_in_drive     = true;
     286           2 :     safety_mgr_step_50ms(&in);  /* -> DRIVE_AWAY */
     287           2 :     in.current_state = EPB_STATE_RELEASED;
     288           2 :     safety_mgr_step_50ms(&in);
     289           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     290           2 :     ASSERT_TRUE(!safety_mgr_release_requested());
     291           2 :     TEST_END();
     292           2 : }
     293             : 
     294           2 : static void test_already_applied_does_not_arm_auto_apply(void)
     295             : {
     296           2 :     TEST_BEGIN("Already applied: no auto-apply arming");
     297           2 :     (void)safety_mgr_init();
     298           2 :     SafetyInputs in = make_driving();
     299           2 :     in.engine_running    = false;
     300           2 :     in.vehicle_speed_kmh = 0.0f;
     301           2 :     in.current_state     = EPB_STATE_APPLIED;
     302           2 :     safety_mgr_step_50ms(&in);
     303           2 :     ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
     304           2 :     TEST_END();
     305           2 : }
     306             : 
     307           2 : int main(void)
     308             : {
     309           2 :     printf("== test_safety_manager ==\n");
     310           2 :     test_init();
     311           2 :     test_null_input();
     312           2 :     test_auto_apply_armed_on_engine_off();
     313           2 :     test_auto_apply_triggers_after_2s();
     314           2 :     test_auto_apply_aborts_on_engine_on();
     315           2 :     test_auto_apply_returns_idle_when_applied();
     316           2 :     test_hillhold_arms_on_grade_brake_standstill();
     317           2 :     test_hillhold_negative_grade_also_triggers();
     318           2 :     test_hillhold_below_threshold_no_arm();
     319           2 :     test_hillhold_active_on_brake_release();
     320           2 :     test_hillhold_active_ends_on_vehicle_rolling();
     321           2 :     test_hillhold_armed_to_idle_if_grade_drops();
     322           2 :     test_drive_away_armed_on_intent();
     323           2 :     test_drive_away_blocked_without_safety();
     324           2 :     test_drive_away_blocked_without_seatbelt();
     325           2 :     test_drive_away_blocked_below_gas_threshold();
     326           2 :     test_drive_away_ends_when_released();
     327           2 :     test_already_applied_does_not_arm_auto_apply();
     328           2 :     TEST_SUMMARY();
     329             : }

Generated by: LCOV version 1.14