#include #include #include "secsgem/gem/e84_state.hpp" using namespace secsgem::gem; TEST_CASE("E84: initial state is Idle with all signals low") { E84StateMachine fsm; CHECK(fsm.state() == E84State::Idle); for (auto s : {E84Signal::CS_0, E84Signal::CS_1, E84Signal::VALID, E84Signal::TR_REQ, E84Signal::BUSY, E84Signal::COMPT, E84Signal::L_REQ, E84Signal::U_REQ, E84Signal::READY, E84Signal::ES}) { CHECK_FALSE(fsm.signal(s)); } } TEST_CASE("E84: load handshake sequence") { E84StateMachine fsm; std::vector trace; fsm.set_state_change_handler( [&](E84State, E84State to, E84Signal) { trace.push_back(to); }); // AMHS asserts CS_0 (port 0 selected). fsm.on_signal_change(E84Signal::CS_0, true); CHECK(fsm.state() == E84State::CarrierPresent); // AMHS asserts VALID. fsm.on_signal_change(E84Signal::VALID, true); CHECK(fsm.state() == E84State::ValidAsserted); // Equipment asserts L_REQ (port is ready to receive). fsm.on_signal_change(E84Signal::L_REQ, true); CHECK(fsm.state() == E84State::LoadReady); // AMHS begins transfer. fsm.on_signal_change(E84Signal::BUSY, true); CHECK(fsm.state() == E84State::Transferring); // AMHS completes. Dropping BUSY transiently returns to LoadReady // (VALID + L_REQ still held), then COMPT pushes Complete. fsm.on_signal_change(E84Signal::BUSY, false); CHECK(fsm.state() == E84State::LoadReady); fsm.on_signal_change(E84Signal::COMPT, true); CHECK(fsm.state() == E84State::Complete); // Six transitions: Idle->CarrierPresent->ValidAsserted->LoadReady-> // Transferring->LoadReady->Complete. CHECK(trace.size() == 6); CHECK(trace.back() == E84State::Complete); } TEST_CASE("E84: unload handshake distinguishes U_REQ vs L_REQ") { E84StateMachine fsm; fsm.on_signal_change(E84Signal::CS_1, true); fsm.on_signal_change(E84Signal::VALID, true); fsm.on_signal_change(E84Signal::U_REQ, true); CHECK(fsm.state() == E84State::UnloadReady); } TEST_CASE("E84: ES dominates regardless of other signals") { E84StateMachine fsm; fsm.on_signal_change(E84Signal::CS_0, true); fsm.on_signal_change(E84Signal::VALID, true); fsm.on_signal_change(E84Signal::L_REQ, true); REQUIRE(fsm.state() == E84State::LoadReady); fsm.on_signal_change(E84Signal::ES, true); CHECK(fsm.state() == E84State::EmergencyStop); // ES persists even as other signals drop. fsm.on_signal_change(E84Signal::VALID, false); fsm.on_signal_change(E84Signal::CS_0, false); CHECK(fsm.state() == E84State::EmergencyStop); // Clearing ES returns to whatever the remaining signals indicate. // VALID and CS_0 are gone, so even though L_REQ is still held, the // handshake collapses to Idle (no carrier present + no VALID). fsm.on_signal_change(E84Signal::ES, false); CHECK(fsm.state() == E84State::Idle); } TEST_CASE("E84: reset() drops all signals and returns to Idle") { E84StateMachine fsm; fsm.on_signal_change(E84Signal::CS_0, true); fsm.on_signal_change(E84Signal::VALID, true); REQUIRE(fsm.state() != E84State::Idle); fsm.reset(); CHECK(fsm.state() == E84State::Idle); CHECK_FALSE(fsm.signal(E84Signal::CS_0)); CHECK_FALSE(fsm.signal(E84Signal::VALID)); } TEST_CASE("E84: handler suppresses no-op signal-change events") { E84StateMachine fsm; int calls = 0; fsm.set_state_change_handler( [&](E84State, E84State, E84Signal) { ++calls; }); // Setting CS_0 false (already false) doesn't change state. fsm.on_signal_change(E84Signal::CS_0, false); CHECK(calls == 0); // Real change. fsm.on_signal_change(E84Signal::CS_0, true); CHECK(calls == 1); // Setting it true again (idempotent). fsm.on_signal_change(E84Signal::CS_0, true); CHECK(calls == 1); }