Merge pull request #801 from eightyeight/gtest-tests

Googletest tests
This commit is contained in:
LuisAntonRebollo 2014-10-02 01:31:13 +02:00
commit 535f56af6e
71 changed files with 3056 additions and 20158 deletions

View file

@ -19,7 +19,7 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
/*
#include "unit/test.h"
#include "platform/async/asyncPacketQueue.h"
#include "console/console.h"
@ -149,3 +149,4 @@ CreateUnitTest( TestAsyncPacketQueue, "Platform/AsyncPacketQueue" )
};
#endif // !TORQUE_SHIPPING
*/

View file

@ -0,0 +1,174 @@
//-----------------------------------------------------------------------------
// 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/platformNet.h"
#include "core/util/journal/process.h"
TEST(Net, TCPRequest)
{
struct handle
{
NetSocket mSocket;
S32 mDataReceived;
void notify(NetSocket sock, U32 state)
{
// Only consider our own socket.
if(mSocket != sock)
return;
// Ok - what's the state? We do some dumb responses to given states
// in order to fulfill the request.
if(state == Net::Connected)
{
U8 reqBuffer[] = {
"GET / HTTP/1.0\nUser-Agent: Torque/1.0\n\n"
};
Net::Error e = Net::sendtoSocket(mSocket, reqBuffer, sizeof(reqBuffer));
ASSERT_EQ(Net::NoError, e)
<< "Got an error sending our HTTP request!";
}
else
{
Process::requestShutdown();
mSocket = NULL;
ASSERT_EQ(Net::Disconnected, state)
<< "Ended with a network error!";
}
}
void receive(NetSocket sock, RawData incomingData)
{
// Only consider our own socket.
if(mSocket != sock)
return;
mDataReceived += incomingData.size;
}
} handler;
handler.mSocket = InvalidSocket;
handler.mDataReceived = 0;
// Hook into the signals.
Net::smConnectionNotify. notify(&handler, &handle::notify);
Net::smConnectionReceive.notify(&handler, &handle::receive);
// Open a TCP connection to garagegames.com
handler.mSocket = Net::openConnectTo("72.246.107.193:80");
const U32 limit = Platform::getRealMilliseconds() + (5*1000);
while(Process::processEvents() && (Platform::getRealMilliseconds() < limit) ) {}
// Unhook from the signals.
Net::smConnectionNotify. remove(&handler, &handle::notify);
Net::smConnectionReceive.remove(&handler, &handle::receive);
EXPECT_GT(handler.mDataReceived, 0)
<< "Didn't get any data back!";
}
TEST(Net, JournalTCPRequest)
{
struct handle
{
NetSocket mSocket;
S32 mDataReceived;
void notify(NetSocket sock, U32 state)
{
// Only consider our own socket.
if(mSocket != sock)
return;
// Ok - what's the state? We do some dumb responses to given states
// in order to fulfill the request.
if(state == Net::Connected)
{
U8 reqBuffer[] = {
"GET / HTTP/1.0\nUser-Agent: Torque/1.0\n\n"
};
Net::Error e = Net::sendtoSocket(mSocket, reqBuffer, sizeof(reqBuffer));
ASSERT_EQ(Net::NoError, e)
<< "Got an error sending our HTTP request!";
}
else
{
Process::requestShutdown();
mSocket = NULL;
ASSERT_EQ(Net::Disconnected, state)
<< "Ended with a network error!";
}
}
void receive(NetSocket sock, RawData incomingData)
{
// Only consider our own socket.
if(mSocket != sock)
return;
mDataReceived += incomingData.size;
}
void makeRequest()
{
mSocket = InvalidSocket;
mDataReceived = 0;
// Hook into the signals.
Net::smConnectionNotify. notify(this, &handle::notify);
Net::smConnectionReceive.notify(this, &handle::receive);
// Open a TCP connection to garagegames.com
mSocket = Net::openConnectTo("72.246.107.193:80");
// Let the callbacks enable things to process.
while(Process::processEvents()) {}
// Unhook from the signals.
Net::smConnectionNotify. remove(this, &handle::notify);
Net::smConnectionReceive.remove(this, &handle::receive);
EXPECT_GT(mDataReceived, 0)
<< "Didn't get any data back!";
}
} handler;
Journal::Record("journalTCP.jrn");
ASSERT_TRUE(Journal::IsRecording());
handler.makeRequest();
S32 bytesRead = handler.mDataReceived;
Journal::Stop();
Journal::Play("journalTCP.jrn");
handler.makeRequest();
Journal::Stop();
EXPECT_EQ(bytesRead, handler.mDataReceived)
<< "Didn't get same data back from journal playback.";
}
#endif

View file

@ -0,0 +1,118 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifdef TORQUE_TESTS_ENABLED
#include "testing/unitTesting.h"
#include "platform/platform.h"
#include "core/fileio.h"
#include "core/util/tVector.h"
#include "console/console.h"
TEST(Platform, ExcludedDirectories)
{
// Just dump everything under the current directory. We should
// find at least one file.
// Exclude .svn and CVS
Platform::clearExcludedDirectories();
Platform::addExcludedDirectory(".svn");
Platform::addExcludedDirectory("CVS");
EXPECT_TRUE(Platform::isExcludedDirectory(".svn"))
<< "On list, should be excluded.";
EXPECT_TRUE(Platform::isExcludedDirectory("CVS"))
<< "On list, should be excluded.";
EXPECT_FALSE(Platform::isExcludedDirectory("foo"))
<< "Doesn't match list, shouldn't be excluded.";
EXPECT_FALSE(Platform::isExcludedDirectory(".svnCVS"))
<< "Looks like a duck, but it shouldn't be excluded cuz it's distinct from all entries on the exclusion list.";
// Ok, now our exclusion list is setup, so let's dump some paths.
Vector<Platform::FileInfo> pathInfo;
Platform::dumpPath(Platform::getCurrentDirectory(), pathInfo, 2);
EXPECT_GT(pathInfo.size(), 0)
<< "Should find at least SOMETHING in the current directory!";
// This'll nuke info if we run it in a live situation... so don't run unit
// tests in a live situation. ;)
Platform::clearExcludedDirectories();
};
TEST(File, TouchAndTime)
{
FileTime create[2], modify[2];
// Create a file and sleep for a second.
File f;
f.open("testTouch.file", File::WriteAppend);
f.close();
// Touch a file and note its last-modified.
dFileTouch("testTouch.file");
EXPECT_TRUE(Platform::isFile("testTouch.file"))
<< "We just touched this file - it should exist.";
EXPECT_TRUE(Platform::getFileTimes("testTouch.file", &create[0], &modify[0]))
<< "Failed to get filetimes for a file we just created.";
// Sleep for a tick
Platform::sleep(10);
// Touch it again, and compare the last-modifieds.
EXPECT_TRUE(Platform::isFile("testTouch.file"))
<< "We just touched this file - it should exist.";
dFileTouch("testTouch.file");
EXPECT_TRUE(Platform::isFile("testTouch.file"))
<< "We just touched this file - it should exist.";
EXPECT_TRUE(Platform::getFileTimes("testTouch.file", &create[1], &modify[1]))
<< "Failed to get filetimes for a file we just created.";
// Now compare the times...
EXPECT_LT(Platform::compareFileTimes(modify[0], modify[1]), 0)
<< "Timestamps are wrong - modify[0] should be before modify[1]!";
EXPECT_EQ(Platform::compareFileTimes(create[0], create[1]), 0)
<< "Create timestamps should match - we didn't delete the file during this test.";
// Clean up..
dFileDelete("testTouch.file");
EXPECT_FALSE(Platform::isFile("testTouch.file"))
<< "Somehow failed to delete our test file.";
};
// Mac has no implementations for these functions, so we 'def it out for now.
#ifndef __MACOSX__
TEST(Platform, Volumes)
{
Vector<const char*> names;
Platform::getVolumeNamesList(names);
EXPECT_GT(names.size(), 0)
<< "We should have at least one volume...";
Vector<Platform::VolumeInformation> info;
Platform::getVolumeInformationList(info);
EXPECT_EQ(names.size(), info.size())
<< "Got inconsistent number of volumes back from info vs. name list functions!";
};
#endif
#endif

View file

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// 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/platformTimer.h"
#include "core/util/journal/process.h"
#include "math/mMath.h"
TEST(Platform, AdvanceTime)
{
U32 time = Platform::getVirtualMilliseconds();
Platform::advanceTime(10);
U32 newTime = Platform::getVirtualMilliseconds();
EXPECT_EQ(10, newTime - time)
<< "We advanced 10ms but didn't get a 10ms delta!";
}
TEST(Platform, Sleep)
{
U32 start = Platform::getRealMilliseconds();
Platform::sleep(500);
U32 end = Platform::getRealMilliseconds();
EXPECT_GE(end - start, 500-10) // account for clock resolution
<< "We didn't sleep at least as long as we requested!";
};
TEST(TimeManager, BasicAPI)
{
struct handle
{
S32 mElapsedTime;
S32 mNumberCalls;
void timeEvent(S32 timeDelta)
{
mElapsedTime += timeDelta;
mNumberCalls++;
if(mElapsedTime >= 1000)
Process::requestShutdown();
}
} handler;
handler.mElapsedTime = handler.mNumberCalls = 0;
// Initialize the time manager...
TimeManager time;
time.timeEvent.notify(&handler, &handle::timeEvent);
// Event loop till at least one second has passed.
const U32 start = Platform::getRealMilliseconds();
while(Process::processEvents())
{
// If we go too long, kill it off...
if(Platform::getRealMilliseconds() - start > 30*1000)
{
EXPECT_TRUE(false)
<< "Terminated process loop due to watchdog, not due to time manager event, after 30 seconds.";
Process::requestShutdown();
}
}
const U32 end = Platform::getRealMilliseconds();
// Now, confirm we have approximately similar elapsed times.
S32 elapsedRealTime = end - start;
EXPECT_LT(mAbs(elapsedRealTime - handler.mElapsedTime), 50)
<< "Failed to elapse time to within the desired tolerance.";
EXPECT_GT(handler.mNumberCalls, 0)
<< "Somehow got no event callbacks from TimeManager?";
};
#endif

View file

@ -0,0 +1,118 @@
//-----------------------------------------------------------------------------
// 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/platform.h"
#include "core/util/endian.h"
TEST(PlatformTypes, Sizes)
{
// Run through all the types and ensure they're the right size.
#define CheckType(typeName, expectedSize) \
EXPECT_EQ( sizeof(typeName), expectedSize) \
<< "Wrong size for a " #typeName ", expected " #expectedSize;
// One byte types.
CheckType(bool, 1);
CheckType(U8, 1);
CheckType(S8, 1);
CheckType(UTF8, 1);
// Two byte types.
CheckType(U16, 2);
CheckType(S16, 2);
CheckType(UTF16, 2);
// Four byte types.
CheckType(U32, 4);
CheckType(S32, 4);
CheckType(F32, 4);
CheckType(UTF32, 4);
// Eight byte types.
CheckType(U64, 8);
CheckType(S64, 8);
CheckType(F64, 8);
// 16 byte (128bit) types will go here, when we get some.
#undef CheckType
};
TEST(PlatformTypes, EndianConversion)
{
// Convenient and non-palindrome byte patterns to test with.
const U16 U16Test = 0xA1B2;
const S16 S16Test = 0x52A1;
const U32 U32Test = 0xA1B2C3D4;
const S32 S32Test = 0xD4C3B2A1;
const F32 F32Test = 1234.5678f;
//const U64 U64Test = 0xA1B2C3D4E3F2E10A;
//const S64 S64Test = 0x1A2B3C4D3E2F1EA0;
const F64 F64Test = 12345678.9101112131415;
// Run through all the conversions - bump stuff from host to little or big
// endian and back again.
#define CheckEndianRoundTrip(type, b_or_l) \
EXPECT_EQ( type##Test, convert##b_or_l##EndianToHost(convertHostTo##b_or_l##Endian(type##Test))) \
<< "Failed to convert the " #type " test value to " #b_or_l " endian and back to host endian order.";
#define CheckTypeBothWays(type) \
CheckEndianRoundTrip(type, B); \
CheckEndianRoundTrip(type, L);
#define CheckIntsForBitSize(bits) \
CheckTypeBothWays( U##bits ); \
CheckTypeBothWays( S##bits );
// Don't check 8-bit types - they aren't affected by endian issues.
// Check the >1 byte int types, though.
CheckIntsForBitSize(16);
CheckIntsForBitSize(32);
// CheckIntsForBitSize(64); // don't have convertHostToLEndian(const U64/S64) so this doesn't work
// And check the float types.
CheckTypeBothWays(F32);
CheckTypeBothWays(F64);
// We'd check 128bit types here, if we had any.
#undef CheckIntsForBitSize
#undef CheckTypeBothWays
#undef CheckEndianRoundTrip
};
TEST(PlatformTypes, EndianSwap)
{
U32 swap32 = 0xABCDEF12;
U16 swap16 = 0xABCD;
EXPECT_EQ(endianSwap(swap32), 0x12EFCDAB)
<< "32 bit endianSwap should reverse byte order, but didn't.";
EXPECT_EQ(endianSwap(swap16), 0xCDAB)
<< "16 bit endianSwap should reverse byte order, but didn't.";
};
#endif

View file

@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h" // Allows us to see TORQUE_ENABLE_PROFILER
#ifdef TORQUE_ENABLE_PROFILER
#include "testing/unitTesting.h"
#include "platform/profiler.h"
TEST(Profiler, ProfileStartEnd)
{
PROFILE_START(ProfileStartEndTest);
// Do work.
if(true)
{
PROFILE_END(ProfileStartEndTest);
return;
}
PROFILE_END(ProfileStartEndTest);
}
TEST(Profiler, ProfileScope)
{
PROFILE_SCOPE(ScopedProfilerTest);
// Do work and return whenever you want.
}
#endif
#endif

View file

@ -1,50 +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 "unit/test.h"
using namespace UnitTesting;
CreateInteractiveTest(CheckPlatformAlerts, "Platform/Alerts")
{
void run()
{
// Run through all the alert types.
Platform::AlertOK("Test #1 - AlertOK", "This is a test of Platform::AlertOK. I am a blocking dialog with an OK button. Please hit OK to continue.");
test(true, "AlertOK should return when the user clicks on it..."); // <-- gratuitous test point.
bool res;
res = Platform::AlertOKCancel("Test #2 - AlertOKCancel", "This is a test of Platform::alertOKCancel. I am a blocking dialog with an OK and a Cancel button. Please hit Cancel to continue.");
test(res==false,"AlertOKCancel - Didn't get cancel. User error, or just bad code?");
res = Platform::AlertOKCancel("Test #3 - AlertOKCancel", "This is a test of Platform::alertOKCancel. I am a blocking dialog with an OK and a Cancel button. Please hit OK to continue.");
test(res==true,"AlertOKCancel - Didn't get ok. User error, or just bad code?");
res = Platform::AlertRetry("Test #4 - AlertRetry", "This is a test of Platform::AlertRetry. I am a blocking dialog with an Retry and a Cancel button. Please hit Retry to continue.");
test(res==true,"AlertRetry - Didn't get retry. User error, or just bad code?");
res = Platform::AlertRetry("Test #5 - AlertRetry", "This is a test of Platform::AlertRetry. I am a blocking dialog with an Retry and a Cancel button. Please hit Cancel to continue.");
test(res==false,"AlertRetry - Didn't get cancel. User error, or just bad code?");
}
};

View file

@ -1,124 +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 "core/util/endian.h"
#include "unit/test.h"
using namespace UnitTesting;
CreateUnitTest(CheckTypeSizes, "Platform/Types/Sizes")
{
void run()
{
// Run through all the types and ensure they're the right size.
#define CheckType(typeName, expectedSize) \
test( sizeof(typeName) == expectedSize, "Wrong size for a " #typeName ", expected " #expectedSize);
// One byte types.
CheckType(bool, 1);
CheckType(U8, 1);
CheckType(S8, 1);
CheckType(UTF8, 1);
// Two byte types.
CheckType(U16, 2);
CheckType(S16, 2);
CheckType(UTF16, 2);
// Four byte types.
CheckType(U32, 4);
CheckType(S32, 4);
CheckType(F32, 4);
CheckType(UTF32, 4);
// Eight byte types.
CheckType(U64, 8);
CheckType(S64, 8);
CheckType(F64, 8);
// 16 byte (128bit) types will go here, when we get some.
#undef CheckType
}
};
CreateUnitTest(CheckEndianConversion, "Platform/Types/EndianRoundTrip")
{
void run()
{
// Convenient and non-palindrome byte patterns to test with.
const U16 U16Test = 0xA1B2;
const S16 S16Test = 0x52A1;
const U32 U32Test = 0xA1B2C3D4;
const S32 S32Test = 0xD4C3B2A1;
const F32 F32Test = 1234.5678f;
//const U64 U64Test = 0xA1B2C3D4E3F2E10A;
//const S64 S64Test = 0x1A2B3C4D3E2F1EA0;
const F64 F64Test = 12345678.9101112131415;
// Run through all the conversions - bump stuff from host to little or big
// endian and back again.
#define CheckEndianRoundTrip(type, b_or_l) \
test( type##Test == convert##b_or_l##EndianToHost(convertHostTo##b_or_l##Endian(type##Test)), "Failed to convert the " #type " test value to " #b_or_l " endian and back to host endian order.");
#define CheckTypeBothWays(type) \
CheckEndianRoundTrip(type, B); \
CheckEndianRoundTrip(type, L);
#define CheckIntsForBitSize(bits) \
CheckTypeBothWays( U##bits ); \
CheckTypeBothWays( S##bits );
// Don't check 8-bit types - they aren't affected by endian issues.
// Check the >1 byte int types, though.
CheckIntsForBitSize(16);
CheckIntsForBitSize(32);
// CheckIntsForBitSize(64); // don't have convertHostToLEndian(const U64/S64) so this doesn't work
// And check the float types.
CheckTypeBothWays(F32);
CheckTypeBothWays(F64);
// We'd check 128bit types here, if we had any.
#undef CheckIntsForBitSize
#undef CheckTypeBothWays
#undef CheckEndianRoundTrip
}
};
CreateUnitTest(CheckEndianSwap, "Platform/Types/EndianSwap")
{
void run()
{
U32 swap32 = 0xABCDEF12;
U16 swap16 = 0xABCD;
test(endianSwap(swap32) == 0x12EFCDAB, "32 bit endianSwap should reverse byte order, but didn't.");
test(endianSwap(swap16) == 0xCDAB, "16 bit endianSwap should reverse byte order, but didn't.");
}
};

View file

@ -1,150 +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 "core/fileio.h"
#include "unit/test.h"
#include "core/util/tVector.h"
#include "console/console.h"
using namespace UnitTesting;
CreateUnitTest(CheckFileListingAndExclusion, "File/ListDirectoryAndExclusions")
{
void run()
{
// Just dump everything under the current directory. We should
// find at least one file.
// Exclude .svn and CVS
Platform::clearExcludedDirectories();
Platform::addExcludedDirectory(".svn");
Platform::addExcludedDirectory("CVS");
test(Platform::isExcludedDirectory("foo") == false, "Doesn't match list, shouldn't be excluded.");
test(Platform::isExcludedDirectory(".svn") == true, "On list, should be excluded.");
test(Platform::isExcludedDirectory("CVS") == true, "On list, should be excluded.");
test(Platform::isExcludedDirectory(".svnCVS") == false, "Looks like a duck, but it shouldn't be excluded cuz it's distinct from all entries on the exclusion list.");
// Ok, now our exclusion list is setup, so let's dump some paths.
Vector < Platform::FileInfo > pathInfo;
Platform::dumpPath (Platform::getCurrentDirectory(), pathInfo, 2);
Con::printf("Dump of files in '%s', up to 2 levels deep...", Platform::getCurrentDirectory());
for(S32 i=0; i<pathInfo.size(); i++)
{
Platform::FileInfo &file = pathInfo[i];
Con::printf(" %s (%s) %d bytes", file.pFullPath, file.pFileName, file.fileSize);
}
test(pathInfo.size() > 0, "Should find at least SOMETHING in the current directory!");
// This'll nuke info if we run it in a live situation... so don't run unit
// tests in a live situation. ;)
Platform::clearExcludedDirectories();
}
};
CreateUnitTest(CheckFileTouchAndTime, "File/TouchAndTime")
{
void run()
{
FileTime create[2], modify[2];
// Create a file and sleep for a second.
File f;
f.open("testTouch.file", File::WriteAppend);
f.close();
Platform::sleep(2000);
// Touch a file and note its last-modified.
dFileTouch("testTouch.file");
test(Platform::isFile("testTouch.file"), "We just touched this file - it should exist.");
test(Platform::getFileTimes("testTouch.file", &create[0], &modify[0]), "Failed to get filetimes for a file we just created.");
// Sleep for a few seconds...
Platform::sleep(5000);
// Touch it again, and compare the last-modifieds.
test(Platform::isFile("testTouch.file"), "We just touched this file - it should exist.");
dFileTouch("testTouch.file");
test(Platform::isFile("testTouch.file"), "We just touched this file - it should exist.");
test(Platform::getFileTimes("testTouch.file", &create[1], &modify[1]), "Failed to get filetimes for a file we just created.");
// Now compare the times...
test(Platform::compareFileTimes(modify[0], modify[1]) < 0, "Timestamps are wrong - modify[0] should be before modify[1]!");
// This seems to fail even on a valid case...
// test(Platform::compareFileTimes(create[0], create[1]) == 0, "Create timestamps should match - we didn't delete the file during this test.");
// Clean up..
dFileDelete("testTouch.file");
test(!Platform::isFile("testTouch.file"), "Somehow failed to delete our test file.");
}
};
// Mac has no implementations for these functions, so we 'def it out for now.
#if 0
CreateUnitTest(CheckVolumes, "File/Volumes")
{
void run()
{
Con::printf("Dumping volumes by name:");
Vector<const char*> names;
Platform::getVolumeNamesList(names);
test(names.size() > 0, "We should have at least one volume...");
for(S32 i=0; i<names.size(); i++)
Con::printf(" %s", names[i]);
Con::printf("Dumping volume info:");
Vector<Platform::VolumeInformation> info;
Platform::getVolumeInformationList(info);
test(names.size() == info.size(), "Got inconsistent number of volumes back from info vs. name list functions!");
for(S32 i=0; i<info.size(); i++)
Con::printf(" %s rootPath = %s filesystem = %s ser. num. = %d type = %d readonly = %s",
info[i].Name,
info[i].RootPath,
info[i].FileSystem,
info[i].SerialNumber,
info[i].Type,
info[i].ReadOnly ? "true" : "false");
}
};
#endif
CreateUnitTest(CheckFileWriteAndRead, "File/ReadAndWrite")
{
void run()
{
// Open a file, write some junk to it, close it,
// check size is correct, and open it again.
}
};

View file

@ -1,200 +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/platformNet.h"
#include "unit/test.h"
#include "core/util/journal/process.h"
using namespace UnitTesting;
CreateUnitTest( TestTCPRequest, "Platform/Net/TCPRequest")
{
NetSocket mSocket;
S32 mDataRecved;
void handleNotify(NetSocket sock, U32 state)
{
// Only consider our own socket.
if(mSocket != sock)
return;
// Ok - what's the state? We do some dumb responses to given states
// in order to fulfill the request.
if(state == Net::Connected)
{
U8 reqBuffer[] = {
"GET / HTTP/1.0\nUser-Agent: Torque/1.0\n\n"
};
Net::Error e = Net::sendtoSocket(mSocket, reqBuffer, sizeof(reqBuffer));
test(e == Net::NoError, "Got an error sending our HTTP request!");
}
else if(state == Net::Disconnected)
{
Process::requestShutdown();
mSocket = NULL;
}
}
void handleReceive(NetSocket sock, RawData incomingData)
{
// Only consider our own socket.
if(mSocket != sock)
return;
char buff[4096];
dMemcpy(buff, incomingData.data, incomingData.size);
buff[incomingData.size] = 0;
UnitPrint("Got a message...\n");
UnitPrint(buff);
UnitPrint("------\n");
mDataRecved += incomingData.size;
}
void run()
{
mSocket = InvalidSocket;
mDataRecved = 0;
// Initialize networking - done by initLibraries currently
//test(Net::init(), "Failed to initialize networking!");
// Hook into the signals.
Net::smConnectionNotify. notify(this, &TestTCPRequest::handleNotify);
Net::smConnectionReceive.notify(this, &TestTCPRequest::handleReceive);
// Open a TCP connection to garagegames.com
mSocket = Net::openConnectTo("ip:72.246.107.193:80");
U32 limit = Platform::getRealMilliseconds() + (5*1000);
while(Process::processEvents() && (Platform::getRealMilliseconds() < limit) )
;
// Unhook from the signals.
Net::smConnectionNotify. remove(this, &TestTCPRequest::handleNotify);
Net::smConnectionReceive.remove(this, &TestTCPRequest::handleReceive);
test(mDataRecved > 0, "Didn't get any data back!");
}
};
CreateUnitTest( TestTCPRequestJournal, "Platform/Net/JournalTCPRequest")
{
NetSocket mSocket;
S32 mDataRecved;
void handleNotify(NetSocket sock, U32 state)
{
// Only consider our own socket.
if(mSocket != sock)
return;
// Ok - what's the state? We do some dumb responses to given states
// in order to fulfill the request.
if(state == Net::Connected)
{
U8 reqBuffer[] = {
"GET / HTTP/1.0\nUser-Agent: Torque/1.0\n\n"
};
Net::Error e = Net::sendtoSocket(mSocket, reqBuffer, sizeof(reqBuffer));
test(e == Net::NoError, "Got an error sending our HTTP request!");
}
else if(state == Net::Disconnected)
{
Process::requestShutdown();
mSocket = NULL;
}
}
void handleReceive(NetSocket sock, RawData incomingData)
{
// Only consider our own socket.
if(mSocket != sock)
return;
char buff[4096];
dMemcpy(buff, incomingData.data, incomingData.size);
buff[incomingData.size] = 0;
UnitPrint("Got a message...\n");
UnitPrint(buff);
UnitPrint("------\n");
mDataRecved += incomingData.size;
}
void makeRequest()
{
mSocket = InvalidSocket;
mDataRecved = 0;
// Initialize networking - done by initLibraries currently
//test(Net::init(), "Failed to initialize networking!");
// Hook into the signals.
Net::smConnectionNotify. notify(this, &TestTCPRequestJournal::handleNotify);
Net::smConnectionReceive.notify(this, &TestTCPRequestJournal::handleReceive);
// Open a TCP connection to garagegames.com
mSocket = Net::openConnectTo("ip:72.246.107.193:80");
// Let the callbacks enable things to process.
while(Process::processEvents())
;
// Unhook from the signals.
Net::smConnectionNotify. remove(this, &TestTCPRequestJournal::handleNotify);
Net::smConnectionReceive.remove(this, &TestTCPRequestJournal::handleReceive);
test(mDataRecved > 0, "Didn't get any data back!");
}
void run()
{
Journal::Record("journalTCP.jrn");
if( !Journal::IsRecording() )
{
test(0, "Failed.");
return;
}
makeRequest();
S32 bytesRead = mDataRecved;
Journal::Stop();
Journal::Play("journalTCP.jrn");
makeRequest();
Journal::Stop();
test(bytesRead == mDataRecved, "Didn't get same data back from journal playback.");
}
};

View file

@ -1,403 +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 "unit/test.h"
#include "platform/threads/threadSafeDeque.h"
#include "platform/threads/thread.h"
#include "core/util/tVector.h"
#include "console/console.h"
#ifndef TORQUE_SHIPPING
using namespace UnitTesting;
#define TEST( x ) test( ( x ), "FAIL: " #x )
#define XTEST( t, x ) t->test( ( x ), "FAIL: " #x )
// Test deque without concurrency.
CreateUnitTest( TestThreadSafeDequeSerial, "Platform/ThreadSafeDeque/Serial" )
{
void test1()
{
ThreadSafeDeque< char > deque;
String str = "teststring";
for( U32 i = 0; i < str.length(); ++ i )
deque.pushBack( str[ i ] );
TEST( !deque.isEmpty() );
for( U32 i = 0; i < str.length(); ++ i )
{
char ch;
TEST( deque.tryPopFront( ch ) && ch == str[ i ] );
}
}
void test2()
{
ThreadSafeDeque< char > 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;
}
#ifdef TORQUE_DEBUG
deque.dumpDebug();
#endif
for( U32 i = 0; i < str.length(); ++ i )
{
char ch;
TEST( deque.tryPopFront( ch ) && ch == str[ i ] );
}
}
void test3()
{
ThreadSafeDeque< char > 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;
}
#ifdef TORQUE_DEBUG
deque.dumpDebug();
#endif
for( S32 i = ( str.length() - 1 ); i >= 0; -- i )
{
char ch;
TEST( deque.tryPopBack( ch ) && ch == str[ i ] );
}
}
void test4()
{
ThreadSafeDeque< char > deque;
char ch;
TEST( deque.isEmpty() );
deque.pushFront( 'a' );
TEST( !deque.isEmpty() );
TEST( deque.tryPopFront( ch ) );
TEST( ch == 'a' );
deque.pushBack( 'a' );
TEST( !deque.isEmpty() );
TEST( deque.tryPopFront( ch ) );
TEST( ch == 'a' );
deque.pushBack( 'a' );
TEST( !deque.isEmpty() );
TEST( deque.tryPopBack( ch ) );
TEST( ch == 'a' );
deque.pushFront( 'a' );
TEST( !deque.isEmpty() );
TEST( deque.tryPopBack( ch ) );
TEST( ch == 'a' );
}
void run()
{
test1();
test2();
test3();
test4();
}
};
// Test deque in a concurrent setting.
CreateUnitTest( TestThreadSafeDequeConcurrentSimple, "Platform/ThreadSafeDeque/ConcurrentSimple" )
{
public:
typedef TestThreadSafeDequeConcurrentSimple TestType;
enum
{
DEFAULT_NUM_VALUES = 100000,
};
struct Value : public ThreadSafeRefCount< Value >
{
U32 mIndex;
U32 mTick;
Value() {}
Value( U32 index, U32 tick )
: mIndex( index ), mTick( tick ) {}
};
typedef ThreadSafeRef< Value > ValueRef;
struct Deque : public ThreadSafeDeque< ValueRef >
{
typedef ThreadSafeDeque<ValueRef> Parent;
U32 mPushIndex;
U32 mPopIndex;
Deque()
: mPushIndex( 0 ), mPopIndex( 0 ) {}
void pushBack( const ValueRef& value )
{
AssertFatal( value->mIndex == mPushIndex, "index out of line" );
mPushIndex ++;
Parent::pushBack( value );
}
bool tryPopFront( ValueRef& outValue )
{
if( Parent::tryPopFront( outValue ) )
{
AssertFatal( outValue->mIndex == mPopIndex, "index out of line" );
mPopIndex ++;
return true;
}
else
return false;
}
};
Deque mDeque;
Vector< U32 > mValues;
struct ProducerThread : public Thread
{
ProducerThread( TestType* test )
: Thread( 0, test ) {}
virtual void run( void* arg )
{
_setName( "ProducerThread" );
Platform::outputDebugString( "Starting ProducerThread" );
TestType* test = ( TestType* ) arg;
for( U32 i = 0; i < test->mValues.size(); ++ i )
{
U32 tick = Platform::getRealMilliseconds();
test->mValues[ i ] = tick;
ValueRef val = new Value( i, tick );
test->mDeque.pushBack( val );
}
Platform::outputDebugString( "Stopping ProducerThread" );
}
};
struct ConsumerThread : public Thread
{
ConsumerThread( TestType* test )
: Thread( 0, test ) {}
virtual void run( void* arg )
{
_setName( "ConsumerThread" );
Platform::outputDebugString( "Starting CosumerThread" );
TestType* t = ( TestType* ) arg;
for( U32 i = 0; i < t->mValues.size(); ++ i )
{
ValueRef value;
while( !t->mDeque.tryPopFront( value ) );
XTEST( t, value->mIndex == i );
XTEST( t, t->mValues[ i ] == value->mTick );
}
Platform::outputDebugString( "Stopping ConsumerThread" );
}
};
void run()
{
U32 numValues = Con::getIntVariable( "$testThreadSafeDeque::numValues", DEFAULT_NUM_VALUES );
mValues.setSize( numValues );
ProducerThread pThread( this );
ConsumerThread cThread( this );
pThread.start();
cThread.start();
pThread.join();
cThread.join();
mValues.clear();
}
};
CreateUnitTest( TestThreadSafeDequeConcurrent, "Platform/ThreadSafeDeque/Concurrent" )
{
public:
typedef TestThreadSafeDequeConcurrent TestType;
enum
{
DEFAULT_NUM_VALUES = 100000,
DEFAULT_NUM_CONSUMERS = 10,
DEFAULT_NUM_PRODUCERS = 10
};
struct Value : public ThreadSafeRefCount< Value >
{
U32 mIndex;
U32 mTick;
Value() {}
Value( U32 index, U32 tick )
: mIndex( index ), mTick( tick ) {}
};
typedef ThreadSafeRef< Value > ValueRef;
U32 mProducerIndex;
U32 mConsumerIndex;
ThreadSafeDeque< ValueRef > mDeque;
Vector< U32 > mValues;
struct ProducerThread : public Thread
{
ProducerThread( TestType* test )
: Thread( 0, test ) {}
virtual void run( void* arg )
{
_setName( "ProducerThread" );
Platform::outputDebugString( "Starting ProducerThread" );
TestType* test = ( TestType* ) arg;
while( 1 )
{
U32 index = test->mProducerIndex;
if( index == test->mValues.size() )
break;
if( dCompareAndSwap( test->mProducerIndex, index, index + 1 ) )
{
U32 tick = Platform::getRealMilliseconds();
test->mValues[ index ] = tick;
ValueRef val = new Value( index, tick );
test->mDeque.pushBack( val );
}
}
Platform::outputDebugString( "Stopping ProducerThread" );
}
};
struct ConsumerThread : public Thread
{
ConsumerThread( TestType* test )
: Thread( 0, test ) {}
virtual void run( void* arg )
{
_setName( "ConsumerThread" );
Platform::outputDebugString( "Starting ConsumerThread" );
TestType* t = ( TestType* ) arg;
while( t->mConsumerIndex < t->mValues.size() )
{
ValueRef value;
if( t->mDeque.tryPopFront( value ) )
{
dFetchAndAdd( t->mConsumerIndex, 1 );
XTEST( t, t->mValues[ value->mIndex ] == value->mTick );
t->mValues[ value->mIndex ] = 0;
}
}
Platform::outputDebugString( "Stopping ConsumerThread" );
}
};
void run()
{
U32 numValues = Con::getIntVariable( "$testThreadSafeDeque::numValues", DEFAULT_NUM_VALUES );
U32 numConsumers = Con::getIntVariable( "$testThreadSafeDeque::numConsumers", DEFAULT_NUM_CONSUMERS );
U32 numProducers = Con::getIntVariable( "$testThreadSafeDeque::numProducers", DEFAULT_NUM_PRODUCERS );
mProducerIndex = 0;
mConsumerIndex = 0;
mValues.setSize( numValues );
U32 tick = Platform::getRealMilliseconds();
for( U32 i = 0; i < numValues; ++ i )
mValues[ i ] = tick;
Vector< ProducerThread* > producers;
Vector< ConsumerThread* > consumers;
producers.setSize( numProducers );
consumers.setSize( numConsumers );
for( U32 i = 0; i < numProducers; ++ i )
{
producers[ i ] = new ProducerThread( this );
producers[ i ]->start();
}
for( U32 i = 0; i < numConsumers; ++ i )
{
consumers[ i ] = new ConsumerThread( this );
consumers[ i ]->start();
}
for( U32 i = 0; i < numProducers; ++ i )
{
producers[ i ]->join();
delete producers[ i ];
}
for( U32 i = 0; i < numConsumers; ++ i )
{
consumers[ i ]->join();
delete consumers[ i ];
}
for( U32 i = 0; i < mValues.size(); ++ i )
TEST( mValues[ i ] == 0 );
mValues.clear();
}
};
#endif // !TORQUE_SHIPPING

View file

@ -1,245 +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 "unit/test.h"
#include "platform/threads/threadSafePriorityQueue.h"
#include "platform/threads/thread.h"
#include "core/util/tVector.h"
#include "console/console.h"
#ifndef TORQUE_SHIPPING
using namespace UnitTesting;
#define TEST( x ) test( ( x ), "FAIL: " #x )
#define XTEST( t, x ) t->test( ( x ), "FAIL: " #x )
// Test queue without concurrency.
CreateUnitTest( TestThreadSafePriorityQueueSerial, "Platform/ThreadSafePriorityQueue/Serial" )
{
struct Value
{
F32 mPriority;
U32 mIndex;
Value() {}
Value( F32 priority, U32 index )
: mPriority( priority ), mIndex( index ) {}
};
template< bool SORT_MIN_TO_MAX >
void test1()
{
Vector< Value > values;
values.push_back( Value( 0.2f, 2 ) );
values.push_back( Value( 0.7f, 7 ) );
values.push_back( Value( 0.4f, 4 ) );
values.push_back( Value( 0.6f, 6 ) );
values.push_back( Value( 0.1f, 1 ) );
values.push_back( Value( 0.5f, 5 ) );
values.push_back( Value( 0.3f, 3 ) );
values.push_back( Value( 0.8f, 8 ) );
values.push_back( Value( 0.6f, 6 ) );
values.push_back( Value( 0.9f, 9 ) );
values.push_back( Value( 0.0f, 0 ) );
const S32 min = 0;
const S32 max = 9;
ThreadSafePriorityQueue< U32, F32, SORT_MIN_TO_MAX > queue;
for( U32 i = 0; i < values.size(); ++ i )
queue.insert( values[ i ].mPriority, values[ i ].mIndex );
TEST( !queue.isEmpty() );
S32 index;
if( SORT_MIN_TO_MAX )
index = min - 1;
else
index = max + 1;
for( U32 i = 0; i < values.size(); ++ i )
{
U32 value;
TEST( queue.takeNext( value ) );
if( value != index )
{
if( SORT_MIN_TO_MAX )
index ++;
else
index --;
}
TEST( value == index );
}
}
void run()
{
test1< true >();
test1< false >();
}
};
// Test queue with concurrency.
CreateUnitTest( TestThreadSafePriorityQueueConcurrent, "Platform/ThreadSafePriorityQueue/Concurrent" )
{
public:
typedef TestThreadSafePriorityQueueConcurrent TestType;
enum
{
DEFAULT_NUM_VALUES = 100000,
DEFAULT_NUM_CONSUMERS = 10,
DEFAULT_NUM_PRODUCERS = 10
};
struct Value : public ThreadSafeRefCount< Value >
{
U32 mIndex;
F32 mPriority;
bool mCheck;
Value() : mCheck( false ) {}
Value( U32 index, F32 priority )
: mIndex( index ), mPriority( priority ), mCheck( false ) {}
};
typedef ThreadSafeRef< Value > ValueRef;
U32 mProducerIndex;
U32 mConsumerIndex;
ThreadSafePriorityQueue< ValueRef > mQueue;
Vector< ValueRef > mValues;
struct ProducerThread : public Thread
{
ProducerThread( TestType* test )
: Thread( 0, test ) {}
virtual void run( void* arg )
{
_setName( "ProducerThread" );
Platform::outputDebugString( "Starting ProducerThread" );
TestType* test = ( TestType* ) arg;
while( 1 )
{
U32 index = test->mProducerIndex;
if( index == test->mValues.size() )
break;
if( dCompareAndSwap( test->mProducerIndex, index, index + 1 ) )
{
F32 priority = Platform::getRandom();
ValueRef val = new Value( index, priority );
test->mValues[ index ] = val;
test->mQueue.insert( priority, val );
}
}
Platform::outputDebugString( "Stopping ProducerThread" );
}
};
struct ConsumerThread : public Thread
{
ConsumerThread( TestType* test )
: Thread( 0, test ) {}
virtual void run( void* arg )
{
_setName( "ConsumerThread" );
Platform::outputDebugString( "Starting ConsumerThread" );
TestType* t = ( TestType* ) arg;
while( t->mConsumerIndex < t->mValues.size() )
{
ValueRef value;
if( t->mQueue.takeNext( value ) )
{
dFetchAndAdd( t->mConsumerIndex, 1 );
XTEST( t, t->mValues[ value->mIndex ] == value );
value->mCheck = true;
}
else
Platform::sleep( 5 );
}
Platform::outputDebugString( "Stopping ConsumerThread" );
}
};
void run()
{
U32 numValues = Con::getIntVariable( "$testThreadSafePriorityQueue::numValues", DEFAULT_NUM_VALUES );
U32 numConsumers = Con::getIntVariable( "$testThreadSafePriorityQueue::numConsumers", DEFAULT_NUM_CONSUMERS );
U32 numProducers = Con::getIntVariable( "$testThreadSafePriorityQueue::numProducers", DEFAULT_NUM_PRODUCERS );
mProducerIndex = 0;
mConsumerIndex = 0;
mValues.setSize( numValues );
Vector< ProducerThread* > producers;
Vector< ConsumerThread* > consumers;
producers.setSize( numProducers );
consumers.setSize( numConsumers );
for( U32 i = 0; i < numProducers; ++ i )
{
producers[ i ] = new ProducerThread( this );
producers[ i ]->start();
}
for( U32 i = 0; i < numConsumers; ++ i )
{
consumers[ i ] = new ConsumerThread( this );
consumers[ i ]->start();
}
for( U32 i = 0; i < numProducers; ++ i )
{
producers[ i ]->join();
delete producers[ i ];
}
for( U32 i = 0; i < numConsumers; ++ i )
{
consumers[ i ]->join();
delete consumers[ i ];
}
for( U32 i = 0; i < mValues.size(); ++ i )
{
TEST( mValues[ i ] != NULL );
if( mValues[ i ] != NULL )
TEST( mValues[ i ]->mCheck );
}
mValues.clear();
}
};
#endif // !TORQUE_SHIPPING

View file

@ -1,227 +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 "unit/test.h"
#include "platform/threads/threadSafeRefCount.h"
#include "platform/threads/thread.h"
#include "core/util/tVector.h"
#include "console/console.h"
#ifndef TORQUE_SHIPPING
using namespace UnitTesting;
#define TEST( x ) test( ( x ), "FAIL: " #x )
CreateUnitTest( TestThreadSafeRefCountSerial, "Platform/ThreadSafeRefCount/Serial" )
{
struct TestObject : public ThreadSafeRefCount< TestObject >
{
static bool smDeleted;
TestObject()
{
smDeleted = false;
}
~TestObject()
{
smDeleted = true;
}
};
typedef ThreadSafeRef< TestObject > TestObjectRef;
void run()
{
TestObjectRef ref1 = new TestObject;
TEST( !ref1->isShared() );
TEST( ref1 != NULL );
TestObjectRef ref2 = ref1;
TEST( ref1->isShared() );
TEST( ref2->isShared() );
TEST( ref1 == ref2 );
ref1 = NULL;
TEST( !ref2->isShared() );
ref2 = NULL;
TEST( TestObject::smDeleted );
}
};
bool TestThreadSafeRefCountSerial::TestObject::smDeleted;
CreateUnitTest( TestThreadSafeRefCountConcurrent, "Platform/ThreadSafeRefCount/Concurrent" )
{
public:
typedef TestThreadSafeRefCountConcurrent TestType;
enum
{
NUM_ADD_REFS_PER_THREAD = 1000,
NUM_EXTRA_REFS_PER_THREAD = 1000,
NUM_THREADS = 10
};
class TestObject : public ThreadSafeRefCount< TestObject >
{
public:
};
ThreadSafeRef< TestObject > mRef;
class TestThread : public Thread
{
public:
TestType* mTest;
Vector< ThreadSafeRef< TestObject > > mExtraRefs;
TestThread( TestType* test )
: mTest( test ) {}
void run( void* arg )
{
if( !arg )
{
for( U32 i = 0; i < NUM_ADD_REFS_PER_THREAD; ++ i )
mTest->mRef->addRef();
mExtraRefs.setSize( NUM_EXTRA_REFS_PER_THREAD );
for( U32 i = 0; i < NUM_EXTRA_REFS_PER_THREAD; ++ i )
mExtraRefs[ i ] = mTest->mRef;
}
else
{
mExtraRefs.clear();
for( U32 i = 0; i < NUM_ADD_REFS_PER_THREAD; ++ i )
mTest->mRef->release();
}
}
};
void run()
{
mRef = new TestObject;
TEST( mRef->getRefCount() == 2 ); // increments of 2
Vector< TestThread* > threads;
threads.setSize( NUM_THREADS );
// Create threads.
for( U32 i = 0; i < NUM_THREADS; ++ i )
threads[ i ] = new TestThread( this );
// Run phase 1: create references.
for( U32 i = 0; i < NUM_THREADS; ++ i )
threads[ i ]->start( NULL );
// Wait for completion.
for( U32 i = 0; i < NUM_THREADS; ++ i )
threads[ i ]->join();
Con::printf( "REF: %i", mRef->getRefCount() );
TEST( mRef->getRefCount() == 2 + ( ( NUM_ADD_REFS_PER_THREAD + NUM_EXTRA_REFS_PER_THREAD ) * NUM_THREADS * 2 ) );
// Run phase 2: release references.
for( U32 i = 0; i < NUM_THREADS; ++ i )
threads[ i ]->start( ( void* ) 1 );
// Wait for completion.
for( U32 i = 0; i < NUM_THREADS; ++ i )
{
threads[ i ]->join();
delete threads[ i ];
}
TEST( mRef->getRefCount() == 2 ); // increments of two
mRef = NULL;
}
};
CreateUnitTest( TestThreadSafeRefCountTagging, "Platform/ThreadSafeRefCount/Tagging" )
{
struct TestObject : public ThreadSafeRefCount< TestObject > {};
typedef ThreadSafeRef< TestObject > TestObjectRef;
void run()
{
TestObjectRef ref;
TEST( !ref.isTagged() );
TEST( !ref );
TEST( !ref.ptr() );
TEST( ref.trySetFromTo( ref, NULL ) );
TEST( !ref.isTagged() );
TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Set ) );
TEST( ref.isTagged() );
TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Set ) );
TEST( ref.isTagged() );
TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Unset ) );
TEST( !ref.isTagged() );
TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Unset ) );
TEST( !ref.isTagged() );
TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_SetOrFail ) );
TEST( ref.isTagged() );
TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_SetOrFail ) );
TEST( ref.isTagged() );
TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_FailIfSet ) );
TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_UnsetOrFail ) );
TEST( !ref.isTagged() );
TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_UnsetOrFail ) );
TEST( !ref.isTagged() );
TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_FailIfUnset ) );
TestObjectRef objectA = new TestObject;
TestObjectRef objectB = new TestObject;
TEST( !objectA->isShared() );
TEST( !objectB->isShared() );
ref = objectA;
TEST( !ref.isTagged() );
TEST( ref == objectA );
TEST( ref == objectA.ptr() );
TEST( objectA->isShared() );
TEST( ref.trySetFromTo( objectA, objectB, TestObjectRef::TAG_Set ) );
TEST( ref.isTagged() );
TEST( ref == objectB );
TEST( ref == objectB.ptr() );
TEST( objectB->isShared() );
TEST( !objectA->isShared() );
TEST( ref.trySetFromTo( ref, objectA ) );
TEST( ref.isTagged() );
TEST( ref == objectA );
TEST( ref == objectA.ptr() );
}
};
#endif // !TORQUE_SHIPPING

View file

@ -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; i<mThreadCount; i++)
{
mThreads[i] = new Thread(threadBody, arg);
mThreads[i]->start();
}
}
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; i<mThreadCount; i++)
{
if(!mThreads[i]->isAlive())
continue;
someAlive = true;
liveCount++;
}
}
mEndTime = Platform::getRealMilliseconds();
// Clean up memory at this point.
for(S32 i=0; i<mThreadCount; i++)
delete mThreads[i];
delete[] mThreads;
// Make sure we didn't take a long time to complete.
mCleanupTime = Platform::getRealMilliseconds();
// And dump some stats.
Con::printf(" Took approximately %dms (+/- %dms) to run %d threads, and %dms to cleanup.",
(mEndTime - mStartTime),
checkFrequencyMs,
mThreadCount,
mCleanupTime - mEndTime);
}
};
CreateUnitTest( ThreadSanityCheck, "Platform/Threads/BasicSanity")
{
const static S32 amountOfWork = 100;
const static S32 numberOfThreads = 8;
static void threadBody(void *)
{
S32 work = 0x381f4fd3;
// Spin on some work, then exit.
for(S32 i=0; i<amountOfWork; i++)
{
// Do a little computation...
work ^= (i + work | amountOfWork);
// And sleep a slightly variable bit.
Platform::sleep(10 + ((work+i) % 10));
}
}
void runNThreads(S32 threadCount)
{
ThreadTestHarness tth;
tth.startThreads(&threadBody, NULL, threadCount);
tth.waitForThreadExit(32);
}
void run()
{
for(S32 i=0; i<numberOfThreads; i++)
runNThreads(i);
}
};
CreateUnitTest( MutexStressTest, "Platform/Threads/MutexStress")
{
const static S32 numberOfLocks = 100;
const static S32 numberOfThreads = 4;
void *mMutex;
static void threadBody(void *mutex)
{
// Acquire the mutex numberOfLocks times. Sleep for 1ms, acquire, sleep, release.
S32 lockCount = numberOfLocks;
while(lockCount--)
{
Platform::sleep(1);
Mutex::lockMutex(mutex, true);
Platform::sleep(1);
Mutex::unlockMutex(mutex);
}
}
void runNThreads(S32 threadCount)
{
ThreadTestHarness tth;
mMutex = Mutex::createMutex();
tth.startThreads(&threadBody, mMutex, threadCount);
// We fudge the wait period to be about the expected time assuming
// perfect execution speed.
tth.waitForThreadExit(32); //threadCount * 2 * numberOfLocks + 100);
Mutex::destroyMutex(mMutex);
}
void run()
{
for(S32 i=0; i<numberOfThreads; i++)
runNThreads(i);
}
};
CreateUnitTest( MemoryStressTest, "Platform/Threads/MemoryStress")
{
const static S32 numberOfAllocs = 1000;
const static S32 minAllocSize = 13;
const static S32 maxAllocSize = 1024 * 1024;
const static S32 numberOfThreads = 4;
void *mMutex;
// Cheap little RNG so we can vary our allocations more uniquely per thread.
static U32 threadRandom(U32 &seed, U32 min, U32 max)
{
seed = (1664525 * seed + 1013904223);
U32 res = seed;
res %= (max - min);
return res + min;
}
static void threadBody(void *mutex)
{
// Acquire the mutex numberOfLocks times. Sleep for 1ms, acquire, sleep, release.
S32 allocCount = numberOfAllocs;
U32 seed = (U32)((U32)mutex + (U32)&allocCount);
while(allocCount--)
{
U8 *mem = new U8[threadRandom(seed, minAllocSize, maxAllocSize)];
delete[] mem;
}
}
void runNThreads(S32 threadCount)
{
ThreadTestHarness tth;
mMutex = Mutex::createMutex();
tth.startThreads(&threadBody, mMutex, threadCount);
// We fudge the wait period to be about the expected time assuming
// perfect execution speed.
tth.waitForThreadExit(32);
Mutex::destroyMutex(mMutex);
}
void run()
{
for(S32 i=0; i<numberOfThreads; i++)
runNThreads(i);
}
};
CreateUnitTest( ThreadGymnastics, "Platform/Threads/BasicSynchronization")
{
void run()
{
// 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();
test(mutex1, "First Mutex::createMutex call failed - that's pretty bad!");
void *mutex2 = Mutex::createMutex();
test(mutex2, "Second Mutex::createMutex call failed - that's pretty bad, too!");
test(Mutex::lockMutex(mutex1, false), "Nonblocking call to brand new mutex failed - should not be.");
test(Mutex::lockMutex(mutex1, true), "Failed relocking a mutex from the same thread - should be able to do this.");
// Unlock & kill mutex 1
Mutex::unlockMutex(mutex1);
Mutex::unlockMutex(mutex1);
Mutex::destroyMutex(mutex1);
// Kill mutex2, which was never touched.
Mutex::destroyMutex(mutex2);
// Now we can test semaphores.
Semaphore *sem1 = new Semaphore(1);
Semaphore *sem2 = new Semaphore(1);
// Test that we can do non-blocking acquires that succeed.
test(sem1->acquire(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; i<csmThreadCount/2; i++)
mSemaphore->release();
// And wait for 500 postbacks.
for(S32 i=0; i<csmThreadCount/2; i++)
mPostbackSemaphore->acquire();
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; i<csmThreadCount/2; i++)
mSemaphore->release();
// And wait for 500 postbacks.
for(S32 i=0; i<csmThreadCount/2; i++)
mPostbackSemaphore->acquire();
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);
}
};

View file

@ -1,105 +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/platformTimer.h"
#include "core/util/journal/journaledSignal.h"
#include "core/util/journal/process.h"
#include "math/mMath.h"
#include "console/console.h"
#include "unit/test.h"
using namespace UnitTesting;
CreateUnitTest(Check_advanceTime, "Platform/Time/advanceTime")
{
void run()
{
U32 time = Platform::getVirtualMilliseconds();
Platform::advanceTime(10);
U32 newTime = Platform::getVirtualMilliseconds();
test(newTime - time == 10, "Platform::advanceTime is borked, we advanced 10ms but didn't get a 10ms delta!");
}
};
CreateUnitTest(Check_platformSleep, "Platform/Time/Sleep")
{
const static S32 sleepTimeMs = 500;
void run()
{
U32 start = Platform::getRealMilliseconds();
Platform::sleep(sleepTimeMs);
U32 end = Platform::getRealMilliseconds();
test(end - start >= sleepTimeMs, "We didn't sleep at least as long as we requested!");
}
};
CreateUnitTest(Check_timeManager, "Platform/Time/Manager")
{
void handleTimeEvent(S32 timeDelta)
{
mElapsedTime += timeDelta;
mNumberCalls++;
if(mElapsedTime >= 1000)
Process::requestShutdown();
}
S32 mElapsedTime;
S32 mNumberCalls;
void run()
{
mElapsedTime = mNumberCalls = 0;
// Initialize the time manager...
TimeManager time;
time.timeEvent.notify(this, &Check_timeManager::handleTimeEvent);
// Event loop till at least one second has passed.
const U32 start = Platform::getRealMilliseconds();
while(Process::processEvents())
{
// If we go too long, kill it off...
if(Platform::getRealMilliseconds() - start > 30*1000)
{
test(false, "Terminated process loop due to watchdog, not due to time manager event, after 30 seconds.");
Process::requestShutdown();
}
}
const U32 end = Platform::getRealMilliseconds();
// Now, confirm we have approximately similar elapsed times.
S32 elapsedRealTime = end - start;
test(mAbs(elapsedRealTime - mElapsedTime) < 50, "Failed to elapse time to within the desired tolerance.");
test(mNumberCalls > 0, "Somehow got no event callbacks from TimeManager?");
Con::printf(" Got %d time events, and elapsed %dms from TimeManager, "
"%dms according to Platform::getRealMilliseconds()",
mNumberCalls, mElapsedTime, elapsedRealTime);
}
};

View file

@ -0,0 +1,139 @@
//-----------------------------------------------------------------------------
// 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"
// This unit test will blow up without thread static support
#include "core/threadStatic.h"
#ifdef TORQUE_ENABLE_THREAD_STATICS
// Declare a test thread static
DITTS(U32, gUnitTestFoo, 42);
DITTS(F32, gUnitTestF32, 1.0);
TEST(ThreadStatic, BasicAPI)
{
// ThreadStatic list should be initialized right now, so lets see if it has
// any entries.
EXPECT_FALSE(_TorqueThreadStaticReg::getStaticList().empty())
<< "Self-registration has failed, or no statics declared";
// Spawn a new copy.
TorqueThreadStaticListHandle testInstance = _TorqueThreadStaticReg::spawnThreadStaticsInstance();
// Test the copy
ASSERT_EQ(_TorqueThreadStaticReg::getStaticList(0).size(), testInstance->size())
<< "Spawned static list has a different size from master copy.";
// Traverse the list and compare it to the initial value copy (index 0)
for(S32 i = 0; i < _TorqueThreadStaticReg::getStaticList().size(); i++)
{
_TorqueThreadStatic *master = _TorqueThreadStaticReg::getStaticList()[i];
_TorqueThreadStatic *cpy = (*testInstance)[i];
// Make sure it is not the same memory
EXPECT_NE(master, cpy)
<< "Copy not spawned properly.";
// Make sure the sizes are the same
ASSERT_EQ(master->getMemInstSize(), cpy->getMemInstSize())
<< "Size mismatch between master and copy";
// Make sure the initialization occurred properly
EXPECT_EQ(0, dMemcmp(master->getMemInstPtr(), cpy->getMemInstPtr(), master->getMemInstSize()))
<< "Initial value for spawned list is not correct";
}
// Test metrics if enabled
#ifdef TORQUE_ENABLE_THREAD_STATIC_METRICS
U32 fooHitCount = (*testInstance)[_gUnitTestFooTorqueThreadStatic::getListIndex()]->getHitCount();
#endif
// Set/get some values (If we test static metrics, this is hit 1)
ATTS_(gUnitTestFoo, 1) = 55;
// Test to see that the master copy and the spawned copy differ
// (If we test metrics, this is hit 2, for the instance, and hit 1 for the master)
EXPECT_NE(ATTS_(gUnitTestFoo, 0), ATTS_(gUnitTestFoo, 1))
<< "Assignment for spawned instanced memory failed";
#ifdef TORQUE_ENABLE_THREAD_STATIC_METRICS
U32 fooHitCount2 = (*testInstance)[_gUnitTestFooTorqueThreadStatic::getListIndex()]->getHitCount();
EXPECT_EQ(fooHitCount2, (fooHitCount + 2))
<< "Thread static metric hit count failed";
#endif
// Destroy instances
_TorqueThreadStaticReg::destroyInstance(testInstance);
}
#ifdef TORQUE_ENABLE_PROFILER
#include "math/mRandom.h"
#include "platform/profiler.h"
// Declare a test thread static
DITTS(U32, gInstancedStaticFoo, 42);
static U32 gTrueStaticFoo = 42;
TEST(ThreadStatic, StressThreadStatic)
{
ASSERT_FALSE(gProfiler->isEnabled())
<< "Profiler is currently enabled, test cannot continue";
// Spawn an instance
TorqueThreadStaticListHandle testInstance = _TorqueThreadStaticReg::spawnThreadStaticsInstance();
static const dsize_t TEST_SIZE = 100000;
// What we are going to do in this test is to test some U32 static
// performance. The test will be run TEST_SIZE times, and so first create
// an array of values to standardize the tests on.
U32 testValue[TEST_SIZE];
for(S32 i = 0; i < TEST_SIZE; i++)
testValue[i] = gRandGen.randI();
// Reset the profiler, tell it to dump to console when done
gProfiler->dumpToConsole();
gProfiler->enable(true);
// Value array is initialized, so lets put the foo's through the paces
PROFILE_START(ThreadStaticPerf_TrueStaticAssign);
for(int i = 0; i < TEST_SIZE; i++)
gTrueStaticFoo = testValue[i];
PROFILE_END();
PROFILE_START(ThreadStaticPerf_InstanceStaticAssign);
for(S32 i = 0; i < TEST_SIZE; i++)
ATTS_(gInstancedStaticFoo, 1) = testValue[i];
PROFILE_END();
gProfiler->enable(false);
// Clean up instance
_TorqueThreadStaticReg::destroyInstance(testInstance);
}
#endif
#endif
#endif

View file

@ -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

View file

@ -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<Semaphore*>(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<Semaphore*>(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

View file

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
// 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
@ -20,66 +20,56 @@
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "unit/test.h"
#ifdef TORQUE_TESTS_ENABLED
#include "testing/unitTesting.h"
#include "platform/threads/threadPool.h"
#include "console/console.h"
#include "core/util/tVector.h"
#ifndef TORQUE_SHIPPING
using namespace UnitTesting;
#define TEST( x ) test( ( x ), "FAIL: " #x )
// Simple test that creates and verifies an array of numbers using
// thread pool work items.
CreateUnitTest( TestThreadPool, "Platform/ThreadPool/Simple" )
FIXTURE(ThreadPool)
{
enum { DEFAULT_NUM_ITEMS = 4000 };
static Vector< U32 > results;
public:
// Represents a single unit of work. In this test we just set an element in
// a result vector.
struct TestItem : public ThreadPool::WorkItem
{
typedef ThreadPool::WorkItem Parent;
U32 mIndex;
TestItem( U32 index )
: mIndex( index ) {}
protected:
virtual void execute()
{
results[ mIndex ] = mIndex;
}
};
void run()
{
U32 numItems = Con::getIntVariable( "$testThreadPool::numValues", DEFAULT_NUM_ITEMS );
ThreadPool* pool = &ThreadPool::GLOBAL();
results.setSize( numItems );
U32 mIndex;
Vector<U32>& mResults;
TestItem(U32 index, Vector<U32>& results)
: mIndex(index), mResults(results) {}
for( U32 i = 0; i < numItems; ++ i )
results[ i ] = U32( -1 );
for( U32 i = 0; i < numItems; ++ i )
protected:
virtual void execute()
{
ThreadSafeRef< TestItem > item( new TestItem( i ) );
pool->queueWorkItem( item );
mResults[mIndex] = mIndex;
}
pool->flushWorkItems();
for( U32 i = 0; i < numItems; ++ i )
test( results[ i ] == i, "result mismatch" );
results.clear();
}
};
};
Vector< U32 > TestThreadPool::results( __FILE__, __LINE__ );
TEST_FIX(ThreadPool, BasicAPI)
{
// Construct the vector of results from the work items.
const U32 numItems = 100;
Vector<U32> results(__FILE__, __LINE__);
results.setSize(numItems);
for (U32 i = 0; i < numItems; i++)
results[i] = U32(-1);
#endif // !TORQUE_SHIPPING
// Launch the work items.
ThreadPool* pool = &ThreadPool::GLOBAL();
for (U32 i = 0; i < numItems; i++)
{
ThreadSafeRef<TestItem> item(new TestItem(i, results));
pool->queueWorkItem(item);
}
// Wait for all items to complete.
pool->flushWorkItems();
// Verify.
for (U32 i = 0; i < numItems; i++)
EXPECT_EQ(results[i], i) << "result mismatch";
results.clear();
}
#endif

View file

@ -0,0 +1,186 @@
//-----------------------------------------------------------------------------
// 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"
FIXTURE(ThreadSafeDeque)
{
public:
// Used by the concurrent test.
struct Value : public ThreadSafeRefCount<Value>
{
U32 mIndex;
U32 mTick;
Value() {}
Value(U32 index, U32 tick)
: mIndex(index), mTick(tick) {}
};
typedef ThreadSafeRef<Value> ValueRef;
struct Deque : public ThreadSafeDeque<ValueRef>
{
typedef ThreadSafeDeque<ValueRef> 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;
}
};
struct ProducerThread : public Thread
{
Vector<U32>& mValues;
Deque& mDeque;
ProducerThread(Vector<U32>& 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<U32>& mValues;
Deque& mDeque;
ConsumerThread(Vector<U32>& 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]);
}
}
};
};
// Test deque without concurrency.
TEST_FIX(ThreadSafeDeque, PopFront)
{
ThreadSafeDeque<char> 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_FIX(ThreadSafeDeque, PopBack)
{
ThreadSafeDeque<char> 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_FIX(ThreadSafeDeque, Concurrent1)
{
const U32 NumValues = 100;
Deque mDeque;
Vector<U32> mValues;
mValues.setSize(NumValues);
ProducerThread pThread(mValues, mDeque);
ConsumerThread cThread(mValues, mDeque);
pThread.start();
cThread.start();
pThread.join();
cThread.join();
mValues.clear();
};
#endif

View file

@ -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<U32, F32, true> minQueue;
ThreadSafePriorityQueue<U32, F32, false> 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<U32, F32, true> MinQueue;
typedef ThreadSafePriorityQueue<U32, F32, false> 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

View file

@ -0,0 +1,205 @@
//-----------------------------------------------------------------------------
// 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/threadSafeRefCount.h"
#include "platform/threads/thread.h"
#include "core/util/tVector.h"
#include "console/console.h"
FIXTURE(ThreadSafeRefCount)
{
public:
struct TestObjectDtor : public ThreadSafeRefCount<TestObjectDtor>
{
bool &flag;
TestObjectDtor(bool &f) : flag(f)
{
flag = false;
}
~TestObjectDtor()
{
flag = true;
}
};
typedef ThreadSafeRef<TestObjectDtor> TestObjectDtorRef;
enum
{
NUM_ADD_REFS_PER_THREAD = 10,
NUM_EXTRA_REFS_PER_THREAD = 10,
NUM_THREADS = 10
};
class TestObject : public ThreadSafeRefCount<TestObject> {};
typedef ThreadSafeRef<TestObject> TestObjectRef;
class TestThread : public Thread
{
public:
TestObjectRef mRef;
Vector<TestObjectRef> mExtraRefs;
TestThread(TestObjectRef ref) : mRef(ref) {}
void run(void* arg)
{
if (!arg)
{
// Create references.
for (U32 i = 0; i < NUM_ADD_REFS_PER_THREAD; i++)
mRef->addRef();
mExtraRefs.setSize(NUM_EXTRA_REFS_PER_THREAD);
for (U32 i = 0; i < NUM_EXTRA_REFS_PER_THREAD; i++)
mExtraRefs[i] = mRef;
}
else
{
// Clear references.
mExtraRefs.clear();
for (U32 i = 0; i < NUM_ADD_REFS_PER_THREAD; i++)
mRef->release();
}
}
};
};
TEST_FIX(ThreadSafeRefCount, Serial)
{
bool deleted = false;
TestObjectDtorRef ref1 = new TestObjectDtor(deleted);
ASSERT_FALSE(deleted);
EXPECT_FALSE(ref1->isShared());
EXPECT_TRUE(ref1 != NULL);
TestObjectDtorRef ref2 = ref1;
EXPECT_TRUE(ref1->isShared());
EXPECT_TRUE(ref2->isShared());
EXPECT_EQ(ref1, ref2);
ref1 = NULL;
EXPECT_FALSE(ref2->isShared());
ref2 = NULL;
ASSERT_TRUE(deleted);
}
TEST_FIX(ThreadSafeRefCount, Concurrent)
{
TestObjectRef mRef = new TestObject;
EXPECT_EQ(2, mRef->getRefCount()); // increments of 2
Vector<TestThread*> threads;
threads.setSize(NUM_THREADS);
// Create threads.
for (U32 i = 0; i < NUM_THREADS; i++)
threads[i] = new TestThread(mRef);
// Run phase 1: create references.
for (U32 i = 0; i < NUM_THREADS; i++)
threads[i]->start(NULL);
// Wait for completion.
for (U32 i = 0; i < NUM_THREADS; i++)
threads[i]->join();
EXPECT_EQ(2 + ((1 + NUM_ADD_REFS_PER_THREAD + NUM_EXTRA_REFS_PER_THREAD) * NUM_THREADS * 2),
mRef->getRefCount());
// Run phase 2: release references.
for (U32 i = 0; i < NUM_THREADS; i++)
threads[i]->start((void*) 1);
// Wait for completion.
for (U32 i = 0; i < NUM_THREADS; i++)
{
threads[i]->join();
delete threads[i];
}
EXPECT_EQ(2, mRef->getRefCount()); // increments of two
mRef = NULL;
}
TEST_FIX(ThreadSafeRefCount, Tagging)
{
TestObjectRef ref;
EXPECT_FALSE(ref.isTagged());
EXPECT_FALSE(bool(ref));
EXPECT_FALSE(bool(ref.ptr()));
EXPECT_TRUE(ref.trySetFromTo(ref, NULL));
EXPECT_FALSE(ref.isTagged());
EXPECT_TRUE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_Set));
EXPECT_TRUE(ref.isTagged());
EXPECT_TRUE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_Set));
EXPECT_TRUE(ref.isTagged());
EXPECT_TRUE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_Unset));
EXPECT_FALSE(ref.isTagged());
EXPECT_TRUE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_Unset));
EXPECT_FALSE(ref.isTagged());
EXPECT_TRUE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_SetOrFail));
EXPECT_TRUE(ref.isTagged());
EXPECT_FALSE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_SetOrFail));
EXPECT_TRUE(ref.isTagged());
EXPECT_FALSE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_FailIfSet));
EXPECT_TRUE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_UnsetOrFail));
EXPECT_FALSE(ref.isTagged());
EXPECT_FALSE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_UnsetOrFail));
EXPECT_FALSE(ref.isTagged());
EXPECT_FALSE(ref.trySetFromTo(ref, NULL, TestObjectRef::TAG_FailIfUnset));
TestObjectRef objectA = new TestObject;
TestObjectRef objectB = new TestObject;
EXPECT_FALSE(objectA->isShared());
EXPECT_FALSE(objectB->isShared());
ref = objectA;
EXPECT_FALSE(ref.isTagged());
EXPECT_TRUE(ref == objectA);
EXPECT_TRUE(ref == objectA.ptr());
EXPECT_TRUE(objectA->isShared());
EXPECT_TRUE(ref.trySetFromTo(objectA, objectB, TestObjectRef::TAG_Set));
EXPECT_TRUE(ref.isTagged());
EXPECT_EQ(ref, objectB);
EXPECT_EQ(ref, objectB.ptr());
EXPECT_TRUE(objectB->isShared());
EXPECT_FALSE(objectA->isShared());
EXPECT_TRUE(ref.trySetFromTo(ref, objectA));
EXPECT_TRUE(ref.isTagged());
EXPECT_EQ(ref, objectA);
EXPECT_EQ(ref, objectA.ptr());
}
#endif

View file

@ -0,0 +1,87 @@
//-----------------------------------------------------------------------------
// 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, CallbackAPI)
{
#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<U32*>(arg);
*value = VALUE_TO_SET;
}
};
// Test most basic Thread API functions.
U32 value = ~VALUE_TO_SET;
Thread thread(&thread::body, reinterpret_cast<void*>(&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
}
TEST(Thread, InheritanceAPI)
{
#define VALUE_TO_SET 10
// This struct exists just so we can define run as a local function.
struct thread : public Thread
{
U32* mPtr;
thread(U32* ptr): mPtr(ptr) {}
// Do some work we can observe.
virtual void run(void*)
{
*mPtr = VALUE_TO_SET;
}
};
// Test most basic Thread API functions.
U32 value = ~VALUE_TO_SET;
thread thread(&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