diff options
Diffstat (limited to 'Runtime/Testing')
-rw-r--r-- | Runtime/Testing/ConsoleTestReporter.cpp | 216 | ||||
-rw-r--r-- | Runtime/Testing/ConsoleTestReporter.h | 63 | ||||
-rw-r--r-- | Runtime/Testing/HighLevelTest.cpp | 537 | ||||
-rw-r--r-- | Runtime/Testing/HighLevelTest.h | 6 | ||||
-rw-r--r-- | Runtime/Testing/JobSchedulerTest/Test.sln | 21 | ||||
-rw-r--r-- | Runtime/Testing/JobSchedulerTest/Test.vcproj | 162 | ||||
-rw-r--r-- | Runtime/Testing/JobSchedulerTest/main.cpp | 82 | ||||
-rw-r--r-- | Runtime/Testing/MathPerformanceTest/MatrixMultiplyTest.cpp | 166 | ||||
-rw-r--r-- | Runtime/Testing/TestFixtures.h | 163 | ||||
-rw-r--r-- | Runtime/Testing/Testing.cpp | 344 | ||||
-rw-r--r-- | Runtime/Testing/Testing.h | 56 |
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(¶mMainThread, 0, sizeof(paramMainThread)); + paramMainThread.mode = kCreateObjectDefault; + RunObjectResetTests (¶mMainThread); + + ClearAllManagers(); + + Parameters paramResetThread; + memset(¶mResetThread, 0, sizeof(paramResetThread)); + paramResetThread.mode = kCreateObjectFromNonMainThread; + Thread resetThread; + resetThread.Run(RunObjectResetTests, ¶mResetThread); + 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(¶mMainThread, 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 |