mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-11 14:44:36 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
50
Engine/source/platform/test/testAlerts.cpp
Normal file
50
Engine/source/platform/test/testAlerts.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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?");
|
||||
}
|
||||
};
|
||||
151
Engine/source/platform/test/testAsyncPacketQueue.cpp
Normal file
151
Engine/source/platform/test/testAsyncPacketQueue.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/async/asyncPacketQueue.h"
|
||||
#include "console/console.h"
|
||||
#include "core/util/tVector.h"
|
||||
|
||||
#ifndef TORQUE_SHIPPING
|
||||
|
||||
using namespace UnitTesting;
|
||||
|
||||
#define TEST( x ) test( ( x ), "FAIL: " #x )
|
||||
|
||||
CreateUnitTest( TestAsyncPacketQueue, "Platform/AsyncPacketQueue" )
|
||||
{
|
||||
struct Packet
|
||||
{
|
||||
typedef void Parent;
|
||||
|
||||
StringChar mChar;
|
||||
U32 mDuration;
|
||||
S32 mWriteIndex;
|
||||
U32 mWriteTime;
|
||||
|
||||
Packet() {}
|
||||
Packet( StringChar ch, U32 duration )
|
||||
: mChar( ch ), mDuration( duration ), mWriteIndex( -2 ), mWriteTime( 0 ) {}
|
||||
};
|
||||
|
||||
struct TimeSource
|
||||
{
|
||||
typedef void Parent;
|
||||
|
||||
U32 mStartTime;
|
||||
|
||||
TimeSource()
|
||||
: mStartTime( Platform::getRealMilliseconds() ) {}
|
||||
|
||||
U32 getPosition()
|
||||
{
|
||||
return ( Platform::getRealMilliseconds() - mStartTime );
|
||||
}
|
||||
};
|
||||
|
||||
struct Consumer : public IOutputStream< Packet* >
|
||||
{
|
||||
typedef IOutputStream< Packet* > Parent;
|
||||
|
||||
U32 mIndex;
|
||||
|
||||
Consumer()
|
||||
: mIndex( 0 ) {}
|
||||
|
||||
virtual void write( Packet* const* packets, U32 num )
|
||||
{
|
||||
for( U32 i = 0; i < num; ++ i )
|
||||
{
|
||||
Packet* p = packets[ i ];
|
||||
|
||||
Con::printf( "%c", p->mChar );
|
||||
|
||||
p->mWriteTime = Platform::getRealMilliseconds();
|
||||
p->mWriteIndex = mIndex;
|
||||
mIndex ++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void test1( bool dropPackets, U32 queueLength )
|
||||
{
|
||||
F32 factor = Con::getFloatVariable( "$testAsyncPacketQueue::timeFactor", 100.0f );
|
||||
String str = Con::getVariable( "$testAsyncPacketQueue::string" );
|
||||
if( str.isEmpty() )
|
||||
str = "This is a test string";
|
||||
|
||||
Vector< Packet > packets;
|
||||
for( U32 i = 0; i < str.size(); ++ i )
|
||||
packets.push_back( Packet( str[ i ], U32( Platform::getRandom() * factor ) ) );
|
||||
|
||||
U32 totalTime = 0;
|
||||
for( U32 i = 0; i < packets.size(); ++ i )
|
||||
totalTime += packets[ i ].mDuration;
|
||||
|
||||
TimeSource timeSource;
|
||||
Consumer consumer;
|
||||
AsyncPacketQueue< Packet*, TimeSource* > queue( queueLength, &timeSource, &consumer, totalTime, dropPackets );
|
||||
|
||||
U32 index = 0;
|
||||
while( !queue.isAtEnd() )
|
||||
{
|
||||
if( queue.needPacket()
|
||||
&& index < packets.size() )
|
||||
{
|
||||
|
||||
Packet* packet = &packets[ index ];
|
||||
index ++;
|
||||
|
||||
queue.submitPacket( packet, packet->mDuration );
|
||||
}
|
||||
}
|
||||
|
||||
U32 time = timeSource.mStartTime;
|
||||
S32 lastIndex = -1;
|
||||
for( U32 i = 0; i < packets.size(); ++ i )
|
||||
{
|
||||
TEST( ( packets[ i ].mWriteIndex == -2 && dropPackets ) // not written = dropped
|
||||
|| packets[ i ].mWriteIndex == lastIndex + 1 );
|
||||
|
||||
if( packets[ i ].mWriteIndex != -2 )
|
||||
lastIndex ++;
|
||||
|
||||
if( queueLength == 1 )
|
||||
TEST( packets[ i ].mWriteTime >= time || dropPackets ); // start time okay
|
||||
|
||||
time += packets[ i ].mDuration;
|
||||
if( dropPackets )
|
||||
TEST( packets[ i ].mWriteTime < time ); // end time okay (if not dropping)
|
||||
}
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
test1( false, 1 );
|
||||
test1( true, 1 );
|
||||
|
||||
test1( false, 4 );
|
||||
test1( true, 4 );
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !TORQUE_SHIPPING
|
||||
124
Engine/source/platform/test/testBasicTypes.cpp
Normal file
124
Engine/source/platform/test/testBasicTypes.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.");
|
||||
}
|
||||
};
|
||||
|
||||
150
Engine/source/platform/test/testFile.cpp
Normal file
150
Engine/source/platform/test/testFile.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
|
||||
}
|
||||
};
|
||||
194
Engine/source/platform/test/testNet.cpp
Normal file
194
Engine/source/platform/test/testNet.cpp
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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");
|
||||
|
||||
while(Process::processEvents())
|
||||
;
|
||||
|
||||
// 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");
|
||||
|
||||
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.");
|
||||
|
||||
}
|
||||
};
|
||||
85
Engine/source/platform/test/testThreadPool.cpp
Normal file
85
Engine/source/platform/test/testThreadPool.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/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" )
|
||||
{
|
||||
enum { DEFAULT_NUM_ITEMS = 4000 };
|
||||
|
||||
static Vector< U32 > results;
|
||||
|
||||
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 );
|
||||
|
||||
for( U32 i = 0; i < numItems; ++ i )
|
||||
results[ i ] = U32( -1 );
|
||||
|
||||
for( U32 i = 0; i < numItems; ++ i )
|
||||
{
|
||||
ThreadSafeRef< TestItem > item( new TestItem( i ) );
|
||||
pool->queueWorkItem( item );
|
||||
}
|
||||
|
||||
pool->flushWorkItems();
|
||||
|
||||
for( U32 i = 0; i < numItems; ++ i )
|
||||
test( results[ i ] == i, "result mismatch" );
|
||||
|
||||
results.clear();
|
||||
}
|
||||
};
|
||||
|
||||
Vector< U32 > TestThreadPool::results( __FILE__, __LINE__ );
|
||||
|
||||
#endif // !TORQUE_SHIPPING
|
||||
403
Engine/source/platform/test/testThreadSafeDeque.cpp
Normal file
403
Engine/source/platform/test/testThreadSafeDeque.cpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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
|
||||
245
Engine/source/platform/test/testThreadSafePriorityQueue.cpp
Normal file
245
Engine/source/platform/test/testThreadSafePriorityQueue.cpp
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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
|
||||
227
Engine/source/platform/test/testThreadSafeRefCount.cpp
Normal file
227
Engine/source/platform/test/testThreadSafeRefCount.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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
|
||||
417
Engine/source/platform/test/testThreading.cpp
Normal file
417
Engine/source/platform/test/testThreading.cpp
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 int 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 int 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);
|
||||
}
|
||||
};
|
||||
105
Engine/source/platform/test/testTimeManager.cpp
Normal file
105
Engine/source/platform/test/testTimeManager.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue