#include #include #include "PatternEngine.h" using namespace timbre_core; void setUp(void) {} void tearDown(void) {} struct OutputEvent { RelayId relay; bool on; }; static void test_absolute_deadlines_and_final_off(void) { std::vector events; PatternEngine engine([&events](RelayId relay, bool on) { events.push_back(OutputEvent{relay, on}); }); const Pattern pattern{PatternId::A, {2, 1, 1, 1}}; TEST_ASSERT_TRUE(engine.start(pattern, 1000)); TEST_ASSERT_TRUE(engine.running()); TEST_ASSERT_TRUE(engine.outputOn()); TEST_ASSERT_EQUAL_UINT64(3000, engine.deadlineMs()); TEST_ASSERT_EQUAL_size_t(1, events.size()); TEST_ASSERT_TRUE(events[0].on); TEST_ASSERT_EQUAL_UINT8(static_cast(RelayId::Main), static_cast(events[0].relay)); TEST_ASSERT_FALSE(engine.update(2999)); TEST_ASSERT_TRUE(engine.outputOn()); TEST_ASSERT_TRUE(engine.update(3000)); TEST_ASSERT_FALSE(engine.outputOn()); TEST_ASSERT_EQUAL_UINT8(1, engine.activePhase()); TEST_ASSERT_EQUAL_UINT64(4000, engine.deadlineMs()); TEST_ASSERT_TRUE(engine.update(5000)); // At 5000 ms the third phase has also reached its boundary; the // absolute-deadline engine therefore advances to the final OFF phase. TEST_ASSERT_FALSE(engine.outputOn()); TEST_ASSERT_EQUAL_UINT8(3, engine.activePhase()); TEST_ASSERT_EQUAL_UINT64(6000, engine.deadlineMs()); TEST_ASSERT_TRUE(engine.update(7000)); TEST_ASSERT_FALSE(engine.running()); TEST_ASSERT_FALSE(engine.outputOn()); TEST_ASSERT_EQUAL_UINT8(PatternEngine::kNoPhase, engine.activePhase()); TEST_ASSERT_EQUAL_size_t(4, events.size()); TEST_ASSERT_FALSE(events.back().on); } static void test_clock_regression_turns_output_off(void) { std::vector events; PatternEngine engine([&events](RelayId relay, bool on) { events.push_back({relay, on}); }); const Pattern pattern{PatternId::A, {2, 1}}; TEST_ASSERT_TRUE(engine.start(pattern, 1000)); TEST_ASSERT_TRUE(engine.outputOn()); TEST_ASSERT_TRUE(engine.update(500)); TEST_ASSERT_FALSE(engine.running()); TEST_ASSERT_FALSE(engine.outputOn()); TEST_ASSERT_FALSE(events.empty()); TEST_ASSERT_FALSE(events.back().on); } static void test_late_poll_does_not_accumulate_or_leave_on(void) { std::vector events; PatternEngine engine([&events](RelayId, bool on) { events.push_back({RelayId::Main, on}); }); const Pattern pattern{PatternId::B, {1, 1, 1, 1, 1, 1}}; TEST_ASSERT_TRUE(engine.start(pattern, 10)); TEST_ASSERT_TRUE(engine.update(1000000)); TEST_ASSERT_FALSE(engine.running()); TEST_ASSERT_FALSE(engine.outputOn()); TEST_ASSERT_EQUAL_UINT64(0, engine.deadlineMs()); TEST_ASSERT_FALSE(events.empty()); TEST_ASSERT_FALSE(events.back().on); } static void test_fixed_queue_capacity_and_absolute_request_time(void) { std::vector events; PatternEngine engine([&events](RelayId relay, bool on) { events.push_back({relay, on}); }); const Pattern pattern{PatternId::C, {1, 1}}; for (std::size_t i = 0; i < 8; ++i) { TEST_ASSERT_TRUE(engine.enqueue(pattern, 1000)); } TEST_ASSERT_EQUAL_size_t(8, engine.queueSize()); TEST_ASSERT_FALSE(engine.enqueue(pattern, 1000)); TEST_ASSERT_EQUAL_size_t(8, engine.queueSize()); TEST_ASSERT_FALSE(engine.running()); engine.update(999); TEST_ASSERT_FALSE(engine.running()); TEST_ASSERT_EQUAL_size_t(8, engine.queueSize()); engine.update(1000); TEST_ASSERT_TRUE(engine.running()); TEST_ASSERT_EQUAL_size_t(7, engine.queueSize()); TEST_ASSERT_TRUE(engine.outputOn()); TEST_ASSERT_EQUAL_UINT64(2000, engine.deadlineMs()); } static void test_invalid_pattern_id_and_zero_phase_are_safe(void) { std::vector events; PatternEngine engine([&events](RelayId relay, bool on) { events.push_back({relay, on}); }); TEST_ASSERT_FALSE(engine.start(static_cast(99), 0)); TEST_ASSERT_FALSE(engine.running()); TEST_ASSERT_TRUE(events.empty()); const Pattern silent{PatternId::A, {0, 0}}; TEST_ASSERT_TRUE(engine.start(silent, 0)); TEST_ASSERT_FALSE(engine.running()); TEST_ASSERT_FALSE(engine.outputOn()); TEST_ASSERT_EQUAL_size_t(0, events.size()); } static void test_reserved_gpio_is_never_driven(void) { std::vector events; PatternEngine engine([&events](RelayId relay, bool on) { events.push_back({relay, on}); }); const Pattern pattern{PatternId::A, {1, 1}}; TEST_ASSERT_TRUE(engine.start(pattern, 0)); TEST_ASSERT_TRUE(engine.update(2000)); TEST_ASSERT_FALSE(engine.reservedRelayDriven()); TEST_ASSERT_EQUAL_UINT8(static_cast(RelayId::Main), static_cast(engine.lastOutputRelay())); for (const OutputEvent& event : events) { TEST_ASSERT_EQUAL_UINT8(static_cast(RelayId::Main), static_cast(event.relay)); } } int main(int, char**) { UNITY_BEGIN(); RUN_TEST(test_absolute_deadlines_and_final_off); RUN_TEST(test_clock_regression_turns_output_off); RUN_TEST(test_late_poll_does_not_accumulate_or_leave_on); RUN_TEST(test_fixed_queue_capacity_and_absolute_request_time); RUN_TEST(test_invalid_pattern_id_and_zero_phase_are_safe); RUN_TEST(test_reserved_gpio_is_never_driven); return UNITY_END(); }