From 2f95583df8e55d80ad76c13e6b10996bf418f467 Mon Sep 17 00:00:00 2001 From: Daniel Buckmaster Date: Sat, 2 Aug 2014 16:42:07 +1000 Subject: [PATCH] Ported thread tests without the stress tests. --- Engine/source/platform/test/testThreading.cpp | 417 ------------------ .../platform/threads/test/mutexTest.cpp | 70 +++ .../platform/threads/test/semaphoreTest.cpp | 90 ++++ .../platform/threads/test/threadTest.cpp | 56 +++ Tools/projectGenerator/modules/core.inc | 1 + 5 files changed, 217 insertions(+), 417 deletions(-) delete mode 100644 Engine/source/platform/test/testThreading.cpp create mode 100644 Engine/source/platform/threads/test/mutexTest.cpp create mode 100644 Engine/source/platform/threads/test/semaphoreTest.cpp create mode 100644 Engine/source/platform/threads/test/threadTest.cpp diff --git a/Engine/source/platform/test/testThreading.cpp b/Engine/source/platform/test/testThreading.cpp deleted file mode 100644 index 6eea80ee3..000000000 --- a/Engine/source/platform/test/testThreading.cpp +++ /dev/null @@ -1,417 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -#include "platform/platform.h" -#include "platform/threads/thread.h" -#include "platform/threads/semaphore.h" -#include "platform/threads/mutex.h" -#include "unit/test.h" -#include "core/util/tVector.h" -#include "console/console.h" - -using namespace UnitTesting; - -class ThreadTestHarness -{ - U32 mStartTime, mEndTime, mCleanupTime; - void (*mThreadBody)(void*); - S32 mThreadCount; - Thread **mThreads; - -public: - ThreadTestHarness() - { - mStartTime = mEndTime = mCleanupTime = 0; - mThreadBody = NULL; - mThreadCount = 1; - mThreads = NULL; - } - - void startThreads(void (*threadBody)(void*), void *arg, U32 threadCount) - { - mThreadCount = threadCount; - mThreadBody = threadBody; - - // Start up threadCount threads... - mThreads = new Thread*[threadCount]; - - mStartTime = Platform::getRealMilliseconds(); - - //Con::printf(" Running with %d threads...", threadCount); - for(S32 i=0; istart(); - } - } - - void waitForThreadExit(U32 checkFrequencyMs) - { - // And wait for them to complete. - bool someAlive = true; - S32 liveCount = mThreadCount; - - while(someAlive) - { - //Con::printf(" - Sleeping for %dms with %d live threads.", checkFrequencyMs, liveCount); - Platform::sleep(checkFrequencyMs); - - someAlive = false; - liveCount = 0; - - for(S32 i=0; iisAlive()) - continue; - - someAlive = true; - liveCount++; - } - - } - - mEndTime = Platform::getRealMilliseconds(); - - // Clean up memory at this point. - for(S32 i=0; iacquire(false), "Should succeed at acquiring a new semaphore with count 1."); - test(sem2->acquire(false), "This one should succeed too, see previous test."); - - // Test that we can do non-blocking acquires that fail. - test(sem1->acquire(false)==false, "Should failed, as we've already got the sem."); - sem1->release(); - test(sem2->acquire(false)==false, "Should also fail."); - sem2->release(); - - // Test that we can do blocking acquires that succeed. - test(sem1->acquire(true)==true, "Should succeed as we just released."); - test(sem2->acquire(true)==true, "Should succeed as we just released."); - - // Can't test blocking acquires that never happen... :) - - // Clean up. - delete sem1; - delete sem2; - } -}; - -CreateUnitTest( SemaphoreWaitTest, "Platform/Threads/SemaphoreWaitTest") -{ - static void threadBody(void *self) - { - SemaphoreWaitTest *me = (SemaphoreWaitTest*)self; - - // Wait for the semaphore to get released. - me->mSemaphore->acquire(); - - // Increment the counter. - Mutex::lockMutex(me->mMutex); - me->mDoneCount++; - Mutex::unlockMutex(me->mMutex); - - // Signal back to the main thread we're done. - me->mPostbackSemaphore->release(); - } - - Semaphore *mSemaphore; - Semaphore *mPostbackSemaphore; - void *mMutex; - U32 mDoneCount; - - const static S32 csmThreadCount = 10; - - void run() - { - ThreadTestHarness tth; - - mDoneCount = 0; - mSemaphore = new Semaphore(0); - mPostbackSemaphore = new Semaphore(0); - mMutex = Mutex::createMutex(); - - tth.startThreads(&threadBody, this, csmThreadCount); - - Platform::sleep(500); - - Mutex::lockMutex(mMutex); - test(mDoneCount == 0, "no threads should have touched the counter yet."); - Mutex::unlockMutex(mMutex); - - // Let 500 come out. - for(S32 i=0; irelease(); - - // And wait for 500 postbacks. - for(S32 i=0; iacquire(); - - Mutex::lockMutex(mMutex); - test(mDoneCount == csmThreadCount / 2, "Didn't get expected number of done threads! (a)"); - Mutex::unlockMutex(mMutex); - - // Ok, now do the rest. - // Let 500 come out. - for(S32 i=0; irelease(); - - // And wait for 500 postbacks. - for(S32 i=0; iacquire(); - - Mutex::lockMutex(mMutex); - test(mDoneCount == csmThreadCount, "Didn't get expected number of done threads! (b)"); - Mutex::unlockMutex(mMutex); - - // Wait for the threads to exit - shouldn't have to wait ever though. - tth.waitForThreadExit(10); - - // Make sure no one touched our data after shutdown time. - Mutex::lockMutex(mMutex); - test(mDoneCount == csmThreadCount, "Didn't get expected number of done threads! (c)"); - Mutex::unlockMutex(mMutex); - } -}; - -CreateUnitTest( MutexWaitTest, "Platform/Threads/MutexWaitTest") -{ - static void threadBody(void *self) - { - MutexWaitTest *me = (MutexWaitTest*)self; - - // Increment the counter. We'll block until the mutex - // is open. - Mutex::lockMutex(me->mMutex); - me->mDoneCount++; - Mutex::unlockMutex(me->mMutex); - } - - void *mMutex; - U32 mDoneCount; - - const static S32 csmThreadCount = 10; - - void run() - { - mMutex = Mutex::createMutex(); - mDoneCount = 0; - - // We lock the mutex before we create any threads, so that all the threads - // block on the mutex. Then we unlock it and let them all work their way - // through the increment. - Mutex::lockMutex(mMutex); - - ThreadTestHarness tth; - tth.startThreads(&threadBody, this, csmThreadCount); - - Platform::sleep(5000); - - // Check count is still zero. - test(mDoneCount == 0, "Uh oh - a thread somehow didn't get blocked by the locked mutex!"); - - // Open the flood gates... - Mutex::unlockMutex(mMutex); - - // Wait for the threads to all finish executing. - tth.waitForThreadExit(10); - - Mutex::lockMutex(mMutex); - test(mDoneCount == csmThreadCount, "Hmm - all threads reported done, but we didn't get the expected count."); - Mutex::unlockMutex(mMutex); - - // Kill the mutex. - Mutex::destroyMutex(mMutex); - } -}; \ No newline at end of file diff --git a/Engine/source/platform/threads/test/mutexTest.cpp b/Engine/source/platform/threads/test/mutexTest.cpp new file mode 100644 index 000000000..53379651b --- /dev/null +++ b/Engine/source/platform/threads/test/mutexTest.cpp @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_TESTS_ENABLED +#include "testing/unitTesting.h" +#include "platform/threads/mutex.h" +#include "platform/threads/thread.h" + +TEST(Mutex, BasicSynchronization) +{ + // We test various scenarios wrt to locking and unlocking, in a single + // thread, just to make sure our basic primitives are working in the + // most basic case. + void *mutex1 = Mutex::createMutex(); + EXPECT_TRUE(mutex1 != NULL) + << "First Mutex::createMutex call failed - that's pretty bad!"; + + // This mutex is intentionally unused. + void *mutex2 = Mutex::createMutex(); + EXPECT_TRUE(mutex2 != NULL) + << "Second Mutex::createMutex call failed - that's pretty bad, too!"; + + EXPECT_TRUE(Mutex::lockMutex(mutex1, false)) + << "Nonblocking call to brand new mutex failed - should not be."; + EXPECT_TRUE(Mutex::lockMutex(mutex1, true)) + << "Failed relocking a mutex from the same thread - should be able to do this."; + + // Try to acquire the mutex from another thread. + struct thread + { + static void body(void* mutex) + { + // We should not be able to lock the mutex from a separate thread, but + // we don't want to block either. + EXPECT_FALSE(Mutex::lockMutex(mutex, false)); + } + }; + Thread thread(&thread::body, mutex1); + thread.start(); + thread.join(); + + // Unlock & kill mutex 1 + Mutex::unlockMutex(mutex1); + Mutex::unlockMutex(mutex1); + Mutex::destroyMutex(mutex1); + + // Kill mutex2, which was never touched. + Mutex::destroyMutex(mutex2); +} + +#endif \ No newline at end of file diff --git a/Engine/source/platform/threads/test/semaphoreTest.cpp b/Engine/source/platform/threads/test/semaphoreTest.cpp new file mode 100644 index 000000000..d74971074 --- /dev/null +++ b/Engine/source/platform/threads/test/semaphoreTest.cpp @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_TESTS_ENABLED +#include "testing/unitTesting.h" +#include "platform/threads/semaphore.h" +#include "platform/threads/thread.h" + +TEST(Semaphore, BasicSynchronization) +{ + Semaphore *sem1 = new Semaphore(1); + Semaphore *sem2 = new Semaphore(1); + + // Test that we can do non-blocking acquires that succeed. + EXPECT_TRUE(sem1->acquire(false)) + << "Should succeed at acquiring a new semaphore with count 1."; + EXPECT_TRUE(sem2->acquire(false)) + << "This one should succeed too, see previous test."; + + // Test that we can do non-blocking acquires that fail. + EXPECT_FALSE(sem1->acquire(false)) + << "Should failed, as we've already got the sem."; + sem1->release(); + EXPECT_FALSE(sem2->acquire(false)) + << "Should also fail."; + sem2->release(); + + // Test that we can do blocking acquires that succeed. + EXPECT_TRUE(sem1->acquire(true)) + << "Should succeed as we just released."; + EXPECT_TRUE(sem2->acquire(true)) + << "Should succeed as we just released."; + + // Clean up. + delete sem1; + delete sem2; +} + +TEST(Semaphore, MultiThreadSynchronization) +{ + Semaphore semaphore(1); + + struct thread + { + // Try to acquire the semaphore from another thread. + static void body1(void* sem) + { + Semaphore *semaphore = reinterpret_cast(sem); + EXPECT_TRUE(semaphore->acquire(true)); + // Note that this semaphore is never released. Bad programmer! + } + // One more acquisition should fail! + static void body2(void* sem) + { + Semaphore *semaphore = reinterpret_cast(sem); + EXPECT_FALSE(semaphore->acquire(false)); + } + }; + + Thread thread1(&thread::body1, &semaphore); + EXPECT_TRUE(semaphore.acquire(true)); + thread1.start(); + semaphore.release(); + thread1.join(); + + Thread thread2(&thread::body2, &semaphore); + thread2.start(); + thread2.join(); +} + +#endif \ No newline at end of file diff --git a/Engine/source/platform/threads/test/threadTest.cpp b/Engine/source/platform/threads/test/threadTest.cpp new file mode 100644 index 000000000..9ff5af3aa --- /dev/null +++ b/Engine/source/platform/threads/test/threadTest.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_TESTS_ENABLED +#include "testing/unitTesting.h" +#include "platform/threads/thread.h" + +TEST(Thread, BasicAPI) +{ +#define VALUE_TO_SET 10 + + // This struct exists just so we can define run as a local function. + struct thread + { + // Do some work we can observe. + static void body(void* arg) + { + U32* value = reinterpret_cast(arg); + *value = VALUE_TO_SET; + } + }; + + // Test most basic Thread API functions. + U32 value = ~VALUE_TO_SET; + Thread thread(&thread::body, reinterpret_cast(&value)); + thread.start(); + EXPECT_TRUE(thread.isAlive()); + thread.join(); + EXPECT_FALSE(thread.isAlive()); + + EXPECT_EQ(value, VALUE_TO_SET) + << "Thread did not set expected value!"; + +#undef VALUE_TO_SET +}; + +#endif \ No newline at end of file diff --git a/Tools/projectGenerator/modules/core.inc b/Tools/projectGenerator/modules/core.inc index ede86f187..0e981dca5 100644 --- a/Tools/projectGenerator/modules/core.inc +++ b/Tools/projectGenerator/modules/core.inc @@ -72,6 +72,7 @@ switch( T3D_Generator::$platform ) } addEngineSrcDir('platform/threads'); +addEngineSrcDir('platform/threads/test'); addEngineSrcDir('platform/async'); addEngineSrcDir('platform/input'); addEngineSrcDir('platform/output');