7bff01c363
Adds the six E116-0712 §6.2 buckets for classifying equipment time: NonScheduledTime (0) not scheduled to operate ScheduledDowntime (1) planned maintenance window UnscheduledDowntime (2) faults / unplanned stoppage Engineering (3) engineering / qualification time Standby (4) idle but available Productive (5) actively producing Wire-byte values pinned via static_assert to E116 §10.3. The FSM is a classifier rather than a strict lifecycle — every (state, event) pair is legal — but it remains data-driven through the shared CarrierTransitionTable template so the default cross-product is expressible declaratively. The state-change handler also surfaces dwell time (how long the previous state was held) computed off std::chrono::steady_clock, so accounting code can compute MTBF / availability / utilization from a single source without maintaining a parallel timestamp log. 4 test cases cover the initial state, every event firing, dwell-time reporting, and the no-op same-state event (no handler call). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
61 lines
2.1 KiB
C++
61 lines
2.1 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#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<std::pair<EptState, std::chrono::milliseconds>> 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);
|
|
}
|