#include #include #include #include #include "secsgem/gem/ept_state.hpp" using namespace secsgem::gem; TEST_CASE("EPT: initial state is NonScheduledTime") { EptStateMachine ept; CHECK(ept.state() == EptState::NonScheduledTime); } TEST_CASE("EPT: every event transitions to exactly its target state") { EptStateMachine ept; CHECK(ept.on_event(EptEvent::EnterStandby)); CHECK(ept.state() == EptState::Standby); CHECK(ept.on_event(EptEvent::EnterProductive)); CHECK(ept.state() == EptState::Productive); CHECK(ept.on_event(EptEvent::EnterUnscheduledDown)); CHECK(ept.state() == EptState::UnscheduledDowntime); CHECK(ept.on_event(EptEvent::EnterEngineering)); CHECK(ept.state() == EptState::Engineering); CHECK(ept.on_event(EptEvent::EnterScheduledDown)); CHECK(ept.state() == EptState::ScheduledDowntime); CHECK(ept.on_event(EptEvent::EnterNonScheduled)); CHECK(ept.state() == EptState::NonScheduledTime); } TEST_CASE("EPT: change handler reports dwell time") { EptStateMachine ept; std::vector> transitions; ept.set_state_change_handler( [&](EptState, EptState to, EptEvent, std::chrono::milliseconds dwell) { transitions.emplace_back(to, dwell); }); std::this_thread::sleep_for(std::chrono::milliseconds(5)); ept.on_event(EptEvent::EnterStandby); std::this_thread::sleep_for(std::chrono::milliseconds(5)); ept.on_event(EptEvent::EnterProductive); REQUIRE(transitions.size() == 2); CHECK(transitions[0].first == EptState::Standby); CHECK(transitions[0].second.count() >= 1); // dwelled in NonScheduledTime CHECK(transitions[1].first == EptState::Productive); CHECK(transitions[1].second.count() >= 1); // dwelled in Standby } TEST_CASE("EPT: same-state event is a no-op (no handler call)") { EptStateMachine ept; ept.on_event(EptEvent::EnterStandby); int calls = 0; ept.set_state_change_handler( [&](EptState, EptState, EptEvent, std::chrono::milliseconds) { ++calls; }); CHECK(ept.on_event(EptEvent::EnterStandby)); // already in Standby CHECK(calls == 0); } TEST_CASE("EPT: time buckets accumulate per state") { EptStateMachine ept; std::this_thread::sleep_for(std::chrono::milliseconds(10)); ept.on_event(EptEvent::EnterStandby); std::this_thread::sleep_for(std::chrono::milliseconds(10)); ept.on_event(EptEvent::EnterProductive); std::this_thread::sleep_for(std::chrono::milliseconds(10)); ept.on_event(EptEvent::EnterStandby); // Each bucket should have at least the slept time. CHECK(ept.accumulated(EptState::NonScheduledTime).count() >= 5); CHECK(ept.accumulated(EptState::Standby).count() >= 5); CHECK(ept.accumulated(EptState::Productive).count() >= 5); // Total = sum of buckets (modulo current-state in-progress dwell). CHECK(ept.total_elapsed().count() >= 25); } TEST_CASE("EPT: current-state dwell is counted in accumulated()") { EptStateMachine ept; ept.on_event(EptEvent::EnterProductive); std::this_thread::sleep_for(std::chrono::milliseconds(15)); // No transition out; accumulated should still include the live dwell. CHECK(ept.accumulated(EptState::Productive).count() >= 10); } TEST_CASE("EPT: reset_history zeroes all buckets") { EptStateMachine ept; ept.on_event(EptEvent::EnterStandby); std::this_thread::sleep_for(std::chrono::milliseconds(5)); ept.on_event(EptEvent::EnterProductive); REQUIRE(ept.accumulated(EptState::Standby).count() > 0); ept.reset_history(); for (auto s : {EptState::NonScheduledTime, EptState::ScheduledDowntime, EptState::UnscheduledDowntime, EptState::Engineering, EptState::Standby}) { CHECK(ept.accumulated(s).count() == 0); } // Current state (Productive) starts re-accumulating from zero. CHECK(ept.accumulated(EptState::Productive).count() < 5); }