Initial commit
This commit is contained in:
490
ZTestSuite/Test-ZSmartPointer.cpp
Normal file
490
ZTestSuite/Test-ZSmartPointer.cpp
Normal file
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
Unit tests for ZSmartPointer to ensure correct behavior.
|
||||
*/
|
||||
|
||||
#define ZSTL_DISABLE_RUNTIME_CHECKS 1
|
||||
#include "ZUnitTest.hpp"
|
||||
|
||||
#include <ZUtil/ZSmartPointer.hpp>
|
||||
#include <ZUtil/ZConcurrency.hpp>
|
||||
#include <SST/SST_MemBarrier.h>
|
||||
|
||||
static const char* test_Constructors();
|
||||
static const char* test_Operators();
|
||||
static const char* test_Cast();
|
||||
static const char* test_Detach();
|
||||
static const char* test_ArrayContainment();
|
||||
static const char* test_ListContainment();
|
||||
static const char* test_RingBufferContainment();
|
||||
static const char* test_MultithreadAccess();
|
||||
|
||||
//List of unit tests
|
||||
ZUnitTest ZSmartPointerUnitTests[] =
|
||||
{
|
||||
{ "ZSmartPointer: Constructors", test_Constructors },
|
||||
{ "ZSmartPointer: Operators", test_Operators },
|
||||
{ "ZSmartPointer: Cast", test_Cast },
|
||||
{ "ZSmartPointer: Detach", test_Detach },
|
||||
{ "ZSmartPointer: Containment in ZArray", test_ArrayContainment },
|
||||
{ "ZSmartPointer: Containment in ZList", test_ListContainment },
|
||||
{ "ZSmartPointer: Containment in ZRingBuffer", test_RingBufferContainment },
|
||||
{ "ZSmartPointer: Multithreaded Access", test_MultithreadAccess }
|
||||
};
|
||||
|
||||
DECLARE_ZTESTBLOCK(ZSmartPointer);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
namespace ZSmartPointerTestObjects
|
||||
{
|
||||
struct TestObject
|
||||
{
|
||||
//Constructor
|
||||
TestObject() { TestObject::Instances++; }
|
||||
|
||||
//Destructor
|
||||
~TestObject() { TestObject::Instances--; }
|
||||
|
||||
//Value field
|
||||
size_t Value;
|
||||
|
||||
//Static Instance Count
|
||||
static size_t Instances;
|
||||
|
||||
//Virtual Function
|
||||
virtual bool Foo() const { return true; }
|
||||
};
|
||||
|
||||
struct TestObjectSubclass : public TestObject
|
||||
{
|
||||
//Constructor
|
||||
TestObjectSubclass() : TestObject() {}
|
||||
|
||||
//Subclass Override
|
||||
bool Foo() const { return false; }
|
||||
|
||||
//Subclass Method
|
||||
bool Bar() const { return true; }
|
||||
};
|
||||
|
||||
//Defaults to zero
|
||||
size_t TestObject::Instances = 0;
|
||||
|
||||
//Factory Method
|
||||
ZSmartPointer<TestObject> getAllocatedTestObject()
|
||||
{
|
||||
return ZSmartPointer<TestObject>(new TestObject());
|
||||
}
|
||||
|
||||
//This function is used to test the auto up-casting of contained types
|
||||
bool autoCast(ZSmartPointer<TestObject> testObj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace ZSmartPointerTestObjects;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_Constructors()
|
||||
{
|
||||
ZSmartPointer<TestObject> nullPtr;
|
||||
TASSERT(nullPtr.Pointer() == NULL, "ZSmartPointer() constructed default pointer not set to null!\n");
|
||||
TASSERT(nullPtr.Counter() == NULL, "ZSmartPointer() constructed default pointer with counter!\n");
|
||||
|
||||
TestObject* testObj = new TestObject();
|
||||
ZSmartPointer<TestObject> fromPtr(testObj);
|
||||
TASSERT(fromPtr.Pointer() != NULL, "ZSmartPointer() constructed pointer from valid pointer as null!\n");
|
||||
TASSERT(fromPtr.Pointer() == testObj, "ZSmartPointer() constructed pointer from valid pointer and changed pointer!\n");
|
||||
TASSERT(fromPtr.Counter() != NULL, "ZSmartPointer() constructed pointer without counter!\n");
|
||||
TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(fromPtr.Counter()->CombinedCount) == 1, "ZSmartPointer() constructed default pointer with bad strong count!\n");
|
||||
TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(fromPtr.Counter()->CombinedCount) == 0, "ZSmartPointer() constructed default pointer with bad weak count!\n");
|
||||
|
||||
ZSmartPointer<TestObject> fromOtherPtr(fromPtr);
|
||||
TASSERT(fromOtherPtr.Pointer() != NULL, "ZSmartPointer() constructed pointer from valid pointer as null!\n");
|
||||
TASSERT(fromOtherPtr.Pointer() == testObj, "ZSmartPointer() constructed pointer from valid pointer and changed pointer!\n");
|
||||
TASSERT(fromOtherPtr.Counter() != NULL, "ZSmartPointer() constructed pointer without counter!\n");
|
||||
TASSERT(ZREFCOUNTER_EXTRACT_STRONG_REF(fromOtherPtr.Counter()->CombinedCount) == 2, "ZSmartPointer() constructed default pointer with bad strong count!\n");
|
||||
TASSERT(ZREFCOUNTER_EXTRACT_WEAK_REF(fromOtherPtr.Counter()->CombinedCount) == 0, "ZSmartPointer() constructed default pointer with bad weak count!\n");
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_Operators()
|
||||
{
|
||||
//Scope in to ensure all destructors get called
|
||||
{
|
||||
TestObject *temp;
|
||||
|
||||
//Equivalent to
|
||||
// TestObject* testObj1 = NULL;
|
||||
ZSmartPointer<TestObject> testObj1;
|
||||
|
||||
TASSERT(TestObject::Instances == 0, "Default constructor allocated object!");
|
||||
|
||||
//Equivalent to
|
||||
// TestObject* testObj2 = new TestObject();
|
||||
ZSmartPointer<TestObject> testObj2(new TestObject());
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "TestObject failed to construct!");
|
||||
TASSERT(testObj1 != testObj2, "ZSmartPointer != operator failed!");
|
||||
|
||||
//Equivalent to
|
||||
// testObj1 = new TestObject();
|
||||
temp = new TestObject();
|
||||
|
||||
TASSERT(TestObject::Instances == 2, "TestObject failed to construct!");
|
||||
|
||||
testObj1 = temp;
|
||||
|
||||
//Equivalent syntax
|
||||
testObj1->Value = 5;
|
||||
testObj2->Value = 10;
|
||||
|
||||
TASSERT(testObj1->Value == 5 && testObj2->Value == 10, "ZSmartPointer = operator failed!");
|
||||
TASSERT(testObj1 != testObj2, "ZSmartPointer != operator failed!");
|
||||
|
||||
//Equivalent to
|
||||
// delete testObj2;
|
||||
// testObj2 = testObj1;
|
||||
testObj2 = testObj1;
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "ZSmartPointer failed to deallocate!");
|
||||
TASSERT(testObj2->Value == 5, "ZSmartPointer = operator failed!");
|
||||
TASSERT(testObj2 == testObj1, "ZSmartPointer == operator failed!");
|
||||
|
||||
//Hard to equate to new/delete, as it allocates a new object and returns a smart pointer to it
|
||||
testObj1 = getAllocatedTestObject();
|
||||
|
||||
TASSERT(TestObject::Instances == 2, "ZSmartPointer copy constructor and assignment operator failed!");
|
||||
|
||||
//Equivalent syntax
|
||||
testObj1->Value = 10;
|
||||
|
||||
TASSERT(testObj2->Value == 5, "ZSmartPointer referencing incorrect object!");
|
||||
TASSERT(testObj1->Value == 10, "ZSmartPointer referencing incorrect object!");
|
||||
|
||||
//Equivalent to
|
||||
// delete testObj1;
|
||||
// testObj1 = testObj2;
|
||||
testObj1 = testObj2;
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "ZSmartPointer failed to deallocate!");
|
||||
|
||||
testObj2 = NULL;
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "ZSmartPointer deallocated incorrectly!");
|
||||
|
||||
testObj1 = NULL;
|
||||
|
||||
TASSERT(TestObject::Instances == 0, "ZSmartPointer failed to deallocate!");
|
||||
}
|
||||
|
||||
TASSERT(TestObject::Instances == 0, "ZSmartPointer failed to call destructors!");
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_Cast()
|
||||
{
|
||||
ZSmartPointer<TestObject> testObj1(new TestObject());
|
||||
TASSERT(TestObject::Instances == 1, "Superclass failed to allocate!");
|
||||
|
||||
ZSmartPointer<TestObject> testObj2(new TestObjectSubclass());
|
||||
TASSERT(TestObject::Instances == 2, "Subclass failed to allocate!");
|
||||
|
||||
TASSERT(testObj1->Foo(), "TestObject virtual method call failed!");
|
||||
TASSERT(!testObj2->Foo(), "TestObjectSubclass virtual method call failed!");
|
||||
TASSERT(testObj2.Cast<TestObjectSubclass>()->Bar(), "ZSmartPointer failed to cast!");
|
||||
|
||||
ZSmartPointer<TestObjectSubclass> testObjSub = testObj2.Cast<TestObjectSubclass>();
|
||||
|
||||
TASSERT(TestObject::Instances == 2, "ZSmartPointer cast function created new object!");
|
||||
|
||||
TASSERT(autoCast(testObjSub), "ZSmartPointer failed to auto up-cast!");
|
||||
|
||||
testObj2 = NULL;
|
||||
|
||||
TASSERT(TestObject::Instances == 2, "ZSmartPointer casted to subclass failed to maintain reference!");
|
||||
|
||||
testObjSub = NULL;
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "ZSmartPointer subclass nullify failed to call destructor!");
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_Detach()
|
||||
{
|
||||
TestObject* object = new TestObject();
|
||||
|
||||
ZSmartPointer<TestObject> sPtr(object);
|
||||
ZSmartPointer<TestObject> sPtr2(sPtr);
|
||||
|
||||
TASSERT(sPtr.Detach() == NULL, "Detach() succeeded when two references are held!");
|
||||
|
||||
sPtr2 = NULL;
|
||||
|
||||
TASSERT(sPtr.Detach() == object, "Detach() failed when one reference was held!");
|
||||
|
||||
delete object;
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_ArrayContainment()
|
||||
{
|
||||
ZArray< ZPtr< TestObject > > testArray(10);
|
||||
|
||||
TASSERT(TestObject::Instances == 0, "ZArray of smart pointers allocated instances!");
|
||||
|
||||
for (size_t i = 0; i < 10; i++)
|
||||
{
|
||||
testArray.PushBack(ZSmartPointer<TestObject>(new TestObject()));
|
||||
testArray[i]->Value = i;
|
||||
}
|
||||
|
||||
TASSERT(TestObject::Instances == 10, "ZArray of smart pointers failing to maintain instances!");
|
||||
|
||||
//After PopBack, we should still have 10 objects (we held onto the popped element)
|
||||
ZPtr<TestObject> testObj1 = testArray.PopBack();
|
||||
|
||||
TASSERT(TestObject::Instances == 10, "ZArray failed to deallocate contained smart pointer!");
|
||||
TASSERT(testObj1->Value == 9, "ZArray failed to return correct smart pointer from PopBack!");
|
||||
|
||||
//Now we should have lost the popped element from earlier
|
||||
testObj1 = testArray.PopFront();
|
||||
|
||||
TASSERT(TestObject::Instances == 9, "ZArray failed to deallocate smart pointer after PopFront!");
|
||||
TASSERT(testObj1->Value == 0, "ZArray failed to return correct smart pointer from PopFront!");
|
||||
|
||||
//This assignment will cause us to lose a test object, but also gain one
|
||||
testObj1 = ZPtr<TestObject>(new TestObject());
|
||||
testObj1->Value = 11;
|
||||
|
||||
//This should keep the same amount of smart pointers (two references to the last one)
|
||||
testArray.PushBack(testObj1);
|
||||
|
||||
TASSERT(TestObject::Instances == 9, "ZArray failed to accept smart pointer with PushBack!");
|
||||
TASSERT(testArray[testArray.Size() - 1]->Value == 11, "ZArray failed to push correct smart pointer with PushBack!");
|
||||
|
||||
//This will cause us to lose one
|
||||
testArray.Erase(5);
|
||||
|
||||
TASSERT(TestObject::Instances == 8, "ZArray failed to deallocate smart pointer after Erase(5)!");
|
||||
|
||||
//This will cause us to lose two more
|
||||
testArray.Erase(2, 4);
|
||||
|
||||
TASSERT(TestObject::Instances == 6, "ZArray failed to deallocate smart pointers after Erase(2, 4)!");
|
||||
|
||||
//This should cause us to lose them all, but testObj1 still has a single reference
|
||||
testArray.Clear();
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "ZArray failed to deallocate all smart pointers after Clear()!");
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_ListContainment()
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
ZList< ZPtr<TestObject> > testList;
|
||||
|
||||
TASSERT(TestObject::Instances == 0, "ZList of smart pointers allocated instances!");
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
{
|
||||
testList.PushBack(ZPtr<TestObject>(new TestObject()));
|
||||
testList.Back()->Value = i;
|
||||
}
|
||||
|
||||
TASSERT(TestObject::Instances == 10, "ZList of smart pointers failing to maintain instances!");
|
||||
|
||||
ZPtr<TestObject> testObj1 = testList.PopBack();
|
||||
|
||||
TASSERT(TestObject::Instances == 10, "ZList failed to deallocate smart pointer!");
|
||||
TASSERT(testObj1->Value == 9, "ZList failed to return correct smart pointer!");
|
||||
|
||||
testObj1 = testList.PopFront();
|
||||
|
||||
TASSERT(TestObject::Instances == 9, "ZSmartPointer failed to deallocate!");
|
||||
TASSERT(testObj1->Value == 0, "ZList failed to return correct smart pointer!");
|
||||
|
||||
testList.PushBack(testObj1);
|
||||
|
||||
TASSERT(TestObject::Instances == 9, "ZList failed to accept SmartPointer!");
|
||||
TASSERT(testList.Back()->Value == 0, "ZList failed to push correct smart pointer!");
|
||||
|
||||
ZList< ZSmartPointer< TestObject > >::Iterator itr3 = testList[5];
|
||||
|
||||
testList.Erase(itr3);
|
||||
|
||||
TASSERT(TestObject::Instances == 8, "ZList failed to deallocate smart pointer!");
|
||||
|
||||
ZList< ZSmartPointer< TestObject > >::Iterator itr1 = testList[2];
|
||||
ZList< ZSmartPointer< TestObject > >::Iterator itr2 = testList[4];
|
||||
|
||||
testList.Erase(itr1, itr2);
|
||||
|
||||
TASSERT(TestObject::Instances == 6, "ZList failed to deallocate smart pointers!");
|
||||
|
||||
testList.Clear();
|
||||
|
||||
TASSERT(TestObject::Instances == 1, "ZList failed to deallocate all smart pointers!");
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static const char* test_RingBufferContainment()
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#define TEST_NUM_POINTERS (1024)
|
||||
#define TEST_NUM_THREADS 4
|
||||
|
||||
static ZArray< ZSmartPointer<TestObject> > g_threadPointers[TEST_NUM_THREADS];
|
||||
static SST_TLS tls;
|
||||
static SST_Event readyGo;
|
||||
|
||||
static void testFunction(ZSmartPointer<TestObject> _sPtr)
|
||||
{
|
||||
ZSmartPointer<TestObject>* sPtr = (ZSmartPointer<TestObject>*)SST_Concurrency_GetTLSValue(tls); //This is just here to hold the reference for a while
|
||||
|
||||
*sPtr = _sPtr;
|
||||
}
|
||||
|
||||
static int threadTest(void* tid)
|
||||
{
|
||||
size_t index = (size_t)tid;
|
||||
ZArray< ZSmartPointer<TestObject> >* thrPtrs = &g_threadPointers[index];
|
||||
ZSmartPointer<TestObject>* ptr;
|
||||
size_t numnullified = 0;
|
||||
|
||||
//Allocate a smart pointer object, store in TLS slot
|
||||
ptr = new ZSmartPointer<TestObject>();
|
||||
SST_Concurrency_SetTLSValue(tls, (void*)ptr);
|
||||
|
||||
//For for ready
|
||||
SST_Concurrency_WaitEvent(readyGo, SST_WAIT_INFINITE);
|
||||
|
||||
//Iterate over and nullify if value is above the loop count
|
||||
while (WHILE_TRUE)
|
||||
{
|
||||
for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
|
||||
{
|
||||
if (i == 0) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if ((*thrPtrs)[i] != NULL && (*thrPtrs)[i]->Value > i)
|
||||
{
|
||||
(*thrPtrs)[i] = NULL;
|
||||
numnullified++;
|
||||
}
|
||||
else
|
||||
{
|
||||
testFunction((*thrPtrs)[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (numnullified == TEST_NUM_POINTERS)
|
||||
break;
|
||||
|
||||
//ZConcurrency::SleepThread(10);
|
||||
}
|
||||
|
||||
testFunction(ZSmartPointer<TestObject>()); //Nullify
|
||||
|
||||
delete ptr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
This is a bit of a strange test. The only way to really ensure that it is safe to lose and gain references
|
||||
across threads is to have two threads operating on smart pointers that reference the same object. If the method
|
||||
terminates, than it was safe. Otherwise... we loop because of bad reference count state.
|
||||
*/
|
||||
static const char* test_MultithreadAccess()
|
||||
{
|
||||
ZArray< ZSmartPointer<TestObject> > pointers;
|
||||
ZThreadContext ctx[TEST_NUM_THREADS];
|
||||
|
||||
pointers.Resize(TEST_NUM_POINTERS);
|
||||
|
||||
//Resize all thread smart pointer arrays
|
||||
for(size_t j=0; j<TEST_NUM_THREADS; j++)
|
||||
g_threadPointers[j].Resize(TEST_NUM_POINTERS);
|
||||
|
||||
for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
|
||||
{
|
||||
pointers[i] = znew TestObject();
|
||||
|
||||
//Copy smart pointers to other threads
|
||||
for(size_t j=0; j< TEST_NUM_THREADS; j++)
|
||||
g_threadPointers[j][i] = pointers[i];
|
||||
}
|
||||
|
||||
//Create TLS and event
|
||||
tls = SST_Concurrency_CreateTLS();
|
||||
readyGo = SST_Concurrency_CreateEvent();
|
||||
|
||||
for(size_t i=0; i<TEST_NUM_THREADS; i++)
|
||||
ctx[i] = ZConcurrency::CreateThread(threadTest, (void*)i);
|
||||
|
||||
size_t numdeleted = 0;
|
||||
size_t itrval = 0;
|
||||
|
||||
SST_Concurrency_SignalEvent(readyGo);
|
||||
while (numdeleted < TEST_NUM_POINTERS)
|
||||
{
|
||||
TestObject *object;
|
||||
|
||||
for (size_t i = 0; i < TEST_NUM_POINTERS; i++)
|
||||
{
|
||||
object = pointers[i].Detach(); //This will only work if this thread holds the only reference
|
||||
|
||||
if (object != NULL)
|
||||
{
|
||||
zdelete object;
|
||||
numdeleted++;
|
||||
}
|
||||
else if (pointers[i] != NULL)
|
||||
pointers[i]->Value = itrval;
|
||||
}
|
||||
|
||||
if(itrval < TEST_NUM_POINTERS)
|
||||
itrval++;
|
||||
}
|
||||
|
||||
//Await thread exit
|
||||
for(size_t i=0; i<TEST_NUM_THREADS; i++)
|
||||
{
|
||||
ZConcurrency::WaitThread(ctx[i]);
|
||||
ZConcurrency::DestroyThread(ctx[i]);
|
||||
}
|
||||
|
||||
SST_Concurrency_DestroyEvent(readyGo);
|
||||
SST_Concurrency_DestroyTLS(tls);
|
||||
|
||||
return ZTEST_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user