// 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_deferred #endif #include // back-end #include "BackCommon.hpp" // front-end #include "FrontCommon.hpp" #include "Utils.hpp" using namespace boost::msm::front; using namespace boost::msm::backmp11; namespace mp11 = boost::mp11; namespace { // Events struct Event1 { size_t id{}; }; struct Event2 { size_t id{}; }; struct Event3 { bool marked_for_deferral{false}; size_t id{}; }; struct FromHandleAllToHandleNone {}; struct FromHandleNoneToHandleAll {}; struct FromDeferAllToDeferEvent1 {}; struct FromDeferEvent1ToHandleNone {}; struct FromDeferEvent1ToHandleAll {}; // Actions struct Action { template void operator()(const Event1& event, Fsm& fsm, SourceState&, TargetState&) { fsm.event1_action_calls++; fsm.event1_processed_ids.push_back(event.id); } template void operator()(const Event2& event, Fsm& fsm, SourceState&, TargetState&) { fsm.event2_action_calls++; fsm.event2_processed_ids.push_back(event.id); } template void operator()(const Event3& event, Fsm& fsm, SourceState&, TargetState&) { fsm.event3_action_calls++; fsm.event3_processed_ids.push_back(event.id); } }; // Common states struct StateHandleNone : state<> {}; struct StateHandleAll : state<> {}; template struct StateMachineBase_ : test::StateMachineBase_ { size_t event1_action_calls{}; std::vector event1_processed_ids; size_t event2_action_calls{}; std::vector event2_processed_ids; size_t event3_action_calls{}; std::vector event3_processed_ids; }; // Test event deferral with the deferred_events property. namespace property_deferred { struct StateDeferEvent1 : state<> { using deferred_events = mp11::mp_list; }; struct StateDeferAll : state<> { using deferred_events = mp11::mp_list; }; struct StateDeferEvent3Conditionally : state<> { using deferred_events = mp11::mp_list; template bool is_event_deferred(const Event3& event, Fsm&) const { return event.marked_for_deferral; } }; struct StateMachine_ : StateMachineBase_ { using initial_state = mp11::mp_list; using transition_table = mp11::mp_list< Row, Row, Row, Row, Row >; }; // Pick a back-end using Fsms = mp11::mp_list< #ifndef BOOST_MSM_TEST_SKIP_BACKMP11 StateMachine, StateMachine #endif // BOOST_MSM_TEST_SKIP_BACKMP11 >; BOOST_AUTO_TEST_CASE_TEMPLATE(property_deferred, Fsm, Fsms) { Fsm fsm; fsm.start(); fsm.process_event(Event1{0}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.process_event(Event1{1}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 2); fsm.process_event(Event2{0}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 3); // StateDeferEvent3Conditionally would process, // but StateDeferAll defers the event. fsm.process_event(Event3{false, 0}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 0); ASSERT_AND_RESET(fsm.event3_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 4); fsm.process_event(FromDeferAllToDeferEvent1{}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 1); ASSERT_AND_RESET(fsm.event3_action_calls, 1); BOOST_REQUIRE(fsm.event3_processed_ids.at(0) == 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 2); fsm.process_event(FromDeferEvent1ToHandleNone{}); ASSERT_AND_RESET(fsm.event1_action_calls, 2); BOOST_REQUIRE(fsm.event1_processed_ids.at(0) == 0); BOOST_REQUIRE(fsm.event1_processed_ids.at(1) == 1); ASSERT_AND_RESET(fsm.event2_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 0); // The event gets conditionally deferred. fsm.process_event(Event3{true, 1}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); // The previous event stays conditionally deferred, // the new event gets consumed. fsm.process_event(Event3{false, 2}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); ASSERT_AND_RESET(fsm.event2_action_calls, 0); ASSERT_AND_RESET(fsm.event3_action_calls, 1); BOOST_REQUIRE(fsm.event3_processed_ids.at(1) == 2); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.stop(); } struct FromLowerMachineToHandleAll {}; template struct hierarchical_state_machine { struct LowerMachine_; struct UpperMachine_; struct StateDeferEvent1 : state<> { using deferred_events = mp11::mp_list; template bool is_event_deferred(const Event1&, Fsm&) const { static_assert(std::is_base_of_v); return true; } }; struct LowerMachine_ : StateMachineBase_ { using initial_state = mp11::mp_list; }; using LowerMachine = StateMachine; struct UpperMachine_ : StateMachineBase_ { using initial_state = mp11::mp_list; using transition_table = mp11::mp_list< Row, Row >; }; using UpperMachine = StateMachine; }; // Pick a back-end using Fsms_2 = mp11::mp_list< #ifndef BOOST_MSM_TEST_SKIP_BACKMP11 hierarchical_state_machine<>, hierarchical_state_machine #endif // BOOST_MSM_TEST_SKIP_BACKMP11 >; // Ensure an event gets deferred if there // is no immediate substate that defers the event, // but a subsubstate in the hierarchy defers it. BOOST_AUTO_TEST_CASE_TEMPLATE(hierarchical_deferral_subsubstate, Fsm, Fsms_2) { using UpperMachine = typename Fsm::UpperMachine; UpperMachine fsm; fsm.start(); fsm.process_event(Event1{0}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.process_event(FromLowerMachineToHandleAll{}); ASSERT_AND_RESET(fsm.event1_action_calls, 1); BOOST_REQUIRE(fsm.get_pending_events().size() == 0); fsm.stop(); } struct FromLowerMachineToDeferEvent1 {}; template struct hierarchical_state_machine_2 { struct LowerMachine_; struct UpperMachine_; struct StateDeferEvent1 : state<> { using deferred_events = mp11::mp_list; template bool is_event_deferred(const Event1&, Fsm&) const { if (id == 0) { BOOST_REQUIRE((std::is_base_of_v)); } else { BOOST_REQUIRE((std::is_base_of_v)); } return true; } size_t id{}; }; struct LowerMachine_ : StateMachineBase_ { using initial_state = mp11::mp_list; using transition_table = mp11::mp_list< Row >; }; using LowerMachine = StateMachine; struct UpperMachine_ : StateMachineBase_ { using initial_state = mp11::mp_list; using transition_table = mp11::mp_list< Row, Row, Row >; }; using UpperMachine = StateMachine; }; // Pick a back-end using Fsms_3 = mp11::mp_list< #ifndef BOOST_MSM_TEST_SKIP_BACKMP11 hierarchical_state_machine_2<>, hierarchical_state_machine_2 #endif // BOOST_MSM_TEST_SKIP_BACKMP11 >; // Ensure that the Fsm parameter passed to is_event_deferred is correct. BOOST_AUTO_TEST_CASE_TEMPLATE(hierarchical_deferral_fsm_parameter, Fsm, Fsms_3) { using UpperMachine = typename Fsm::UpperMachine; UpperMachine fsm; fsm.template get_state().id = 1; fsm.start(); fsm.process_event(Event1{0}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.process_event(FromLowerMachineToDeferEvent1{}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.process_event(Event1{1}); ASSERT_AND_RESET(fsm.event1_action_calls, 0); BOOST_REQUIRE(fsm.get_pending_events().size() == 2); fsm.process_event(FromDeferEvent1ToHandleAll{}); ASSERT_AND_RESET(fsm.event1_action_calls, 2); BOOST_REQUIRE(fsm.get_pending_events().size() == 0); BOOST_REQUIRE(fsm.event1_processed_ids.at(0) == 0); BOOST_REQUIRE(fsm.event1_processed_ids.at(1) == 1); fsm.stop(); } } // namespace property_deferred // Test case for manual deferral by using transitions with Defer actions. // Not specified in UML and thus no clear semantics how it should behave. // Currently a Defer action consumes the event (it gets removed from the queue) // and then defers it (it gets pushed back to the queue), the Defer action // returns HANDLED_DEFERRED as processing result). namespace action_deferred { struct StateDeferEvent1 : state<> { }; struct StateDeferAll : state<> { }; struct StateMachine_ : StateMachineBase_ { using activate_deferred_events = int; using initial_state = mp11::mp_list; using transition_table = mp11::mp_list< Row, Row, Row, Row, Row, Row, Row >; }; // Pick a back-end using Fsms = mp11::mp_list< #ifndef BOOST_MSM_TEST_SKIP_BACKMP11 StateMachine, StateMachine #endif // BOOST_MSM_TEST_SKIP_BACKMP11 >; BOOST_AUTO_TEST_CASE_TEMPLATE(action_deferred, Fsm, Fsms) { Fsm fsm; fsm.start(); fsm.process_event(Event1{}); // Processed by StateHandleAll, deferred by StateDeferAll. // Queue: Event1 ASSERT_AND_RESET(fsm.event1_action_calls, 1); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.process_event(Event2{}); // Processed by StateHandleAll, deferred by StateDeferAll. // StateHandleAll processes Event1 a 2nd time. // Queue: Event2, Event1 ASSERT_AND_RESET(fsm.event1_action_calls, 1); ASSERT_AND_RESET(fsm.event2_action_calls, 1); BOOST_REQUIRE(fsm.get_pending_events().size() == 2); fsm.process_event(FromDeferAllToDeferEvent1{}); // Event2 is no more deferred. // StateHandleAll processes Event2 a 2nd time & Event1 a 3rd time, // StateDeferEvent1 defers Event1. // Queue: Event1 ASSERT_AND_RESET(fsm.event1_action_calls, 1); ASSERT_AND_RESET(fsm.event2_action_calls, 1); BOOST_REQUIRE(fsm.get_pending_events().size() == 1); fsm.process_event(FromDeferEvent1ToHandleNone{}); // Event1 is no more deferred. // StateHandleAll processes Event1 a 4th time. ASSERT_AND_RESET(fsm.event1_action_calls, 1); BOOST_REQUIRE(fsm.get_pending_events().size() == 0); fsm.stop(); } } // namespace action_deferred } // namespace