////////////////////////////////////////////////////////////////////////////// // Copyright 2004-2007 Andreas Huber Doenni // Distributed under the Boost Software License, Version 1.0. (See accompany- // ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ////////////////////////////////////////////////////////////////////////////// #include "OuterOrthogonal.hpp" #include "InnermostDefault.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace sc = boost::statechart; namespace mpl = boost::mpl; typedef std::string ActionDescription(); typedef ActionDescription * ActionDescriptionPtr; typedef std::vector< ActionDescriptionPtr > ActionDescriptionSequence; typedef ActionDescriptionSequence::const_iterator SequenceIterator; typedef void Action( ActionDescriptionSequence & ); typedef Action * ActionPtr; template< class State > std::string EntryDescription() { static const std::string entry = "Entry: "; return entry + typeid( State ).name() + "\n"; } template< class State > std::string ExitFnDescription() { static const std::string exitFunction = "exit(): "; return exitFunction + typeid( State ).name() + "\n"; } template< class State > std::string DtorDescription() { static const std::string destructor = "Destructor: "; return destructor + typeid( State ).name() + "\n"; } template< class Context, class Event > std::string TransDescription() { static const std::string transition = "Transition: "; static const std::string event = " with Event: "; return transition + typeid( Context ).name() + event + typeid( Event ).name() + "\n"; } template< ActionPtr pAction > std::string ThrowDescription() { static const std::string throwing = "Throwing exception in "; ActionDescriptionSequence sequence; pAction( sequence ); return throwing + sequence.front()(); } template< class State > void Entry( ActionDescriptionSequence & sequence ) { sequence.push_back( &EntryDescription< State > ); } template< class State > void ExitFn( ActionDescriptionSequence & sequence ) { sequence.push_back( &ExitFnDescription< State > ); } template< class State > void Dtor( ActionDescriptionSequence & sequence ) { sequence.push_back( &DtorDescription< State > ); } template< class State > void Exit( ActionDescriptionSequence & sequence ) { ExitFn< State >( sequence ); Dtor< State >( sequence ); } template< class Context, class Event > void Trans( ActionDescriptionSequence & sequence ) { sequence.push_back( &TransDescription< Context, Event > ); } template< ActionPtr pAction > void Throw( ActionDescriptionSequence & sequence ) { sequence.push_back( &ThrowDescription< pAction > ); } const int arrayLength = 30; typedef ActionPtr ActionArray[ arrayLength ]; class TransitionTestException : public std::runtime_error { public: TransitionTestException() : std::runtime_error( "Oh la la!" ) {} }; // This test state machine is a beefed-up version of the one presented in // "Practical Statecharts in C/C++" by Miro Samek, CMP Books 2002 struct A : sc::event< A > {}; struct B : sc::event< B > {}; struct C : sc::event< C > {}; struct D : sc::event< D > {}; struct E : sc::event< E > {}; struct F : sc::event< F > {}; struct G : sc::event< G > {}; struct H : sc::event< H > {}; template< class M > struct S0; template< class Translator > struct TransitionTest : sc::state_machine< TransitionTest< Translator >, S0< TransitionTest< Translator > >, std::allocator< sc::none >, Translator > { public: ////////////////////////////////////////////////////////////////////////// TransitionTest() : pThrowAction_( 0 ), unconsumedEventCount_( 0 ) {} ~TransitionTest() { // Since state destructors access the state machine object, we need to // make sure that all states are destructed before this subtype // portion is destructed. this->terminate(); } void CompareToExpectedActionSequence( ActionArray & actions ) { expectedSequence_.clear(); // Copy all non-null pointers in actions into expectedSequence_ for ( ActionPtr * pCurrent = &actions[ 0 ]; ( pCurrent != &actions[ arrayLength ] ) && ( *pCurrent != 0 ); ++pCurrent ) { ( *pCurrent )( expectedSequence_ ); } if ( ( expectedSequence_.size() != actualSequence_.size() ) || !std::equal( expectedSequence_.begin(), expectedSequence_.end(), actualSequence_.begin() ) ) { std::string message = "\nExpected action sequence:\n"; for ( SequenceIterator pExpected = expectedSequence_.begin(); pExpected != expectedSequence_.end(); ++pExpected ) { message += ( *pExpected )(); } message += "\nActual action sequence:\n"; for ( SequenceIterator pActual = actualSequence_.begin(); pActual != actualSequence_.end(); ++pActual ) { message += ( *pActual )(); } BOOST_FAIL( message.c_str() ); } actualSequence_.clear(); } void ClearActualSequence() { actualSequence_.clear(); } void ThrowAction( ActionPtr pThrowAction ) { pThrowAction_ = pThrowAction; } template< class State > void ActualEntry() { StoreActualAction< &Entry< State > >(); } template< class State > void ActualExitFunction() { StoreActualAction< &ExitFn< State > >(); } template< class State > void ActualDestructor() { StoreActualAction< &Dtor< State > >(); } template< class Context, class Event > void ActualTransition() { StoreActualAction< &Trans< Context, Event > >(); } void unconsumed_event( const sc::event_base & ) { ++unconsumedEventCount_; } unsigned int GetUnconsumedEventCount() const { return unconsumedEventCount_; } private: ////////////////////////////////////////////////////////////////////////// template< ActionPtr pAction > void StoreActualAction() { if ( pAction == pThrowAction_ ) { Throw< pAction >( actualSequence_ ); throw TransitionTestException(); } else { pAction( actualSequence_ ); } } ActionPtr pThrowAction_; ActionDescriptionSequence actualSequence_; ActionDescriptionSequence expectedSequence_; unsigned int unconsumedEventCount_; }; template< class M > struct S1; template< class M > struct S211; template< class M > struct S0 : Orthogonal0< S0< M >, M, S1< M > > { typedef Orthogonal0< S0< M >, M, S1< M > > my_base; public: typedef sc::transition< E, S211< M > > reactions; S0( typename my_base::my_context ctx ) : my_base( ctx ) {} void Transit( const A & evt ) { TransitImpl( evt ); } void Transit( const B & evt ) { TransitImpl( evt ); } void Transit( const C & evt ) { TransitImpl( evt ); } void Transit( const D & evt ) { TransitImpl( evt ); } void Transit( const F & evt ) { TransitImpl( evt ); } void Transit( const G & evt ) { TransitImpl( evt ); } void Transit( const H & evt ) { TransitImpl( evt ); } private: template< class Event > void TransitImpl( const Event & ) { this->outermost_context().template ActualTransition< S0< M >, Event >(); } }; template< class M > struct S11; template< class M > struct S21; template< class M > struct S2 : Orthogonal2< S2< M >, S0< M >, S21< M > > { typedef Orthogonal2< S2< M >, S0< M >, S21< M > > my_base; typedef mpl::list< sc::transition< C, S1< M >, S0< M >, &S0< M >::Transit >, sc::transition< F, S11< M >, S0< M >, &S0< M >::Transit > > reactions; S2( typename my_base::my_context ctx ) : my_base( ctx ) {} }; template< class M > struct S21 : Orthogonal1< S21< M >, typename S2< M >::template orthogonal< 2 >, S211< M > > { typedef Orthogonal1< S21< M >, typename S2< M >::template orthogonal< 2 >, S211< M > > my_base; typedef mpl::list< sc::transition< H, S21< M >, S0< M >, &S0< M >::Transit >, sc::transition< B, S211< M >, S0< M >, &S0< M >::Transit > > reactions; S21( typename my_base::my_context ctx ) : my_base( ctx ) {} }; template< class M > struct S211 : InnermostDefault< S211< M >, typename S21< M >::template orthogonal< 1 > > { typedef InnermostDefault< S211< M >, typename S21< M >::template orthogonal< 1 > > my_base; typedef mpl::list< sc::transition< D, S21< M >, S0< M >, &S0< M >::Transit >, sc::transition< G, S0< M > > > reactions; S211( typename my_base::my_context ctx ) : my_base( ctx ) {} }; template< class M > struct S1 : Orthogonal1< S1< M >, S0< M >, S11< M > > { typedef Orthogonal1< S1< M >, S0< M >, S11< M > > my_base; typedef mpl::list< sc::transition< A, S1< M >, S0< M >, &S0< M >::Transit >, sc::transition< B, S11< M >, S0< M >, &S0< M >::Transit >, sc::transition< C, S2< M >, S0< M >, &S0< M >::Transit >, sc::transition< D, S0< M > >, sc::transition< F, S211< M >, S0< M >, &S0< M >::Transit > > reactions; S1( typename my_base::my_context ctx ) : my_base( ctx ) {} }; template< class M > struct S11 : InnermostDefault< S11< M >, typename S1< M >::template orthogonal< 1 > > { typedef InnermostDefault< S11< M >, typename S1< M >::template orthogonal< 1 > > my_base; typedef mpl::list< sc::transition< G, S211< M >, S0< M >, &S0< M >::Transit >, sc::custom_reaction< H > > reactions; S11( typename my_base::my_context ctx ) : my_base( ctx ) {} sc::result react( const H & ) { this->outermost_context().template ActualTransition< S11< M >, H >(); return this->discard_event(); } }; struct X1; struct TransitionEventBaseTest : sc::state_machine< TransitionEventBaseTest, X1 > { public: TransitionEventBaseTest() : actionCallCounter_( 0 ) {} void Transit( const sc::event_base & eventBase ) { BOOST_REQUIRE( ( dynamic_cast< const B * >( &eventBase ) != 0 ) || ( dynamic_cast< const D * >( &eventBase ) != 0 ) ); ++actionCallCounter_; } unsigned int GetActionCallCounter() const { return actionCallCounter_; } private: unsigned int actionCallCounter_; }; struct X2 : sc::simple_state< X2, TransitionEventBaseTest > { typedef sc::transition< sc::event_base, X1, TransitionEventBaseTest, &TransitionEventBaseTest::Transit > reactions; }; struct X1 : sc::simple_state< X1, TransitionEventBaseTest > { typedef sc::transition< sc::event_base, X2 > reactions; }; template< class M > void TestTransitions( M & machine ) { machine.initiate(); ActionArray init = { Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > > }; machine.CompareToExpectedActionSequence( init ); machine.process_event( A() ); ActionArray a1 = { Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Trans< S0< M >, A >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > > }; machine.CompareToExpectedActionSequence( a1 ); machine.process_event( B() ); ActionArray b1 = { Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Trans< S0< M >, B >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > > }; machine.CompareToExpectedActionSequence( b1 ); machine.process_event( C() ); ActionArray c1 = { Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Trans< S0< M >, C >, Entry< S2< M > >, Entry< Default0< S2< M > > >, Entry< Default1< S2< M > > >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > > }; machine.CompareToExpectedActionSequence( c1 ); machine.process_event( D() ); ActionArray d2 = { Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Trans< S0< M >, D >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > > }; machine.CompareToExpectedActionSequence( d2 ); machine.process_event( E() ); ActionArray e2 = { Exit< Default2< S0< M > > >, Exit< Default1< S0< M > > >, Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Exit< Default1< S2< M > > >, Exit< Default0< S2< M > > >, Exit< S2< M > >, Exit< S0< M > >, Entry< S0< M > >, Entry< S2< M > >, Entry< Default0< S2< M > > >, Entry< Default1< S2< M > > >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > > }; machine.CompareToExpectedActionSequence( e2 ); machine.process_event( F() ); ActionArray f2 = { Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Exit< Default1< S2< M > > >, Exit< Default0< S2< M > > >, Exit< S2< M > >, Trans< S0< M >, F >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > > }; machine.CompareToExpectedActionSequence( f2 ); machine.process_event( G() ); ActionArray g1 = { Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Trans< S0< M >, G >, Entry< S2< M > >, Entry< Default0< S2< M > > >, Entry< Default1< S2< M > > >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > > }; machine.CompareToExpectedActionSequence( g1 ); machine.process_event( H() ); ActionArray h2 = { Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Trans< S0< M >, H >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > > }; machine.CompareToExpectedActionSequence( h2 ); BOOST_REQUIRE( machine.GetUnconsumedEventCount() == 0 ); machine.process_event( A() ); BOOST_REQUIRE( machine.GetUnconsumedEventCount() == 1 ); ActionArray a2 = { }; machine.CompareToExpectedActionSequence( a2 ); machine.process_event( B() ); ActionArray b2 = { Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Trans< S0< M >, B >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > > }; machine.CompareToExpectedActionSequence( b2 ); machine.process_event( C() ); ActionArray c2 = { Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Exit< Default1< S2< M > > >, Exit< Default0< S2< M > > >, Exit< S2< M > >, Trans< S0< M >, C >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > > }; machine.CompareToExpectedActionSequence( c2 ); machine.process_event( D() ); ActionArray d1 = { Exit< Default2< S0< M > > >, Exit< Default1< S0< M > > >, Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Exit< S0< M > >, Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > > }; machine.CompareToExpectedActionSequence( d1 ); machine.process_event( F() ); ActionArray f1 = { Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Trans< S0< M >, F >, Entry< S2< M > >, Entry< Default0< S2< M > > >, Entry< Default1< S2< M > > >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > > }; machine.CompareToExpectedActionSequence( f1 ); machine.process_event( G() ); ActionArray g2 = { Exit< Default2< S0< M > > >, Exit< Default1< S0< M > > >, Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Exit< Default1< S2< M > > >, Exit< Default0< S2< M > > >, Exit< S2< M > >, Exit< S0< M > >, Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > > }; machine.CompareToExpectedActionSequence( g2 ); machine.process_event( H() ); ActionArray h1 = { Trans< S11< M >, H > }; machine.CompareToExpectedActionSequence( h1 ); machine.process_event( E() ); ActionArray e1 = { Exit< Default2< S0< M > > >, Exit< Default1< S0< M > > >, Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Exit< S0< M > >, Entry< S0< M > >, Entry< S2< M > >, Entry< Default0< S2< M > > >, Entry< Default1< S2< M > > >, Entry< S21< M > >, Entry< Default0< S21< M > > >, Entry< S211< M > >, Entry< Default2< S21< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > > }; machine.CompareToExpectedActionSequence( e1 ); machine.terminate(); ActionArray term = { Exit< Default2< S0< M > > >, Exit< Default1< S0< M > > >, Exit< Default2< S21< M > > >, Exit< S211< M > >, Exit< Default0< S21< M > > >, Exit< S21< M > >, Exit< Default1< S2< M > > >, Exit< Default0< S2< M > > >, Exit< S2< M > >, Exit< S0< M > > }; machine.CompareToExpectedActionSequence( term ); machine.ThrowAction( &Entry< Default0< S1< M > > > ); BOOST_REQUIRE_THROW( machine.initiate(), TransitionTestException ); ActionArray initThrow1 = { Entry< S0< M > >, Entry< S1< M > >, &::Throw< &::Entry< Default0< S1< M > > > >, Dtor< S1< M > >, Dtor< S0< M > > }; machine.CompareToExpectedActionSequence( initThrow1 ); BOOST_REQUIRE( machine.terminated() ); machine.ThrowAction( &Entry< S11< M > > ); BOOST_REQUIRE_THROW( machine.initiate(), TransitionTestException ); ActionArray initThrow2 = { Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, &::Throw< &::Entry< S11< M > > >, Dtor< Default0< S1< M > > >, Dtor< S1< M > >, Dtor< S0< M > > }; machine.CompareToExpectedActionSequence( initThrow2 ); BOOST_REQUIRE( machine.terminated() ); machine.ThrowAction( &Trans< S0< M >, A > ); machine.initiate(); BOOST_REQUIRE_THROW( machine.process_event( A() ), TransitionTestException ); ActionArray a1Throw1 = { Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > >, Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, &::Throw< &::Trans< S0< M >, A > >, Dtor< Default2< S0< M > > >, Dtor< Default1< S0< M > > >, Dtor< S0< M > > }; machine.CompareToExpectedActionSequence( a1Throw1 ); BOOST_REQUIRE( machine.terminated() ); machine.ThrowAction( &Entry< S211< M > > ); machine.initiate(); BOOST_REQUIRE_THROW( machine.process_event( C() ), TransitionTestException ); ActionArray c1Throw1 = { Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > >, Exit< Default2< S1< M > > >, Exit< S11< M > >, Exit< Default0< S1< M > > >, Exit< S1< M > >, Trans< S0< M >, C >, Entry< S2< M > >, Entry< Default0< S2< M > > >, Entry< Default1< S2< M > > >, Entry< S21< M > >, Entry< Default0< S21< M > > >, &::Throw< &::Entry< S211< M > > >, Dtor< Default2< S0< M > > >, Dtor< Default1< S0< M > > >, Dtor< Default0< S21< M > > >, Dtor< S21< M > >, Dtor< Default1< S2< M > > >, Dtor< Default0< S2< M > > >, Dtor< S2< M > >, Dtor< S0< M > > }; machine.CompareToExpectedActionSequence( c1Throw1 ); BOOST_REQUIRE( machine.terminated() ); machine.ThrowAction( &ExitFn< S11< M > > ); machine.initiate(); BOOST_REQUIRE_THROW( machine.process_event( C() ), TransitionTestException ); ActionArray c1Throw2 = { Entry< S0< M > >, Entry< S1< M > >, Entry< Default0< S1< M > > >, Entry< S11< M > >, Entry< Default2< S1< M > > >, Entry< Default1< S0< M > > >, Entry< Default2< S0< M > > >, Exit< Default2< S1< M > > >, &::Throw< &::ExitFn< S11< M > > >, Dtor< S11< M > >, Dtor< Default2< S0< M > > >, Dtor< Default1< S0< M > > >, Dtor< Default0< S1< M > > >, Dtor< S1< M > >, Dtor< S0< M > > }; machine.CompareToExpectedActionSequence( c1Throw2 ); BOOST_REQUIRE( machine.terminated() ); BOOST_REQUIRE( machine.GetUnconsumedEventCount() == 1 ); } int test_main( int, char* [] ) { TransitionTest< sc::null_exception_translator > null_machine; TestTransitions( null_machine ); TransitionTest< sc::exception_translator<> > machine; TestTransitions( machine ); TransitionEventBaseTest eventBaseMachine; eventBaseMachine.initiate(); BOOST_REQUIRE_NO_THROW( eventBaseMachine.state_cast< const X1 & >() ); eventBaseMachine.process_event( A() ); BOOST_REQUIRE_NO_THROW( eventBaseMachine.state_cast< const X2 & >() ); BOOST_REQUIRE( eventBaseMachine.GetActionCallCounter() == 0 ); eventBaseMachine.process_event( B() ); BOOST_REQUIRE_NO_THROW( eventBaseMachine.state_cast< const X1 & >() ); BOOST_REQUIRE( eventBaseMachine.GetActionCallCounter() == 1 ); eventBaseMachine.process_event( C() ); BOOST_REQUIRE_NO_THROW( eventBaseMachine.state_cast< const X2 & >() ); BOOST_REQUIRE( eventBaseMachine.GetActionCallCounter() == 1 ); eventBaseMachine.process_event( D() ); BOOST_REQUIRE_NO_THROW( eventBaseMachine.state_cast< const X1 & >() ); BOOST_REQUIRE( eventBaseMachine.GetActionCallCounter() == 2 ); return 0; }