/* Unit tests for ZSmartPointer to ensure correct behavior. */ #define ZSTL_DISABLE_RUNTIME_CHECKS 1 #include "ZUnitTest.hpp" #include #include #include 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 getAllocatedTestObject() { return ZSmartPointer(new TestObject()); } //This function is used to test the auto up-casting of contained types bool autoCast(ZSmartPointer testObj) { return true; } } using namespace ZSmartPointerTestObjects; /*************************************************************************/ static const char* test_Constructors() { ZSmartPointer 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 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 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 testObj1; TASSERT(TestObject::Instances == 0, "Default constructor allocated object!"); //Equivalent to // TestObject* testObj2 = new TestObject(); ZSmartPointer 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 testObj1(new TestObject()); TASSERT(TestObject::Instances == 1, "Superclass failed to allocate!"); ZSmartPointer 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()->Bar(), "ZSmartPointer failed to cast!"); ZSmartPointer testObjSub = testObj2.Cast(); 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 sPtr(object); ZSmartPointer 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(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 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(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 > testList; TASSERT(TestObject::Instances == 0, "ZList of smart pointers allocated instances!"); for (i = 0; i < 10; i++) { testList.PushBack(ZPtr(new TestObject())); testList.Back()->Value = i; } TASSERT(TestObject::Instances == 10, "ZList of smart pointers failing to maintain instances!"); ZPtr 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 > g_threadPointers[TEST_NUM_THREADS]; static SST_TLS tls; static SST_Event readyGo; static void testFunction(ZSmartPointer _sPtr) { ZSmartPointer* sPtr = (ZSmartPointer*)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 >* thrPtrs = &g_threadPointers[index]; ZSmartPointer* ptr; size_t numnullified = 0; //Allocate a smart pointer object, store in TLS slot ptr = new ZSmartPointer(); 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()); //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 > pointers; ZThreadContext ctx[TEST_NUM_THREADS]; pointers.Resize(TEST_NUM_POINTERS); //Resize all thread smart pointer arrays for(size_t j=0; jValue = itrval; } if(itrval < TEST_NUM_POINTERS) itrval++; } //Await thread exit for(size_t i=0; i