summaryrefslogtreecommitdiff
path: root/Runtime/Testing
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Testing')
-rw-r--r--Runtime/Testing/ConsoleTestReporter.cpp216
-rw-r--r--Runtime/Testing/ConsoleTestReporter.h63
-rw-r--r--Runtime/Testing/HighLevelTest.cpp537
-rw-r--r--Runtime/Testing/HighLevelTest.h6
-rw-r--r--Runtime/Testing/JobSchedulerTest/Test.sln21
-rw-r--r--Runtime/Testing/JobSchedulerTest/Test.vcproj162
-rw-r--r--Runtime/Testing/JobSchedulerTest/main.cpp82
-rw-r--r--Runtime/Testing/MathPerformanceTest/MatrixMultiplyTest.cpp166
-rw-r--r--Runtime/Testing/TestFixtures.h163
-rw-r--r--Runtime/Testing/Testing.cpp344
-rw-r--r--Runtime/Testing/Testing.h56
11 files changed, 1816 insertions, 0 deletions
diff --git a/Runtime/Testing/ConsoleTestReporter.cpp b/Runtime/Testing/ConsoleTestReporter.cpp
new file mode 100644
index 0000000..0039448
--- /dev/null
+++ b/Runtime/Testing/ConsoleTestReporter.cpp
@@ -0,0 +1,216 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "ConsoleTestReporter.h"
+#include <algorithm>
+#include <stdio.h>
+
+#ifdef WIN32
+#define snprintf _snprintf
+#endif
+
+using namespace UnitTest;
+using namespace std;
+
+static ConsoleTestReporter* g_Instance;
+
+static bool UnitTestLogEntryHandler (LogType type, const char* log, va_list args)
+{
+ Assert (g_Instance != NULL);
+
+ // Expand message string.
+ char buffer[4096];
+ vsnprintf (buffer, sizeof (buffer), log, args);
+ va_end (args);
+
+ // If we're not currently running a test, just dump the message straight to
+ // stdout. This path is used for printing the test start/finish/fail messages.
+ if (!g_Instance->IsCurrentlyRunningTest ())
+ {
+ fputs (buffer, stdout);
+ fflush (stdout);
+
+ #if UNITY_WIN
+ OutputDebugString (buffer);
+ #endif
+ }
+ else
+ {
+ // Otherwise, feed the message into the reporter.
+ // Ignore debug log messages.
+ if (type != LogType_Debug)
+ g_Instance->ReportMessage (type, buffer);
+ }
+
+ // Disable all normal logging.
+ return false;
+}
+
+ConsoleTestReporter::ConsoleTestReporter ()
+ : m_ShowOnlySummary (false)
+ , m_TestNameColumnIndex (100)
+ , m_ResultColumnIndex (256)
+ , m_IsCurrentlyRunningTest (false)
+ , m_CurrentTestIsFailure (false)
+{
+ Assert (g_Instance == NULL);
+ g_Instance = this;
+
+ // Install our log override.
+ SetLogEntryHandler (UnitTestLogEntryHandler);
+}
+
+ConsoleTestReporter::~ConsoleTestReporter ()
+{
+ SetLogEntryHandler (NULL);
+ g_Instance = NULL;
+}
+
+ConsoleTestReporter* ConsoleTestReporter::GetInstance ()
+{
+ return g_Instance;
+}
+
+void ConsoleTestReporter::ExpectLogMessage (LogType type, const char* logFragment)
+{
+ Assert (type != LogType_Debug);
+
+ if (!IsCurrentlyRunningTest ())
+ return;
+
+ m_ExpectedLogMessagesForCurrentTest.push_back (LogMessage (type, logFragment));
+}
+
+void ConsoleTestReporter::ReportTestStart (TestDetails const& test)
+{
+ if (!m_ShowOnlySummary)
+ {
+ Assert (m_ResultColumnIndex > m_TestNameColumnIndex);
+
+ char buffer[1024];
+ const int suiteNameLength = strlen (test.suiteName);
+ Assert (suiteNameLength < sizeof(buffer) - 4);
+
+ // Create '[TestSuite] ' string padded out with blanks up to the column
+ // for the test name.
+ memset (buffer, ' ', sizeof (buffer));
+ buffer[0] = '[';
+ memcpy (&buffer[1], test.suiteName, suiteNameLength);
+ buffer[suiteNameLength + 1] = ']';
+ buffer[std::min<int> (m_TestNameColumnIndex, sizeof (buffer))] = '\0';
+
+ // Print '[TestSuite] TestName'.
+ printf_console ("%s%s", buffer, test.testName);
+
+ // Print blanks to pad out to result column.
+ const int numSpacesToPad = m_ResultColumnIndex - m_TestNameColumnIndex - strlen (test.testName);
+ memset (buffer, ' ', sizeof (buffer));
+ buffer[std::min<int> (numSpacesToPad, sizeof (buffer))] = '\0';
+ printf_console (buffer);
+ }
+
+ m_CurrentTest = test;
+ m_IsCurrentlyRunningTest = true;
+ m_CurrentTestIsFailure = false;
+}
+
+void ConsoleTestReporter::ReportFailure (TestDetails const& test, char const* failure)
+{
+ // Memorize failure.
+ Failure details;
+ details.fileName = test.filename;
+ details.lineNumber = test.lineNumber;
+ details.text = failure;
+ m_CurrentTestFailures.push_back (details);
+
+ MarkCurrentTestAsFailure ();
+}
+
+void ConsoleTestReporter::ReportMessage (LogType type, string message)
+{
+ // Check whether we have expected this message to come in.
+ for (size_t i = 0; i < m_ExpectedLogMessagesForCurrentTest.size (); ++i)
+ {
+ // Skip if type doesn't match.
+ if (m_ExpectedLogMessagesForCurrentTest[i].first != type)
+ continue;
+
+ // Check whether the expected fragment is found in the current message.
+ if (message.find (m_ExpectedLogMessagesForCurrentTest[i].second) != string::npos)
+ {
+ // Remove it. We only accept one occurrence.
+ m_ExpectedLogMessagesForCurrentTest.erase (m_ExpectedLogMessagesForCurrentTest.begin () + i);
+
+ // Yes, so all ok.
+ return;
+ }
+ }
+
+ // Not an expected message. Record.
+ m_UnexpectedLogMessagesForCurrentTest.push_back (LogMessage (type, message));
+
+ MarkCurrentTestAsFailure ();
+}
+
+void ConsoleTestReporter::ReportTestFinish (TestDetails const& test, float secondsElapsed)
+{
+ m_IsCurrentlyRunningTest = false;
+
+ // If we are still expecting messages, fail the test.
+ if (!m_ExpectedLogMessagesForCurrentTest.empty ())
+ MarkCurrentTestAsFailure ();
+
+ if (!m_ShowOnlySummary)
+ {
+ // Print status.
+ if (m_CurrentTestIsFailure)
+ printf_console ("FAIL!!!!\n");
+ else
+ printf_console ("PASS (%ims)\n", (int) (secondsElapsed * 1000.f));
+
+ // Print failures.
+ for (size_t i = 0; i < m_CurrentTestFailures.size (); ++i)
+ {
+ const Failure& failure = m_CurrentTestFailures[i];
+ printf_console ("\tCHECK FAILURE: %s\n\t\t(%s:%i)\n",
+ failure.text.c_str (),
+ failure.fileName.c_str (),
+ failure.lineNumber);
+ }
+
+ // Print unexpected messages.
+ for (size_t i = 0; i < m_UnexpectedLogMessagesForCurrentTest.size (); ++i)
+ printf_console ("\tUNEXPECTED %s: %s\n",
+ LogTypeToString (m_UnexpectedLogMessagesForCurrentTest[i].first),
+ m_UnexpectedLogMessagesForCurrentTest[i].second.c_str ());
+
+ // Print expected messages that didn't show.
+ for (size_t i = 0; i < m_ExpectedLogMessagesForCurrentTest.size (); ++i)
+ printf_console ("\tEXPECTED %s: %s\n",
+ LogTypeToString (m_ExpectedLogMessagesForCurrentTest[i].first),
+ m_ExpectedLogMessagesForCurrentTest[i].second.c_str ());
+ }
+
+ // Clear state of current test.
+ m_CurrentTestFailures.clear ();
+ m_UnexpectedLogMessagesForCurrentTest.clear ();
+ m_ExpectedLogMessagesForCurrentTest.clear ();
+ m_CurrentTest = TestDetails ();
+}
+
+void ConsoleTestReporter::ReportSummary (int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed)
+{
+ // Print counters (rely on our fail test counter since we also count unexpected messages
+ // as failures).
+ printf_console ("Ran %i tests with %i failures in %.2f seconds\n", totalTestCount, m_FailedTests.size (), secondsElapsed);
+
+ // Print failures.
+ for (int i = 0; i < m_FailedTests.size (); ++i)
+ {
+ const TestDetails& test = m_FailedTests[i];
+ printf_console ("\tFAILED: %s [%s]\n", test.testName, test.suiteName);
+ }
+}
+
+#endif
diff --git a/Runtime/Testing/ConsoleTestReporter.h b/Runtime/Testing/ConsoleTestReporter.h
new file mode 100644
index 0000000..37f296b
--- /dev/null
+++ b/Runtime/Testing/ConsoleTestReporter.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "External/UnitTest++/src/TestReporter.h"
+#include "External/UnitTest++/src/TestDetails.h"
+
+
+/// Unit test reporter that logs to console output.
+class ConsoleTestReporter : public UnitTest::TestReporter
+{
+public:
+
+ ConsoleTestReporter ();
+ virtual ~ConsoleTestReporter ();
+
+ virtual void ReportTestStart (UnitTest::TestDetails const& test);
+ virtual void ReportFailure (UnitTest::TestDetails const& test, char const* failure);
+ virtual void ReportTestFinish (UnitTest::TestDetails const& test, float secondsElapsed);
+ virtual void ReportSummary (int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed);
+ void ReportMessage (LogType type, std::string message);
+
+ void ExpectLogMessage (LogType type, const char* logFragment);
+
+ void SetShowOnlySummary (bool value) { m_ShowOnlySummary = value; }
+ void SetTestNameColumnIndex (int value) { m_TestNameColumnIndex = value; }
+ void SetResultColumnIndex (int value) { m_ResultColumnIndex = value; }
+
+ bool IsCurrentlyRunningTest () const { return m_IsCurrentlyRunningTest; }
+
+ static ConsoleTestReporter* GetInstance();
+
+private:
+
+ bool m_IsCurrentlyRunningTest;
+ bool m_ShowOnlySummary;
+ int m_TestNameColumnIndex;
+ int m_ResultColumnIndex;
+
+ struct Failure
+ {
+ std::string text;
+ std::string fileName;
+ int lineNumber;
+ };
+
+ typedef std::pair<LogType, std::string> LogMessage;
+
+ std::vector<UnitTest::TestDetails> m_FailedTests;
+
+ bool m_CurrentTestIsFailure;
+ UnitTest::TestDetails m_CurrentTest;
+ std::vector<Failure> m_CurrentTestFailures;
+ std::vector<LogMessage> m_UnexpectedLogMessagesForCurrentTest;
+ std::vector<LogMessage> m_ExpectedLogMessagesForCurrentTest;
+
+ void MarkCurrentTestAsFailure ()
+ {
+ if (m_CurrentTestIsFailure)
+ return;
+
+ m_CurrentTestIsFailure = true;
+ m_FailedTests.push_back (m_CurrentTest);
+ }
+};
diff --git a/Runtime/Testing/HighLevelTest.cpp b/Runtime/Testing/HighLevelTest.cpp
new file mode 100644
index 0000000..dc4018d
--- /dev/null
+++ b/Runtime/Testing/HighLevelTest.cpp
@@ -0,0 +1,537 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "HighLevelTest.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Serialize/DumpSerializedDataToText.h"
+#include "Runtime/Serialize/TypeTree.h"
+#include "Runtime/BaseClasses/SupportedMessageOptimization.h"
+#include "Runtime/Allocator/MemoryManager.h"
+
+#if !UNITY_XENON && !UNITY_ANDROID
+#include "PlatformDependent/CommonWebPlugin/Verification.h"
+#endif
+#include "Runtime/Serialize/TransferUtility.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Serialize/PersistentManager.h"
+#include "Runtime/Graphics/Texture2D.h"
+
+#include "Runtime/Misc/BuildSettings.h"
+#if UNITY_EDITOR
+#include "Editor/Platform/Interface/EditorUtility.h"
+#include "Editor/Src/Prefabs/PrefabMergingTest.h"
+#include "Editor/Src/Prefabs/PropertyModification.h"
+#endif
+
+void* RunObjectResetTests (void* userData);
+void RunAllObjectResetTests();
+void TestVersions ();
+void TestLaunchingALotOfTasks ();
+void TestSerializedPropertyPath ();
+void TestEditorComponentUtility();
+void TestAssetImporterCallsPostTransfer ();
+
+void RunHighLevelTest ()
+{
+ #if !UNITY_XENON && !UNITY_ANDROID && !UNITY_PEPPER
+ ErrorIf(!VerifySignature("a", "98ad49733651a83f333d7b91a2f76f77510bfaf89316e34d0025b5b38526c8f8d269e47be24044b9f0dd8675fc781b21fc425da801e33702f9744462c488b400ce9af75b8ae3ec02e6915ce980adcc700fd9d5b2633812ac168d2dea24906bb1cdb3de2bffa4ddeeeb9bc5966b0768e7bc20a8320bf6a0a48cf57b26e31f98b0"));
+ ErrorIf(!VerifySignature("abc", "5ac52e1f8f7e331cf1b1c5bef6254cfbc81c4b48296ae4b4e8a8c536de61c9cade1c77c45b047ce880b8f74829f52860c87b2ecf82a96803059e9189c879a63770a071483ab9d7b688cadcf3aa2a857849f86ff3fdecd725cf34e99f5fc5e4496ca9b810c841dc3a5527c7b3bc866ced1daf120ade08614ec6273d293c293880"));
+ ErrorIf(!VerifySignature("abcd", "584e7bf6042e8a93585bd9a1c08115705f59e0e7e07656a09b16a526b4b76cbc92f5a7246fdd706031f4186361d8cf87ae2062ab877ea9a2576c59be8a9a3139c374345b61a56046376e174ec510cc0d9be46176f8fb0096aeca259b8e754abd424dd3ef50f3473d552fcba6c946bff1908e70cea57584c9002c4bc245e15e35"));
+
+ ErrorIf(!VerifySignature("http://webplayer.unity3d.com/", "99874573f0651e46ac2c9b0f872a7ecf1ee9c101195bf2d06cd6cd00508b412aa58db8b61fa898ecc0f5e37b7efd6c2762081eb5aa9810e6001c9e5a7d32d9b1fdd6ce11d7cd666a997b2c443f123173dac380ca9e06d9ac7aef7c3794d4f3dfc2b62eaf0684cb5a8ba762700b1ae74ef35ce768312c483020455936ed47bf46"));
+ ErrorIf(!VerifySignature("http://wp-fusionfall.unity3d.com/", "606558a42652f6d31130505aa3a9a24dfbd9801b15f79181fac171e0807064799313a6f4369883c276361efb757fd6e979e607be4e981e6ff36308078c13b7bdf59a1f4f06a8f4a5bf78848fb8f4597e2028e9fcd43882a1dcdcf2c1f3756ebf2afab65492e117d53894b81e19435ba8c913a89d07c64c1095b1abe7fa632c15"));
+ ErrorIf(!VerifySignature("http://wp-jumpstart.unity3d.com/", "9f5dce353ddccf0adca627c070135fdc8de70925b366ee661d704c4e90c33fa760a452cc741862ca2e87bc722a2cbf1b0948b2116132f88e45a11e6461cc70190db80f97268a55e0cc96d062272d9eb22d5041e9152d1cc980a4448de1282bdf4dbfe27c558ec0f922b1277fcad57b607364d724d0658500a2b5c2dd0245b050"));
+ #endif
+
+ #if !UNITY_PEPPER
+ //Some access the default resources as part of their reset procedure, which are not loaded at this stage in NaCl yet.
+ //So we skip this test in Pepper.
+ RunAllObjectResetTests ();
+ #endif
+
+ TestVersions();
+
+ void TestHighLevelShaderNameRegistryTests();
+ TestHighLevelShaderNameRegistryTests();
+
+
+ #if UNITY_EDITOR
+
+ TestPropertyModification();
+ TestPrefabModification();
+
+ // TestLaunchingALotOfTasks ();
+
+ TestEditorComponentUtility();
+
+ TestAssetImporterCallsPostTransfer();
+
+ #if UNITY_WIN
+ void TestGetActualPathWindows();
+ TestGetActualPathWindows();
+ #endif // #if UNITY_WIN
+
+ #endif // #if UNITY_EDITOR
+}
+
+#if UNITY_EDITOR
+/// @TODO: Cant do this on every launch of unity, integrate into properl C++ unit test suite
+void TestLaunchingALotOfTasks ()
+{
+ #if UNITY_OSX
+ for (int i=0;i<400;i++)
+ {
+ if (!LaunchTask ("/bin/ls", NULL, NULL))
+ LogString("Fail");
+ }
+
+ for (int i=0;i<400;i++)
+ {
+ string output;
+ if (!LaunchTask ("/bin/ls", &output, NULL) && !output.empty())
+ LogString("Fail");
+ }
+ #endif
+}
+#endif
+
+
+void TestVersions ()
+{
+ int current = 0;
+ int previous = 0;
+
+ current = GetNumericVersion("2.6.0b1"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0b7"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0b9"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0f1"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0f4"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0f8"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0f9"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.0f14"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.1b1"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.1b10"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.1"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.1f1"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.1f15"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.1f16"); Assert(current > previous); previous = current;
+ current = GetNumericVersion("2.6.2b1"); Assert(current > previous); previous = current;
+}
+
+// If tests fail because they access the GameObject or other components in Awake, DO NOT
+// disable them here. Instead fix Awake not to do that in if IsActive() is not true.
+// That will also fix crashes for importing broken prefabs, and is cleaner in general.
+const int kDisabledAwakeFromLoad[] = {
+0
+, 89 // Cubemap (Error using invalid texture)
+, 93 // TextureRect (Error using invalid texture)
+, 117 // Texture3D (Error using invalid texture)
+, 117 // Texture3D (Error using invalid texture)
+, 28 // Texture2D (Error using invalid texture)
+, 48 // Shader (Gives error parsing invalid shader)
+, 152 // MovieTexture gives error on awake from load because movie data is not valid
+, 156 // TerrainData gives error loading because of invalid heightmap
+
+};
+
+#define kDisabledAwakeFromLoadThreaded kDisabledAwakeFromLoad
+
+#if ENABLE_MEMORY_MANAGER
+#define ADD_CUSTOM_ALLOCATOR(Allocator) GetMemoryManager().AddCustomAllocator(Allocator)
+#define REMOVE_CUSTOM_ALLOCATOR(Allocator) GetMemoryManager().RemoveCustomAllocator(Allocator)
+#else
+#define ADD_CUSTOM_ALLOCATOR(Allocator) kMemDefault
+#define REMOVE_CUSTOM_ALLOCATOR(Allocator) {}
+#endif
+
+class UsePreallocatedMemory : public BaseAllocator
+ {
+ public:
+ UInt8 uninitializedValue;
+
+ UsePreallocatedMemory (int size, UInt8 value, const char* name) : BaseAllocator(name)
+ {
+ baseMemory = memory = (unsigned char*) MemoryManager::LowLevelAllocate(size);
+ total = 0;
+ totalSize = size;
+ uninitializedValue = value;
+ for (int i=0;i<size;i++)
+ baseMemory[i] = uninitializedValue;
+ id = ADD_CUSTOM_ALLOCATOR(this).label;
+ }
+
+ virtual ~UsePreallocatedMemory () {
+ MemoryManager::LowLevelFree(baseMemory);
+ REMOVE_CUSTOM_ALLOCATOR(this);
+ }
+
+ virtual void* Allocate (size_t size, int align)
+ {
+ Assert(*memory == uninitializedValue);
+
+ memory = (unsigned char*)AlignPtr(memory + sizeof(SInt32), align);
+ *reinterpret_cast<SInt32*> (memory - sizeof(SInt32)) = size;
+ memory += size;
+ total += size;
+
+ Assert(baseMemory+totalSize > memory);
+
+ return memory - size;
+ }
+
+ virtual void Deallocate (void* p) { total -= *(reinterpret_cast<SInt32*> (p) - 1); }
+ virtual bool Contains (const void* p) { return p > baseMemory && p <= memory ; }
+
+ virtual size_t GetPtrSize(const void* ptr) const {return *(reinterpret_cast<const SInt32*> (ptr) - 1); }
+
+ unsigned char* memory;
+ unsigned char* baseMemory;
+ int total;
+ int totalSize;
+ MemLabelIdentifier id;
+ };
+
+enum { kMaxGeneratedObjectCount = 5000 };
+
+struct Parameters
+{
+ ObjectCreationMode mode;
+ Object* objects[kMaxGeneratedObjectCount];
+ BaseAllocator* allocator[kMaxGeneratedObjectCount];
+};
+
+void SetObjectDirtyDisallowCalling (Object* obj)
+{
+ AssertString("SetDirty should never be called from Awake / Reset / Constructor etc");
+}
+
+static void ClearAllManagers ()
+{
+ for (int i=0;i<ManagerContext::kManagerCount;i++)
+ SetManagerPtrInContext(i, NULL);
+}
+
+static void AssignAllManagers (Object** managers)
+{
+ for (int i=0;i<ManagerContext::kManagerCount;i++)
+ SetManagerPtrInContext(i, managers[i]);
+}
+
+
+void RunAllObjectResetTests ()
+{
+#if UNITY_EDITOR
+ Object::ObjectDirtyCallbackFunction* oldCallBack = Object::GetDirtyCallback ();
+
+////@TODO: Enable this some day. Creating an object, especially when loaded from disk should not mark it dirty.
+// Object::RegisterDirtyCallback (SetObjectDirtyDisallowCalling);
+
+
+ Object* managers[ManagerContext::kManagerCount];
+ memcpy(managers, GetManagerContext().m_Managers, sizeof(managers));
+
+ Parameters paramMainThread;
+ memset(&paramMainThread, 0, sizeof(paramMainThread));
+ paramMainThread.mode = kCreateObjectDefault;
+ RunObjectResetTests (&paramMainThread);
+
+ ClearAllManagers();
+
+ Parameters paramResetThread;
+ memset(&paramResetThread, 0, sizeof(paramResetThread));
+ paramResetThread.mode = kCreateObjectFromNonMainThread;
+ Thread resetThread;
+ resetThread.Run(RunObjectResetTests, &paramResetThread);
+ resetThread.WaitForExit();
+
+ for (int i=0;i<kMaxGeneratedObjectCount;i++)
+ {
+ if (paramResetThread.objects[i])
+ {
+ Object::RegisterInstanceID(paramResetThread.objects[i]);
+ bool ignoreAwake = false;
+ for (int j=0;j<sizeof(kDisabledAwakeFromLoadThreaded) / sizeof(int);j++)
+ ignoreAwake |= kDisabledAwakeFromLoadThreaded[j] == paramResetThread.objects[i]->GetClassID();
+ if (!ignoreAwake)
+ paramResetThread.objects[i]->AwakeFromLoad(kDidLoadThreaded);
+ }
+
+ // IntegrateLoadedImmediately must be called between AwakeFromLoadThreaded and AwakeFromLoad!
+ Texture2D::IntegrateLoadedImmediately();
+
+ AssignAllManagers(managers);
+
+ DestroyObjectHighLevel(paramResetThread.objects[i]);
+ memset(&paramMainThread, 0, sizeof(paramMainThread));
+ REMOVE_CUSTOM_ALLOCATOR(paramResetThread.allocator[i]);
+ UNITY_DELETE(paramResetThread.allocator[i],kMemDefault);
+ }
+
+ AssignAllManagers(managers);
+
+ Object::RegisterDirtyCallback (oldCallBack);
+#endif
+}
+
+// Compare only every 4 bytes. This is because we can't detect unaligned variables.
+// Unaligned blocks of memory will never be touched, thus will trigger an error.
+void CompareMemoryForAllValuesTouched (UsePreallocatedMemory& lhs, UsePreallocatedMemory& rhs, int klassID, const char* msg)
+{
+ // this is not a valid or complete test. We need custom code in order to test this for each object. turned off for now
+ return;
+ const int kAlignedDataSize = 16;
+ for (int j=0;j<lhs.total;j+=kAlignedDataSize)
+ {
+ bool anythingModified = false;
+ for (int p=0;p<kAlignedDataSize;p++)
+ {
+ if (lhs.baseMemory[j+p] != lhs.uninitializedValue || rhs.baseMemory[j+p] != rhs.uninitializedValue)
+ anythingModified = true;
+ }
+
+ if (!anythingModified)
+ {
+ ErrorString("Class is not completely initialized by constructor + " + string(msg) + " " + Object::ClassIDToString(klassID));
+ return;
+ }
+ }
+}
+
+void DumpData (Object& obj)
+{
+ printf_console("--- Dumping %s - %s\n", obj.GetClassName().c_str(), obj.GetName());
+ TypeTree typeTree;
+ dynamic_array<UInt8> output(kMemTempAlloc);
+ GenerateTypeTree(obj, &typeTree);
+ WriteObjectToVector(obj, &output);
+ DumpSerializedDataToText(typeTree, output);
+}
+
+const static int kBaseVeryHighInstanceID = (std::numeric_limits<SInt32>::max()-1) - 5000 * 4;
+const static int kBaseVeryHighInstanceID2 = (std::numeric_limits<SInt32>::max()-1) - 10000 * 4;
+
+void* RunObjectResetTests (void* userData)
+{
+ Parameters& parameters = *((Parameters*)userData);
+
+ vector<SInt32> klasses;
+ Object::FindAllDerivedClasses(ClassID(Object), &klasses);
+
+ int kMaxSize = 1024 * 64;
+
+ for (int i=0;i<klasses.size();i++)
+ {
+ int klassID = klasses[i];
+
+ if (Object::ClassIDToRTTI(klassID)->isAbstract)
+ continue;
+ // Ignore game manager derived
+ if (Object::IsDerivedFromClassID(klassID, 9))
+ continue;
+
+ if(klassID == ClassID(Transform))
+ continue; // ???
+
+ if(klassID == 1027) // ignore GUISerializer. This is really just a very custom class that sets up some global state
+ continue;
+ if(klassID == 1048) // ignore InspectorExpandedState since it is a singleton class
+ continue;
+ if(klassID == 1049) // ignore AnnotationManager since it is a singleton class
+ continue;
+ if(klassID == 159) // ignore editor settings
+ continue;
+ if(klassID == 162) // ignore editor user settings
+ continue;
+ if(klassID == 115) // ignore MonoScript, it accesses MonoManager from serialization. This is fine but doesn't fit well into the framework.
+ continue;
+ if(klassID == 148) // ignore NetworkView, Registers itself in constructor with networkmanager
+ continue;
+ if(klassID == 1037) // ignore AssetServerCache, singleton
+ continue;
+ if(klassID == 142) // ignore AssetBundle, for it to destroy we need to call UnloadAssetBundle
+ continue;
+ if(klassID == 184 || klassID == 185 || klassID == 186) // ignore SubstanceArchive, ProceduralMaterial, ProceduralTexture
+ continue;
+
+ ////// @TODO: Work in progress, need to be fixed!!!!
+ if (klassID == 156) // Terrain has some issues left
+ continue;
+
+ bool ignoreAwake = false;
+ for (int j=0;j<sizeof(kDisabledAwakeFromLoad) / sizeof(int);j++)
+ ignoreAwake |= kDisabledAwakeFromLoad[j] == klassID;
+
+ /// Run for editor classes , we might be using datatemp
+
+ dynamic_array<UInt8> serialized1(kMemTempAlloc);
+ dynamic_array<UInt8> serialized2(kMemTempAlloc);
+
+ UsePreallocatedMemory* mem1 = UNITY_NEW(UsePreallocatedMemory (kMaxSize, 0, "Mem1"),kMemDefault);
+ MemLabelId mem1Label = ADD_CUSTOM_ALLOCATOR(mem1);
+ Object* obj1 = Object::Produce(klassID, kBaseVeryHighInstanceID + i * 2, mem1Label, parameters.mode);
+
+ obj1->Reset();
+
+ WriteObjectToVector(*obj1, &serialized1);
+
+ UsePreallocatedMemory* mem2 = UNITY_NEW(UsePreallocatedMemory (kMaxSize, 255, "Mem2"),kMemDefault);
+ MemLabelId mem2Label = ADD_CUSTOM_ALLOCATOR(mem2);
+ Object* obj2 = Object::Produce(klassID, kBaseVeryHighInstanceID + 2 + i * 2, mem2Label, parameters.mode);
+
+ obj2->Reset();
+
+ WriteObjectToVector(*obj2, &serialized2);
+
+ if (!ignoreAwake)
+ {
+ if (parameters.mode == kCreateObjectDefault)
+ {
+ obj1->AwakeFromLoad(kDidLoadFromDisk);
+ obj2->AwakeFromLoad(kDidLoadFromDisk);
+ }
+ else if (parameters.mode == kCreateObjectFromNonMainThread)
+ {
+ obj1->AwakeFromLoadThreaded();
+ obj2->AwakeFromLoadThreaded();
+ }
+ }
+ else
+ {
+ obj1->HackSetAwakeWasCalled();
+ obj2->HackSetAwakeWasCalled();
+ }
+
+ if (mem1->total != mem2->total)
+ {
+ ErrorString("Class is different size after initialized by constructor + Reset " + Object::ClassIDToString(klassID));
+ // Dump serialized data to console.log
+ DumpData(*obj1);
+ DumpData(*obj2);
+ }
+
+ if( !serialized1.equals(serialized2) )
+ {
+ ErrorString("Class is serialized different after initialized by constructor + Reset " + Object::ClassIDToString(klassID));
+ // Dump serialized data to console.log
+ DumpData(*obj1);
+ DumpData(*obj2);
+ }
+
+ if (!ignoreAwake && parameters.mode == kCreateObjectDefault)
+ {
+ // Compare only every 4 bytes. This is because we can't detect unaligned variables.
+ // Unaligned blocks of memory will never be touched, thus will trigger an error.
+ CompareMemoryForAllValuesTouched (*mem1, *mem2, klassID, "Reset");
+ }
+
+ UsePreallocatedMemory* mem3 = UNITY_NEW(UsePreallocatedMemory (kMaxSize, 125, "Mem3"),kMemDefault);
+ MemLabelId mem3Label = ADD_CUSTOM_ALLOCATOR(mem3);
+ Object* obj3 = Object::Produce(klassID, kBaseVeryHighInstanceID2 + i * 2, mem3Label, parameters.mode);
+
+ ReadObjectFromVector(obj3, serialized1);
+ obj3->HackSetAwakeWasCalled();
+
+ CompareMemoryForAllValuesTouched (*mem1, *mem3, klassID, "Serialize Read");
+
+ WriteObjectToVector(*obj3, &serialized2);
+ if(!serialized1.equals(serialized2))
+ {
+ ErrorString("Class is serialized different after write and reading back " + Object::ClassIDToString(klassID));
+ // Dump serialized data to console.log
+ DumpData(*obj3);
+ }
+
+ if (parameters.mode == kCreateObjectDefault)
+ {
+ DestroyObjectHighLevel(obj1);
+ DestroyObjectHighLevel(obj2);
+ DestroyObjectHighLevel(obj3);
+
+ AssertIf(mem1->total != 0);
+ AssertIf(mem2->total != 0);
+ AssertIf(mem3->total != 0);
+
+ REMOVE_CUSTOM_ALLOCATOR(mem1);
+ UNITY_DELETE( mem1,kMemDefault);
+ REMOVE_CUSTOM_ALLOCATOR(mem2);
+ UNITY_DELETE( mem2,kMemDefault);
+ REMOVE_CUSTOM_ALLOCATOR(mem3);
+ UNITY_DELETE( mem3,kMemDefault);
+ }
+ else
+ {
+ parameters.objects[i * 3 + 0] = obj1;
+ parameters.objects[i * 3 + 1] = obj2;
+ parameters.objects[i * 3 + 2] = obj3;
+
+ parameters.allocator[i * 3 + 0] = mem1;
+ parameters.allocator[i * 3 + 1] = mem2;
+ parameters.allocator[i * 3 + 2] = mem3;
+ }
+
+ }
+
+ return NULL;
+}
+
+struct TestData
+{
+ PPtr<Texture2D> m_Tex;
+ Texture2D* m_CachedTexture;
+
+ // Property m_ActiveMatrixName; /// HOW TO SUPPORT THIS???
+
+ Vector2f m_Position;
+ Vector2f m_Scale;
+
+ TestData ()
+ {
+ m_CachedTexture = NULL;
+ m_Tex = NULL;
+ m_Position = Vector2f(0,0);
+ m_Scale = Vector2f(1,1);
+ }
+};
+
+#if UNITY_EDITOR
+#include "Editor/Src/AssetPipeline/AssetImporter.h"
+
+void TestAssetImporterCallsPostTransfer ()
+{
+ vector<SInt32> klasses;
+ Object::FindAllDerivedClasses (ClassID (AssetImporter), &klasses);
+ UsePreallocatedMemory* mem1 = new UsePreallocatedMemory (50 * 1024, 0, "Mem1");
+ MemLabelId mem1Label = ADD_CUSTOM_ALLOCATOR(mem1);
+
+ for (int i = 0; i < klasses.size (); i++)
+ {
+ int klassID = klasses[i];
+ AssetImporter* importer = static_cast<AssetImporter*> (Object::Produce (klassID, 0, mem1Label, kCreateObjectDefault));
+ importer->Reset ();
+ importer->HackSetAwakeWasCalled ();
+
+ YAMLWrite write (0);
+ importer->VirtualRedirectTransfer (write);
+ std::string str;
+ write.OutputToString (str);
+
+ size_t offset = str.find ("m_UserData:");
+ if (offset == string::npos)
+ ErrorString (Format ("%s did not call PostTransfer in its Transfer function", importer->GetClassName ().c_str ()).c_str ());
+
+ DestroyObjectHighLevel (importer);
+ }
+
+ REMOVE_CUSTOM_ALLOCATOR(mem1);
+ delete mem1;
+}
+
+#endif
+
+
+
+#endif
diff --git a/Runtime/Testing/HighLevelTest.h b/Runtime/Testing/HighLevelTest.h
new file mode 100644
index 0000000..df33ccd
--- /dev/null
+++ b/Runtime/Testing/HighLevelTest.h
@@ -0,0 +1,6 @@
+#ifndef HIGHLEVELTEST_H
+#define HIGHLEVELTEST_H
+
+void RunHighLevelTest ();
+
+#endif
diff --git a/Runtime/Testing/JobSchedulerTest/Test.sln b/Runtime/Testing/JobSchedulerTest/Test.sln
new file mode 100644
index 0000000..c6f9aa5
--- /dev/null
+++ b/Runtime/Testing/JobSchedulerTest/Test.sln
@@ -0,0 +1,21 @@
+Microsoft Visual Studio Solution File, Format Version 8.00
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "{4FB80099-804E-4ABE-9680-31290A6F0D89}"
+ ProjectSection(ProjectDependencies) = postProject
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfiguration) = preSolution
+ Debug = Debug
+ Release = Release
+ EndGlobalSection
+ GlobalSection(ProjectConfiguration) = postSolution
+ {4FB80099-804E-4ABE-9680-31290A6F0D89}.Debug.ActiveCfg = Debug|Win32
+ {4FB80099-804E-4ABE-9680-31290A6F0D89}.Debug.Build.0 = Debug|Win32
+ {4FB80099-804E-4ABE-9680-31290A6F0D89}.Release.ActiveCfg = Release|Win32
+ {4FB80099-804E-4ABE-9680-31290A6F0D89}.Release.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ EndGlobalSection
+ GlobalSection(ExtensibilityAddIns) = postSolution
+ EndGlobalSection
+EndGlobal
diff --git a/Runtime/Testing/JobSchedulerTest/Test.vcproj b/Runtime/Testing/JobSchedulerTest/Test.vcproj
new file mode 100644
index 0000000..58b742f
--- /dev/null
+++ b/Runtime/Testing/JobSchedulerTest/Test.vcproj
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="windows-1257"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="7.10"
+ Name="Test"
+ ProjectGUID="{4FB80099-804E-4ABE-9680-31290A6F0D89}"
+ Keyword="Win32Proj">
+ <Platforms>
+ <Platform
+ Name="Win32"/>
+ </Platforms>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="build/debug"
+ IntermediateDirectory="build/debug"
+ ConfigurationType="1"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".;..;..\..\Utilities;..\..\Threads"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="TRUE"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="TRUE"
+ DebugInformationFormat="4"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ OutputFile="Test_d.exe"
+ LinkIncremental="2"
+ GenerateDebugInformation="TRUE"
+ ProgramDatabaseFile="$(OutDir)/Test.pdb"
+ SubSystem="1"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="build/release"
+ IntermediateDirectory="build/release"
+ ConfigurationType="1"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".;..;..\..\Utilities;..\..\Threads"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="0"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="TRUE"
+ DebugInformationFormat="3"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ OutputFile="Test.exe"
+ LinkIncremental="1"
+ GenerateDebugInformation="TRUE"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="unity"
+ Filter="">
+ <File
+ RelativePath="..\..\Threads\JobScheduler.cpp">
+ </File>
+ <File
+ RelativePath="..\..\Threads\JobScheduler.h">
+ </File>
+ <File
+ RelativePath="..\..\Utilities\Mutex.cpp">
+ </File>
+ <File
+ RelativePath="..\..\Utilities\Mutex.h">
+ </File>
+ <File
+ RelativePath="..\..\Threads\Semaphore.h">
+ </File>
+ <File
+ RelativePath="..\..\Utilities\Thread.cpp">
+ </File>
+ <File
+ RelativePath="..\..\Utilities\Thread.h">
+ </File>
+ <File
+ RelativePath="..\..\Threads\ThreadMessageQueue.cpp">
+ </File>
+ <File
+ RelativePath="..\..\Threads\ThreadMessageQueue.h">
+ </File>
+ <File
+ RelativePath="..\..\Threads\ThreadUtility.h">
+ </File>
+ <File
+ RelativePath="..\..\Utilities\Word.cpp">
+ </File>
+ <File
+ RelativePath="..\..\Utilities\Word.h">
+ </File>
+ </Filter>
+ <File
+ RelativePath=".\main.cpp">
+ </File>
+ <File
+ RelativePath="..\UnityPrefix.h">
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/Runtime/Testing/JobSchedulerTest/main.cpp b/Runtime/Testing/JobSchedulerTest/main.cpp
new file mode 100644
index 0000000..8554107
--- /dev/null
+++ b/Runtime/Testing/JobSchedulerTest/main.cpp
@@ -0,0 +1,82 @@
+#include <cstdio>
+#include <cstdlib>
+#include "UnityPrefix.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include <time.h>
+#include <math.h>
+
+struct WorkData {
+ float input;
+ float output;
+};
+
+void* WorkFunction( void* data )
+{
+ WorkData* d = (WorkData*)data;
+ d->output = 0.0f;
+ for( int i = 0; i < 1000000; ++i ) {
+ d->output += sinf(d->output) + cosf(d->input) - sinf(d->output + d->input * 3.0f);
+ }
+
+ return NULL;
+}
+
+// Windows, Core2Quad 2.40
+// 200 jobs, 100000 iters:
+// Sum=590573.192871
+// 0=1.55s 1=0.80s 2=0.55s 3=0.45s 4=0.45s 5=0.44s 6=0.45s
+// 100 jobs, 1000000 iters:
+// Sum=2992744.398470
+// 0=7.78s 1=3.94s 2=2.66s 3=2.00s 4=2.00s 5=2.00s 6=2.02s
+
+void DoTests()
+{
+ JobScheduler scheduler(3,1);
+
+ JobScheduler::JobGroupID group = scheduler.BeginGroup();
+
+ const int kJobs = 100;
+ WorkData datas[kJobs];
+ for( int i = 0; i < kJobs; ++i )
+ {
+ datas[i].input = i+1;
+ scheduler.SubmitJob( group, WorkFunction, &datas[i], NULL );
+ }
+ scheduler.WaitForGroup(group);
+
+ float sum = 0.0f;
+ for( int i = 0; i < kJobs; ++i )
+ sum += datas[i].output;
+ printf("Sum of results: %f\n", sum);
+}
+
+
+
+int main()
+{
+ #if UNITY_WIN
+ DWORD ttt0 = GetTickCount();
+ #else
+ timeval ttt0;
+ gettimeofday( &ttt0, NULL );
+ #endif
+
+ DoTests();
+
+ #if UNITY_WIN
+ DWORD ttt1 = GetTickCount();
+ float timeTaken = (ttt1-ttt0) * 0.001f;
+ #else
+ timeval ttt1;
+ gettimeofday( &ttt1, NULL );
+ timeval ttt2;
+ timersub( &ttt1, &ttt0, &ttt2 );
+ float timeTaken = ttt2.tv_sec + ttt2.tv_usec * 1.0e-6f;
+ #endif
+
+ printf( "Test time: %.2fs\n", timeTaken );
+
+ return 0;
+}
diff --git a/Runtime/Testing/MathPerformanceTest/MatrixMultiplyTest.cpp b/Runtime/Testing/MathPerformanceTest/MatrixMultiplyTest.cpp
new file mode 100644
index 0000000..8e3c2c7
--- /dev/null
+++ b/Runtime/Testing/MathPerformanceTest/MatrixMultiplyTest.cpp
@@ -0,0 +1,166 @@
+#include "UnityPrefix.h"
+#include "Runtime/Testing/Testing.h"
+
+#if ENABLE_PERFORMANCE_TESTS
+
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Profiler/TimeHelper.h"
+
+extern void MultiplyMatrices4x4REF (const Matrix4x4f* __restrict lhs, const Matrix4x4f* __restrict rhs, Matrix4x4f* __restrict res);
+extern void MultiplyMatrices4x4SSE (const Matrix4x4f* __restrict lhs, const Matrix4x4f* __restrict rhs, Matrix4x4f* __restrict res);
+
+extern void CopyMatrixREF( const float* __restrict lhs, float* __restrict res);
+extern void CopyMatrixSSE( const float* __restrict lhs, float* __restrict res);
+
+#define ITERATIONS_COUNT 10000
+
+
+/*
+ one of the good results ( launched with Shift+F5 in release mode ):
+ ITERATIONS_COUNT: 10000
+ timeElapsedREF: 1393
+ timeElapsedSSE: 639
+ timeElapsedJOE: 647
+ total cycles REF: 1423390
+ total cycles SSE: 650381
+ total cycles JOE: 660177
+ avg cycles REF: 142
+ avg cycles SSE: 65
+ avg cycles JOE: 66
+
+
+ Matrix Copy: Ref vs SSE
+ ITERATIONS_COUNT=10000
+ time elapsedCPY: 2114
+ time elapsedCPYSSE: 964
+ total cycles CPY: 2157582
+ total cycles CPYSSE: 980763
+ avg cycles CPY: 215
+ avg cycles CPYSSE: 98
+ ctrl:20000.000000
+
+*/
+
+
+void TestMultiplyMatrices()
+{
+ Matrix4x4f m0, m1, m2;
+#define RESET_MATS() for(int i=0;i<16;i++) { m0.m_Data[i] = (float)(i+1); m1.m_Data[15-i] = (float)(i+1); }
+
+
+ RESET_MATS();
+
+ ABSOLUTE_TIME startTimeREF = GetStartTime();
+ UInt64 cycStartREF = __rdtsc();
+ for(UInt32 i=0;i<ITERATIONS_COUNT;i++)
+ {
+ MultiplyMatrices4x4REF(&m0, &m1, &m2);
+ m0.m_Data[0]*=-1.f;
+ m1.m_Data[0]*=-1.f;
+ }
+ UInt64 cycEndREF = __rdtsc();
+ ABSOLUTE_TIME elapsedREF = GetElapsedTime(startTimeREF);
+
+ RESET_MATS();
+
+ startTimeREF = GetStartTime();
+ cycStartREF = __rdtsc();
+ for(UInt32 i=0;i<ITERATIONS_COUNT;i++)
+ {
+ MultiplyMatrices4x4REF(&m0, &m1, &m2);
+ m0.m_Data[0]*=-1.f;
+ m1.m_Data[0]*=-1.f;
+ }
+ cycEndREF = __rdtsc();
+ elapsedREF = GetElapsedTime(startTimeREF);
+
+ RESET_MATS();
+
+ ABSOLUTE_TIME startTimeJOE = GetStartTime();
+ UInt64 cycStartJOE = __rdtsc();
+ for(UInt32 i=0;i<ITERATIONS_COUNT;i++)
+ {
+ MultiplyMatrices4x4(&m0, &m1, &m2);
+ m0.m_Data[0]*=-1.f;
+ m1.m_Data[0]*=-1.f;
+ }
+ UInt64 cycEndJOE = __rdtsc();
+ ABSOLUTE_TIME elapsedJOE = GetElapsedTime(startTimeJOE);
+
+ RESET_MATS();
+
+ startTimeJOE = GetStartTime();
+ cycStartJOE = __rdtsc();
+ for(UInt32 i=0;i<ITERATIONS_COUNT;i++)
+ {
+ MultiplyMatrices4x4(&m0, &m1, &m2);
+ m0.m_Data[0]*=-1.f;
+ m1.m_Data[0]*=-1.f;
+ }
+ cycEndJOE = __rdtsc();
+ elapsedJOE = GetElapsedTime(startTimeJOE);
+
+
+ UInt64 avgCycREF = (cycEndREF - cycStartREF) / ITERATIONS_COUNT;
+ UInt64 avgCycJOE = (cycEndJOE - cycStartJOE) / ITERATIONS_COUNT;
+
+#if UNITY_WIN
+ {
+ char szMsg[1024];
+ sprintf(szMsg, "ITERATIONS_COUNT=%d\r\ntime elapsedREF: %I64d\r\ntime elapsedJOE: %I64d\r\ntotal cycles REF: %I64d\r\ntotal cycles JOE: %I64d\r\navg cycles REF: %I64d\r\navg cycles JOE: %I64d\r\nctrl:%f" ,
+ ITERATIONS_COUNT, elapsedREF, elapsedJOE, cycEndREF - cycStartREF, cycEndJOE - cycStartJOE, avgCycREF, avgCycJOE, m0.m_Data[4] + m1.m_Data[5] + m2.m_Data[7]);
+ OutputDebugString(LPCSTR(szMsg));
+ MessageBox(0, szMsg, "REF vs SSE Multiply", MB_ICONINFORMATION);
+ }
+#else
+ printf_console("REF vs SSE Multiply: ITERATIONS_COUNT=%d\r\ntime elapsedREF: %I64d\r\ntime elapsedJOE: %I64d\r\ntotal cycles REF: %I64d\r\ntotal cycles JOE: %I64d\r\navg cycles REF: %I64d\r\navg cycles JOE: %I64d\r\nctrl:%f" ,
+ ITERATIONS_COUNT, elapsedREF, elapsedJOE, cycEndREF - cycStartREF, cycEndJOE - cycStartJOE, avgCycREF, avgCycJOE, m0.m_Data[4] + m1.m_Data[5] + m2.m_Data[7]);
+#endif
+
+ RESET_MATS();
+
+ ABSOLUTE_TIME startTimeCPY = GetStartTime();
+ UInt64 cycStartCPY = __rdtsc();
+ float f=0.f;
+ for(UInt32 i=0;i<ITERATIONS_COUNT;i++)
+ {
+ CopyMatrixREF(m0.GetPtr(), m1.GetPtr());
+ f+=m1.m_Data[0];
+ }
+ UInt64 cycEndCPY = __rdtsc();
+ ABSOLUTE_TIME elapsedCPY = GetElapsedTime(startTimeCPY);
+
+ RESET_MATS();
+
+ ABSOLUTE_TIME startTimeCPYSSE = GetStartTime();
+ UInt64 cycStartCPYSSE = __rdtsc();
+ for(UInt32 i=0;i<ITERATIONS_COUNT;i++)
+ {
+ CopyMatrixSSE(m0.GetPtr(), m1.GetPtr());
+ f+=m1.m_Data[0];
+ }
+ UInt64 cycEndCPYSSE = __rdtsc();
+ ABSOLUTE_TIME elapsedCPYSSE = GetElapsedTime(startTimeCPYSSE);
+
+ UInt64 avgCycCPY = (cycEndCPY - cycStartCPY) / ITERATIONS_COUNT;
+ UInt64 avgCycCPYSSE = (cycEndCPYSSE - cycStartCPYSSE) / ITERATIONS_COUNT;
+
+#if UNITY_WIN
+ {
+ char szMsg[1024];
+ sprintf(szMsg, "ITERATIONS_COUNT=%d\r\ntime elapsedCPY: %I64d\r\ntime elapsedCPYSSE: %I64d\r\ntotal cycles CPY: %I64d\r\ntotal cycles CPYSSE: %I64d\r\navg cycles CPY: %I64d\r\navg cycles CPYSSE: %I64d\r\nctrl:%f" ,
+ ITERATIONS_COUNT, elapsedCPY, elapsedCPYSSE, cycEndCPY - cycStartCPY, cycEndCPYSSE - cycStartCPYSSE, avgCycCPY, avgCycCPYSSE, f);
+ OutputDebugString(LPCSTR(szMsg));
+ MessageBox(0, szMsg, "REF vs SSE copy", MB_ICONINFORMATION);
+ }
+#else
+ printf_console("REF vs SSE copy: ITERATIONS_COUNT=%d\r\ntime elapsedCPY: %I64d\r\ntime elapsedCPYSSE: %I64d\r\ntotal cycles CPY: %I64d\r\ntotal cycles CPYSSE: %I64d\r\navg cycles CPY: %I64d\r\navg cycles CPYSSE: %I64d\r\nctrl:%f" ,
+ ITERATIONS_COUNT, elapsedCPY, elapsedCPYSSE, cycEndCPY - cycStartCPY, cycEndCPYSSE - cycStartCPYSSE, avgCycCPY, avgCycCPYSSE, f);
+#endif
+
+
+
+
+}
+
+#endif \ No newline at end of file
diff --git a/Runtime/Testing/TestFixtures.h b/Runtime/Testing/TestFixtures.h
new file mode 100644
index 0000000..94b125f
--- /dev/null
+++ b/Runtime/Testing/TestFixtures.h
@@ -0,0 +1,163 @@
+#pragma once
+
+#include <memory>
+#include <algorithm>
+#include <map>
+
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferUtility.h"
+#include "Runtime/Serialize/WriteTypeToBuffer.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+
+
+/// Test fixture that allows accumulating objects that are cleaned up automatically
+/// when the test finishes.
+class TestFixtureBase
+{
+public:
+
+ ~TestFixtureBase()
+ {
+ std::for_each (m_Objects.begin (), m_Objects.end (), DestroySingleObject);
+ }
+
+ template<typename T>
+ T* AddObjectToCleanup (T* object)
+ {
+ if (object != 0)
+ {
+ m_Objects.push_back (object);
+ }
+ return object;
+ }
+
+ template<typename X>
+ X* NewTestObject ()
+ {
+ X* result = NEW_OBJECT_RESET_AND_AWAKE (X);
+ AddObjectToCleanup (result);
+ return result;
+ }
+
+private:
+ std::vector<Object*> m_Objects;
+};
+
+
+/// Fixture that automatically creates an object of type "T".
+template<typename T>
+class ObjectTestFixture : public TestFixtureBase
+{
+public:
+ ObjectTestFixture ()
+ {
+ m_ObjectUnderTest = NewTestObject<T> ();
+ }
+
+protected:
+
+ T* m_ObjectUnderTest;
+};
+
+
+/// Fixture that simplifies serializing and deserializing an object and provides various
+/// helpers to simplify setting up tests for transfers.
+template<class T>
+struct SerializationTestFixture : public TestFixtureBase, public GenerateIDFunctor
+{
+ T m_ObjectUnderTest;
+ TypeTree m_TypeTree;
+ dynamic_array<UInt8> m_Buffer;
+ int m_TransferOptions;
+
+ SerializationTestFixture ()
+ : m_TransferOptions (0)
+ {
+ }
+
+ void GenerateTypeTree ()
+ {
+ ProxyTransfer proxyTransfer (m_TypeTree, m_TransferOptions, &m_ObjectUnderTest, sizeof (T));
+ proxyTransfer.Transfer (m_ObjectUnderTest, "Base");
+ }
+
+ void WriteObjectToBuffer ()
+ {
+ WriteTypeToVector (m_ObjectUnderTest, &m_Buffer, m_TransferOptions);
+ }
+
+ void DoSafeBinaryTransfer ()
+ {
+ #if SUPPORT_SERIALIZED_TYPETREES
+
+ GenerateTypeTree();
+ WriteObjectToBuffer ();
+
+ SafeBinaryRead m_Transfer;
+ CachedReader& reader = m_Transfer.Init (m_TypeTree, 0, m_Buffer.size (), 0);
+ MemoryCacheReader memoryCache (m_Buffer);
+
+ reader.InitRead (memoryCache, 0, m_Buffer.size ());
+ m_Transfer.Transfer (m_ObjectUnderTest, "Base");
+
+ reader.End ();
+
+ #endif
+ }
+
+ void DoTextTransfer ()
+ {
+ #if SUPPORT_TEXT_SERIALIZATION
+ YAMLWrite write (m_TransferOptions);
+ write.Transfer (m_ObjectUnderTest, "Base");
+
+ string text;
+ write.OutputToString(text);
+
+ YAMLRead read (text.c_str (), text.size (), m_TransferOptions);
+ read.Transfer (m_ObjectUnderTest, "Base");
+ #endif
+ }
+
+ /// @name RemapPPtrTransfer Helpers
+ /// @{
+
+ typedef std::map<SInt32, SInt32> PPtrRemapTable;
+ PPtrRemapTable m_PPtrRemapTable;
+
+ void DoRemapPPtrTransfer (bool readPPtrs = true)
+ {
+ RemapPPtrTransfer transfer (m_TransferOptions, readPPtrs);
+ transfer.SetGenerateIDFunctor (this);
+ transfer.Transfer (m_ObjectUnderTest, "Base");
+ }
+
+ void AddPPtrRemap (SInt32 oldInstanceID, SInt32 newInstanceID)
+ {
+ m_PPtrRemapTable[oldInstanceID] = newInstanceID;
+ }
+
+ virtual SInt32 GenerateInstanceID (SInt32 oldInstanceID, TransferMetaFlags metaFlag)
+ {
+ PPtrRemapTable::const_iterator iter = m_PPtrRemapTable.find (oldInstanceID);
+ if (iter == m_PPtrRemapTable.end ())
+ return oldInstanceID;
+
+ return iter->second;
+ }
+
+ /// @}
+};
+
+
+/// Define a "<Name>Test" class with a transfer function.
+/// @note The class is not derived from Object.
+#define DEFINE_TRANSFER_TEST_FIXTURE(NAME) \
+ struct NAME ## Test \
+ { \
+ DECLARE_SERIALIZE (NAME ## Test) \
+ }; \
+ typedef SerializationTestFixture<NAME ## Test> NAME ## TestFixture; \
+ template<typename TransferFunction> \
+ void NAME ## Test::Transfer (TransferFunction& transfer)
diff --git a/Runtime/Testing/Testing.cpp b/Runtime/Testing/Testing.cpp
new file mode 100644
index 0000000..bc129a3
--- /dev/null
+++ b/Runtime/Testing/Testing.cpp
@@ -0,0 +1,344 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+
+// Disclaimer: What we do here isn't real unit testing. It is a reasonable compromise where we accept the
+// codebase mostly as is without major refactors and run reasonably small tests that sorta look like unit
+// tests but are really integration tests. The key compromise here is that we create a global execution
+// environment that all the tests share and run within. This environment dictates what tests are allowed
+// to do and what shared functionality they have access to.
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "External/UnitTest++/src/NunitTestReporter.h"
+#include "External/UnitTest++/src/TestReporterStdout.h"
+#include "External/UnitTest++/src/TestList.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Allocator/LinearAllocator.h"
+#include "Runtime/Utilities/Argv.h"
+#if !UNITY_EXTERNAL_TOOL
+#include "Runtime/Serialize/PathNamePersistentManager.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/BaseClasses/GameManager.h"
+#include "Runtime/Misc/SaveAndLoadHelper.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/Camera/GraphicsSettings.h"
+#include "Runtime/Dynamics/PhysicsManager.h"
+#include "Runtime/Input/GetInput.h"
+#include "Runtime/BaseClasses/Tags.h"
+#endif
+
+#include "Testing.h"
+#include "ConsoleTestReporter.h"
+
+using namespace UnitTest;
+using namespace std;
+
+#endif
+
+#include <iostream>
+#include <fstream>
+#include <algorithm>
+
+
+#if ENABLE_UNIT_TESTS
+static void GetLengthsOfLongestSuiteAndTestNames (int& longestSuiteNameLength, int& longestTestNameLength)
+{
+ longestSuiteNameLength = 0;
+ longestTestNameLength = 0;
+
+ TestList& allTests = Test::GetTestList ();
+ for (Test* test = allTests.GetHead (); test != NULL; test = test->next)
+ {
+ longestSuiteNameLength = std::max<int> ((int) strlen (test->m_details.suiteName), longestSuiteNameLength);
+ longestTestNameLength = std::max<int> ((int) strlen (test->m_details.testName), longestTestNameLength);
+ }
+}
+
+static bool SwallowLogMessages (LogType type, const char* log, va_list args)
+{
+ // Ignore everything.
+ return false;
+}
+#endif
+
+template<typename Predicate>
+static int RunUnitTests (const std::string& resultLog, bool summaryOnly, const Predicate& predicate)
+{
+#if !ENABLE_UNIT_TESTS
+ return 0;
+#else
+ int failures;
+
+ if (!resultLog.empty ())
+ {
+ std::ostringstream stringStream;
+ UnitTest::NunitTestReporter reporter (stringStream);
+ UnitTest::TestRunner runner (reporter);
+
+ failures = runner.RunTestsIf (Test::GetTestList (), NULL, predicate, 0);
+
+ std::ofstream fileStream (resultLog.c_str (), std::ios_base::out | std::ios_base::trunc);
+ fileStream << stringStream.str();
+ }
+ else
+ {
+ int longestSuiteNameLength;
+ int longestTestNameLength;
+ GetLengthsOfLongestSuiteAndTestNames (longestSuiteNameLength, longestTestNameLength);
+
+ ConsoleTestReporter reporter;
+ reporter.SetShowOnlySummary (summaryOnly);
+ reporter.SetTestNameColumnIndex (longestSuiteNameLength + 4);
+ reporter.SetResultColumnIndex (longestSuiteNameLength + 4 + longestTestNameLength + 4);
+
+ UnitTest::TestRunner runner (reporter);
+
+ failures = runner.RunTestsIf (Test::GetTestList (), NULL, predicate, 0);
+ }
+
+ return failures;
+#endif
+}
+
+template<typename Predicate>
+static void PrintUnitTestList (const Predicate& filter)
+{
+#if ENABLE_UNIT_TESTS
+
+ // Group tests by their files.
+
+ int matchingTestCount = 0;
+ vector<const char*> listedFileNames;
+ for (Test* temp = Test::GetTestList ().GetHead(); temp != NULL; temp = temp->next)
+ {
+ if (!filter (temp))
+ continue;
+
+ const char* filename = temp->m_details.filename;
+
+ // Find out whether we've already listed this file.
+ bool alreadyListedThisFile = false;
+ for (int i = 0; i < listedFileNames.size (); ++i)
+ if (strcmp (listedFileNames[i], filename) == 0)
+ {
+ alreadyListedThisFile = true;
+ break;
+ }
+
+ if (alreadyListedThisFile)
+ continue;
+
+ // Print filename.
+ printf_console ("%s:\n", filename);
+ listedFileNames.push_back (filename);
+
+ // List matching tests in file.
+ for (Test* test = Test::GetTestList ().GetHead(); test != NULL; test = test->next)
+ {
+ if (!filter (test))
+ continue;
+
+ if (strcmp (test->m_details.filename, filename) != 0)
+ continue;
+
+ printf_console ("\t[%s] %s\n", test->m_details.suiteName, test->m_details.testName);
+ ++ matchingTestCount;
+ }
+ }
+
+ printf_console ("%i test%s\n", matchingTestCount, matchingTestCount == 1 ? "" : "s");
+
+#endif
+}
+
+#if !UNITY_EXTERNAL_TOOL
+
+#if ENABLE_UNIT_TESTS
+struct TestFilter
+{
+ std::vector<std::string> m_MatchNames;
+ TestFilter(const std::vector<std::string>& matchNames)
+ : m_MatchNames (matchNames)
+ {
+ // Lowercase everything.
+ for (int i = 0; i < m_MatchNames.size(); ++i)
+ m_MatchNames[i] = ToLower (m_MatchNames[i]);
+ }
+
+ bool operator () (const Test* test) const
+ {
+ if (m_MatchNames.empty ())
+ return true;
+
+ for (int i = 0; i < m_MatchNames.size(); ++i)
+ {
+ if (ToLower (std::string (test->m_details.suiteName)).find (m_MatchNames[i]) != std::string::npos ||
+ ToLower (std::string (test->m_details.testName)).find (m_MatchNames[i]) != std::string::npos)
+ return true;
+ }
+
+ return false;
+ }
+};
+
+inline void CreateAndInstallGameManager (ManagerContext::Managers id)
+{
+ GameManager* instance = CreateGameManager (GetManagerContext ().m_ManagerClassIDs[id]);
+ SetManagerPtrInContext (id, instance);
+}
+
+static void InitializeScriptMapper ()
+{
+ // Create manager.
+ CreateAndInstallGameManager (ManagerContext::kScriptMapper);
+
+ #if UNITY_EDITOR
+ // Populate with builtin shaders. Only available in editor.
+ GetBuiltinExtraResourceManager ().RegisterShadersWithRegistry (GetScriptMapperPtr ());
+ #endif
+}
+
+static void InitializeGraphicsSettings ()
+{
+ CreateAndInstallGameManager (ManagerContext::kGraphicsSettings);
+ GetGraphicsSettings ().SetDefaultAlwaysIncludedShaders ();
+}
+
+static void InitializePhysicsManager ()
+{
+ #if ENABLE_PHYSICS
+ CreateAndInstallGameManager (ManagerContext::kPhysicsManager);
+ #endif
+}
+
+static void InitializeTagManager ()
+{
+ CreateAndInstallGameManager (ManagerContext::kTagManager);
+ RegisterDefaultTagsAndLayerMasks ();
+}
+
+static void InitializeBuildSettings ()
+{
+ CreateAndInstallGameManager (ManagerContext::kBuildSettings);
+}
+#endif
+
+void RunUnitTestsIfRequiredAndExit ()
+{
+#if !ENABLE_UNIT_TESTS
+ return;
+#else
+
+ const bool listUnitTests = HasARGV ("listUnitTests");
+ const bool runUnitTests = HasARGV ("runUnitTests");
+
+ // Check if we are supposed to run or list unit tests.
+ if (!listUnitTests && !runUnitTests)
+ return;
+
+ // Yes, so initialize the systems we need.
+
+ // We log to stdout so get that in place on Windows.
+#if UNITY_WIN
+ OpenConsole ();
+#endif
+
+ // For unit testing, we're not interested in log output from engine initialization.
+ // Temporarily disable logging.
+ SetLogEntryHandler (SwallowLogMessages);
+
+ // We want the test runner to behave like batchmode and not pop up
+ // any dialogs, so force running in batchmode.
+ SetIsBatchmode (true);
+
+ // Start with persistent manager. Required by InitializeEngineNoGraphics().
+ UNITY_NEW_AS_ROOT (PathNamePersistentManager (0), kMemManager, "PersistentManager", "");
+
+ if (!InitializeEngineNoGraphics ())
+ {
+ fprintf (stderr, "Failed to initialize engine (no graphics)!\n");
+ exit (-1);
+ }
+
+ if (!InitializeEngineGraphics ())
+ {
+ fprintf (stderr, "Failed to initialize engine!\n");
+ exit (-1);
+ }
+
+ // Initialize the global game managers we want to have available.
+ InitializeScriptMapper ();
+ InitializeGraphicsSettings ();
+ InitializePhysicsManager ();
+ InitializeTagManager ();
+ InitializeBuildSettings ();
+
+ ////TODO: this path is broken; the EXPECT stuff doesn't work for it
+ std::string log;
+ #if 0
+ if (HasARGV ("unitTestsLog"))
+ log = GetFirstValueForARGV ("unitTestsLog");
+ #endif
+
+ const bool showOnlySummary = HasARGV ("unitTestsSummaryOnly");
+
+ std::vector<std::string> matchNames;
+ if (runUnitTests)
+ matchNames = GetValuesForARGV ("runUnitTests");
+ else if (listUnitTests)
+ matchNames = GetValuesForARGV ("listUnitTests");
+
+ TestFilter filter (matchNames);
+
+ // Run or list tests.
+ int numFailures = 0;
+ if (runUnitTests)
+ {
+ numFailures = RunUnitTests (log, showOnlySummary, filter);
+ }
+ else if (listUnitTests)
+ {
+ SetLogEntryHandler (NULL);
+ PrintUnitTestList (filter);
+ }
+
+ // Shut down.
+ CleanupEngine ();
+ InputShutdown ();
+
+ // Done.
+ exit (numFailures != 0 ? 1 : 0);
+
+#endif // ENABLE_UNIT_TESTS
+}
+
+#endif // UNITY_EXTERNAL_TOOL
+
+int RunUnitTests (const std::string& resultLog)
+{
+#if !ENABLE_UNIT_TESTS
+ return 0;
+#else
+ return RunUnitTests (resultLog, false, UnitTest::True ());
+#endif
+}
+
+void ExpectLogMessageTriggeredByTest (LogType type, const char* logFragment)
+{
+#if ENABLE_UNIT_TESTS
+ ConsoleTestReporter::GetInstance ()->ExpectLogMessage (type, logFragment);
+#endif
+}
+
+#if ENABLE_PERFORMANCE_TESTS
+
+ void TestMultiplyMatrices();
+ void RUN_PERFORMANCE_TESTS ()
+ {
+ TestMultiplyMatrices();
+ }
+
+#endif
diff --git a/Runtime/Testing/Testing.h b/Runtime/Testing/Testing.h
new file mode 100644
index 0000000..7ae823a
--- /dev/null
+++ b/Runtime/Testing/Testing.h
@@ -0,0 +1,56 @@
+#ifndef TESTING_H
+#define TESTING_H
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+/// Use this to deal with asserts and other things that get logged.
+/// If your test explicitly *expects* a certain piece of code to detect
+/// and log an error, set up an expected message.
+///
+/// The given log fragment can simply be a substring of the actual log
+/// message.
+///
+/// @example
+/// EXPECT (Error, "Cannot find");
+/// MethodThatTriggersError ();
+/// @endexample
+///
+/// @param type Log type (i.e. enum value from LogType without the "LogType_" prefix").
+/// @param logFragment Substring of log message that is expected.
+///
+/// @note Debug messages are ignored entirely and cannot be expected.
+/// @note Any log messages that are not expected will lead to a failure of the
+/// running unit test.
+/// @note The order of log messages is not checked but a single EXPECT will only
+/// cause the acceptance of a single occurrence of the message.
+/// @note Expecting messages that do not arrive will cause tests to fail.
+#define EXPECT(type, logFragment) \
+ ExpectLogMessageTriggeredByTest (LogType_##type, logFragment)
+
+/// Expect a log message to be triggered by the currently
+/// executing unit test.
+///
+/// @see EXPECT
+void ExpectLogMessageTriggeredByTest (LogType type, const char* logFragment);
+
+#endif // ENABLE_UNIT_TESTS
+
+
+/// If the "-runUnitTests" command-line argument is present, run unit tests
+/// and exit the process with a status code indicating success or failure.
+void RunUnitTestsIfRequiredAndExit ();
+
+int RunUnitTests (const std::string& resultLog);
+
+
+////TODO: needs to be cleaned up
+#define ENABLE_PERFORMANCE_TESTS 0
+#if ENABLE_PERFORMANCE_TESTS
+ void RUN_PERFORMANCE_TESTS ();
+#else
+ #define RUN_PERFORMANCE_TESTS()
+#endif
+
+#endif