Files
demo-epb/tests/unit/test_safety_manager.c
T
Stefan Lohmaier fb2c083551
Validate / build-test (macos-latest) (push) Failing after 3s
Validate / build-test (windows-latest) (push) Failing after 15s
Validate / build-test (ubuntu-latest) (push) Successful in 17s
Validate / reports (push) Successful in 50s
Release / release (push) Successful in 50s
feat(i18n): full English translation of demo-epb
Phase 2 of the English translation:

Word documents (filled, EPB-specific):
- 8 plans (PID, PM, QA, SWE, Test, Project Manual, CM, RM)
- 6 safety docs (HARA, Safety Case, FMEDA, MISRA Compliance,
  Verification Report, Tool Qualification Cppcheck)
- 2 manuals (User, Service)
- 3 audit artefacts (Review minutes, NC-001, MISRA-REC-001)
- All regenerated via pandoc from English markdown sources

Code, tests, headers:
- All file headers, struct comments, function docstrings in English
- All test names (TEST_BEGIN strings) translated
- Inline comments translated
- 46 tests still green after translation

CI workflows:
- All step names in English
- Step descriptions, comments, release notes template in English

README.md fully rewritten in English with proper guided tour.

Phase 3 (still pending): dev-process repo templates + toolstack/setup docs.
2026-05-12 03:37:51 -07:00

330 lines
10 KiB
C

/**
* @file test_safety_manager.c
* @brief Unit tests for the Safety Manager (ASIL-D).
*
* @reqs SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012
* @arch SWA-001
*/
#include "../unit_test_framework.h"
#include "../../src/safety_manager.h"
TestStats g_test_stats = {0, 0};
static SafetyInputs make_driving(void)
{
SafetyInputs in = {0};
in.engine_running = true;
in.brake_pedal_pressed = false;
in.vehicle_speed_kmh = 50.0f;
in.grade_percent = 0.0f;
in.current_state = EPB_STATE_RELEASED;
return in;
}
static SafetyInputs make_standstill(void)
{
SafetyInputs in = make_driving();
in.vehicle_speed_kmh = 0.0f;
in.brake_pedal_pressed = true;
return in;
}
static void step_n(SafetyInputs* in, int n)
{
for (int i = 0; i < n; ++i) {
safety_mgr_step_50ms(in);
}
}
static void test_init(void)
{
TEST_BEGIN("init -> IDLE, no apply request");
ASSERT_EQ(safety_mgr_init(), EPB_OK);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
ASSERT_TRUE(!safety_mgr_apply_requested());
TEST_END();
}
static void test_null_input(void)
{
TEST_BEGIN("NULL input is a no-op");
(void)safety_mgr_init();
safety_mgr_step_50ms(NULL);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
/* ---- Auto-Apply (SWE-007 + SWE-008) ---- */
static void test_auto_apply_armed_on_engine_off(void)
{
TEST_BEGIN("SWE-007: engine off + standstill -> AUTO_APPLY_ARMED");
(void)safety_mgr_init();
SafetyInputs in = make_driving();
in.engine_running = false;
in.vehicle_speed_kmh = 0.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_ARMED);
ASSERT_TRUE(!safety_mgr_apply_requested());
TEST_END();
}
static void test_auto_apply_triggers_after_2s(void)
{
TEST_BEGIN("SWE-008: after ~2s -> TRIGGERED + apply request");
(void)safety_mgr_init();
SafetyInputs in = make_driving();
in.engine_running = false;
in.vehicle_speed_kmh = 0.0f;
/* Halfway: still armed, no request */
step_n(&in, 20);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_ARMED);
ASSERT_TRUE(!safety_mgr_apply_requested());
/* Run up to TRIGGERED within at most 25 more steps (~2 s leeway) */
for (int i = 0; i < 25; ++i) {
safety_mgr_step_50ms(&in);
if (safety_mgr_get_state() == SAFETY_AUTO_APPLY_TRIGGERED) break;
}
ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_TRIGGERED);
ASSERT_TRUE(safety_mgr_apply_requested());
TEST_END();
}
static void test_auto_apply_aborts_on_engine_on(void)
{
TEST_BEGIN("Auto-Apply aborts when engine comes back on");
(void)safety_mgr_init();
SafetyInputs in = make_driving();
in.engine_running = false;
in.vehicle_speed_kmh = 0.0f;
step_n(&in, 20);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_ARMED);
in.engine_running = true;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
static void test_auto_apply_returns_idle_when_applied(void)
{
TEST_BEGIN("AUTO_APPLY_TRIGGERED -> IDLE when Apply Controller is done");
(void)safety_mgr_init();
SafetyInputs in = make_driving();
in.engine_running = false;
in.vehicle_speed_kmh = 0.0f;
/* Drive to TRIGGERED */
for (int i = 0; i < 50; ++i) {
safety_mgr_step_50ms(&in);
if (safety_mgr_get_state() == SAFETY_AUTO_APPLY_TRIGGERED) break;
}
ASSERT_EQ(safety_mgr_get_state(), SAFETY_AUTO_APPLY_TRIGGERED);
in.current_state = EPB_STATE_APPLIED;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
ASSERT_TRUE(!safety_mgr_apply_requested());
TEST_END();
}
/* ---- Hill-Hold (SWE-009 + SWE-010) ---- */
static void test_hillhold_arms_on_grade_brake_standstill(void)
{
TEST_BEGIN("SWE-009: grade >5% + standstill + brake -> HILL_HOLD_ARMED");
(void)safety_mgr_init();
SafetyInputs in = make_standstill();
in.grade_percent = 8.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ARMED);
ASSERT_TRUE(!safety_mgr_apply_requested());
TEST_END();
}
static void test_hillhold_negative_grade_also_triggers(void)
{
TEST_BEGIN("Hill-hold also on negative grade (downhill)");
(void)safety_mgr_init();
SafetyInputs in = make_standstill();
in.grade_percent = -7.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ARMED);
TEST_END();
}
static void test_hillhold_below_threshold_no_arm(void)
{
TEST_BEGIN("Grade < 5% -> no hill-hold");
(void)safety_mgr_init();
SafetyInputs in = make_standstill();
in.grade_percent = 4.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
static void test_hillhold_active_on_brake_release(void)
{
TEST_BEGIN("SWE-010: brake released -> HILL_HOLD_ACTIVE + apply request");
(void)safety_mgr_init();
SafetyInputs in = make_standstill();
in.grade_percent = 8.0f;
safety_mgr_step_50ms(&in); /* -> HILL_HOLD_ARMED */
in.brake_pedal_pressed = false;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ACTIVE);
ASSERT_TRUE(safety_mgr_apply_requested());
TEST_END();
}
static void test_hillhold_active_ends_on_vehicle_rolling(void)
{
TEST_BEGIN("HILL_HOLD_ACTIVE ends when vehicle rolls (v > 2 km/h)");
(void)safety_mgr_init();
SafetyInputs in = make_standstill();
in.grade_percent = 8.0f;
safety_mgr_step_50ms(&in);
in.brake_pedal_pressed = false;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ACTIVE);
in.vehicle_speed_kmh = 3.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
static void test_hillhold_armed_to_idle_if_grade_drops(void)
{
TEST_BEGIN("HILL_HOLD_ARMED -> IDLE when grade < 5%");
(void)safety_mgr_init();
SafetyInputs in = make_standstill();
in.grade_percent = 8.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_HILL_HOLD_ARMED);
in.grade_percent = 1.0f;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
/* ---- Mutually exclusive: nicht in beiden Modi gleichzeitig ---- */
/* ---- Drive-Away-Assist (SWE-011 + SWE-012) ---- */
static SafetyInputs make_applied_at_rest(void)
{
SafetyInputs in = {0};
in.engine_running = true;
in.brake_pedal_pressed = false;
in.vehicle_speed_kmh = 0.0f;
in.grade_percent = 0.0f;
in.current_state = EPB_STATE_APPLIED;
in.gas_pedal_percent = 0.0f;
in.gear_in_drive = false;
in.door_closed = true;
in.seatbelt_fastened = true;
return in;
}
static void test_drive_away_armed_on_intent(void)
{
TEST_BEGIN("SWE-011 + SWE-012: intent + safety -> DRIVE_AWAY + release request");
(void)safety_mgr_init();
SafetyInputs in = make_applied_at_rest();
in.gas_pedal_percent = 25.0f;
in.gear_in_drive = true;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_DRIVE_AWAY);
ASSERT_TRUE(safety_mgr_release_requested());
TEST_END();
}
static void test_drive_away_blocked_without_safety(void)
{
TEST_BEGIN("SWE-012: door open blocks drive-away");
(void)safety_mgr_init();
SafetyInputs in = make_applied_at_rest();
in.gas_pedal_percent = 25.0f;
in.gear_in_drive = true;
in.door_closed = false; /* door open */
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
ASSERT_TRUE(!safety_mgr_release_requested());
TEST_END();
}
static void test_drive_away_blocked_without_seatbelt(void)
{
TEST_BEGIN("SWE-012: seatbelt not fastened blocks drive-away");
(void)safety_mgr_init();
SafetyInputs in = make_applied_at_rest();
in.gas_pedal_percent = 25.0f;
in.gear_in_drive = true;
in.seatbelt_fastened = false;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
static void test_drive_away_blocked_below_gas_threshold(void)
{
TEST_BEGIN("SWE-011: throttle < 10% does not trigger drive-away");
(void)safety_mgr_init();
SafetyInputs in = make_applied_at_rest();
in.gas_pedal_percent = 5.0f;
in.gear_in_drive = true;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
static void test_drive_away_ends_when_released(void)
{
TEST_BEGIN("DRIVE_AWAY -> IDLE when Apply Controller has released");
(void)safety_mgr_init();
SafetyInputs in = make_applied_at_rest();
in.gas_pedal_percent = 25.0f;
in.gear_in_drive = true;
safety_mgr_step_50ms(&in); /* -> DRIVE_AWAY */
in.current_state = EPB_STATE_RELEASED;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
ASSERT_TRUE(!safety_mgr_release_requested());
TEST_END();
}
static void test_already_applied_does_not_arm_auto_apply(void)
{
TEST_BEGIN("Already applied: no auto-apply arming");
(void)safety_mgr_init();
SafetyInputs in = make_driving();
in.engine_running = false;
in.vehicle_speed_kmh = 0.0f;
in.current_state = EPB_STATE_APPLIED;
safety_mgr_step_50ms(&in);
ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE);
TEST_END();
}
int main(void)
{
printf("== test_safety_manager ==\n");
test_init();
test_null_input();
test_auto_apply_armed_on_engine_off();
test_auto_apply_triggers_after_2s();
test_auto_apply_aborts_on_engine_on();
test_auto_apply_returns_idle_when_applied();
test_hillhold_arms_on_grade_brake_standstill();
test_hillhold_negative_grade_also_triggers();
test_hillhold_below_threshold_no_arm();
test_hillhold_active_on_brake_release();
test_hillhold_active_ends_on_vehicle_rolling();
test_hillhold_armed_to_idle_if_grade_drops();
test_drive_away_armed_on_intent();
test_drive_away_blocked_without_safety();
test_drive_away_blocked_without_seatbelt();
test_drive_away_blocked_below_gas_threshold();
test_drive_away_ends_when_released();
test_already_applied_does_not_arm_auto_apply();
TEST_SUMMARY();
}