/**
 * @file TestProcess.cpp
 * @brief Tests for ODEMx class Process
 */

#include "TestBase.h"

/// @cond DOXYGEN_SKIP
SUITE( Base )
{
/// @endcond

	/**
	 * @struct ProcessFixture
	 * @brief Helper struct providing set-up/tear-down of Process tests
	 *
	 * @copydetails EventFixture
	 */
	struct ProcessFixture
	{
		ProcessFixture():
			observer( false ),
			sim( "SimulationTest" ),
			log( TestLogConsumer::create() ),
			process( sim, "ProcessTest", &observer ),
			type( typeid(Process) )
			{
				sim.addConsumer( log );
			}

		bool processInList( const std::list< Process* >& l, const Process* p )
		{
			return std::find( l.begin(), l.end(), p ) != l.end();
		}

		ProcessTestObserver observer; ///< a Process observer for monitoring calls
		SimulationTest sim; ///< a user-defined Simulation context
		TestLogConsumer::Ptr log; ///< log consumer to check sim records
		ProcessTest process; ///< a test Process
		data::TypeInfo type;
	};

	/**
	 * @test odemx::Process construction and destruction
	 *
	 * Tests correct Process member initialization:
	 * @li Simulation context
	 * @li SchedType
	 * @li Label
	 * @li ProcessState
	 * @li execution time
	 * @li scheduling priority
	 * @li Queue pointer, enqueue and dequeue time, queue priority
	 * @li interrupt status and interrupter
	 * @li alert status and alerter
	 * @li insertion into Simulation's list of created processes
	 * @li observation calls of \c onCreate() and \c onDestroy()
	 *
	 * @note One object uses the constructor for a given Simulation object,
	 * while the other one uses the DefaultSimulation.
	 */
	TEST_FIXTURE( ProcessFixture, ConstructionDestruction )
	{
		observer.history.clear();
		data::Label label = "ProcessTestConstructionDestruction";
		{
			ProcessTest p( sim, label, &observer );
			CHECK( &p.getSimulation() == &sim );
			CHECK( p.getSchedType() == Sched::PROCESS );
			CHECK( p.getLabel() == label );
			CHECK( p.getProcessState() == Process::CREATED );
			CHECK( p.data::Observable<ProcessObserver>::getObservers().front() == &observer );
			CHECK( p.getExecutionTime() == 0 );
			CHECK( p.hasReturned() == false );
			CHECK( p.getPriority() == 0 );
			CHECK( p.getQueue() == 0 );
			CHECK( p.getEnqueueTime() == 0 );
			CHECK( p.getDequeueTime() == 0 );
			CHECK( p.getQueuePriority() == 0 );
			CHECK( p.isInterrupted() == false );
			CHECK( p.getInterrupter() == 0 );
			CHECK( p.isAlerted() == false );
			CHECK( p.getAlerter() == 0 );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( observer.lastObservation()->event == "onCreate" );
			CHECK( observer.lastObservation()->sender == p.getLabel() );
			CHECK_EQUAL( &sim, &p.getSimulation() );
			CHECK( processInList( static_cast<const Simulation&>(sim).getCreatedProcesses(), &p ) );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
	}

	/**
	 * @test odemx::Process::activate()
	 *
	 * Expected function call effects:
	 * @li Simulation is not empty, i.e. an object is scheduled in ExecutionList
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to the current SimTime
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onActivate(), \c onChangeProcessState()
	 * and \c onChangeExecutionTime()
	 */
	TEST_FIXTURE( ProcessFixture, Activate )
	{
		observer.history.clear();
		process.activate();
		CHECK( ! sim.getScheduler().getExecutionList().isEmpty() );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == sim.getTime() );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "activate", type ) );
		CHECK( observer.observed( "onActivate", process.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", "0" ) );
	}

	/**
	 * @test odemx::Process::activate() LIFO scheduling
	 *
	 * Two Processes are scheduled for execution using \c activate(), first
	 * \c p, then \c process.
	 *
	 * Expected function call effects:
	 * @li one step of the simulation first executes \c process
	 * @li a second step leads to execution of \c p
	 */
	TEST_FIXTURE( ProcessFixture, ActivateLifo )
	{
		ProcessTest p( sim, "ActivateLifoHelperProcess" );
		p.activate();
		process.activate();
		CHECK( ! p.executed );
		CHECK( ! process.executed );

		// check LIFO order after activate(): process, p
		sim.step();
		CHECK( process.executed );
		CHECK( ! p.executed );
		sim.step();
		CHECK( p.executed );
	}

	/**
	 * @test odemx::Process::activateIn(SimTime)
	 *
	 * An Event is scheduled and the Simulation makes a step to ensure this
	 * test is done at a SimTime other than 0.
	 *
	 * Expected function call effects:
	 * @li Simulation is not empty, i.e. an object is scheduled in ExecutionList
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to the current SimTime + delay
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onActivateIn(), \c onChangeProcessState()
	 * and \c onChangeExecutionTime()
	 */
	TEST_FIXTURE( ProcessFixture, ActivateIn )
	{
		// set and execute an event to advance simulation time
		// to be able to check the correct process delay
		EventTest e( sim, "SimAdvanceEvent" );
		SimTime advanceTime = 15;
		e.scheduleAt( advanceTime );
		sim.step();
		CHECK_EQUAL( advanceTime, sim.getTime() );
		CHECK( sim.getScheduler().getExecutionList().isEmpty() );

		// process activation delay
		SimTime delay = 20;

		// schedule the process with a delay
		observer.history.clear();
		process.activateIn( delay );
		CHECK( ! sim.getScheduler().getExecutionList().isEmpty() );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == sim.getTime() + delay );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "activate in", type ) );
		CHECK( observer.observed( "onActivateIn", process.getLabel(), toString( delay ) ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", toString( advanceTime + delay ) ) );
	}

	/**
	 * @test odemx::Process::activateIn(SimTime) LIFO scheduling
	 *
	 * Two Processes are scheduled for execution using \c activateIn(delay),
	 * first \c p, then \c process.
	 *
	 * Expected function call effects:
	 * @li one step of the simulation first executes \c process
	 * @li a second step leads to execution of \c p
	 */
	TEST_FIXTURE( ProcessFixture, ActivateInLifo )
	{
		// process activation delay
		SimTime delay = 45;

		ProcessTest p( sim, "ActivateInLifoHelperProcess" );
		p.activateIn( delay );
		process.activateIn( delay );

		// check LIFO order after activateIn(): process, p
		sim.step();
		CHECK( process.executed );
		sim.step();
		CHECK( p.executed );
	}

	/**
	 * @test odemx::Process::activateAt(SimTime)
	 *
	 * Expected function call effects:
	 * @li Simulation is not empty, i.e. an object is scheduled in ExecutionList
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to the given absolute time
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onActivateAt(), \c onChangeProcessState()
	 * and \c onChangeExecutionTime()
	 */
	TEST_FIXTURE( ProcessFixture, ActivateAt )
	{
		// schedule the processes at the specified simulation time
		SimTime time = 4723;

		observer.history.clear();
		process.activateAt( time );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == time );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "activate at", type ) );
		CHECK( observer.observed( "onActivateAt", process.getLabel(), toString( time ) ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", toString( time ) ) );
	}

	/**
	 * @test odemx::Process::activateAt(SimTime) LIFO scheduling
	 *
	 * Two Processes are scheduled for execution using \c activateAt(absoluteTime),
	 * first \c p, then \c process.
	 *
	 * Expected function call effects:
	 * @li one step of the simulation first executes \c process
	 * @li a second step leads to execution of \c p
	 */
	TEST_FIXTURE( ProcessFixture, ActivateAtLifo )
	{
		// schedule the processes at the specified simulation time
		SimTime time = 27;

		ProcessTest p( sim, "ActivateAtLifoHelperProcess" );
		p.activateAt( time );
		process.activateAt( time );

		// check LIFO order after activateAt(): process, p
		sim.step();
		CHECK( process.executed );
		sim.step();
		CHECK( p.executed );
	}

	/**
	 * @test odemx::Process::activateBefore(Sched*)
	 *
	 * In order to test this function for a Process, an Event \c e is
	 * scheduled at some time, with increased priority.
	 *
	 * Expected function call effects:
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to \c e's execution time
	 * @li the Process priority must be equal to \c e's priority
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onActivateBefore(), \c onChangeProcessState(),
	 * \c onChangeExecutionTime(), and \c onChangePriority()
	 * @li stepping through the Simulation executes first \c process, then \c e
	 */
	TEST_FIXTURE( ProcessFixture, ActivateBefore )
	{
		// set priority and execution time for an event
		EventTest e( sim, "ActivateBeforeHelperEvent" );
		SimTime eventTime = 6;
		Priority eventPriority = 10;

		e.setPriority( eventPriority );
		e.scheduleAt( eventTime );
		CHECK( e.isScheduled() );

		// schedule the process before Event e,
		// process priority must be at least equal to e's
		// to keep the ExecutionList sorted, is automatically set
		observer.history.clear();

		process.activateBefore( &e );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == e.getExecutionTime() );
		CHECK( process.getPriority() == e.getPriority() );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "activate before", type ) );
		CHECK( observer.observed( "onActivateBefore", process.getLabel(), e.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", toString( eventTime ) ) );
		CHECK( observer.observed( "onChangePriority", process.getLabel(), "0", toString( eventPriority ) ) );

		// check the order of event and process
		// by testing for their execution
		sim.step();
		CHECK( process.executed );

		sim.step();
		CHECK( e.executed );
	}

	/**
	 * @test odemx::Process::activateAfter(Sched*)
	 *
	 * In order to test this function for a Process, an Event \c e is
	 * scheduled at some time, with lowered priority.
	 *
	 * Expected function call effects:
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to \c e's execution time
	 * @li the Process priority must be equal to \c e's priority
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onActivateAfter(), \c onChangeProcessState(),
	 * \c onChangeExecutionTime(), and \c onChangePriority()
	 * @li stepping through the Simulation executes first \c e, then \c process
	 */
	TEST_FIXTURE( ProcessFixture, ActivateAfter )
	{
		// set priority and execution time for an event
		EventTest e( sim, "ActivateAfterHelperEvent" );
		SimTime eventTime = 6;
		Priority eventPriority = -10;

		e.setPriority( eventPriority );
		e.scheduleAt( eventTime );
		CHECK( e.isScheduled() );

		// schedule the process after Event e,
		// process priority must be at most equal to e's
		// to keep the ExecutionList sorted, is automatically set
		observer.history.clear();
		process.activateAfter( &e );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == e.getExecutionTime() );
		CHECK( process.getPriority() == e.getPriority() );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "activate after", type ) );
		CHECK( observer.observed( "onActivateAfter", process.getLabel(), e.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", toString( eventTime ) ) );
		CHECK( observer.observed( "onChangePriority", process.getLabel(), "0", toString( eventPriority ) ) );

		sim.step();
		CHECK( e.executed );

		sim.step();
		CHECK( process.executed );
	}

	/**
	 * @test odemx::Process::hold()
	 *
	 * Expected function call effects:
	 * @li Simulation is not empty, i.e. an object is scheduled in ExecutionList
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to the current SimTime
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onHold(), \c onChangeProcessState()
	 * and \c onChangeExecutionTime()
	 */
	TEST_FIXTURE( ProcessFixture, Hold )
	{
		observer.history.clear();
		process.hold();
		CHECK( ! sim.getScheduler().getExecutionList().isEmpty() );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == sim.getTime() );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "hold", type ) );
		CHECK( observer.observed( "onHold", process.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", "0" ) );
	}

	/**
	 * @test odemx::Process::hold() FIFO scheduling
	 *
	 * Two Processes are scheduled for execution using \c hold(), first
	 * \c p, then \c process.
	 *
	 * Expected function call effects:
	 * @li one step of the simulation first executes \c p
	 * @li a second step leads to execution of \c process
	 */
	TEST_FIXTURE( ProcessFixture, HoldFifo )
	{
		ProcessTest p( sim, "HoldFifoHelperProcess" );
		p.hold();
		process.hold();

		// check FIFO order after hold(): p, process
		sim.step();
		CHECK( p.executed );
		CHECK( ! process.executed );
		sim.step();
		CHECK( process.executed );
	}

	/**
	 * @test odemx::Process::holdFor(SimTime)
	 *
	 * An Event is scheduled and the Simulation makes a step to ensure this
	 * test is done at a SimTime other than 0.
	 *
	 * Expected function call effects:
	 * @li Simulation is not empty, i.e. an object is scheduled in ExecutionList
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to the current SimTime + delay
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onHoldFor(), \c onChangeProcessState()
	 * and \c onChangeExecutionTime()
	 */
	TEST_FIXTURE( ProcessFixture, HoldFor )
	{
		// set and execute an event to advance simulation time
		// to be able to check the correct process delay
		EventTest e( sim, "SimAdvanceEvent" );
		SimTime advanceTime = 155;
		e.scheduleAt( advanceTime );
		sim.step();
		CHECK( sim.getTime() == advanceTime );

		// schedule the process with the specified delay
		SimTime delay = 479;

		observer.history.clear();
		process.holdFor( delay );
		CHECK( ! sim.getScheduler().getExecutionList().isEmpty() );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == sim.getTime() + delay );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "hold for", type ) );
		CHECK( observer.observed( "onHoldFor", process.getLabel(), toString( delay ) ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", toString( advanceTime + delay ) ) );
	}

	/**
	 * @test odemx::Process::holdFor() FIFO scheduling
	 *
	 * Two Processes are scheduled for execution using \c holdFor(delay), first
	 * \c p, then \c process.
	 *
	 * Expected function call effects:
	 * @li one step of the simulation first executes \c p
	 * @li a second step leads to execution of \c process
	 */
	TEST_FIXTURE( ProcessFixture, HoldForFifo )
	{
		SimTime delay = 479;

		ProcessTest p( sim, "HoldForFifoHelperProcess" );
		p.holdFor( delay );
		process.holdFor( delay );

		// check FIFO order after holdFor(): p, process
		sim.step();
		CHECK( p.executed );
		CHECK( ! process.executed );
		sim.step();
		CHECK( process.executed );
	}

	/**
	 * @test odemx::Process::holdUntil(SimTime)
	 *
	 * Expected function call effects:
	 * @li Simulation is not empty, i.e. an object is scheduled in ExecutionList
	 * @li the Process object knows it is scheduled
	 * @li the Process execution time is equal to the given absolute time
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onHoldUntil(), \c onChangeProcessState()
	 * and \c onChangeExecutionTime()
	 */
	TEST_FIXTURE( ProcessFixture, HoldUntil )
	{
		// schedule the processes at the specified simulation time
		SimTime time = 725;

		observer.history.clear();
		process.holdUntil( time );
		CHECK( process.isScheduled() );
		CHECK( process.getExecutionTime() == time );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "hold until", type ) );
		CHECK( observer.observed( "onHoldUntil", process.getLabel(), toString( time ) ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", toString( time ) ) );
	}

	/**
	 * @test odemx::Process::holdUntil() FIFO scheduling
	 *
	 * Two Processes are scheduled for execution using \c hold(absoluteTime),
	 * first \c p, then \c process.
	 *
	 * Expected function call effects:
	 * @li one step of the simulation first executes \c p
	 * @li a second step leads to execution of \c process
	 */
	TEST_FIXTURE( ProcessFixture, HoldUntilFifo )
	{
		// schedule the processes at the specified simulation time
		SimTime time = 725;
		ProcessTest p( sim, "HoldUntilFifoHelperProcess" );
		p.holdUntil( time );
		process.holdUntil( time );

		// check FIFO order after holdUntil(): p, process
		sim.step();
		CHECK( ! process.executed );
		CHECK( p.executed );
		sim.step();
		CHECK( process.executed );
	}

	/**
	 * @test odemx::Process::sleep()
	 *
	 * Precondition: Process \c process is scheduled.
	 *
	 * Expected function call effects:
	 * @li Simulation is empty, i.e. no object is scheduled in ExecutionList
	 * @li the Process object is not scheduled
	 * @li the ProcessState is now IDLE
	 * @li the Process is now listed in Simulation's list of idle processes
	 * @li observation calls of \c onSleep(), and \c onChangeProcessState()
	 */
	TEST_FIXTURE( ProcessFixture, Sleep )
	{
		// schedule the process because sleep() calls removeSched()
		process.activateAt( 1 );
		CHECK( process.isScheduled() );

		observer.history.clear();
		process.sleep();
		CHECK( sim.getScheduler().getExecutionList().isEmpty() );
		CHECK( ! process.isScheduled() );
		CHECK( process.getProcessState() == Process::IDLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getIdleProcesses(), &process ) );
		CHECK( log->getTraceRecord( "sleep", type ) );
		CHECK( observer.observed( "onSleep", process.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "RUNNABLE", "IDLE" ) );

	}

	/**
	 * @test odemx::Process::sleep()
	 *
	 * Precondition: Process \c process is not scheduled. It can either be
	 * in CREATED or IDLE state.
	 *
	 * An InterruptEvent is scheduled to awaken \c process. As soon as it
	 * is executed by taking a Simulation step, the effects on \c process
	 * are tested.
	 *
	 * Expected function call effects:
	 * @li the Process object not scheduled
	 * @li the Process knows it was interrupted
	 * @li the interrupter is set to the InterruptEvent
	 * @li the Process execution time is set to current SimTime
	 * @li the ProcessState is now RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onInterrupt(), \c onChangeProcessState(),
	 * and \c onChangeExecutionTime()
	 * @li a second InterruptEvent causes a warning about interruption of an
	 * already interrupted Process
	 * @li taking a Simulation step executes the process
	 */
	TEST_FIXTURE( ProcessFixture, Interrupt )
	{
		observer.history.clear();
		InterruptEvent i( sim, &process );
		i.schedule();
		sim.step();

		// at this position, process.interrupt has just been called
		CHECK( process.isScheduled() );
		CHECK( process.isInterrupted() );
		CHECK( process.getInterrupter() == &i );
		CHECK( process.getExecutionTime() == sim.getTime() );
		CHECK( process.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &process ) );
		CHECK( log->getTraceRecord( "interrupt", type ) );
		CHECK( observer.observed( "onInterrupt", process.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "RUNNABLE" ) );
		CHECK( observer.observed( "onChangeExecutionTime", process.getLabel(), "0", "0" ) );

		InterruptEvent i2( sim, &process );
		i2.schedule();
		sim.step();
		CHECK( log->getWarningRecord( "Process::interrupt(): process is already interrupted", type ) );

		sim.step();
		CHECK( process.executed );
	}


	/**
	 * @test odemx::Process::wait() with alert
	 *
	 * A Timer is scheduled and added to a MemoryVector. A ProcessWaitTest
	 * object \c p is created, which will call wait() in its main() function and
	 * allow for checking of the alerter.
	 *
	 * Expected function call effects with empty MemoryVector:
	 * @li a warning is sent to the errorStream()
	 *
	 * Expected function call effects after activation and Simulation step:
	 * @li the Process was executed
	 * @li the Process is not scheduled, because it is waiting
	 * @li the ProcesState is IDLE
	 * @li the Process is now listed in Simulation's list of idle processes
	 * @li the Process is not alerted
	 * @li observation calls of \c onChangeProcessState(), \c onWait(),
	 * \c onSleep, and \c onChangeProcessState()
	 * @li a warning about unset Timer is sent to the errorStream()
	 *
	 * Another Simulation step will then execute the scheduled Timer, which
	 * should reschedule the alerted Process \c p.
	 *
	 * Expected function call effects after alert:
	 * @li the Process is alerted
	 * @li the alerter is the Timer object
	 * @li the Process is scheduled
	 * @li the Process is not interrupted
	 * @li the ProcesState is RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation calls of \c onAlert(), and \c onChangeProcessState()
	 *
	 * Finally, another Simulation step executes the Process.
	 *
	 * Expected function call effects after alert:
	 * @li the returned value of wait() is the Timer object
	 * @li observation call of \c onExecute()
	 */
	TEST_FIXTURE( ProcessFixture, WaitAlert )
	{
		// try to have a process wait without alerter
		odemx::synchronization::IMemoryVector mv;
		ProcessWaitTest pError( sim, "WaitAlertTestErrorProcess", mv, &observer );

		pError.activate();
		sim.step();
		CHECK( log->getWarningRecord( "Process::wait(): no memory objects given", type ) );

		CHECK( sim.getScheduler().getExecutionList().isEmpty() );
		// schedule the timer to alert p
		odemx::synchronization::Timer t1( sim, "WaitAlertHelperTimer" );
		mv.push_back( &t1 );
		ProcessWaitTest p( sim, "WaitAlertTestProcess", mv, &observer );

		// schedule the process and execute it to call wait()
		p.activate();
		CHECK( p.isScheduled() );
		observer.history.clear();
		sim.step();

		CHECK( p.executed );
		CHECK( ! p.isScheduled() );
		CHECK( p.getProcessState() == Process::IDLE );
		CHECK( ! process.isAlerted() );
		CHECK( processInList( static_cast< const Simulation& >(sim).getIdleProcesses(), &p ) );
		CHECK( log->getTraceRecord( "wait", type ) );
		CHECK( log->getTraceRecord( "available", type ) );
		CHECK( log->getWarningRecord( "Process::wait(): unset timer available", type ) );
		CHECK( log->getTraceRecord( "sleep", type ) );
		CHECK( observer.observed( "onChangeProcessState", p.getLabel(), "RUNNABLE", "CURRENT" ) );
		CHECK( observer.observed( "onWait", p.getLabel(), t1.getLabel() ) );
		CHECK( observer.observed( "onSleep", p.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", p.getLabel(), "CURRENT", "IDLE" ) );

		// Timer t1 will alert p and thus reschedule it
		t1.setIn( 12345 );
		CHECK( t1.isScheduled() );
		sim.step();
		CHECK( p.isAlerted() );
		CHECK( p.getAlerter() == &t1 );
		CHECK( p.isScheduled() );
		CHECK( ! p.isInterrupted() );
		CHECK( p.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &p ) );
		CHECK( log->getTraceRecord( "alert", type ) );
		CHECK( observer.observed( "onAlert", p.getLabel(), t1.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", p.getLabel(), "IDLE", "RUNNABLE" ) );

		// only now will p be continued (at the IP in wait())
		observer.history.clear();
		sim.step();
		CHECK( p.waitReturnValue == &t1 );
		CHECK( log->getTraceRecord( "execute process", type ) );
		CHECK( observer.observed( "onExecute", p.getLabel() ) );
	}

	/**
	 * @test odemx::Process::wait() with interrupt
	 *
	 * A Timer is added to a MemoryVector. A ProcessWaitTest
	 * object \c p is created, which will call wait() in its
	 * main() function. However, the waiting period will be ended
	 * by an InterruptEvent.
	 *
	 * Another Simulation step will then execute the scheduled InterruptEvent,
	 * which should reschedule the interrupted waiting Process \c p.
	 *
	 * Expected function call effects after alert:
	 * @li the Process is not alerted
	 * @li the alerter is \c 0
	 * @li the Process is scheduled
	 * @li the Process is interrupted
	 * @li the interrupter is the InterruptEvent
	 * @li the ProcesState is RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li observation call of \c onInterrupt()
	 *
	 * Finally, another Simulation step executes the Process.
	 *
	 * Expected function call effects after alert:
	 * @li the returned value of wait() is 0, which indicates interruption
	 * @li observation call of \c onExecute()
	 * @li a warning about the \c wait() interruption is sent to the errorStream()
	 */
	TEST_FIXTURE( ProcessFixture, WaitInterrupt )
	{
		odemx::synchronization::Timer t1( sim, "WaitInterruptHelperTimer" );
		odemx::synchronization::IMemoryVector mv;
		mv.push_back( &t1 );

		ProcessWaitTest p( sim, "WaitInterruptTestProcess", mv, &observer );

		// schedule the process and execute it to call wait(),
		// has been tested above already
		p.activate();
		sim.step();
		CHECK( log->getTraceRecord( "wait", type ) );
		CHECK( observer.observed( "onWait", p.getLabel(), t1.getLabel() ) );

		// interrupt the process before the timer
		InterruptEvent ie( sim, &p );
		ie.schedule();
		CHECK( ie.isScheduled() );

		// here the interrupt will wake up p and thus reschedule it
		sim.step();
		CHECK( ! p.isAlerted() );
		CHECK( p.getAlerter() == 0 );
		CHECK( p.isScheduled() );
		CHECK( p.isInterrupted() );
		CHECK( p.getInterrupter() == &ie );
		CHECK( p.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &p ) );
		CHECK( log->getTraceRecord( "interrupt", type ) );
		CHECK( observer.observed( "onInterrupt", p.getLabel() ) );

		// here, p will be restarted in wait()
		observer.history.clear();
		sim.step();
		CHECK( p.waitReturnValue == 0 );
		CHECK( observer.observed( "onExecute", p.getLabel() ) );
		CHECK( log->getWarningRecord( "Process::wait(): waiting period ended by interrupt", type ) );
	}

	/**
	 * @test odemx::Process::wait() with error
	 *
	 * A ProcessWaitTest object \c p is created, which will call wait() in its
	 * main() function. However, the waiting period will be ended by calling
	 * activate().
	 *
	 * Another Simulation step will then execute the scheduled InterruptEvent,
	 * which should reschedule the interrupted waiting Process \c p.
	 *
	 * Expected function call effects after alert:
	 * @li the Process is not alerted
	 * @li the alerter is \c 0
	 * @li the Process is scheduled
	 * @li the ProcesState is RUNNABLE
	 * @li the Process is now listed in Simulation's list of runnable processes
	 * @li an error message is generated
	 * @li the returned value of wait() is 0, which indicates interruption
	 */
	TEST_FIXTURE( ProcessFixture, WaitError )
	{
		odemx::synchronization::Timer t1( sim, "WaitInterruptHelperTimer" );
		odemx::synchronization::IMemoryVector mv;
		mv.push_back( &t1 );

		ProcessWaitTest p( sim, "WaitInterruptTestProcess", mv, &observer );

		// schedule the process and execute it to call wait(),
		// has been tested above already
		p.activate();
		sim.step();
		CHECK( ! p.isScheduled() );
		CHECK( log->getTraceRecord( "wait", type ) );

		// here the interrupt will wake up p and thus reschedule it
		p.activate();
		CHECK( ! p.isAlerted() );
		CHECK( p.getAlerter() == 0 );
		CHECK( p.isScheduled() );
		CHECK( p.getProcessState() == Process::RUNNABLE );
		CHECK( processInList( static_cast<const Simulation&>(sim).getRunnableProcesses(), &p ) );

		// here, p will be restarted in wait()
		observer.history.clear();
		sim.step();
		CHECK( p.waitReturnValue == 0 );
		CHECK( observer.observed( "onExecute", p.getLabel() ) );
		CHECK( log->getErrorRecord( "Process::wait(): Process awakened without alert or interrupt", type ) );
	}

	/**
	 * @test odemx::Process::cancel()
	 *
	 * In this test, \c cancel() is called on an unscheduled and a scheduled
	 * Process.
	 *
	 * Expected function call effects in both cases:
	 * @li the Process is not scheduled
	 * @li the ProcesState is TERMINATED
	 * @li the Process is now listed in Simulation's list of terminated processes
	 * @li observation calls of \c onCancel(), and \c onChangeProcessState()
	 */
	TEST_FIXTURE( ProcessFixture, Cancel )
	{
		// cancel unscheduled process
		observer.history.clear();
		process.cancel();
		CHECK( ! process.isScheduled() );
		CHECK( process.getProcessState() == Process::TERMINATED );
		CHECK( processInList( static_cast<const Simulation&>(sim).getTerminatedProcesses(), &process ) );
		CHECK( log->getTraceRecord( "cancel",type ) );
		CHECK( observer.observed( "onCancel", process.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CREATED", "TERMINATED" ) );

		// schedule a process and cancel it
		ProcessTest p( sim, "CancelProcess", &observer );
		p.activateAt( 523 );
		CHECK( p.isScheduled() );

		observer.history.clear();
		p.cancel();
		CHECK( ! process.isScheduled() );
		CHECK( process.getProcessState() == Process::TERMINATED );
		CHECK( processInList( static_cast<const Simulation&>(sim).getTerminatedProcesses(), &process ) );
		CHECK( observer.observed( "onCancel", p.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", p.getLabel(), "RUNNABLE", "TERMINATED" ) );
	}

	/**
	 * @test odemx::Process::execute()
	 *
	 * This function can only be tested indirectly because processes are
	 * executed by their Simulation context. Thus, a Process must be activated
	 * first and then executed by performing a simulation step
	 *
	 * Expected function call effects after the first call:
	 * @li the \c boolean member \c executed is \c true, i.e. \c main() was called
	 * @li the ProcessState is TERMINATED
	 * @li the Process is now listed in Simulation's list of terminated processes
	 * @li observation calls of \c onExecute(), twice of \c onChangeProcessState(),
	 * and finally \c onReturn()
	 */
	TEST_FIXTURE( ProcessFixture, Execute )
	{
		observer.history.clear();
		process.activate();

		// making a simulation step will call execute on the process
		sim.step();
		CHECK( process.executed );
		CHECK( process.getProcessState() == Process::TERMINATED );
		CHECK( processInList( static_cast<const Simulation&>(sim).getTerminatedProcesses(), &process ) );
		CHECK( log->getTraceRecord( "execute process", type ) );
		CHECK( observer.observed( "onExecute", process.getLabel() ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "RUNNABLE", "CURRENT" ) );
		CHECK( observer.observed( "onChangeProcessState", process.getLabel(), "CURRENT", "TERMINATED" ) );
		CHECK( observer.observed( "onReturn", process.getLabel() ) );
	}

	/**
	 * @test odemx::Process::getReturnValue()
	 *
	 * The function is called twice; once on a non-terminated Process, and
	 * once on a finished Process
	 *
	 * Expected function call effects after the first call:
	 * @li the Process return value is 0
	 * @li a warning is sent to the errorStream()
	 *
	 * Expected function call effects after the second call:
	 * @li the Process return value
	 * @li observation call of \c onReturn()
	 */
	TEST_FIXTURE( ProcessFixture, ReturnValue )
	{
		int processReturnValue;

		// check warning for invalid return value
		processReturnValue = process.getReturnValue();
		CHECK( log->getWarningRecord( "Process::getReturnValue(): return value is not valid", type ) );
		CHECK_EQUAL( 0, processReturnValue );

		// check return value after valid process return from main
		process.activate();
		sim.step();
		processReturnValue = process.getReturnValue();
		CHECK_EQUAL( 666, processReturnValue );
		CHECK( log->getTraceRecord( "return", type ) );
		CHECK( observer.observed( "onReturn", process.getLabel() ) );
 	}

	/**
	 * @test odemx::Process::setPriority()
	 *
	 * Precondition: an Event with higher priority than the process is scheduled.
	 *
	 * The Process is scheduled after the Event in order to test the
	 * effects of a priority change. Then, set Priority is called,
	 * increasing the Process's priority over that of the event.
	 *
	 * Expected function call effects:
	 * @li the return value equals the Process's old priority
	 * @li the Process priority is set to \c newPriority
	 * @li observation call of \c onChangePriority()
	 * @li correct execution order: first the Process, then the Event
	 */
	TEST_FIXTURE( ProcessFixture, Priority )
	{
		Priority processPriority = -1.0;
		Priority oldPriority = process.setPriority( processPriority );
		CHECK( ! process.isScheduled() );
		CHECK_EQUAL( 0, oldPriority );

		EventTest e( sim, "TestPriorityEvent" );
		CHECK( e.getPriority() == 0 );

		// schedule first the event, then the process
		// change the process priority to a higher one
		SimTime exTime = 1099;
		e.scheduleAppendAt( exTime );
		process.activateAfter( &e );

		Priority newPriority = e.getPriority() + 1;
		observer.history.clear();
		oldPriority = process.setPriority( newPriority );
		CHECK_EQUAL( oldPriority, processPriority );
		CHECK_EQUAL( process.getPriority(), newPriority );
		CHECK( log->getTraceRecord( "change priority", type ) );
		CHECK( observer.observed( "onChangePriority", process.getLabel(), toString( processPriority), toString( newPriority ) ) );

		// check for correct order change in ExecutionList
		sim.step();
		CHECK( process.executed );
		CHECK( ! e.executed );
		sim.step();
		CHECK( e.executed );
	}

	/**
	 * @test odemx::Process::setQueuePriority()
	 *
	 * Expected function call effects with parameter \c reschedule set to \c false:
	 * @li the Process is not scheduled
	 * @li the return value equals the Process's old queue priority
	 * @li the Process's queue priority is set to \c newPriority
	 * @li observation call of \c onChangeQueuePriority()
	 *
	 * Expected function call effects with parameter \c reschedule set to \c true:
	 * @li all of the above, but the Process is scheduled
	 */
	TEST_FIXTURE( ProcessFixture, QueuePriority )
	{
		CHECK_EQUAL( 0, process.getQueuePriority() );

		Priority newPriority = 1.5;
		observer.history.clear();

		// calling with parameter reactivate = false
		// will not reschedule the process
		Priority oldPriority = process.setQueuePriority( newPriority, false );
		CHECK( ! process.isScheduled() );
		CHECK_EQUAL( 0, oldPriority );
		CHECK_EQUAL( newPriority, process.getQueuePriority() );
		CHECK( log->getTraceRecord( "change queue priority", type ) );
		CHECK( observer.observed( "onChangeQueuePriority", process.getLabel(), \
				toString( oldPriority ), toString( newPriority ) ) );

		// calling with parameter reactivate = true
		// will reschedule the process at current SimTime
		// useful when the process needs to check queue conditions
		process.setQueuePriority( newPriority + 1, true );
		CHECK( process.isScheduled() );
		CHECK( process.getQueuePriority() == newPriority + 1 );
	}

/// @cond DOXYGEN_SKIP
}
/// @endcond
