/**
 * @file TestWaitQ.cpp
 * @date Aug 2, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class WaitQ
 */

#include "TestSynchronization.h"
#include "../TestBase/TestBase.h"

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

	/**
	 * @struct WaitQFixture
	 * @brief Helper struct providing set-up/tear-down of WaitQ tests
	 *
	 * @copydetails EventFixture
	 */
	struct WaitQFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		WaitQTestObserver observer;
		WaitQ waitQ;
		data::TypeInfo type;

		WaitQFixture():
			sim( "WaitQTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			waitQ( sim, "WaitQTest", &observer ),
			type( typeid(WaitQ) )
			{
				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::WaitQ construction and destruction
	 *
	 * Expected effects:
	 * @li observer is set correctly
	 * @li label is set
	 * @li simulation environment is set for DefaultSimulation or user-definied
	 * simulation
	 * @li construction and destruction can be observed
	 */
	TEST_FIXTURE( WaitQFixture, ConstructionDestruction )
	{
		observer.history.clear();

		data::Label label = "WaitQConstructionTest2";
		{
			WaitQ testWaitQ2( sim, label, &observer );
			CHECK_EQUAL( &observer, testWaitQ2.getObservers().front() );
			CHECK_EQUAL( label, testWaitQ2.getLabel() );
			CHECK( observer.observed( "onCreate", testWaitQ2.getLabel() ) );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "master queue", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "slave queue", type ) );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
	}

	/**
	 * @test odemx::WaitQ::wait() slave wait call
	 *
	 * Precondition: two processes are waiting as masters.
	 *
	 * Expected function call effects:
	 * @li calling from non-Process objects causes an error message
	 * @li the call can be observed
	 * @li all masters are re-scheduled to check for suitable slave
	 * @li the slave is put into a queue to wait for a master Process
	 * @li on slave interrupt, the function returns false
	 */
	TEST_FIXTURE( WaitQFixture, Wait )
	{
		WaitQTestMaster master1( sim, "WaitQTestWaitMaster1", waitQ );
		WaitQTestMaster master2( sim, "WaitQTestWaitMaster2", waitQ );
		master1.activate();
		master2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue
		const std::list< base::Process* >& masters = waitQ.getWaitingMasters();
		CHECK( std::find( masters.begin(), masters.end(), &master1 ) != masters.end() );
		CHECK( std::find( masters.begin(), masters.end(), &master2 ) != masters.end() );

		waitQ.wait();
		CHECK( log->getErrorRecord( "WaitQ::wait(): called by non-Process object", type ) );

		WaitQTestSlave slave( sim, "WaitQTestWaitSlave", waitQ );
		slave.activate();
		sim.step(); // enqueue and wake masters to check for slaves

		CHECK( log->getTraceRecord( "wait", type ) );
		CHECK( observer.observed( "onWait", waitQ.getLabel(), slave.getLabel() ) );
		CHECK( master1.isScheduled() );
		CHECK( master2.isScheduled() );
		const std::list< base::Process* >& slaves = waitQ.getWaitingSlaves();
		CHECK( std::find( slaves.begin(), slaves.end(), &slave ) != slaves.end() );
		CHECK_EQUAL( base::Process::IDLE, slave.getProcessState() );

		SuiteBase::InterruptEvent interrupt( sim, &slave );
		interrupt.schedule();
		sim.step();
		CHECK( slave.isScheduled() );
		sim.step();
		CHECK_EQUAL( false, slave.waitReturnValue );
	}


	/**
	 * @test odemx::WaitQ::wait(Weight) slave wait call without waiting masters
	 *
	 * Precondition: two processes are waiting as masters.
	 *
	 * Expected function call effects:
	 * @li calling from non-Process objects causes an error message
	 * @li the call can be observed
	 * @li the slave is put into a queue to wait for a master Process
	 * @li on slave interrupt, the function returns false
	 */
	TEST_FIXTURE( WaitQFixture, WaitWeightEmpty )
	{
		waitQ.wait( (base::Weight) &WaitQTestMaster::weight );
		CHECK( log->getErrorRecord( "WaitQ::wait(): called by non-Process object", type ) );

		WaitQTestSlave slave( sim, "WaitQTestWaitWeightEmptySlave", waitQ, 0, (base::Weight) &WaitQTestMaster::weight );
		slave.activate();
		sim.step(); // enqueue and wake masters to check for slaves

		CHECK( log->getTraceRecord( "wait weight", type ) );
		CHECK( observer.observed( "onWaitWeight", waitQ.getLabel(), slave.getLabel() ) );
		const std::list< base::Process* >& slaves = waitQ.getWaitingSlaves();
		CHECK( std::find( slaves.begin(), slaves.end(), &slave ) != slaves.end() );
		CHECK_EQUAL( base::Process::IDLE, slave.getProcessState() );

		SuiteBase::InterruptEvent interrupt( sim, &slave );
		interrupt.schedule();
		sim.step();
		CHECK( slave.isScheduled() );
		sim.step();
		CHECK_EQUAL( false, slave.waitReturnValue );
	}

	/**
	 * @test odemx::WaitQ::wait(Weight) slave wait call without waiting masters
	 *
	 * Precondition: two processes are waiting as masters.
	 *
	 * Expected function call effects:
	 * @li the slave is weighted against all masters and the master with the
	 * highest weight is scheduled in order to acquire this slave
	 */
	TEST_FIXTURE( WaitQFixture, WaitWeight )
	{
		WaitQTestMaster master1( sim, "WaitQTestWaitWeightMaster1", waitQ, 0, true );
		WaitQTestMaster master2( sim, "WaitQTestWaitWeightMaster2", waitQ, 0, true );
		master1.activate();
		master2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue
		const std::list< base::Process* >& masters = waitQ.getWaitingMasters();
		CHECK( std::find( masters.begin(), masters.end(), &master1 ) != masters.end() );
		CHECK( std::find( masters.begin(), masters.end(), &master2 ) != masters.end() );

		// make sure this master will be chosen
		master1.weightValue = 10;
		WaitQTestSlave slave( sim, "WaitQTestWaitWeightSlave", waitQ, 0, (base::Weight) &WaitQTestMaster::weight );
		slave.activate();
		sim.step(); // enqueue and check masters for best weight

		CHECK( ! master2.isScheduled() );
		CHECK( master1.isScheduled() );
		sim.step();

		CHECK_EQUAL( &slave, master1.slave );
	}

	/**
	 * @test odemx::WaitQ::coopt() success
	 *
	 * Precondition: a process is waiting as slave.
	 *
	 * Expected function call effects:
	 * @li calling from non-Process objects causes an error message
	 * @li the successful call can be observed
	 * @li the statistics are updated
	 * @li the waiting slave is taken by the master
	 */
	TEST_FIXTURE( WaitQFixture, Coopt )
	{
		waitQ.coopt();
		CHECK( log->getErrorRecord( "WaitQ::coopt(): called by non-Process object", type ) );

		WaitQTestSlave slave1( sim, "WaitQTestCooptSlave1", waitQ );
		slave1.activate();
		sim.step(); // enqueue

		WaitQTestMaster master( sim, "WaitQTestCooptMaster", waitQ );
		master.activate();
		sim.step(); // get slave
		CHECK( log->getTraceRecord( "coopt succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "synchronizations", type ) );
		CHECK( log->getStatisticsRecord( "update", "master wait time", type ) );
		CHECK( log->getStatisticsRecord( "update", "slave wait time", type ) );
		CHECK( observer.observed( "onCooptSucceed", waitQ.getLabel(), master.getLabel(), slave1.getLabel() ) );
		CHECK_EQUAL( &slave1, master.slave );
	}

	/**
	 * @test odemx::WaitQ::coopt() without waiting slave
	 *
	 * Precondition: no process is waiting as slave.
	 *
	 * Expected function call effects:
	 * @li the calling process is suspended
	 * @li the caller is added to the master waiting queue
	 * @li the failing call can be observed
	 * @li in case of interrupt the return value is a 0-pointer
	 */
	TEST_FIXTURE( WaitQFixture, CooptFail )
	{
		WaitQTestMaster master( sim, "WaitQTestCooptFailMaster", waitQ );
		master.activate();
		sim.step(); // coopt fails
		CHECK( log->getTraceRecord( "coopt failed", type ) );
		CHECK( observer.observed( "onCooptFail", waitQ.getLabel(), master.getLabel() ) );
		const std::list< base::Process* >& masters = waitQ.getWaitingMasters();
		CHECK( std::find( masters.begin(), masters.end(), &master ) != masters.end() );

		SuiteBase::InterruptEvent interrupt( sim, &master );
		interrupt.schedule();
		sim.step();
		CHECK( master.isScheduled() );
		sim.step();
		CHECK_EQUAL( static_cast< base::Process* >( 0 ), master.slave );
	}

	/**
	 * @test odemx::WaitQ::coopt(Selection) success
	 *
	 * Precondition: two processes are waiting as slave, one fits.
	 *
	 * Expected function call effects:
	 * @li the successful call can be observed
	 * @li the statistics are updated
	 * @li the waiting slave is taken by the master
	 */
	TEST_FIXTURE( WaitQFixture, CooptSelect )
	{
		int selectValue = 125;
		WaitQTestSlave slave1( sim, "WaitQTestCooptSelSlave1", waitQ, selectValue );
		WaitQTestSlave slave2( sim, "WaitQTestCooptSelSlave2", waitQ, 5 );
		slave1.activate();
		slave2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue

		// make sure slave1 is chosen
		WaitQTestMaster cooptTester( sim, "WaitQTestCooptMaster", waitQ, selectValue );
		cooptTester.activate();
		sim.step(); // get slave
		CHECK( log->getTraceRecord( "coopt select succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "synchronizations", type ) );
		CHECK( log->getStatisticsRecord( "update", "master wait time", type ) );
		CHECK( log->getStatisticsRecord( "update", "slave wait time", type ) );
		CHECK( observer.observed( "onCooptSelSucceed", waitQ.getLabel(), cooptTester.getLabel(), slave1.getLabel() ) );
		CHECK_EQUAL( &slave1, cooptTester.slave );
	}

	/**
	 * @test odemx::WaitQ::coopt(Selection) failure with waiting
	 *
	 * Precondition: two processes are waiting as slave, but neither fits the selection.
	 *
	 * Expected function call effects:
	 * @li the failing call can be observed
	 * @li the calling process is added to the master waiting queue
	 * @li in case of master interrupt, the return value is a 0-pointer
	 */
	TEST_FIXTURE( WaitQFixture, CooptSelectFail )
	{
		int selectValue = 125;
		WaitQTestSlave slave1( sim, "WaitQTestCooptSelFailSlave1", waitQ, 1 );
		WaitQTestSlave slave2( sim, "WaitQTestCooptSelFailSlave2", waitQ, 2 );
		slave1.activate();
		slave2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue

		WaitQTestMaster cooptTester( sim, "WaitQTestCooptSelMaster", waitQ, selectValue );
		cooptTester.activate();
		sim.step(); // get slave
		CHECK( log->getTraceRecord( "coopt select failed", type ) );
		CHECK( observer.observed( "onCooptSelFail", waitQ.getLabel(), cooptTester.getLabel(), slave1.getLabel() ) );
		const std::list< base::Process* >& masters = waitQ.getWaitingMasters();
		CHECK( std::find( masters.begin(), masters.end(), &cooptTester ) != masters.end() );

		SuiteBase::InterruptEvent interrupt( sim, &cooptTester );
		interrupt.schedule();
		sim.step();
		CHECK( cooptTester.isScheduled() );
		sim.step();
		CHECK_EQUAL( static_cast< base::Process* >( 0 ), cooptTester.slave );
	}

	/**
	 * @test odemx::WaitQ::coopt(Weight) master coopt call with waiting slaves
	 *
	 * Precondition: two processes are waiting as slaves.
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li the statistics are updated
	 * @li the master received the highest weighing slave
	 */
	TEST_FIXTURE( WaitQFixture, CooptWeight )
	{
		WaitQTestSlave slave1( sim, "WaitQTestCooptWeightSlave1", waitQ, 0, (base::Weight) &WaitQTestMaster::weight );
		WaitQTestSlave slave2( sim, "WaitQTestCooptWeightSlave2", waitQ, 0, (base::Weight) &WaitQTestMaster::weight );
		slave1.activate();
		slave2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue

		// make sure slave1 is chosen
		slave1.weightValue = 10;
		WaitQTestMaster master1( sim, "WaitQTestCooptWeightMaster1", waitQ, 0, true );
		master1.activate();
		sim.step(); // enqueue
		CHECK( log->getTraceRecord( "coopt weight succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "synchronizations", type ) );
		CHECK( log->getStatisticsRecord( "update", "master wait time", type ) );
		CHECK( log->getStatisticsRecord( "update", "slave wait time", type ) );
		CHECK( observer.observed( "onCooptWeightSucceed", waitQ.getLabel(), master1.getLabel(), slave1.getLabel() ) );
		CHECK_EQUAL( &slave1, master1.slave );
	}

	/**
	 * @test odemx::WaitQ::coopt(Weight) master coopt call without waiting slaves
	 *
	 * Precondition: two processes are waiting as masters.
	 *
	 * Expected function call effects:
	 * @li calling from non-Process objects causes an error message
	 * @li the calling process is added to a waiting queue
	 * @li the calling process is suspended
	 * @li the failing call can be observed
	 */
	TEST_FIXTURE( WaitQFixture, CooptWeightFail )
	{
		waitQ.coopt( (base::Weight) &WaitQTestMaster::weight );
		CHECK( log->getErrorRecord( "WaitQ::coopt(): called by non-Process object", type ) );

		WaitQTestMaster master1( sim, "WaitQTestCooptWeightMaster1", waitQ, 0, true );
		master1.activate();
		sim.step(); // enqueue
		const std::list< base::Process* >& masters = waitQ.getWaitingMasters();
		CHECK( std::find( masters.begin(), masters.end(), &master1 ) != masters.end() );
		CHECK_EQUAL( base::Process::IDLE, master1.getProcessState() );
		CHECK( log->getTraceRecord( "coopt weight failed", type ) );
		CHECK( observer.observed( "onCooptWeightFail", waitQ.getLabel(), master1.getLabel() ) );

		SuiteBase::InterruptEvent interrupt( sim, &master1 );
		interrupt.schedule();
		sim.step();
		CHECK( master1.isScheduled() );
		sim.step();
		CHECK_EQUAL( static_cast< base::Process* >( 0 ), master1.slave );
	}

	/**
	 * @test odemx::WaitQ::avail() get slave if available without waiting
	 *
	 * Precondition: a process is waiting as slave.
	 *
	 * Expected function call effects:
	 * @li calling from non-Process objects causes an error message
	 * @li the successful call can be observed
	 * @li the statistics are updated
	 * @li the waiting slave is taken by the master
	 */
	TEST_FIXTURE( WaitQFixture, Avail )
	{
		waitQ.avail();
		CHECK( log->getErrorRecord( "WaitQ::avail(): called by non-Process object", type ) );

		WaitQTestSlave slave1( sim, "WaitQTestAvailSlave1", waitQ );
		slave1.activate();
		sim.step(); // enqueue

		WaitQTestAvail availTester( sim, "WaitQTestAvailMaster", waitQ );
		availTester.activate();
		sim.step(); // get slave

		CHECK( log->getTraceRecord( "avail succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "synchronizations", type ) );
		CHECK( log->getStatisticsRecord( "update", "master wait time", type ) );
		CHECK( log->getStatisticsRecord( "update", "slave wait time", type ) );
		CHECK( observer.observed( "onAvailSucceed", waitQ.getLabel(), availTester.getLabel(), slave1.getLabel() ) );
		CHECK_EQUAL( &slave1, availTester.slave );
	}

	/**
	 * @test odemx::WaitQ::avail() without waiting slave
	 *
	 * Precondition: no process is waiting as slave.
	 *
	 * Expected function call effects:
	 * @li the calling process is not suspended
	 * @li the failing call can be observed
	 * @li the return value is a 0-pointer
	 */
	TEST_FIXTURE( WaitQFixture, AvailFail )
	{
		WaitQTestAvail availTester( sim, "WaitQTestAvailEmptyMaster", waitQ );
		availTester.activate();
		sim.step(); // avail fails
		CHECK( log->getTraceRecord( "avail failed", type ) );
		CHECK( observer.observed( "onAvailFail", waitQ.getLabel(), availTester.getLabel() ) );
		CHECK( availTester.hasReturned() );
		CHECK_EQUAL( static_cast< base::Process* >( 0 ), availTester.slave );
	}

	/**
	 * @test odemx::WaitQ::avail(Selection) get selected slave if available without waiting
	 *
	 * Precondition: two processes are waiting as slave.
	 *
	 * Expected function call effects:
	 * @li the successful call can be observed
	 * @li the statistics are updated
	 * @li the waiting slave is taken by the master
	 */
	TEST_FIXTURE( WaitQFixture, AvailSelect )
	{
		int selectValue = 125;
		WaitQTestSlave slave1( sim, "WaitQTestAvailSelSlave1", waitQ, selectValue );
		WaitQTestSlave slave2( sim, "WaitQTestAvailSelSlave2", waitQ, 5 );
		slave1.activate();
		slave2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue

		// make sure slave1 is chosen
		WaitQTestAvail availTester( sim, "WaitQTestAvailMaster", waitQ, selectValue );
		availTester.activate();
		sim.step(); // get slave
		CHECK( log->getTraceRecord( "avail select succeeded", type ) );
		CHECK( log->getStatisticsRecord( "count", "synchronizations", type ) );
		CHECK( log->getStatisticsRecord( "update", "master wait time", type ) );
		CHECK( log->getStatisticsRecord( "update", "slave wait time", type ) );
		CHECK( observer.observed( "onAvailSelSucceed", waitQ.getLabel(), availTester.getLabel(), slave1.getLabel() ) );
		CHECK_EQUAL( &slave1, availTester.slave );
	}

	/**
	 * @test odemx::WaitQ::avail(Selection) get selected slave if available without waiting
	 *
	 * Precondition: two processes are waiting as slave, but neither fits the selection.
	 *
	 * Expected function call effects:
	 * @li the failing call can be observed
	 * @li the return value is a 0-pointer
	 */
	TEST_FIXTURE( WaitQFixture, AvailSelectFail )
	{
		int selectValue = 125;
		WaitQTestSlave slave1( sim, "WaitQTestAvailSelFailSlave1", waitQ, 1 );
		WaitQTestSlave slave2( sim, "WaitQTestAvailSelFailSlave2", waitQ, 2 );
		slave1.activate();
		slave2.activate();
		sim.step(); // enqueue
		sim.step(); // enqueue

		WaitQTestAvail availTester( sim, "WaitQTestAvailSelFailMaster", waitQ, selectValue );
		availTester.activate();
		sim.step(); // get slave
		CHECK( log->getTraceRecord( "avail select failed", type ) );
		CHECK( observer.observed( "onAvailSelFail", waitQ.getLabel(), availTester.getLabel(), slave1.getLabel() ) );
		CHECK( availTester.hasReturned() );
		CHECK_EQUAL( static_cast< base::Process* >( 0 ), availTester.slave );
	}

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