2008-12-31 05:06:17 +01:00
/*
SubsystemRunner ( Test ) - validate starting and stopping of dependent subsystems
Copyright ( C ) Lumiera . org
2008 , Hermann Vosseler < Ichthyostega @ web . de >
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation ; either version 2 of the
License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "lib/test/run.hpp"
# include "common/subsys.hpp"
# include "common/subsystem-runner.hpp"
2009-01-02 10:57:13 +01:00
# include "common/option.hpp"
2008-12-31 05:06:17 +01:00
# include "include/symbol.hpp"
2008-12-31 06:56:31 +01:00
# include "lib/thread-wrapper.hpp"
2008-12-31 05:06:17 +01:00
# include "lib/error.hpp"
# include "lib/query.hpp"
# include "lib/util.hpp"
2008-12-31 06:56:31 +01:00
# include "lib/sync.hpp"
2008-12-31 05:06:17 +01:00
# include <tr1/functional>
# include <iostream>
using std : : cout ;
using std : : tr1 : : bind ;
using util : : isnil ;
2009-01-03 04:29:59 +01:00
using util : : cStr ;
2008-12-31 05:06:17 +01:00
using test : : Test ;
2008-12-31 06:56:31 +01:00
using lib : : Sync ;
using lib : : RecursiveLock_Waitable ;
2009-01-03 16:05:04 +01:00
using lib : : Thread ;
2008-12-31 05:06:17 +01:00
namespace lumiera {
namespace test {
2009-01-03 13:32:24 +01:00
namespace { // private test classes and data...
2008-12-31 05:06:17 +01:00
using query : : extractID ;
/** limit for the randomly selected duration of
* subsystem ' s running phase ( milliseconds ) */
2009-01-03 13:32:24 +01:00
const uint MAX_RUNNING_TIME_ms = 80 ;
2009-01-03 04:29:59 +01:00
const uint MIN_RUNNING_TIME_ms = 10 ;
2008-12-31 05:06:17 +01:00
/** the "running" subsystem checks for a
2009-01-03 13:32:24 +01:00
* shutdown request every XX milliseconds */
2008-12-31 05:06:17 +01:00
const uint TICK_DURATION_ms = 5 ;
2009-01-02 10:57:13 +01:00
/** dummy options just to be ignored */
util : : Cmdline dummyArgs ( " " ) ;
lumiera : : Option dummyOpt ( dummyArgs ) ;
2009-01-03 01:11:21 +01:00
/** marker for simulated failure exceptions */
LUMIERA_ERROR_DEFINE ( TEST , " simulated failure. " ) ;
2009-01-03 13:32:24 +01:00
2009-01-03 01:11:21 +01:00
2009-01-02 10:57:13 +01:00
2008-12-31 05:06:17 +01:00
/**
* A simulated " Lumiera Subsystem " .
* It is capable of starting a separate thread ,
* which may terminate regularly after a random
* time , or may fail in various ways .
* The behaviour is controlled by a number of
* definitions , given at construction in
* logic predicate notation .
*/
class MockSys
: public lumiera : : Subsys ,
2008-12-31 06:56:31 +01:00
public Sync < RecursiveLock_Waitable >
2008-12-31 05:06:17 +01:00
{
Literal id_ ;
Literal spec_ ;
2009-01-03 01:11:21 +01:00
volatile bool isUp_ ;
volatile bool didRun_ ;
2008-12-31 05:06:17 +01:00
volatile bool termRequest_ ;
int running_duration_ ;
2009-01-03 13:32:24 +01:00
bool
2008-12-31 05:06:17 +01:00
shouldStart ( lumiera : : Option & )
{
Literal startSpec ( extractID ( " start " , spec_ ) ) ;
return " true " = = startSpec
| | " fail " = = startSpec
| | " throw " = = startSpec ;
}
2009-01-10 18:01:09 +01:00
2008-12-31 05:06:17 +01:00
bool
start ( lumiera : : Option & , Subsys : : SigTerm termination )
{
2009-01-03 04:29:59 +01:00
REQUIRE ( ! isUp_ , " attempt to start %s twice! " , cStr ( * this ) ) ;
2008-12-31 05:06:17 +01:00
2009-01-03 04:29:59 +01:00
Lock guard ( this ) ;
2008-12-31 05:06:17 +01:00
Literal startSpec ( extractID ( " start " , spec_ ) ) ;
ASSERT ( ! isnil ( startSpec ) ) ;
if ( " true " = = startSpec ) //----simulate successful subsystem start
{
2008-12-31 06:56:31 +01:00
Thread ( id_ , bind ( & MockSys : : run , this , termination ) ) ; /////TODO: the thread description should be rather "Test: "+string(*this), but this requires to implement the class Literal as planned
2008-12-31 05:06:17 +01:00
isUp_ = true ;
}
else
2009-01-03 13:32:24 +01:00
if ( " fail " = = startSpec ) //----not starting, incorrectly reporting success
2009-01-03 01:11:21 +01:00
return true ;
else
2008-12-31 05:06:17 +01:00
if ( " throw " = = startSpec ) //---starting flounders
2009-01-03 01:11:21 +01:00
throw error : : Fatal ( " simulated failure to start the subsystem " , LUMIERA_ERROR_TEST ) ;
2008-12-31 05:06:17 +01:00
return isUp_ ;
}
void
triggerShutdown ( ) throw ( )
{
2009-01-03 04:29:59 +01:00
// note: *not* locking here...
2008-12-31 05:06:17 +01:00
termRequest_ = true ;
2009-01-03 13:32:24 +01:00
INFO ( test , " triggerShutdown() --> %s.... " , cStr ( * this ) ) ;
2008-12-31 05:06:17 +01:00
}
bool
checkRunningState ( ) throw ( )
{
2009-01-03 04:29:59 +01:00
// note: *not* locking here...
2008-12-31 05:06:17 +01:00
return isUp_ ;
}
void
run ( Subsys : : SigTerm termination ) ///< simulates a "running" subsystem
{
Literal runSpec ( extractID ( " run " , spec_ ) ) ;
2008-12-31 06:56:31 +01:00
ASSERT ( ! isnil ( runSpec ) ) ;
2008-12-31 05:06:17 +01:00
2009-01-03 13:32:24 +01:00
if ( " false " ! = runSpec ) //----actually enter running state for some time
2008-12-31 05:06:17 +01:00
{
2009-01-02 10:57:13 +01:00
didRun_ = true ;
2009-01-03 04:29:59 +01:00
running_duration_ = MIN_RUNNING_TIME_ms ;
running_duration_ + = ( rand ( ) % ( MAX_RUNNING_TIME_ms - MIN_RUNNING_TIME_ms ) ) ;
2008-12-31 06:56:31 +01:00
2009-01-03 13:32:24 +01:00
INFO ( test , " thread %s now running.... " , cStr ( * this ) ) ;
2008-12-31 06:56:31 +01:00
Lock wait_blocking ( this , & MockSys : : tick ) ;
2008-12-31 05:06:17 +01:00
}
2009-01-03 01:11:21 +01:00
Error problemIndicator ( " simulated Problem killing a subsystem " , LUMIERA_ERROR_TEST ) ;
2008-12-31 05:06:17 +01:00
lumiera_error ( ) ; // reset error state....
// Note: in real life this actually
2009-01-03 13:32:24 +01:00
// would be an catched exception!
2009-01-10 16:15:17 +01:00
string problemReport ( problemIndicator . what ( ) ) ;
2008-12-31 05:06:17 +01:00
2009-01-03 01:11:21 +01:00
{
Lock guard ( this ) ;
isUp_ = false ;
2009-01-03 13:32:24 +01:00
INFO ( test , " thread %s terminates. " , cStr ( * this ) ) ;
2009-01-10 16:15:17 +01:00
termination ( " true " = = runSpec ? 0 : & problemReport ) ;
2009-01-03 01:11:21 +01:00
} }
2008-12-31 05:06:17 +01:00
bool
2009-01-03 13:32:24 +01:00
tick ( ) ///< simulates async termination, either on request or by timing
2008-12-31 05:06:17 +01:00
{
Lock sync ( this ) ;
if ( ! sync . isTimedWait ( ) )
{
sync . setTimeout ( TICK_DURATION_ms ) ;
running_duration_ + = TICK_DURATION_ms ;
}
running_duration_ - = TICK_DURATION_ms ;
return termRequest_ | | running_duration_ < = 0 ;
}
public :
MockSys ( Literal id , Literal spec )
: id_ ( id ) ,
spec_ ( spec ) ,
isUp_ ( false ) ,
2009-01-02 10:57:13 +01:00
didRun_ ( false ) ,
2008-12-31 05:06:17 +01:00
termRequest_ ( false ) ,
running_duration_ ( 0 )
{ }
2009-01-03 13:32:24 +01:00
~ MockSys ( ) { }
2008-12-31 06:56:31 +01:00
operator string ( ) const { return " MockSys( \" " + id_ + " \" ) " ; }
friend inline ostream &
operator < < ( ostream & os , MockSys const & mosi ) { return os < < string ( mosi ) ; }
2009-01-02 10:57:13 +01:00
bool didRun ( ) const { return didRun_ ; }
2008-12-31 05:06:17 +01:00
} ;
2009-01-03 13:32:24 +01:00
2008-12-31 05:06:17 +01:00
} // (End) test classes and data....
/**************************************************************************
* @ test managing start and stop of several dependent " subsystems "
* under various conditions . Using mock - subsystems , which actually
* spawn a thread and finish by themselves and generally behave sane .
2009-01-03 13:32:24 +01:00
* For each such MockSys , we can define a behaviour pattern , e . g .
* weather the start succeeds and if the run terminates with error .
2008-12-31 05:06:17 +01:00
*
* @ see lumiera : : Subsys
* @ see lumiera : : SubsystemRunner
* @ see lumiera : : AppState
* @ see main . cpp
*/
class SubsystemRunner_test : public Test
{
virtual void
2009-01-03 13:32:24 +01:00
run ( Arg )
2008-12-31 05:06:17 +01:00
{
2009-01-02 10:57:13 +01:00
singleSubsys_complete_cycle ( ) ;
2009-01-03 01:11:21 +01:00
singleSubsys_start_failure ( ) ;
singleSubsys_emegency_exit ( ) ;
dependentSubsys_complete_cycle ( ) ;
2009-01-03 13:32:24 +01:00
dependentSubsys_start_failure ( ) ;
2008-12-31 05:06:17 +01:00
}
2009-01-02 10:57:13 +01:00
void
singleSubsys_complete_cycle ( )
{
2009-01-03 04:29:59 +01:00
cout < < " -----singleSubsys_complete_cycle----- \n " ;
2009-01-02 10:57:13 +01:00
MockSys unit ( " one " , " start(true) , run ( true ) . " ) ;
SubsystemRunner runner ( dummyOpt ) ;
REQUIRE ( ! unit . isRunning ( ) ) ;
REQUIRE ( ! unit . didRun ( ) ) ;
runner . maybeRun ( unit ) ;
bool emergency = runner . wait ( ) ;
ASSERT ( ! emergency ) ;
ASSERT ( ! unit . isRunning ( ) ) ;
ASSERT ( unit . didRun ( ) ) ;
}
2009-01-03 01:11:21 +01:00
void
singleSubsys_start_failure ( )
{
2009-01-03 04:29:59 +01:00
cout < < " -----singleSubsys_start_failure----- \n " ;
2009-01-03 01:11:21 +01:00
MockSys unit1 ( " U1 " , " start(false) , run ( false ) . " ) ;
MockSys unit2 ( " U2 " , " start(throw) , run ( false ) . " ) ;
MockSys unit3 ( " U3 " , " start(fail) , run ( false ) . " ) ; // simulates incorrect behaviour
MockSys unit4 ( " U4 " , " start(true) , run ( false ) . " ) ;
SubsystemRunner runner ( dummyOpt ) ;
2009-01-10 18:01:09 +01:00
runner . maybeRun ( unit1 ) ; // this one doesn't start at all, which isn't considered an error
2009-01-03 01:11:21 +01:00
try
{
runner . maybeRun ( unit2 ) ;
NOTREACHED ;
}
catch ( lumiera : : Error & )
{
ASSERT ( lumiera_error ( ) = = LUMIERA_ERROR_TEST ) ;
}
try
{
runner . maybeRun ( unit3 ) ;
NOTREACHED ;
}
catch ( lumiera : : Error & )
{
ASSERT ( lumiera_error ( ) = = error : : LUMIERA_ERROR_LOGIC ) ; // incorrect behaviour trapped
}
2009-01-10 18:01:09 +01:00
try
{
runner . maybeRun ( unit4 ) ;
}
catch ( lumiera : : Error & )
{
ASSERT ( lumiera_error ( ) = = error : : LUMIERA_ERROR_LOGIC ) ; // detected that the subsystem didn't come up
2009-01-10 18:20:04 +01:00
} // (due to the way the test subsystem is written,
// this may not always be detected, because there
// is a short time window where isUp_==true )
2009-01-03 01:11:21 +01:00
2009-01-10 21:35:43 +01:00
runner . wait ( ) ;
2009-01-03 01:11:21 +01:00
ASSERT ( ! unit1 . isRunning ( ) ) ;
ASSERT ( ! unit2 . isRunning ( ) ) ;
ASSERT ( ! unit3 . isRunning ( ) ) ;
ASSERT ( ! unit4 . isRunning ( ) ) ;
ASSERT ( ! unit1 . didRun ( ) ) ;
ASSERT ( ! unit2 . didRun ( ) ) ;
ASSERT ( ! unit3 . didRun ( ) ) ;
ASSERT ( ! unit4 . didRun ( ) ) ;
}
void
singleSubsys_emegency_exit ( )
{
2009-01-03 04:29:59 +01:00
cout < < " -----singleSubsys_emegency_exit----- \n " ;
2009-01-03 01:11:21 +01:00
MockSys unit ( " one " , " start(true) , run ( fail ) . " ) ;
SubsystemRunner runner ( dummyOpt ) ;
runner . maybeRun ( unit ) ;
bool emergency = runner . wait ( ) ;
ASSERT ( emergency ) ; // emergency state got propagated
ASSERT ( ! unit . isRunning ( ) ) ;
ASSERT ( unit . didRun ( ) ) ;
}
void
dependentSubsys_complete_cycle ( )
{
2009-01-03 04:29:59 +01:00
cout < < " -----dependentSubsys_complete_cycle----- \n " ;
2009-01-03 01:11:21 +01:00
MockSys unit1 ( " U1 " , " start(true) , run ( true ) . " ) ;
MockSys unit2 ( " U2 " , " start(true) , run ( true ) . " ) ;
MockSys unit3 ( " U3 " , " start(true) , run ( true ) . " ) ;
MockSys unit4 ( " U4 " , " start(true) , run ( true ) . " ) ;
unit2 . depends ( unit1 ) ;
unit4 . depends ( unit3 ) ;
unit4 . depends ( unit1 ) ;
unit3 . depends ( unit2 ) ;
SubsystemRunner runner ( dummyOpt ) ;
runner . maybeRun ( unit4 ) ;
ASSERT ( unit1 . isRunning ( ) ) ;
ASSERT ( unit2 . isRunning ( ) ) ;
ASSERT ( unit3 . isRunning ( ) ) ;
ASSERT ( unit4 . isRunning ( ) ) ;
bool emergency = runner . wait ( ) ;
ASSERT ( ! emergency ) ;
ASSERT ( ! unit1 . isRunning ( ) ) ;
ASSERT ( ! unit2 . isRunning ( ) ) ;
ASSERT ( ! unit3 . isRunning ( ) ) ;
ASSERT ( ! unit4 . isRunning ( ) ) ;
ASSERT ( unit1 . didRun ( ) ) ;
ASSERT ( unit2 . didRun ( ) ) ;
ASSERT ( unit3 . didRun ( ) ) ;
ASSERT ( unit4 . didRun ( ) ) ;
}
2009-01-03 13:32:24 +01:00
void
dependentSubsys_start_failure ( )
{
cout < < " -----dependentSubsys_start_failure----- \n " ;
MockSys unit1 ( " U1 " , " start(true) , run ( true ) . " ) ;
MockSys unit2 ( " U2 " , " start(true) , run ( true ) . " ) ;
MockSys unit3 ( " U3 " , " start(false) , run ( false ) . " ) ; // note
MockSys unit4 ( " U4 " , " start(true) , run ( true ) . " ) ;
unit2 . depends ( unit1 ) ;
unit4 . depends ( unit3 ) ;
unit4 . depends ( unit1 ) ;
unit3 . depends ( unit2 ) ;
SubsystemRunner runner ( dummyOpt ) ;
try
{
runner . maybeRun ( unit4 ) ;
NOTREACHED ;
}
catch ( lumiera : : Error & )
{
ASSERT ( lumiera_error ( ) = = error : : LUMIERA_ERROR_LOGIC ) ; // failure to bring up prerequisites is detected
}
ASSERT ( unit1 . isRunning ( ) ) ;
ASSERT ( unit2 . isRunning ( ) ) ;
ASSERT ( ! unit3 . isRunning ( ) ) ;
// shutdown has been triggered for unit4, but may require some time
bool emergency = runner . wait ( ) ;
ASSERT ( ! emergency ) ; // no problems with the subsystems actually running...
ASSERT ( ! unit1 . isRunning ( ) ) ;
ASSERT ( ! unit2 . isRunning ( ) ) ;
ASSERT ( ! unit3 . isRunning ( ) ) ;
ASSERT ( ! unit4 . isRunning ( ) ) ;
ASSERT ( unit1 . didRun ( ) ) ;
ASSERT ( unit2 . didRun ( ) ) ;
ASSERT ( ! unit3 . didRun ( ) ) ;
// can't say for sure if unit4 actually did run
}
2008-12-31 05:06:17 +01:00
} ;
2009-01-03 13:32:24 +01:00
2008-12-31 05:06:17 +01:00
/** Register this test class... */
LAUNCHER ( SubsystemRunner_test , " function common " ) ;
} // namespace test
} // namespace lumiera