Line data Source code
1 : /** 2 : * @file test_apply_controller.c 3 : * @brief Unit tests for the Apply Controller (ASIL-D core). 4 : * 5 : * @reqs SWE-001 SWE-002 SWE-003 SWE-004 SWE-005 6 : * @arch SWA-002 7 : */ 8 : #include "../unit_test_framework.h" 9 : #include "../../src/apply_controller.h" 10 : #include "../../src/actuator_driver.h" 11 : 12 : TestStats g_test_stats = {0, 0}; 13 : 14 20 : static ApplyInputs make_idle_inputs(void) 15 : { 16 20 : ApplyInputs in = {0}; 17 20 : in.sw_state = SWITCH_NEUTRAL; 18 20 : in.standstill = true; 19 20 : in.engine_running = true; 20 20 : in.brake_pedal_pressed = true; 21 20 : in.gear_engaged = true; 22 20 : in.safety_apply_request = false; 23 20 : in.left_force_n = 0; 24 20 : in.right_force_n = 0; 25 20 : return in; 26 : } 27 : 28 4 : static void apply_full_cycle_until_state(ApplyInputs* in, EpbState target, int max_steps) 29 : { 30 120 : for (int i = 0; i < max_steps; ++i) { 31 120 : apply_ctrl_step_50ms(in); 32 120 : if (apply_ctrl_get_state() == target) { 33 4 : return; 34 : } 35 : } 36 : } 37 : 38 2 : static void test_init(void) 39 : { 40 2 : TEST_BEGIN("init -> RELEASED"); 41 2 : (void)actuator_init(); 42 2 : ASSERT_EQ(apply_ctrl_init(), EPB_OK); 43 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED); 44 2 : TEST_END(); 45 2 : } 46 : 47 2 : static void test_null_input(void) 48 : { 49 2 : TEST_BEGIN("step_50ms(NULL) -> last_error=EINVAL"); 50 2 : (void)actuator_init(); 51 2 : (void)apply_ctrl_init(); 52 2 : apply_ctrl_step_50ms(NULL); 53 2 : ASSERT_EQ(apply_ctrl_last_error(), EPB_EINVAL); 54 2 : TEST_END(); 55 2 : } 56 : 57 2 : static void test_apply_request_starts_applying(void) 58 : { 59 2 : TEST_BEGIN("Apply request at standstill -> APPLYING"); 60 2 : (void)actuator_init(); 61 2 : (void)apply_ctrl_init(); 62 2 : ApplyInputs in = make_idle_inputs(); 63 2 : in.sw_state = SWITCH_APPLY; 64 2 : apply_ctrl_step_50ms(&in); 65 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLYING); 66 2 : TEST_END(); 67 2 : } 68 : 69 2 : static void test_no_apply_without_standstill(void) 70 : { 71 2 : TEST_BEGIN("Apply request without standstill is ignored"); 72 2 : (void)actuator_init(); 73 2 : (void)apply_ctrl_init(); 74 2 : ApplyInputs in = make_idle_inputs(); 75 2 : in.standstill = false; 76 2 : in.sw_state = SWITCH_APPLY; 77 2 : apply_ctrl_step_50ms(&in); 78 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED); 79 2 : TEST_END(); 80 2 : } 81 : 82 2 : static void test_applying_reaches_applied_on_target_force(void) 83 : { 84 2 : TEST_BEGIN("APPLYING -> APPLIED when both clamping forces >= target"); 85 2 : (void)actuator_init(); 86 2 : (void)apply_ctrl_init(); 87 2 : ApplyInputs in = make_idle_inputs(); 88 2 : in.sw_state = SWITCH_APPLY; 89 2 : apply_ctrl_step_50ms(&in); /* -> APPLYING */ 90 2 : in.sw_state = SWITCH_NEUTRAL; 91 2 : in.left_force_n = 12000; 92 2 : in.right_force_n = 12000; 93 2 : apply_ctrl_step_50ms(&in); 94 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLIED); 95 2 : TEST_END(); 96 2 : } 97 : 98 2 : static void test_applying_timeout_to_error(void) 99 : { 100 2 : TEST_BEGIN("APPLYING timeout (>30 steps) -> ERROR"); 101 2 : (void)actuator_init(); 102 2 : (void)apply_ctrl_init(); 103 2 : ApplyInputs in = make_idle_inputs(); 104 2 : in.sw_state = SWITCH_APPLY; 105 2 : apply_ctrl_step_50ms(&in); /* -> APPLYING */ 106 2 : in.sw_state = SWITCH_NEUTRAL; 107 : /* Clamping forces stay at 0 -> timeout after 30 steps */ 108 2 : apply_full_cycle_until_state(&in, EPB_STATE_ERROR, 35); 109 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_ERROR); 110 2 : ASSERT_EQ(apply_ctrl_last_error(), EPB_ETIMEOUT); 111 2 : TEST_END(); 112 2 : } 113 : 114 2 : static void test_applied_holds_force(void) 115 : { 116 2 : TEST_BEGIN("APPLIED maintains clamping force (SWE-001)"); 117 2 : (void)actuator_init(); 118 2 : (void)apply_ctrl_init(); 119 2 : ApplyInputs in = make_idle_inputs(); 120 2 : in.sw_state = SWITCH_APPLY; 121 2 : apply_ctrl_step_50ms(&in); 122 2 : in.sw_state = SWITCH_NEUTRAL; 123 2 : in.left_force_n = 12000; 124 2 : in.right_force_n = 12000; 125 2 : apply_ctrl_step_50ms(&in); /* -> APPLIED */ 126 : 127 : /* Clamping force drops to 10000 (below tolerance threshold 10800) */ 128 2 : in.left_force_n = 10000; 129 2 : in.right_force_n = 10000; 130 2 : apply_ctrl_step_50ms(&in); 131 : /* Apply controller must re-apply -> APPLYING */ 132 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLYING); 133 2 : TEST_END(); 134 2 : } 135 : 136 2 : static void test_release_requires_preconditions(void) 137 : { 138 2 : TEST_BEGIN("Release without engine is rejected"); 139 2 : (void)actuator_init(); 140 2 : (void)apply_ctrl_init(); 141 2 : ApplyInputs in = make_idle_inputs(); 142 2 : in.sw_state = SWITCH_APPLY; 143 2 : apply_ctrl_step_50ms(&in); 144 2 : in.sw_state = SWITCH_NEUTRAL; 145 2 : in.left_force_n = 12000; 146 2 : in.right_force_n = 12000; 147 2 : apply_ctrl_step_50ms(&in); /* -> APPLIED */ 148 : 149 2 : in.sw_state = SWITCH_RELEASE; 150 2 : in.engine_running = false; 151 2 : apply_ctrl_step_50ms(&in); 152 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLIED); 153 2 : TEST_END(); 154 2 : } 155 : 156 2 : static void test_release_with_preconditions(void) 157 : { 158 2 : TEST_BEGIN("Release with all preconditions -> RELEASING -> RELEASED"); 159 2 : (void)actuator_init(); 160 2 : (void)apply_ctrl_init(); 161 2 : ApplyInputs in = make_idle_inputs(); 162 2 : in.sw_state = SWITCH_APPLY; 163 2 : apply_ctrl_step_50ms(&in); 164 2 : in.sw_state = SWITCH_NEUTRAL; 165 2 : in.left_force_n = 12000; 166 2 : in.right_force_n = 12000; 167 2 : apply_ctrl_step_50ms(&in); /* APPLIED */ 168 : 169 2 : in.sw_state = SWITCH_RELEASE; 170 2 : apply_ctrl_step_50ms(&in); 171 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASING); 172 : 173 2 : in.sw_state = SWITCH_NEUTRAL; 174 2 : in.left_force_n = 0; 175 2 : in.right_force_n = 0; 176 2 : apply_ctrl_step_50ms(&in); 177 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED); 178 2 : TEST_END(); 179 2 : } 180 : 181 2 : static void test_safety_apply_request(void) 182 : { 183 2 : TEST_BEGIN("Safety Manager apply request (Hill-Hold/Auto-Apply)"); 184 2 : (void)actuator_init(); 185 2 : (void)apply_ctrl_init(); 186 2 : ApplyInputs in = make_idle_inputs(); 187 2 : in.sw_state = SWITCH_NEUTRAL; 188 2 : in.safety_apply_request = true; 189 2 : apply_ctrl_step_50ms(&in); 190 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLYING); 191 2 : TEST_END(); 192 2 : } 193 : 194 2 : static void test_watchdog_alive_counter(void) 195 : { 196 2 : TEST_BEGIN("Step counter increases monotonically (SWE-002 watchdog alive)"); 197 2 : (void)actuator_init(); 198 2 : (void)apply_ctrl_init(); 199 2 : ApplyInputs in = make_idle_inputs(); 200 2 : uint32_t before = apply_ctrl_get_step_count(); 201 2 : apply_ctrl_step_50ms(&in); 202 2 : apply_ctrl_step_50ms(&in); 203 2 : apply_ctrl_step_50ms(&in); 204 2 : ASSERT_EQ(apply_ctrl_get_step_count(), before + 3); 205 2 : TEST_END(); 206 2 : } 207 : 208 2 : static void test_error_state_recoverable(void) 209 : { 210 2 : TEST_BEGIN("ERROR -> RELEASED on neutral switch"); 211 2 : (void)actuator_init(); 212 2 : (void)apply_ctrl_init(); 213 2 : ApplyInputs in = make_idle_inputs(); 214 2 : in.sw_state = SWITCH_APPLY; 215 2 : apply_ctrl_step_50ms(&in); 216 2 : in.sw_state = SWITCH_NEUTRAL; 217 2 : apply_full_cycle_until_state(&in, EPB_STATE_ERROR, 35); 218 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_ERROR); 219 2 : apply_ctrl_step_50ms(&in); 220 2 : ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED); 221 2 : TEST_END(); 222 2 : } 223 : 224 2 : int main(void) 225 : { 226 2 : printf("== test_apply_controller ==\n"); 227 2 : test_init(); 228 2 : test_null_input(); 229 2 : test_apply_request_starts_applying(); 230 2 : test_no_apply_without_standstill(); 231 2 : test_applying_reaches_applied_on_target_force(); 232 2 : test_applying_timeout_to_error(); 233 2 : test_applied_holds_force(); 234 2 : test_release_requires_preconditions(); 235 2 : test_release_with_preconditions(); 236 2 : test_safety_apply_request(); 237 2 : test_watchdog_alive_counter(); 238 2 : test_error_state_recoverable(); 239 2 : TEST_SUMMARY(); 240 : }