// 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) #ifndef BOOST_MSM_NONSTANDALONE_TEST #define BOOST_TEST_MODULE backmp11_transitions_test #endif #include // back-end #include #include //front-end #include "FrontCommon.hpp" #include "Utils.hpp" #include "attachments/backmp11/MinimalExample.cpp" namespace msm = boost::msm; namespace mp11 = boost::mp11; using namespace msm::front; using namespace msm::backmp11; namespace { // Events. struct EnterSubmachine{}; struct TriggerTransition{}; struct TriggerInternalTransition{}; struct TriggerInternalTransitionWithGuard { bool guard_value{}; }; struct TriggerSmInternalTransition{}; struct TriggerAnyTransition{}; struct TriggerNoTransition{}; // Actions struct MyAction { template void operator()(const Event&, Fsm& fsm, Source& source, Target&) { source.action_counter++; if constexpr (std::is_same_v) { // An action in favor_runtime_speed shall not receive an event // converted to Kleene, the Kleene event type is only used as a // placeholder in the front-end. static_assert(!std::is_same_v); } // Attempting to process events while the state machine is processing // shall discard the event. BOOST_REQUIRE(fsm.process_event(TriggerNoTransition{}) == process_result::discarded); } }; // Guards struct MyGuard { template bool operator()(const TriggerInternalTransitionWithGuard& event, Fsm&) const { return event.guard_value; } }; struct NotMyGuard { template bool operator()(const TriggerInternalTransitionWithGuard& event, Fsm&) const { return !event.guard_value; } }; // States. struct MyState : public test::StateBase{}; struct MyOtherState : public test::StateBase{}; class StateMachine; struct StateMachine_; struct default_config : state_machine_config { // using root_sm = StateMachine; using event_pool = no_event_pool; }; struct favor_compile_time_config : default_config { using compile_policy = favor_compile_time; }; template struct hierarchical_state_machine { struct Submachine_ : public test::StateMachineBase_ { using initial_state = MyState; using transition_table = mp11::mp_list< Row >; }; using Submachine = state_machine; struct StateMachine_ : public test::StateMachineBase_ { using initial_state = MyState; using transition_table = mp11::mp_list< Row, Row, Row, Row, Row, Row >; using internal_transition_table = mp11::mp_list< Internal, Internal >; }; // Leave this class without inheriting constructors to check // that this only needs to be done in case we use a context // (for convenience). class StateMachine : public state_machine { }; }; using TestMachines = boost::mpl::vector< hierarchical_state_machine<>, hierarchical_state_machine >; BOOST_AUTO_TEST_CASE_TEMPLATE(start_stop, StateMachine, TestMachines) { using TestMachine = typename StateMachine::StateMachine; TestMachine test_machine; test_machine.start(); BOOST_REQUIRE(test_machine.entry_counter == 1); test_machine.stop(); BOOST_REQUIRE(test_machine.exit_counter == 1); } BOOST_AUTO_TEST_CASE_TEMPLATE(transitions, StateMachine, TestMachines) { using TestMachine = typename StateMachine::StateMachine; using compile_policy = typename TestMachine::config_t::compile_policy; TestMachine test_machine; BOOST_REQUIRE(test_machine.process_event(TriggerInternalTransition{}) == process_result::discarded); ASSERT_AND_RESET(test_machine.template get_state().action_counter, 0); test_machine.start(); if constexpr (std::is_same_v) { test_machine.process_event(TriggerAnyTransition{}); ASSERT_ONE_AND_RESET(test_machine.action_counter); } test_machine.process_event(TriggerInternalTransition{}); ASSERT_ONE_AND_RESET(test_machine.template get_state().action_counter); // Ensure MyState consumes the event instead of StateMachine. test_machine.process_event(TriggerSmInternalTransition{}); ASSERT_ONE_AND_RESET(test_machine.template get_state().action_counter); test_machine.process_event(TriggerTransition{}); ASSERT_ONE_AND_RESET(test_machine.template get_state().action_counter); BOOST_REQUIRE(test_machine.template is_state_active()); if constexpr (std::is_same_v) { test_machine.process_event(TriggerAnyTransition{}); ASSERT_ONE_AND_RESET(test_machine.action_counter); } test_machine.process_event(TriggerSmInternalTransition{}); ASSERT_ONE_AND_RESET(test_machine.action_counter); test_machine.stop(); } BOOST_AUTO_TEST_CASE_TEMPLATE(submachine_transitions, StateMachine, TestMachines) { using TestMachine = typename StateMachine::StateMachine; TestMachine test_machine; auto& submachine = test_machine.template get_state(); test_machine.start(); test_machine.process_event(EnterSubmachine{}); ASSERT_ONE_AND_RESET(submachine.entry_counter); test_machine.process_event(TriggerInternalTransitionWithGuard{false}); ASSERT_ONE_AND_RESET(submachine.action_counter); test_machine.process_event(TriggerInternalTransitionWithGuard{true}); ASSERT_ONE_AND_RESET(submachine.template get_state().action_counter); test_machine.stop(); } } // namespace