// 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 //front-end #include #include #include "PumlCommon.hpp" #ifndef BOOST_MSM_NONSTANDALONE_TEST #define BOOST_TEST_MODULE back11_orthogonal_deferred3_with_puml_test #endif #include using namespace std; namespace msm = boost::msm; using namespace msm::front; using namespace msm::front::puml; namespace { // Flags. Allow information about a property of the current state struct PlayingPaused {}; struct CDLoaded {}; struct FirstSongPlaying {}; } namespace boost::msm::front::puml { // events template<> struct Event {}; template<> struct Event {}; // The list of FSM states template<> struct State : public msm::front::state<> { typedef boost::fusion::vector flag_list; template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; template<> struct State : public msm::front::state<> { template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; template<> struct State : public msm::front::state<> { template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; //actions template<> struct Action { template void operator()(EVT const&, FSM& fsm, SourceState&, TargetState&) { ++fsm.start_next_song_counter; } }; template<> struct Action { template void operator()(EVT const&, FSM&, SourceState&, TargetState&) { } }; // guard conditions template<> struct Guard { template bool operator()(EVT const&, FSM& fsm, SourceState&, TargetState&) { ++fsm.start_prev_song_guard_counter; return true; } }; } namespace { // front-end: define the FSM structure struct playing_ : public msm::front::state_machine_def { typedef boost::fusion::vector flag_list; template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; unsigned int start_next_song_counter = 0; unsigned int start_prev_song_guard_counter = 0; BOOST_MSM_PUML_DECLARE_TABLE( R"( @startuml Player skinparam linetype polyline state Player{ [*]-> Song1 Song1 -> Song2 : NextSong Song2 -> Song1 : PreviousSong / start_prev_song [start_prev_song_guard] Song2 -> Song3 : NextSong / start_next_song Song3 -> Song2 : PreviousSong [start_prev_song_guard] } @enduml )" ) // Replaces the default no-transition response. template void no_transition(Event const&, FSM&, int) { BOOST_FAIL("no_transition called!"); } }; } namespace boost::msm::front::puml { template<> struct State : public msm::back11::state_machine { }; } namespace boost::msm::front::puml { // events template<> struct Event {}; template<> struct Event {}; template<> struct Event {}; template<> struct State : public msm::front::state<> { typedef boost::fusion::vector flag_list; template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; template<> struct State : public msm::front::state<> { typedef boost::fusion::vector flag_list; template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; template<> struct State : public msm::front::state<> { template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; template<> struct State : public msm::front::interrupt_state < Event> { template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; template<> struct State : public msm::front::terminate_state<> { template void on_entry(Event const&, FSM&) { ++entry_counter; } template void on_exit(Event const&, FSM&) { ++exit_counter; } int entry_counter = 0; int exit_counter = 0; }; //actions template<> struct Action { template void operator()(EVT const&, FSM& fsm, SourceState&, TargetState&) { ++fsm.report_error_counter; } }; template<> struct Action { template void operator()(EVT const&, FSM& fsm, SourceState&, TargetState&) { ++fsm.report_end_error_counter; } }; template<> struct Action { // mark as deferring to avoid stack overflows in certain conditions typedef int deferring_action; template void operator()(EVT const&, FSM& fsm, SourceState&, TargetState&) const { Event e{}; fsm.defer_event(e); } }; // guard conditions template<> struct Guard { template bool operator()(EVT const& evt, FSM& , SourceState&, TargetState&) { bool is_play = boost::any_cast>(&evt) != 0; return is_play; } }; } namespace { // front-end: define the FSM structure struct player_ : public msm::front::state_machine_def { // we want deferred events and no state requires deferred events (only the fsm in the // transition table), so the fsm does. typedef int activate_deferred_events; unsigned int start_playback_counter=0; unsigned int can_close_drawer_counter=0; unsigned int report_error_counter=0; unsigned int report_end_error_counter=0; BOOST_MSM_PUML_DECLARE_TABLE( R"( @startuml Player skinparam linetype polyline state Player{ [*]-> Empty Stopped2 -> PlayingFsm : play / start_playback Stopped2 -> Open : open_close / open_drawer Stopped2 -> Stopped2 : stop Open -> Empty : open_close [can_close_drawer] Open -> Open : -play / defer Empty --> Open : open_close / open_drawer Empty ---> Stopped2 : cd_detected / store_cd_info Empty -> Empty : -* / MyDefer [is_play_event] PlayingFsm --> Stopped2 : stop / stop_playback PlayingFsm -> Paused2 : pause / pause_playback PlayingFsm --> Open : open_close / stop_and_open Paused2 -> PlayingFsm : end_pause / resume_playback Paused2 --> Stopped2 : stop / stop_playback Paused2 --> Open : open_close / stop_and_open -- [*]-> AllOk AllOk -> ErrorMode : error_found / report_error ErrorMode -> AllOk : end_error / report_end_error AllOk -> ErrorTerminate : do_terminate } @enduml )" ) // Replaces the default no-transition response. template void no_transition(Event const&, FSM&,int) { BOOST_FAIL("no_transition called!"); } }; // Pick a back-end typedef msm::back11::state_machine player; BOOST_AUTO_TEST_CASE( back11_orthogonal_deferred3_with_pumltest ) { player p; static_assert(msm::back11::get_number_of_regions::type::value == 2); // needed to start the highest-level SM. This will call on_entry and mark the start of the SM p.start(); // test deferred event // deferred in Empty and Open, will be handled only after event cd_detected p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 2, "Empty should be active"); //Empty BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 0, "Open exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 0, "Playing entry not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "Empty entry not called correctly"); //flags BOOST_CHECK_MESSAGE(p.is_flag_active() == false, "CDLoaded should not be active"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 1, "Open should be active"); //Open BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "Empty exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "Open entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 2, "Empty should be active"); //Empty BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "Open exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 2, "Empty entry not called correctly"); BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1, "guard not called correctly"); //deferred event should have been processed p.process_event(Event{"louie, louie"}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Empty exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "Stopped entry not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "Stopped exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "Playing entry not called correctly"); BOOST_CHECK_MESSAGE(p.start_playback_counter == 1, "action not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().current_state()[0] == 0, "Song1 should be active"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().entry_counter == 1, "Song1 entry not called correctly"); //flags BOOST_CHECK_MESSAGE(p.is_flag_active() == true, "PlayingPaused should be active"); BOOST_CHECK_MESSAGE(p.is_flag_active() == true, "FirstSongPlaying should be active"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().current_state()[0] == 1, "Song2 should be active"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().entry_counter == 1, "Song2 entry not called correctly"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().exit_counter == 1, "Song1 exit not called correctly"); BOOST_CHECK_MESSAGE( p.get_state&>().start_next_song_counter == 0, "submachine action not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().current_state()[0] == 2, "Song3 should be active"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().entry_counter == 1, "Song3 entry not called correctly"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().exit_counter == 1, "Song2 exit not called correctly"); BOOST_CHECK_MESSAGE( p.get_state&>().start_next_song_counter == 1, "submachine action not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().current_state()[0] == 1, "Song2 should be active"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().entry_counter == 2, "Song2 entry not called correctly"); BOOST_CHECK_MESSAGE( p.get_state&>().get_state&>().exit_counter == 1, "Song3 exit not called correctly"); BOOST_CHECK_MESSAGE( p.get_state&>().start_prev_song_guard_counter == 1, "submachine guard not called correctly"); //flags BOOST_CHECK_MESSAGE(p.is_flag_active() == true, "PlayingPaused should be active"); BOOST_CHECK_MESSAGE(p.is_flag_active() == false, "FirstSongPlaying should not be active"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 4, "Paused should be active"); //Paused BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "Playing exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "Paused entry not called correctly"); //flags BOOST_CHECK_MESSAGE(p.is_flag_active() == true, "PlayingPaused should be active"); // go back to Playing p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "Paused exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 2, "Playing entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 4, "Paused should be active"); //Paused BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Playing exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 2, "Paused entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 0, "Stopped should be active"); //Stopped BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Paused exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 2, "Stopped entry not called correctly"); //flags BOOST_CHECK_MESSAGE(p.is_flag_active() == false, "PlayingPaused should not be active"); BOOST_CHECK_MESSAGE(p.is_flag_active() == true, "CDLoaded should be active"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 0, "Stopped should be active"); //Stopped BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Stopped exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 3, "Stopped entry not called correctly"); //test interrupt BOOST_CHECK_MESSAGE(p.current_state()[1] == 5, "AllOk should be active"); //AllOk p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 6, "ErrorMode should be active"); //ErrorMode BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "AllOk exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "ErrorMode entry not called correctly"); // try generating more events p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 6, "ErrorMode should be active"); //ErrorMode BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "AllOk exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "ErrorMode entry not called correctly"); BOOST_CHECK_MESSAGE(p.current_state()[0] == 0, "Stopped should be active"); //Stopped BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Stopped exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 3, "Stopped entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 5, "AllOk should be active"); //AllOk BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 1, "ErrorMode exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 2, "AllOk entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 3, "Stopped exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 3, "Playing entry not called correctly"); //test terminate BOOST_CHECK_MESSAGE(p.current_state()[1] == 5, "AllOk should be active"); //AllOk p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 7, "ErrorTerminate should be active"); //ErrorTerminate BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "AllOk exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 1, "ErrorTerminate entry not called correctly"); // try generating more events p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 7, "ErrorTerminate should be active"); //ErrorTerminate BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 0, "ErrorTerminate exit not called correctly"); BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Playing exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 3, "Stopped entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 7, "ErrorTerminate should be active"); //ErrorTerminate BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Playing exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 3, "Stopped entry not called correctly"); p.process_event(Event{}); BOOST_CHECK_MESSAGE(p.current_state()[1] == 7, "ErrorTerminate should be active"); //ErrorTerminate BOOST_CHECK_MESSAGE(p.current_state()[0] == 3, "Playing should be active"); //Playing BOOST_CHECK_MESSAGE(p.get_state&>().exit_counter == 2, "Playing exit not called correctly"); BOOST_CHECK_MESSAGE(p.get_state&>().entry_counter == 3, "Stopped entry not called correctly"); } }