1 // Copyright 2010 Christophe Henry
2 // henry UNDERSCORE christophe AT hotmail DOT com
3 // This is an extended version of the state machine available in the boost::mpl library
4 // Distributed under the same license as the original.
5 // Copyright for the original version:
6 // Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed
7 // under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at
9 // http://www.boost.org/LICENSE_1_0.txt)
10 
11 #include <iostream>
12 #include <boost/msm/back/state_machine.hpp>
13 #include <boost/msm/front/euml/euml.hpp>
14 
15 #ifndef BOOST_MSM_NONSTANDALONE_TEST
16 #define BOOST_TEST_MODULE MyTest
17 #endif
18 #include <boost/test/unit_test.hpp>
19 
20 using namespace std;
21 using namespace boost::msm::front::euml;
22 namespace msm = boost::msm;
23 
24 namespace
25 {
26     // A "complicated" event type that carries some data.
27     enum DiskTypeEnum
28     {
29         DISK_CD=0,
30         DISK_DVD=1
31     };
32     // events
33     BOOST_MSM_EUML_EVENT(play)
34     BOOST_MSM_EUML_EVENT(end_pause)
35     BOOST_MSM_EUML_EVENT(stop)
36     BOOST_MSM_EUML_EVENT(pause)
37     BOOST_MSM_EUML_EVENT(open_close)
38     BOOST_MSM_EUML_EVENT(next_song)
39     BOOST_MSM_EUML_EVENT(previous_song)
40     BOOST_MSM_EUML_EVENT(region2_evt)
41     // A "complicated" event type that carries some data.
42     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(std::string,cd_name)
43     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(DiskTypeEnum,cd_type)
44     BOOST_MSM_EUML_ATTRIBUTES((attributes_ << cd_name << cd_type ), cd_detected_attributes)
45     BOOST_MSM_EUML_EVENT_WITH_ATTRIBUTES(cd_detected,cd_detected_attributes)
46 
47     //states
48     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,entry_counter)
49     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,exit_counter)
50 
51     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Empty)
52     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Open)
53     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Stopped)
54     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Paused)
55 
56     // Playing is now a state machine itself.
57     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,start_next_song_counter)
58     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,start_prev_song_guard_counter)
59     // It has 5 substates
60     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Song1)
61     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Song2)
62     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Song3)
63     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Region2State1)
64     BOOST_MSM_EUML_STATE(( ++state_(entry_counter),++state_(exit_counter),attributes_ << entry_counter << exit_counter),Region2State2)
65 
66     // Playing has a transition table
67    BOOST_MSM_EUML_TRANSITION_TABLE((
68         //  +------------------------------------------------------------------------------+
69             Song2         == Song1          + next_song                                                         ,
70             Song1         == Song2          + previous_song   [True_()] / ++fsm_(start_prev_song_guard_counter) ,
71             Song3         == Song2          + next_song       / ++fsm_(start_next_song_counter)                 ,
72             Song2         == Song3          + previous_song   [True_()] / ++fsm_(start_prev_song_guard_counter) ,
73             Region2State2 == Region2State1  + region2_evt
74         //  +------------------------------------------------------------------------------+
75         ),playing_transition_table )
76 
77     BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (playing_transition_table, //STT
78                                         init_ << Song1 << Region2State1, // Init State
79                                         ++state_(entry_counter), // Entry
80                                         ++state_(exit_counter), // Exit
81                                         attributes_ << entry_counter << exit_counter
82                                                     << start_next_song_counter
83                                                     << start_prev_song_guard_counter // Attributes
84                                         ),Playing_)
85     // choice of back-end
86     typedef msm::back::state_machine<Playing_> Playing_type;
87     Playing_type const Playing;
88 
89 
90     //fsm
BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,start_playback_counter)91     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,start_playback_counter)
92     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,can_close_drawer_counter)
93     BOOST_MSM_EUML_DECLARE_ATTRIBUTE(unsigned int,test_fct_counter)
94     BOOST_MSM_EUML_ACTION(No_Transition)
95     {
96         template <class FSM,class Event>
97         void operator()(Event const&,FSM&,int)
98         {
99             BOOST_FAIL("no_transition called!");
100         }
101     };
BOOST_MSM_EUML_ACTION(good_disk_format)102     BOOST_MSM_EUML_ACTION(good_disk_format)
103     {
104         template <class FSM,class EVT,class SourceState,class TargetState>
105         bool operator()(EVT const& evt,FSM&,SourceState& ,TargetState& )
106         {
107             if (evt.get_attribute(cd_type)!=DISK_CD)
108             {
109                 return false;
110             }
111             return true;
112         }
113     };
114     BOOST_MSM_EUML_TRANSITION_TABLE((
115           Playing   == Stopped  + play        / (++fsm_(start_playback_counter),++fsm_(test_fct_counter) ),
116           Playing   == Paused   + end_pause   ,
117           //  +------------------------------------------------------------------------------+
118           Empty     == Open     + open_close  / ++fsm_(can_close_drawer_counter),
119           //  +------------------------------------------------------------------------------+
120           Open      == Empty    + open_close  ,
121           Open      == Paused   + open_close  ,
122           Open      == Stopped  + open_close  ,
123           Open      == Playing  + open_close  ,
124           //  +------------------------------------------------------------------------------+
125           Paused    == Playing  + pause       ,
126           //  +------------------------------------------------------------------------------+
127           Stopped   == Playing  + stop        ,
128           Stopped   == Paused   + stop        ,
129           Stopped   == Empty    + cd_detected [good_disk_format ||
130                                                 (event_(cd_type)==Int_<DISK_CD>())] / process_(play) ,
131           Stopped   == Stopped  + stop
132           //  +------------------------------------------------------------------------------+
133          ),transition_table)
134 
135     BOOST_MSM_EUML_DECLARE_STATE_MACHINE(( transition_table, //STT
136                                         init_ << Empty, // Init State
137                                         no_action, // Entry
138                                         no_action, // Exit
139                                         attributes_ << start_playback_counter
140                                                     << can_close_drawer_counter << test_fct_counter, // Attributes
141                                         configure_ << no_configure_, // configuration
142                                         No_Transition // no_transition handler
143                                         ),
144                                       player_) //fsm name
145 
146     typedef msm::back::state_machine<player_> player;
147 
148 //    static char const* const state_names[] = { "Stopped", "Paused", "Open", "Empty", "Playing" };
149 
150 
BOOST_AUTO_TEST_CASE(my_test)151     BOOST_AUTO_TEST_CASE( my_test )
152     {
153         player p;
154 
155         p.start();
156         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Empty)&>().get_attribute(entry_counter) == 1,
157                             "Empty entry not called correctly");
158 
159         p.process_event(open_close());
160         BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Open should be active"); //Open
161         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Empty)&>().get_attribute(exit_counter) == 1,
162                             "Empty exit not called correctly");
163         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Open)&>().get_attribute(entry_counter) == 1,
164                             "Open entry not called correctly");
165 
166         p.process_event(open_close());
167         BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Empty should be active"); //Empty
168         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Open)&>().get_attribute(exit_counter) == 1,
169                             "Open exit not called correctly");
170         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Empty)&>().get_attribute(entry_counter) == 2,
171                             "Empty entry not called correctly");
172         BOOST_CHECK_MESSAGE(p.get_attribute(can_close_drawer_counter) == 1,"guard not called correctly");
173 
174         p.process_event(
175             cd_detected("louie, louie",DISK_DVD));
176         BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Empty should be active"); //Empty
177         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Open)&>().get_attribute(exit_counter) == 1,
178                             "Open exit not called correctly");
179         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Empty)&>().get_attribute(entry_counter) == 2,
180                             "Empty entry not called correctly");
181 
182         p.process_event(
183             cd_detected("louie, louie",DISK_CD));
184         BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Playing should be active"); //Playing
185         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Empty)&>().get_attribute(exit_counter) == 2,
186                             "Empty exit not called correctly");
187         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Stopped)&>().get_attribute(entry_counter) == 1,
188                             "Stopped entry not called correctly");
189         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Stopped)&>().get_attribute(exit_counter) == 1,
190                             "Stopped exit not called correctly");
191         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().get_attribute(entry_counter) == 1,
192                             "Playing entry not called correctly");
193         BOOST_CHECK_MESSAGE(p.get_attribute(start_playback_counter) == 1,"action not called correctly");
194         BOOST_CHECK_MESSAGE(p.get_attribute(test_fct_counter) == 1,"action not called correctly");
195 
196         p.process_event(next_song);
197         BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Playing should be active"); //Playing
198         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().current_state()[0] == 1,"Song2 should be active");
199         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().current_state()[1] == 3,"Region2State1 should be active");
200         BOOST_CHECK_MESSAGE(
201             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Region2State1)&>().get_attribute(entry_counter) == 1,
202             "Region2State1 entry not called correctly");
203         BOOST_CHECK_MESSAGE(
204             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Song2)&>().get_attribute(entry_counter) == 1,
205             "Song2 entry not called correctly");
206         BOOST_CHECK_MESSAGE(
207             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Song1)&>().get_attribute(exit_counter) == 1,
208             "Song1 exit not called correctly");
209         BOOST_CHECK_MESSAGE(
210             p.get_state<Playing_type&>().get_attribute(start_next_song_counter) == 0,
211             "submachine action not called correctly");
212 
213         p.process_event(next_song);
214         BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Playing should be active"); //Playing
215         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().current_state()[0] == 2,"Song3 should be active");
216         BOOST_CHECK_MESSAGE(
217             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Song3)&>().get_attribute(entry_counter) == 1,
218             "Song3 entry not called correctly");
219         BOOST_CHECK_MESSAGE(
220             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Song2)&>().get_attribute(exit_counter) == 1,
221             "Song2 exit not called correctly");
222         BOOST_CHECK_MESSAGE(
223             p.get_state<Playing_type&>().get_attribute(start_next_song_counter) == 1,
224             "submachine action not called correctly");
225 
226         p.process_event(previous_song);
227         BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Playing should be active"); //Playing
228         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().current_state()[0] == 1,"Song2 should be active");
229         BOOST_CHECK_MESSAGE(
230             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Song2)&>().get_attribute(entry_counter) == 2,
231             "Song2 entry not called correctly");
232         BOOST_CHECK_MESSAGE(
233             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Song3)&>().get_attribute(exit_counter) == 1,
234             "Song3 exit not called correctly");
235         BOOST_CHECK_MESSAGE(
236             p.get_state<Playing_type&>().get_attribute(start_prev_song_guard_counter) == 1,
237             "submachine guard not called correctly");
238         BOOST_CHECK_MESSAGE(
239             p.get_state<Playing_type&>().get_state<BOOST_MSM_EUML_STATE_NAME(Region2State2)&>().get_attribute(entry_counter) == 0,
240             "Region2State2 entry not called correctly");
241 
242 
243         p.process_event(pause());
244         BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Paused should be active"); //Paused
245         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().get_attribute(exit_counter) == 1,
246                             "Playing exit not called correctly");
247         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Paused)&>().get_attribute(entry_counter) == 1,
248                             "Paused entry not called correctly");
249 
250         // go back to Playing
251         p.process_event(end_pause());
252         BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Playing should be active"); //Playing
253         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Paused)&>().get_attribute(exit_counter) == 1,
254                             "Paused exit not called correctly");
255         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().get_attribute(entry_counter) == 2,
256                             "Playing entry not called correctly");
257 
258         p.process_event(pause());
259         BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Paused should be active"); //Paused
260         BOOST_CHECK_MESSAGE(p.get_state<Playing_type&>().get_attribute(exit_counter) == 2,
261                             "Playing exit not called correctly");
262         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Paused)&>().get_attribute(entry_counter) == 2,
263                             "Paused entry not called correctly");
264 
265         p.process_event(stop());
266         BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped
267         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Paused)&>().get_attribute(exit_counter) == 2,
268                             "Paused exit not called correctly");
269         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Stopped)&>().get_attribute(entry_counter) == 2,
270                             "Stopped entry not called correctly");
271 
272         p.process_event(stop());
273         BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped
274         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Stopped)&>().get_attribute(exit_counter) == 2,
275                             "Stopped exit not called correctly");
276         BOOST_CHECK_MESSAGE(p.get_state<BOOST_MSM_EUML_STATE_NAME(Stopped)&>().get_attribute(entry_counter) == 3,
277                             "Stopped entry not called correctly");
278     }
279 }
280 
281