diff --git a/Engine/source/platform/threads/test/threadSafeDequeTest.cpp b/Engine/source/platform/threads/test/threadSafeDequeTest.cpp new file mode 100644 index 000000000..183c31d3f --- /dev/null +++ b/Engine/source/platform/threads/test/threadSafeDequeTest.cpp @@ -0,0 +1,181 @@ +//----------------------------------------------------------------------------- +// 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/threadSafeDeque.h" +#include "platform/threads/thread.h" +#include "core/util/tVector.h" +#include "console/console.h" + +// Test deque without concurrency. +TEST(ThreadSafeDeque, PopFront) +{ + ThreadSafeDeque deque; + String str = "teststring"; + + for(U32 i = 0; i < str.length(); i++) + deque.pushBack(str[i]); + + EXPECT_FALSE(deque.isEmpty()); + + char ch; + for(U32 i = 0; i < str.length(); i++) + { + EXPECT_TRUE(deque.tryPopFront(ch)); + EXPECT_EQ(str[i], ch); + } + + ASSERT_TRUE(deque.isEmpty()); +} + +TEST(ThreadSafeDeque, PopBack) +{ + ThreadSafeDeque deque; + String str = "teststring"; + + const char* p1 = str.c_str() + 4; + const char* p2 = p1 + 1; + while(*p2) + { + deque.pushFront(*p1); + deque.pushBack(*p2); + --p1; + ++p2; + } + + char ch; + for(S32 i = str.length()-1; i >= 0; i--) + { + EXPECT_TRUE(deque.tryPopBack(ch)); + EXPECT_EQ(str[i], ch); + } + + ASSERT_TRUE(deque.isEmpty()); +} + +// Test deque in a concurrent setting. +TEST(ThreadSafeDeque, Concurrent1) +{ + const U32 NumValues = 100; + + struct Value : public ThreadSafeRefCount + { + U32 mIndex; + U32 mTick; + + Value() {} + Value(U32 index, U32 tick) + : mIndex(index), mTick(tick) {} + }; + + typedef ThreadSafeRef ValueRef; + + struct Deque : public ThreadSafeDeque + { + typedef ThreadSafeDeque Parent; + + U32 mPushIndex; + U32 mPopIndex; + + Deque() + : mPushIndex(0), mPopIndex(0) {} + + void pushBack(const ValueRef& value) + { + EXPECT_EQ(value->mIndex, mPushIndex) << "index out of line"; + mPushIndex++; + Parent::pushBack(value); + } + + bool tryPopFront(ValueRef& outValue) + { + if(Parent::tryPopFront(outValue)) + { + EXPECT_EQ(outValue->mIndex, mPopIndex) << "index out of line"; + mPopIndex++; + return true; + } + else + return false; + } + }; + + Deque mDeque; + Vector mValues; + + struct ProducerThread : public Thread + { + Vector& mValues; + Deque& mDeque; + ProducerThread(Vector& values, Deque& deque) + : mValues(values), mDeque(deque) {} + + virtual void run(void*) + { + for(U32 i = 0; i < mValues.size(); i++) + { + U32 tick = Platform::getRealMilliseconds(); + mValues[i] = tick; + + ValueRef val = new Value(i, tick); + mDeque.pushBack(val); + } + } + }; + + struct ConsumerThread : public Thread + { + Vector& mValues; + Deque& mDeque; + ConsumerThread(Vector& values, Deque& deque) + : mValues(values), mDeque(deque) {} + + virtual void run(void*) + { + for(U32 i = 0; i < mValues.size(); i++) + { + ValueRef value; + while(!mDeque.tryPopFront(value)); + + EXPECT_EQ(i, value->mIndex); + EXPECT_EQ(value->mTick, mValues[i]); + } + } + }; + + mValues.setSize(NumValues); + + ProducerThread pThread(mValues, mDeque); + ConsumerThread cThread(mValues, mDeque); + + pThread.start(); + cThread.start(); + + pThread.join(); + cThread.join(); + + mValues.clear(); +}; + +#endif \ No newline at end of file diff --git a/Engine/source/platform/threads/test/threadSafePriorityQueueTest.cpp b/Engine/source/platform/threads/test/threadSafePriorityQueueTest.cpp new file mode 100644 index 000000000..668117633 --- /dev/null +++ b/Engine/source/platform/threads/test/threadSafePriorityQueueTest.cpp @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// 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/threadSafePriorityQueue.h" +#include "platform/threads/thread.h" +#include "core/util/tVector.h" +#include "console/console.h" + +// Test queue without concurrency. +TEST(ThreadSafePriorityQueue, Serial) +{ + const U32 min = 0; + const U32 max = 9; + const U32 len = 11; + + U32 indices[len] = { 2, 7, 4, 6, 1, 5, 3, 8, 6, 9, 0}; + F32 priorities[len] = {0.2, 0.7, 0.4, 0.6, 0.1, 0.5, 0.3, 0.8, 0.6, 0.9, 0}; + + ThreadSafePriorityQueue minQueue; + ThreadSafePriorityQueue maxQueue; + + for(U32 i = 0; i < len; i++) + { + minQueue.insert(priorities[i], indices[i]); + maxQueue.insert(priorities[i], indices[i]); + } + + EXPECT_FALSE(minQueue.isEmpty()); + EXPECT_FALSE(maxQueue.isEmpty()); + + U32 index = min; + for(U32 i = 0; i < len; i++) + { + U32 popped; + EXPECT_TRUE(minQueue.takeNext(popped)) + << "Failed to pop element from minQueue"; + EXPECT_LE(index, popped) + << "Element from minQueue was not in sort order"; + index = popped; + } + + index = max; + for(U32 i = 0; i < len; i++) + { + U32 popped; + EXPECT_TRUE(maxQueue.takeNext(popped)) + << "Failed to pop element from maxQueue"; + EXPECT_GE(index, popped) + << "Element from maxQueue was not in sort order"; + index = popped; + } +} + +// Test queue with concurrency. +TEST(ThreadSafePriorityQueue, Concurrent) +{ +#define MIN 0 +#define MAX 9 +#define LEN 11 + + typedef ThreadSafePriorityQueue MinQueue; + typedef ThreadSafePriorityQueue MaxQueue; + + struct ProducerThread : public Thread + { + MinQueue& minQueue; + MaxQueue& maxQueue; + ProducerThread(MinQueue& min, MaxQueue& max) + : minQueue(min), maxQueue(max) {} + + virtual void run(void*) + { + U32 indices[LEN] = { 2, 7, 4, 6, 1, 5, 3, 8, 6, 9, 0}; + F32 priorities[LEN] = {0.2, 0.7, 0.4, 0.6, 0.1, 0.5, 0.3, 0.8, 0.6, 0.9, 0}; + + for(U32 i = 0; i < LEN; i++) + { + minQueue.insert(priorities[i], indices[i]); + maxQueue.insert(priorities[i], indices[i]); + } + } + }; + + MinQueue minQueue; + MaxQueue maxQueue; + ProducerThread producers[] = { + ProducerThread(minQueue, maxQueue), + ProducerThread(minQueue, maxQueue), + ProducerThread(minQueue, maxQueue) + }; + + const U32 len = sizeof(producers) / sizeof(ProducerThread); + for(U32 i = 0; i < len; i++) + producers[i].start(); + for(U32 i = 0; i < len; i++) + producers[i].join(); + + U32 index = MIN; + for(U32 i = 0; i < LEN * len; i++) + { + U32 popped; + EXPECT_TRUE(minQueue.takeNext(popped)) + << "Failed to pop element from minQueue"; + EXPECT_LE(index, popped) + << "Element from minQueue was not in sort order"; + index = popped; + } + + index = MAX; + for(U32 i = 0; i < LEN * len; i++) + { + U32 popped; + EXPECT_TRUE(maxQueue.takeNext(popped)) + << "Failed to pop element from maxQueue"; + EXPECT_GE(index, popped) + << "Element from maxQueue was not in sort order"; + index = popped; + } + +#undef MIN +#undef MAX +#undef LEN +} + +#endif \ No newline at end of file