unit_tests/tests/controllers/algo/rotational_idle/test_rotational_idle.cpp
| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | /* | |||
| 2 | * @file test_rotational_idle.cpp | |||
| 3 | * | |||
| 4 | * @date: ene 18, 2026 | |||
| 5 | * @author FDSoftware | |||
| 6 | */ | |||
| 7 | ||||
| 8 | #include "pch.h" | |||
| 9 | #include "rotational_idle.h" | |||
| 10 | ||||
| 11 | class RotationalIdleTest : public ::testing::Test | |||
| 12 | { | |||
| 13 | protected: | |||
| 14 | 5 | void SetUp() override | ||
| 15 | { | |||
| 16 |
1/1✓ Branch 2 taken 5 times.
|
5 | EngineTestHelper eth(engine_type_e::TEST_ENGINE); | |
| 17 | ||||
| 18 | // Reset configuration | |||
| 19 | 5 | engineConfiguration->rotationalIdleController.enabled = false; | ||
| 20 | 5 | engineConfiguration->rotationalIdleController.auto_engage = false; | ||
| 21 | 5 | engineConfiguration->rotationalIdleController.auto_engage_clt_enable = false; | ||
| 22 | 5 | engineConfiguration->rotationalIdleController.max_tps = 5; | ||
| 23 | 5 | engineConfiguration->rotationalIdleController.auto_engage_clt = 80; | ||
| 24 | 10 | } | ||
| 25 | }; | |||
| 26 | ||||
| 27 | 4 | TEST_F(RotationalIdleTest, disabledFeature) | ||
| 28 | { | |||
| 29 |
1/1✓ Branch 2 taken 1 time.
|
1 | EngineTestHelper eth(engine_type_e::TEST_ENGINE); | |
| 30 |
1/1✓ Branch 2 taken 1 time.
|
1 | RotationalIdle rotIdle; | |
| 31 | ||||
| 32 | // Feature disabled | |||
| 33 | 1 | engineConfiguration->rotationalIdleController.enabled = false; | ||
| 34 | ||||
| 35 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldEngageRotationalIdle()); | |
| 36 | 2 | } | ||
| 37 | ||||
| 38 | 4 | TEST_F(RotationalIdleTest, autoEngageByClt) | ||
| 39 | { | |||
| 40 |
1/1✓ Branch 2 taken 1 time.
|
1 | EngineTestHelper eth(engine_type_e::TEST_ENGINE); | |
| 41 |
1/1✓ Branch 2 taken 1 time.
|
1 | RotationalIdle rotIdle; | |
| 42 | ||||
| 43 | 1 | engineConfiguration->rotationalIdleController.enabled = true; | ||
| 44 | 1 | engineConfiguration->rotationalIdleController.auto_engage_clt_enable = true; | ||
| 45 | 1 | engineConfiguration->rotationalIdleController.auto_engage_clt = 80; | ||
| 46 | ||||
| 47 | // CLT below threshold - should not engage | |||
| 48 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::Clt, 70); | |
| 49 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldEngageRotationalIdle()); | |
| 50 | ||||
| 51 | // CLT above threshold - should engage | |||
| 52 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::Clt, 85); | |
| 53 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldEngageRotationalIdle()); | |
| 54 | 2 | } | ||
| 55 | ||||
| 56 | 4 | TEST_F(RotationalIdleTest, autoEngageByTps) | ||
| 57 | { | |||
| 58 |
1/1✓ Branch 2 taken 1 time.
|
1 | EngineTestHelper eth(engine_type_e::TEST_ENGINE); | |
| 59 |
1/1✓ Branch 2 taken 1 time.
|
1 | RotationalIdle rotIdle; | |
| 60 | ||||
| 61 | 1 | engineConfiguration->rotationalIdleController.enabled = true; | ||
| 62 | 1 | engineConfiguration->rotationalIdleController.auto_engage = true; | ||
| 63 | 1 | engineConfiguration->rotationalIdleController.max_tps = 5; | ||
| 64 | ||||
| 65 | // TPS above threshold - should not engage | |||
| 66 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::DriverThrottleIntent, 10); | |
| 67 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldEngageRotationalIdle()); | |
| 68 | ||||
| 69 | // TPS below threshold - should engage | |||
| 70 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::DriverThrottleIntent, 3); | |
| 71 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldEngageRotationalIdle()); | |
| 72 | ||||
| 73 | // TPS at threshold - should engage | |||
| 74 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::DriverThrottleIntent, 5); | |
| 75 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldEngageRotationalIdle()); | |
| 76 | 2 | } | ||
| 77 | ||||
| 78 | 4 | TEST_F(RotationalIdleTest, shouldSkipSpark) | ||
| 79 | { | |||
| 80 |
1/1✓ Branch 2 taken 1 time.
|
1 | EngineTestHelper eth(engine_type_e::TEST_ENGINE); | |
| 81 |
1/1✓ Branch 2 taken 1 time.
|
1 | RotationalIdle rotIdle; | |
| 82 | ||||
| 83 | // Should not skip when feature is disabled | |||
| 84 | 1 | engineConfiguration->rotationalIdleController.enabled = false; | ||
| 85 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 86 | ||||
| 87 | // Enable rotational idle | |||
| 88 | 1 | engineConfiguration->rotationalIdleController.enabled = true; | ||
| 89 | 1 | engineConfiguration->rotationalIdleController.auto_engage = true; | ||
| 90 | 1 | engineConfiguration->rotationalIdleController.max_tps = 5; | ||
| 91 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::DriverThrottleIntent, 3); | |
| 92 | ||||
| 93 | // Should not skip when acc_max is 0 (disabled) | |||
| 94 | 1 | engineConfiguration->rotationalIdleController.cut_mode = RotationalCutMode::Spark; | ||
| 95 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 96 | ||||
| 97 | // Test cut_mode = 1 (Fuel only) - should never skip spark | |||
| 98 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_max = 2; | ||
| 99 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_adder = 1; | ||
| 100 | 1 | engineConfiguration->rotationalIdleController.cut_mode = RotationalCutMode::Fuel; | ||
| 101 | 1 | engine->engineState.globalSparkCounter = 1; | ||
| 102 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); // Fuel mode never skips spark | |
| 103 | ||||
| 104 | // Test cut_mode = 0 (Spark mode) - should skip spark based on pattern | |||
| 105 | 1 | engineConfiguration->rotationalIdleController.cut_mode = RotationalCutMode::Spark; | ||
| 106 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=1: (1 % 2) + 1 = 2 >= 2 | |
| 107 | ||||
| 108 | // Test cut_mode = 2 (Both mode) - should skip spark based on pattern | |||
| 109 | 1 | engineConfiguration->rotationalIdleController.cut_mode = RotationalCutMode::Both; | ||
| 110 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=1: (1 % 2) + 1 = 2 >= 2 | |
| 111 | ||||
| 112 | // Configure skip pattern: test with various counter values (cut_mode = 0) | |||
| 113 | 1 | engineConfiguration->rotationalIdleController.cut_mode = RotationalCutMode::Spark; | ||
| 114 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_max = 2; | ||
| 115 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_adder = 0; | ||
| 116 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_offset = 0; | ||
| 117 | ||||
| 118 | // Set global spark counter to test pattern | |||
| 119 | 1 | engine->engineState.globalSparkCounter = 0; | ||
| 120 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=0: (0 % 2) + 0 = 0 < 2 | |
| 121 | ||||
| 122 | 1 | engine->engineState.globalSparkCounter = 1; | ||
| 123 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=1: (1 % 2) + 0 = 1 < 2 | |
| 124 | ||||
| 125 | 1 | engine->engineState.globalSparkCounter = 2; | ||
| 126 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=2: (2 % 2) + 0 = 0 < 2 | |
| 127 | ||||
| 128 | // Test with adder to create skip pattern | |||
| 129 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_adder = 1; | ||
| 130 | ||||
| 131 | 1 | engine->engineState.globalSparkCounter = 0; | ||
| 132 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=0: (0 % 2) + 1 = 1 < 2 | |
| 133 | ||||
| 134 | 1 | engine->engineState.globalSparkCounter = 1; | ||
| 135 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=1: (1 % 2) + 1 = 2 >= 2 | |
| 136 | ||||
| 137 | 1 | engine->engineState.globalSparkCounter = 2; | ||
| 138 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); // counter=2: (2 % 2) + 1 = 1 < 2 | |
| 139 | ||||
| 140 | 2 | } | ||
| 141 | ||||
| 142 | 4 | TEST_F(RotationalIdleTest, multipleAccumulators) | ||
| 143 | { | |||
| 144 |
1/1✓ Branch 2 taken 1 time.
|
1 | EngineTestHelper eth(engine_type_e::TEST_ENGINE); | |
| 145 |
1/1✓ Branch 2 taken 1 time.
|
1 | RotationalIdle rotIdle; | |
| 146 | ||||
| 147 | 1 | engineConfiguration->rotationalIdleController.enabled = true; | ||
| 148 | 1 | engineConfiguration->rotationalIdleController.auto_engage = true; | ||
| 149 | 1 | engineConfiguration->rotationalIdleController.max_tps = 5; | ||
| 150 |
1/1✓ Branch 1 taken 1 time.
|
1 | Sensor::setMockValue(SensorType::DriverThrottleIntent, 3); | |
| 151 | 1 | engineConfiguration->rotationalIdleController.cut_mode = RotationalCutMode::Spark; | ||
| 152 | ||||
| 153 | // Accumulator 0: skip every 2nd (1, 3, 5...) | |||
| 154 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_max = 2; | ||
| 155 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_adder = 1; | ||
| 156 | 1 | engineConfiguration->rotationalIdleController.accumulators[0].acc_offset = 0; | ||
| 157 | ||||
| 158 | // Accumulator 1: skip every 3rd (2, 5, 8...) | |||
| 159 | 1 | engineConfiguration->rotationalIdleController.accumulators[1].acc_max = 3; | ||
| 160 | 1 | engineConfiguration->rotationalIdleController.accumulators[1].acc_adder = 1; | ||
| 161 | 1 | engineConfiguration->rotationalIdleController.accumulators[1].acc_offset = 0; | ||
| 162 | ||||
| 163 | // globalSparkCounter = 0 | |||
| 164 | // Acc 0: (0%2)+1 = 1 < 2 (Fire) | |||
| 165 | // Acc 1: (0%3)+1 = 1 < 3 (Fire) | |||
| 166 | 1 | engine->engineState.globalSparkCounter = 0; | ||
| 167 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 168 | ||||
| 169 | // globalSparkCounter = 1 | |||
| 170 | // Acc 0: (1%2)+1 = 2 >= 2 (Skip) | |||
| 171 | // Acc 1: (1%3)+1 = 2 < 3 (Fire) | |||
| 172 | // Result: Skip | |||
| 173 | 1 | engine->engineState.globalSparkCounter = 1; | ||
| 174 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 175 | ||||
| 176 | // globalSparkCounter = 2 | |||
| 177 | // Acc 0: (2%2)+1 = 1 < 2 (Fire) | |||
| 178 | // Acc 1: (2%3)+1 = 3 >= 3 (Skip) | |||
| 179 | // Result: Skip | |||
| 180 | 1 | engine->engineState.globalSparkCounter = 2; | ||
| 181 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 182 | ||||
| 183 | // globalSparkCounter = 3 | |||
| 184 | // Acc 0: (3%2)+1 = 2 >= 2 (Skip) | |||
| 185 | // Acc 1: (3%3)+1 = 1 < 3 (Fire) | |||
| 186 | // Result: Skip | |||
| 187 | 1 | engine->engineState.globalSparkCounter = 3; | ||
| 188 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_TRUE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 189 | ||||
| 190 | // globalSparkCounter = 4 | |||
| 191 | // Acc 0: (4%2)+1 = 1 < 2 (Fire) | |||
| 192 | // Acc 1: (4%3)+1 = 2 < 3 (Fire) | |||
| 193 | // Result: Fire | |||
| 194 | 1 | engine->engineState.globalSparkCounter = 4; | ||
| 195 |
2/7✓ Branch 3 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 time.
✗ Branch 12 not taken.
✗ Branch 17 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
|
1 | EXPECT_FALSE(rotIdle.shouldSkipSparkRotationalIdle()); | |
| 196 | 2 | } | ||
| 197 |