Files
secs-gem/tests/test_ept.cpp
T
raphael 7bff01c363 F1: EptStateMachine (E116 Equipment Performance Tracking)
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>
2026-06-08 02:17:16 +02:00

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);
}