/*
	Test-ZConcurrency.cpp
	Author: Patrick Baggett

	Purpose :	Unit tests for ZConcurrency implementation to ensure correct behavior

	Changelog :
	12/18/2011 - Removed dependency on ZString (crertel)
	5/15/2011 - Created (ptbaggett)
*/

#include "ZUnitTest.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <ZUtil/ZConcurrency.hpp>

static const char* testCreateThread();
static const char* testSleepGetTicks();
static const char* testUncontLockUnlockMutex();
static const char* testContLockUnlockMutex();
static const char* testUncontSema();
static const char* testContSema();

#define NRTHR 16
#define NRITER 10000

//List of unit tests
ZUnitTest ZConcurrencyUnitTests[] =
{
	{ "CreateThread()/WaitThread()", testCreateThread },
	{ "Sleep()/GetTicks()", testSleepGetTicks },
	{ "Uncontested LockMutex()/UnlockMutex()", testUncontLockUnlockMutex },
	{ "Heavily contested LockMutex/UnlockMutex()", testContLockUnlockMutex },
	{ "Uncontested Semaphore", testUncontSema },
	{ "Heavily contested Semaphore", testContSema }

};

DECLARE_ZTESTBLOCK(ZConcurrency);

/*************************************************************************/

static const char* testThread1Data;
static int testThread1(void* arg)
{
	unsigned int* val = (unsigned int*)arg;

	if(*val == 0xAABBCCDD)
		testThread1Data = ZTEST_SUCCESS;
	else
		testThread1Data = "Error with CreateThread()'s argument passing";

	return 0x11223344;
}

static const char* testCreateThread()
{
	ZThreadContext ctx;
	int arg = 0xAABBCCDD;

	ctx = ZConcurrency::CreateThread(testThread1, &arg);

	TASSERT(ctx.IsValid(), "CreateThread() returned invalid thread context");

	ZConcurrency::WaitThread(ctx);
	
	int thrRetVal = ctx.ThreadExitStatus;

	TASSERT(thrRetVal == 0x11223344, "WaitThread() returned wrong value");

	ZConcurrency::DestroyThread(ctx);

	TASSERT(!ctx.IsValid(), "ZThreadContext is valid after DestroyThread()");

	return testThread1Data;
}

/*************************************************************************/

static const char* testSleepGetTicks()
{
	uint64_t timeStart, timeEnd;

	timeStart = ZConcurrency::GetTicks();
	ZConcurrency::SleepThread(1000);
	timeEnd = ZConcurrency::GetTicks();

	TASSERT(timeStart < timeEnd, "GetTicks() not monotonically increasing");
	
	//We can't require precisely 1,000ms, but we do require it as a lower bound
	TASSERT(timeEnd - timeStart >= 1000, "GetTicks() reports < 1000ms elapsed, but SleepThread(1000) called.");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* testUncontLockUnlockMutex()
{
	//Basically, if this crashes, then FAIL
	ZMutex lock;

	//TASSERT(lock != NULL, "CreateMutex() returned null pointer");

	//Lock/unlock a bunch. This should be fast since it is uncontested.
	//Mainly, we're checking here to see if lock/unlock repeatedly triggers a crash
	for(int i=0; i<1000; i++)
	{
		lock.Acquire();
		lock.Release();
	}

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static ZMutex testContLock;

static int testContFunc(void* arg)
{
	int* inc = (int*) arg;

	for(int i=0; i<NRITER; i++)
	{
		testContLock.Acquire();
		*inc += 1;
		testContLock.Release();
	}
	
	return 0;
}

static const char* testContLockUnlockMutex()
{
	ZThreadContext threads[NRTHR];
	int arg = 0;

	for(int i=0; i<NRTHR; i++)
		threads[i] = ZConcurrency::CreateThread(testContFunc, &arg);

	for(int i=0; i<NRTHR; i++)
	{
		ZConcurrency::WaitThread(threads[i]);
		ZConcurrency::DestroyThread(threads[i]);
	}

	TASSERT(arg == NRTHR * NRITER, "Invalid counter value, mutex not functioning")

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* testUncontSema()
{
	ZSemaphore sem(3);
	bool ok = true;

	//TASSERT(sem != NULL, "CreateSemaphore() failed");

	//Decrement count back down to 0 from 3
	ok = ok && sem.Wait(ZWAIT_INFINITE);
	TASSERT(ok, "First WaitSemaphore() failed");
	ok = ok && sem.Wait(ZWAIT_INFINITE);
	TASSERT(ok, "Second WaitSemaphore() failed");
	ok = ok && sem.Wait(ZWAIT_INFINITE);
	TASSERT(ok, "Third WaitSemaphore() failed");

	//This should immediately return with a failure message
	ok = sem.Wait(0);
	TASSERT(!ok, "sem.Wait() with count of 0 succeeded, but should have failed.");

	//Post to the semaphore, the decrement it again
	sem.Post(2);
	ok = sem.Wait(ZWAIT_INFINITE);
	TASSERT(ok, "First sem.Wait() after PostSemaphore() failed");
	ok = ok && sem.Wait(ZWAIT_INFINITE);
	TASSERT(ok, "Second sem.Wait() after PostSemaphore() failed");

	//This should immediately return with a failure message
	ok = sem.Wait(0);
	TASSERT(!ok, "sem.Wait() (second round) with count of 0 succeeded, but should have failed.");

	return ZTEST_SUCCESS;
}

/*************************************************************************/
static volatile int testContSemaCounter;
static ZMutex testContSemaLock;

#include <ZUtil/ZConcurrency.hpp>
static int testContSemaFunc(void* arg)
{
	ZSemaphore* sem = (ZSemaphore*)arg;
	bool ok;
	do
	{
		ok = sem->Wait(0);
		if (ok)
		{
			testContSemaLock.Acquire();
			testContSemaCounter++;
			testContSemaLock.Release();
		}	
	} while(ok);

	return 0;
}

static const char* testContSema()
{
	ZThreadContext thread[NRTHR];
	

	
	//sem = ZConcurrency::CreateSemaphore(NRITER);
	//testContSemaLock = ZConcurrency::CreateMutex();

	//Run 50 batches of this test just in case there might be a race condition
	for(int j = 0; j < 50; j++)
	{
		ZSemaphore sem(NRITER);	
		testContSemaCounter = 0;
		
		//Create a bunch of threads.
		for(int i=0; i<NRTHR; i++)
			thread[i] = ZConcurrency::CreateThread(testContSemaFunc, &sem);

		//Wait and close their contexts
		for(int i = 0; i < NRTHR; i++)
		{
			ZConcurrency::WaitThread(thread[i]);
			ZConcurrency::DestroyThread(thread[i]);
		}
		TASSERT(testContSemaCounter == NRITER, "Counter after semaphore is incorrect");
		TASSERT(sem.Wait(0) == false, "Semaphore still had non-zero count!");
	}

	return ZTEST_SUCCESS;
}