/**
 * @file TestMemory.cpp
 * @date Jul 22, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class Memory
 */

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

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

	/**
	 * @struct MemoryFixture
	 * @brief Helper struct providing set-up/tear-down of Memory tests
	 *
	 * @copydetails EventFixture
	 */
	struct MemoryFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		MemoryTestObserver observer;
		MemoryTestProcess* p1;
		MemoryTestProcess* p2;
		MemoryTestProcess* p3;
		MemoryTest memo;
		data::TypeInfo type;

		MemoryFixture()
		:	sim( "MemoryTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			p1( 0 ),
			p2( 0 ),
			p3( 0 ),
			memo( sim, "MemoryTest", Memory::USER_DEFINED, &observer ),
			type( typeid(Memory) )
			{
				sim.addConsumer( log );

				p1 = new MemoryTestProcess( sim, "MemoryTestProcess1" );
				p2 = new MemoryTestProcess( sim, "MemoryTestProcess2" );
				p3 = new MemoryTestProcess( sim, "MemoryTestProcess3" );
			}

		~MemoryFixture()
		{
			if( p1 ) delete p1;
			if( p2 ) delete p2;
			if( p3 ) delete p3;
		}
	};

	/**
	 * @test odemx::Memory construction and destruction
	 *
	 * Expected effects:
	 * @li construction can be observed
	 * @li members are correctly initialized
	 * @li destruction can be observed
	 */
	TEST_FIXTURE( MemoryFixture, ConstructionDestruction )
	{
		observer.clear();

		data::Label label = "MemoryTestConstructionDestruction";
		{
			MemoryTest memory( sim, label, Memory::USER_DEFINED, &observer );
			CHECK_EQUAL( label, memory.getLabel() );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( log->getStatisticsRecord( "update", "waiting", type ) );
			CHECK( observer.onCreateMemory );
			CHECK_EQUAL( &memory, observer.createdMemoryObject );
			CHECK_EQUAL( Memory::USER_DEFINED, memory.getMemoryType() );
			CHECK_EQUAL( &observer, memory.getObservers().front() );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );
	}

	/**
	 * @test odemx::Memory::remember(Sched*)
	 *
	 * Expected function call effects:
	 * @li a successful call returns \c true
	 * @li the given object is added to the memory list
	 * @li the call can be observed
	 * @li calling the function twice with the same object sends a warning to
	 * \c errorStream()
	 * @li failed calls return \c false
	 */
	TEST_FIXTURE( MemoryFixture, Remember )
	{
		bool success = memo.remember( p1 );
		CHECK( success );
		CHECK( memo.waiting() == true );
		CHECK_EQUAL( (std::size_t) 1, memo.countWaiting() );
		CHECK( find( memo.getWaiting().begin(), memo.getWaiting().end(), p1 ) != memo.getWaiting().end() );
		CHECK( log->getTraceRecord( "remember", type ) );
		CHECK( observer.observed( "onRemember", memo.getLabel(), p1->getLabel() ) );

		success = memo.remember( p1 );
		CHECK( ! success );
		CHECK( log->getWarningRecord( "Memory::remember(): object already stored in list", type ) );
	}

	/**
	 * @test odemx::Memory::forget(Sched*)
	 *
	 * Expected function call effects:
	 * @li the object was removed from the list of waiting processes
	 * @li trying to remove an object that's not in the list causes an error
	 * message to be sent to \c errorStream()
	 * @li the call can be observed
	 */
	TEST_FIXTURE( MemoryFixture, Forget )
	{
		memo.remember( p1 );
		memo.remember( p2 );
		memo.remember( p3 );

		bool success = memo.forget( p2 );
		CHECK( success );
		CHECK_EQUAL( (std::size_t) 2, memo.countWaiting() );
		CHECK_EQUAL( p1, memo.getWaiting().front() );
		CHECK_EQUAL( p3, memo.getWaiting().back() );
		CHECK( log->getTraceRecord( "forget", type ) );
		CHECK( log->getStatisticsRecord( "update", "waiting", type ) );
		CHECK( observer.observed( "onForget", memo.getLabel(), p2->getLabel() ) );

		clearErrorStream();
		success = memo.forget( p2 );
		CHECK( ! success );
		CHECK( log->getErrorRecord( "Memory::forget(): object not stored in list", type ) );
	}

	/**
	 * @test odemx::eraseMemory()
	 *
	 * Expected function call effects:
	 * @li the list of remembered objects is cleared
	 */
	TEST_FIXTURE( MemoryFixture, EraseMemory )
	{
		memo.remember( p1 );
		memo.remember( p2 );
		memo.remember( p3 );

		memo.eraseMemory();
		CHECK( memo.waiting() == false );
		CHECK( memo.getWaiting().empty() );
		CHECK( log->getTraceRecord( "erase", type ) );
		CHECK( log->getStatisticsRecord( "update", "waiting", type ) );
	}

	/**
	 * @test odemx::Memory::alert()
	 *
	 * Expected function call effects:
	 * @li attempting to alert non-process objects causes a warning to be sent
	 * to \c errorStream()
	 * @li the call can be observed
	 * @li CREATED or IDLE processes are alerted
	 * @li the memory list is empty
	 * @li attempting to alert a terminated process causes a warning to be sent
	 * to \c errorStream()
	 * @li attempting to alert a previously scheduled process causes a warning
	 * to be sent to \c errorStream()
	 */
	TEST_FIXTURE( MemoryFixture, Alert )
	{
		memo.alert();
		CHECK( log->getWarningRecord( "Memory::alert(): list empty, cannot alert any object", type ) );

		MemoryTestEvent e1( sim, "MemoryTestAlertEvent" );
		memo.remember( &e1 );
		memo.alert();
		CHECK( log->getWarningRecord( "Memory::alert(): could not alert Sched object", type ) );
		CHECK( log->getWarningRecord( "Memory::checkSchedForAlert(): object is not a process", type ) );

		p1->sleep();
		CHECK_EQUAL( base::Process::IDLE, p1->getProcessState() );
		CHECK_EQUAL( base::Process::CREATED, p2->getProcessState() );
		memo.remember( p1 );
		memo.remember( p2 );
		memo.alert();
		CHECK( log->getTraceRecord( "alert", type ) );
		CHECK( observer.observed( "onAlert", memo.getLabel() ) );
		CHECK( p1->isAlerted() );
		CHECK( p2->isAlerted() );
		CHECK( memo.getWaiting().empty() );

		p1->cancel();
		CHECK_EQUAL( base::Process::TERMINATED, p1->getProcessState() );
		memo.remember( p1 );
		memo.alert();
		CHECK( log->getErrorRecord( "Memory::checkSchedForAlert(): process already terminated", type ) );

		p2->activate();
		CHECK_EQUAL( base::Process::RUNNABLE, p2->getProcessState() );
		memo.remember( p2 );
		memo.alert();
		CHECK( log->getWarningRecord( "Memory::checkSchedForAlert(): process already scheduled", type ) );
	}

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