Files
libsst/ZTestSuite/Test-ZConcurrency.cpp
2026-04-03 00:22:39 -05:00

241 lines
5.9 KiB
C++

/*
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;
}