#include "ZUnitTest.hpp" #include #include static const char* test_Constructors_Nullify(); static const char* test_Operators(); static const char* test_Cast(); static const char* test_ArrayContainment(); static const char* test_ListContainment(); static const char* test_RingBufferContainment(); static const char* test_DeallocLock(); static const char* test_MultithreadAccess(); //List of unit tests ZUnitTest ZWeakPointerUnitTests[] = { { "ZWeakPointer: Constructors, Nullify", test_Constructors_Nullify }, { "ZWeakPointer: Operators", test_Operators }, { "ZWeakPointer: Cast", test_Cast }, { "ZWeakPointer: Containment in ZArray", test_ArrayContainment }, { "ZWeakPointer: Containment in ZList", test_ListContainment }, { "ZWeakPointer: Containment in ZRingBuffer", test_RingBufferContainment }, { "ZWeakPointer: Deallocation Lock", test_DeallocLock }, { "ZWeakPointer: Multithreaded Access, Deallocation Lock", test_MultithreadAccess } }; DECLARE_ZTESTBLOCK(ZWeakPointer); /*************************************************************************/ namespace ZWeakPointerTestObjects { 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; //This function is used to test the auto up-casting of contained types bool autoCast(ZWeakPointer _testObj) { return true; } //Method used to nullify a smart pointer in another thread int nullifySmartPointer(void* _sPtr) { ZSmartPointer& sPtr = *reinterpret_cast*>(_sPtr); sPtr = NULL; return 0; } } using namespace ZWeakPointerTestObjects; /*************************************************************************/ static const char* test_Constructors_Nullify() { ZSmartPointer sNullPtr; ZSmartPointer sPtr(new TestObject()); ZReferenceCounter* sPtrRefCounter = sPtr.Counter(); ZWeakPointer wNullPtr; TASSERT(wNullPtr.Pointer() == NULL, "ZWeakPointer() constructed default pointer not set to null!\n"); TASSERT(wNullPtr.Counter() == NULL, "ZWeakPointer() constructed default pointer with counter!\n"); ZWeakPointer wNullPtrFromPtr(sNullPtr); TASSERT(wNullPtr.Pointer() == NULL, "ZWeakPointer() constructed from null smart pointer not set to null!\n"); TASSERT(wNullPtr.Counter() == NULL, "ZWeakPointer() constructed from null smart pointer with counter!\n"); ZWeakPointer wPtrFromSPtr = sPtr; TASSERT(TestObject::Instances == 1, "ZWeakPointer() constructed additional test object from smart pointer!"); TASSERT(sPtr.Counter() == wPtrFromSPtr.Counter(), "ZWeakPointer() constructed from smart pointer does not share counter!"); TASSERT(wPtrFromSPtr.Counter()->GetStrongRefCount() == 1, "ZWeakPointer() constructed from smart pointer added strong reference!"); TASSERT(wPtrFromSPtr.Counter()->GetWeakRefCount() == 1, "ZWeakPointer() constructed from smart pointer did not add weak reference!"); ZWeakPointer wPtrFromWPtr = wPtrFromSPtr; TASSERT(TestObject::Instances == 1, "ZWeakPointer() constructed additional test object from weak pointer!"); TASSERT(wPtrFromSPtr.Counter() == wPtrFromWPtr.Counter(), "ZWeakPointer() constructed from weak pointer does not share counter!"); TASSERT(wPtrFromWPtr.Counter()->GetStrongRefCount() == 1, "ZWeakPointer() constructed from weak pointer added strong reference!"); TASSERT(wPtrFromWPtr.Counter()->GetWeakRefCount() == 2, "ZWeakPointer() constructed from weak pointer did not add weak reference!"); //Equality Tests TASSERT(wPtrFromSPtr == sPtr, "Equality test failed on weak pointer from smart pointer and smart pointer!"); TASSERT(wPtrFromWPtr == sPtr, "Equality test failed on weak pointer from weak pointer and smart pointer!"); TASSERT(wPtrFromWPtr == wPtrFromSPtr, "Equality test failed on weak pointer from smart pointer and weak pointer from weak pointer!"); //Lose the object sPtr = NULL; TASSERT(TestObject::Instances == 0, "Weak references prevented ZSmartPointer from deallocating object!"); TASSERT(sPtr.Counter() == NULL, "ZSmartPointer failed to nullify pointer to reference counter!"); TASSERT(sPtrRefCounter->GetStrongRefCount() == 0, "Nullification of smart pointer failed to lose strong reference!"); TASSERT(sPtrRefCounter->GetWeakRefCount() == 2, "Nullification of smart pointer reduced weak reference count!"); TASSERT(wPtrFromSPtr == NULL, "Nullification of weak pointer from smart pointer failed!"); TASSERT(sPtrRefCounter->GetWeakRefCount() == 1, "Auto-nullification of weak pointer failed to lose weak reference!"); TASSERT(wPtrFromWPtr == NULL, "Nullification of weak pointer from weak pointer failed!"); TASSERT(wPtrFromSPtr.Counter() == NULL, "Nullification of weak pointer from smart pointer failed to lose reference counter!"); TASSERT(wPtrFromWPtr.Counter() == NULL, "Nullification of weak pointer from weak pointer failed to lose reference counter!"); return ZTEST_SUCCESS; } /*************************************************************************/ static const char* test_Operators() { TestObject* object1 = new TestObject(); TestObject* object2 = new TestObject(); ZSmartPointer sPtr1(object1); ZSmartPointer sPtr2(object2); ZWeakPointer wPtr1 = sPtr1; ZWeakPointer wPtr2 = sPtr2; TASSERT(wPtr1 == object1, "Equality operator failed between weak pointer and object pointer!"); TASSERT(wPtr1 == sPtr1, "Equality operator failed between weak pointer and smart pointer!"); TASSERT(wPtr1 != sPtr2, "Equality operator succeeded between weak pointer and different smart pointer!"); TASSERT(wPtr1 != wPtr2, "Equality operator succeeded between weak pointer and different weak pointer!"); TASSERT(wPtr1->Foo(), "Access operator, call to Foo() failed to return true!"); TASSERT((*wPtr1).Foo(), "Dereference operator, call to Foo() failed to return true!"); return ZTEST_SUCCESS; } /*************************************************************************/ static const char* test_Cast() { ZSmartPointer sPtr(new TestObjectSubclass()); ZWeakPointer wPtr1(sPtr); ZWeakPointer wPtr2(sPtr); TASSERT(TestObject::Instances == 1, "Subclass weak pointer created additional test object!"); TASSERT(sPtr.Counter()->GetWeakRefCount() == 2, "Subclassed weak pointer failed to share reference counter!"); TASSERT(!wPtr1->Foo(), "TestObject virtual method call failed!"); TASSERT(!wPtr2->Foo(), "TestObjectSubclass virtual method call failed!"); TASSERT(wPtr1.Cast()->Bar(), "ZWeakPointer failed to cast!"); TASSERT(autoCast(wPtr2), "ZWeakPointer failed to auto up-cast!"); return ZTEST_SUCCESS; } /*************************************************************************/ static const char* test_ArrayContainment() { ZArray< ZSmartPointer< TestObject > > sPtrArray(10); ZArray< ZWeakPointer< TestObject > > wPtrArray(10); TASSERT(TestObject::Instances == 0, "ZArray of smart pointers allocated instances!"); for (size_t i = 0; i < 10; i++) { //Add a smart pointer to the set sPtrArray.PushBack(ZSmartPointer(znew TestObject())); sPtrArray[i]->Value = i; //Add a weak pointer created from that smart pointer wPtrArray.PushBack(sPtrArray[i]); } for (size_t i = 0; i < 10; i++) { TASSERT(sPtrArray[i].Counter()->GetStrongRefCount() == 1, "Starting Strong Count is incorrect!"); TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 1, "Starting Weak Count is incorrect!"); } wPtrArray.PopBack(); TASSERT(sPtrArray[sPtrArray.Size() - 1].Counter()->GetWeakRefCount() == 0, "PopBack() with weak pointer array failed to remove weak reference!"); wPtrArray.PopFront(); TASSERT(sPtrArray[0].Counter()->GetWeakRefCount() == 0, "PopFront() with weak pointer array failed to remove weak reference!"); wPtrArray.Erase(0, 2); for (size_t i = 1; i < 3; i++) { TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 0, "Erase() with weak pointer array failed to remove weak reference!"); } //At this point, we have six elements in the weak pointer array wPtrArray.Resize(4); //That call should have ditched weak references from the last two for (size_t i = 7; i < 9; i++) { TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 0, "Resize() with lower count failed to remove weak references!"); } wPtrArray.Clear(); for (size_t i = 0; i < 10; i++) { TASSERT(sPtrArray[i].Counter()->GetWeakRefCount() == 0, "Clear() failed to remove all weak references!"); } return ZTEST_SUCCESS; } /*************************************************************************/ static const char* test_ListContainment() { return "TODO"; } /*************************************************************************/ static const char* test_RingBufferContainment() { return "TODO"; } /*************************************************************************/ static int useWeakPointer(void* _wPtr) { ZWeakPointer& wPtr = *reinterpret_cast*>(_wPtr); DeallocLock(wPtr) { return 0; } else { return 1; } } static const char* test_DeallocLock() { TestObject* object = znew TestObject(); ZSmartPointer sPtr(object); ZWeakPointer wPtr = sPtr; DeallocLock(wPtr) { TASSERT(wPtr.Counter()->State > 0, "ZWeakPointer DeallocLock did not increase State variable!"); } TASSERT(wPtr.Counter()->State == 0, "ZWeakPointer DeallocLock did not decrement State variable!"); ZThreadContext ctxt; ZThreadContext ctxt2; DeallocLock(wPtr) { ctxt = ZConcurrency::CreateThread(nullifySmartPointer, &sPtr); //At this point, the other thread should be waiting on this one, but //should signal us by setting state to NULL with an atomic XOR while (wPtr.Counter()->State > 0) continue; ZWeakPointer wPtr2 = wPtr; ctxt2 = ZConcurrency::CreateThread(useWeakPointer, &wPtr2); ZConcurrency::WaitThread(ctxt2); //At the time we have gotten here, State has been signaled and we should fail to be able to signal 'InUse' TASSERT(ctxt2.ThreadExitStatus == 1, "ZWeakPointer was locked and used after being signaled for deallocation!"); ZConcurrency::DestroyThread(ctxt2); } //Now that we have signaled that we are no longer in use, we can wait ZConcurrency::WaitThread(ctxt); ZConcurrency::DestroyThread(ctxt); TASSERT(TestObject::Instances == 0, "Failed to completely deallocate all TestObject instances!"); return ZTEST_SUCCESS; } /*************************************************************************/ #define TEST_NUM_POINTERS (1024) static ZArray< ZWeakPointer > g_threadPointers; static int threadTest(void *arg) { URFP(arg); while (WHILE_TRUE) { size_t numupdated = 0; for (size_t i = 0; i < TEST_NUM_POINTERS; i++) { if (g_threadPointers[i] == NULL) continue; //Effectively DeallocLock, but with the ability to inspect testLock if (ZWeakPointerDeallocLock testLock = g_threadPointers[i]()) { g_threadPointers[i]->Value++; numupdated++; } } if (numupdated == 0) break; } return 0; } static const char* test_MultithreadAccess() { ZArray< ZSmartPointer > pointers; pointers.Resize(TEST_NUM_POINTERS); g_threadPointers.Resize(TEST_NUM_POINTERS); for (size_t i = 0; i < TEST_NUM_POINTERS; i++) { pointers[i] = znew TestObject(); g_threadPointers[i] = pointers[i]; pointers[i]->Value = 0; } ZThreadContext ctxt = ZConcurrency::CreateThread(threadTest, NULL); //Each object now has a smart pointer in this thread and a weak pointer that will be used //by the other thread, so it should be safe to use the weak pointer as long as the DeallocLock //macro is used to prevent deallocation for (size_t i = 0; i < TEST_NUM_POINTERS; i++) { pointers[i] = NULL; ZConcurrency::SleepThread(10); } ZConcurrency::WaitThread(ctxt); ZConcurrency::DestroyThread(ctxt); TASSERT(TestObject::Instances == 0, "Failed to completely deallocate all TestObject instances!"); return ZTEST_SUCCESS; }