// Copyright 2025 Christian Granzin // Copyright 2010 Christophe Henry // henry UNDERSCORE christophe AT hotmail DOT com // This is an extended version of the state machine available in the boost::mpl library // Distributed under the same license as the original. // Copyright for the original version: // Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed // under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // back-end #include "BackCommon.hpp" //front-end #include #include #ifndef BOOST_MSM_NONSTANDALONE_TEST #define BOOST_TEST_MODULE backmp11_entry_exit_test #endif #include namespace mp11 = boost::mp11; using namespace boost::msm::front; using namespace boost::msm::backmp11; namespace { // events struct EnterSubmachine {}; struct ExitSubmachine {}; struct EnterSubmachineExplicitEntry {}; struct EnterSubmachineForkEntry {}; struct EnterSubmachinePseudoEntry {}; struct ExitSubmachinePseudoExit {}; struct SomeEvent { SomeEvent(){} template SomeEvent(Event const&){} }; // states struct StateBase : state<> { template void on_entry(const Event&, Fsm&) { entry_counter++; } template void on_exit(const Event&, Fsm&) { exit_counter++; } size_t entry_counter{}; size_t exit_counter{}; }; template struct StateMachineBase_ : state_machine_def { template void on_entry(const Event&, Fsm&) { entry_counter++; } template void on_exit(const Event&, Fsm&) { exit_counter++; } template void no_transition(Event const& , FSM&,int ) { BOOST_FAIL("no_transition called!"); } size_t entry_counter{}; size_t exit_counter{}; }; template struct hierarchical_state_machine { struct Submachine_ : StateMachineBase_ { BOOST_MSM_TEST_DEFINE_DEPENDENT_TEMPLATES(Submachine_) struct Substate0 : StateBase {}; struct Substate1 : StateBase {}; struct ExplicitEntry0 : StateBase, explicit_entry<0> {}; struct ExplicitEntry1 : StateBase, explicit_entry<1> {}; struct Substate2 : StateBase {}; // TODO: // According to UML a pseudostate shouldn't have entry/exit behavior. struct PseudoEntry0 : entry_pseudo_state<0> { template void on_entry(const Event&, Fsm&) { entry_counter++; } template void on_exit(const Event&, Fsm&) { exit_counter++; } size_t entry_counter{}; size_t exit_counter{}; }; // TODO: // According to UML a pseudostate shouldn't have entry/exit behavior. struct PseudoExit0 : exit_pseudo_state { template void on_entry(const Event&, Fsm&) { entry_counter++; } template void on_exit(const Event&, Fsm&) { exit_counter++; } size_t entry_counter{}; size_t exit_counter{}; }; struct AssertEnterSubmachinePseudoEntry { template void operator()(const Event&, Fsm&, Source&, Target&) { static_assert(std::is_same_v); static_assert(std::is_same_v); } }; struct AssertExitSubmachinePseudoExit1 { template void operator()(const Event&, Fsm&, Source&, Target&) { static_assert(std::is_same_v); // TODO: // Should the action receive PseudoExit0 instead of Machine::exit_pt? // static_assert(std::is_same_v); } }; using initial_state = mp11::mp_list; using explicit_creation = mp11::mp_list; using transition_table = mp11::mp_list< Row < PseudoEntry0 , EnterSubmachinePseudoEntry , Substate2 , AssertEnterSubmachinePseudoEntry >, Row < ExplicitEntry0 , SomeEvent , Substate0 >, Row < Substate2 , ExitSubmachinePseudoExit , PseudoExit0 , AssertExitSubmachinePseudoExit1 > >; }; using Submachine = state_machine; struct Machine_ : StateMachineBase_ { BOOST_MSM_TEST_DEFINE_DEPENDENT_TEMPLATES(Machine_) struct State0 : StateBase {}; using SubmachineEntryPt = typename Submachine::template entry_pt; using SubmachineExplicitEntryPt = typename Submachine::template direct; using SubmachineForkEntryPt = mp11::mp_list, typename Submachine::template direct>; using SubmachineExitPt = typename Submachine::template exit_pt; struct AssertEnterSubmachine { template void operator()(const Event&, Fsm&, Source&, Target&) { static_assert(std::is_same_v); static_assert(std::is_same_v); } }; struct AssertEnterSubmachinePseudoEntry { template void operator()(const Event&, Fsm&, Source&, Target&) { static_assert(std::is_same_v); // TODO: // Should be PseudoEntry0 instead of Submachine. static_assert(std::is_same_v); } }; struct AssertEnterSubmachineExplicitEntry { template void operator()(const Event&, Fsm&, Source&, Target&) { static_assert(std::is_same_v); // TODO: // Should be ExplicitEntry0 instead of Submachine. // static_assert(std::is_same_v); static_assert(std::is_same_v); } }; struct AssertEnterSubmachineForkEntry { template void operator()(const Event&, Fsm&, Source&, Target&) { static_assert(std::is_same_v); // TODO: // Should be mp11::mp_list instead of Submachine (??). static_assert(std::is_same_v); } }; using initial_state = State0; using transition_table = mp11::mp_list< Row < State0 , EnterSubmachine , Submachine , AssertEnterSubmachine >, Row < State0 , EnterSubmachinePseudoEntry , SubmachineEntryPt , AssertEnterSubmachinePseudoEntry >, Row < State0 , EnterSubmachineExplicitEntry , SubmachineExplicitEntryPt , AssertEnterSubmachineExplicitEntry >, Row < State0 , EnterSubmachineForkEntry , SubmachineForkEntryPt , AssertEnterSubmachineForkEntry>, Row < Submachine , ExitSubmachine , State0 >, Row < SubmachineExitPt , SomeEvent , State0 > >; }; using Machine = state_machine; }; using TestMachines = mp11::mp_list< hierarchical_state_machine<>, hierarchical_state_machine >; #define CHECK_AND_RESET_COUNTER(counter, expected) \ { \ BOOST_REQUIRE(counter == expected); \ counter = 0; \ } BOOST_AUTO_TEST_CASE_TEMPLATE(backmp11_entry_exit_test, test_machine, TestMachines) { using Machine = typename test_machine::Machine; using Machine_ = typename test_machine::Machine_; Machine p; using State0 = typename Machine_::State0; using Submachine = typename test_machine::Submachine; using Substate0 = typename Submachine::Substate0; using Substate1 = typename Submachine::Substate1; using Substate2 = typename Submachine::Substate2; using PseudoEntry0 = typename Submachine::PseudoEntry0; using ExplicitEntry0 = typename Submachine::ExplicitEntry0; using ExplicitEntry1 = typename Submachine::ExplicitEntry1; // TODO: // Can we define PseudoExit0 as seen from the submachine? using PseudoExit0 = typename Machine_::SubmachineExitPt; auto& state_0 = p.template get_state(); auto& submachine = p.template get_state(); auto& substate_0 = submachine.template get_state(); auto& substate_1 = submachine.template get_state(); auto& substate_2 = submachine.template get_state(); auto& pseudo_entry_0 = submachine.template get_state(); auto& explicit_entry_0 = submachine.template get_state(); auto& explicit_entry_1 = submachine.template get_state(); auto& pseudo_exit_0 = submachine.template get_state(); p.start(); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(state_0.entry_counter, 1); // Normal entry/exit: [Substate0, Substate1]. p.process_event(EnterSubmachine{}); BOOST_REQUIRE(p.template is_state_active()); BOOST_REQUIRE(p.template is_state_active()); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(state_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(submachine.entry_counter, 1); CHECK_AND_RESET_COUNTER(substate_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(substate_1.entry_counter, 1); p.process_event(ExitSubmachine{}); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(state_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(submachine.exit_counter, 1); CHECK_AND_RESET_COUNTER(substate_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(substate_1.exit_counter, 1); // Pseudo entry: [Substate1, Substate2] via PseudoEntry0. p.process_event(EnterSubmachinePseudoEntry{}); BOOST_REQUIRE(p.template is_state_active()); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(state_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(submachine.entry_counter, 1); CHECK_AND_RESET_COUNTER(pseudo_entry_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(pseudo_entry_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(substate_2.entry_counter, 1); CHECK_AND_RESET_COUNTER(substate_1.entry_counter, 1); // Pseudo exit: Transitions via PseudoExit0. p.process_event(ExitSubmachinePseudoExit{}); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(pseudo_exit_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(pseudo_exit_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(submachine.exit_counter, 1); CHECK_AND_RESET_COUNTER(substate_1.exit_counter, 1); CHECK_AND_RESET_COUNTER(substate_2.exit_counter, 1); CHECK_AND_RESET_COUNTER(state_0.entry_counter, 1); // Explicit entry: [ExplicitEntry0, SubState1]. p.process_event(EnterSubmachineExplicitEntry{}); BOOST_REQUIRE(p.template is_state_active()); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(state_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(submachine.entry_counter, 1); CHECK_AND_RESET_COUNTER(explicit_entry_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(substate_1.entry_counter, 1); p.process_event(ExitSubmachine{}); CHECK_AND_RESET_COUNTER(state_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(submachine.exit_counter, 1); CHECK_AND_RESET_COUNTER(explicit_entry_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(substate_1.exit_counter, 1); // Explicit entry with fork: [ExplicitEntry0, ExplicitEntry1]. p.process_event(EnterSubmachineForkEntry{}); BOOST_REQUIRE(p.template is_state_active()); BOOST_REQUIRE(p.template is_state_active()); CHECK_AND_RESET_COUNTER(state_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(submachine.entry_counter, 1); CHECK_AND_RESET_COUNTER(explicit_entry_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(explicit_entry_1.entry_counter, 1); p.process_event(ExitSubmachine{}); CHECK_AND_RESET_COUNTER(state_0.entry_counter, 1); CHECK_AND_RESET_COUNTER(submachine.exit_counter, 1); CHECK_AND_RESET_COUNTER(explicit_entry_0.exit_counter, 1); CHECK_AND_RESET_COUNTER(explicit_entry_1.exit_counter, 1); } } // namespace