diff --git a/Sources/Plasma/FeatureLib/pfPython/cyMisc.h b/Sources/Plasma/FeatureLib/pfPython/cyMisc.h
index 12180ec3..4e7e2995 100644
--- a/Sources/Plasma/FeatureLib/pfPython/cyMisc.h
+++ b/Sources/Plasma/FeatureLib/pfPython/cyMisc.h
@@ -67,6 +67,7 @@ class plDisplayMode;
class plUUID;
class plFileName;
struct PipelineParams;
+class plString;
typedef struct _object PyObject;
typedef struct PyMethodDef PyMethodDef;
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
new file mode 100644
index 00000000..9a33c344
--- /dev/null
+++ b/Tests/CMakeLists.txt
@@ -0,0 +1,118 @@
+cmake_minimum_required(VERSION 2.8)
+
+set(gtest_force_shared_crt ON CACHE INTERNAL "don't link the CRT statically into gtest as we later link the test executable dynamically")
+add_subdirectory(gtest-1.6.0)
+
+include_directories(${gtest_SOURCE_DIR}/include)
+include_directories(../Sources/Plasma/CoreLib)
+include_directories(../Sources/Plasma/FeatureLib)
+include_directories(../Sources/Plasma/FeatureLib/inc)
+include_directories(../Sources/Plasma/NucleusLib)
+include_directories(../Sources/Plasma/NucleusLib/inc)
+include_directories(../Sources/Plasma/PubUtilLib)
+include_directories(../Sources/Plasma/PubUtilLib/inc)
+include_directories(${PYTHON_INCLUDE_DIR})
+
+set(test_SOURCES
+ testCyMisc.cpp
+ testPlUnifiedTime.cpp
+)
+set(test_plClient_SOURCES
+ ../Sources/Plasma/Apps/plClient/pfAllCreatables.cpp
+ ../Sources/Plasma/Apps/plClient/plAllCreatables.cpp
+ ../Sources/Plasma/Apps/plClient/pnAllCreatables.cpp
+)
+
+add_executable(runUnitTests ${test_SOURCES} ${test_plClient_SOURCES})
+target_link_libraries(runUnitTests gtest gtest_main)
+
+target_link_libraries(runUnitTests CoreLib)
+target_link_libraries(runUnitTests pfAnimation)
+target_link_libraries(runUnitTests pfAudio)
+target_link_libraries(runUnitTests pfCamera)
+target_link_libraries(runUnitTests pfCharacter)
+target_link_libraries(runUnitTests pfConditional)
+target_link_libraries(runUnitTests pfConsole)
+target_link_libraries(runUnitTests pfConsoleCore)
+target_link_libraries(runUnitTests pfGameGUIMgr)
+target_link_libraries(runUnitTests pfGameMgr)
+target_link_libraries(runUnitTests pfGameScoreMgr)
+target_link_libraries(runUnitTests pfJournalBook)
+target_link_libraries(runUnitTests pfLocalizationMgr)
+target_link_libraries(runUnitTests pfMessage)
+target_link_libraries(runUnitTests pfPython)
+target_link_libraries(runUnitTests pfSurface)
+target_link_libraries(runUnitTests plAgeDescription)
+target_link_libraries(runUnitTests plAgeLoader)
+target_link_libraries(runUnitTests plAudible)
+target_link_libraries(runUnitTests plAudio)
+target_link_libraries(runUnitTests plAudioCore)
+target_link_libraries(runUnitTests plAvatar)
+target_link_libraries(runUnitTests plClientResMgr)
+target_link_libraries(runUnitTests plClipboard)
+target_link_libraries(runUnitTests plCompression)
+target_link_libraries(runUnitTests plContainer)
+target_link_libraries(runUnitTests plDrawable)
+target_link_libraries(runUnitTests plGImage)
+target_link_libraries(runUnitTests plGLight)
+target_link_libraries(runUnitTests plInputCore)
+target_link_libraries(runUnitTests plInterp)
+target_link_libraries(runUnitTests plIntersect)
+target_link_libraries(runUnitTests plMath)
+target_link_libraries(runUnitTests plMessage)
+target_link_libraries(runUnitTests plModifier)
+target_link_libraries(runUnitTests plNetClient)
+target_link_libraries(runUnitTests plNetClientComm)
+target_link_libraries(runUnitTests plNetClientRecorder)
+target_link_libraries(runUnitTests plNetCommon)
+target_link_libraries(runUnitTests plNetGameLib)
+target_link_libraries(runUnitTests plNetMessage)
+target_link_libraries(runUnitTests plNetTransport)
+target_link_libraries(runUnitTests plParticleSystem)
+target_link_libraries(runUnitTests plPhysical)
+target_link_libraries(runUnitTests plPhysX)
+target_link_libraries(runUnitTests plPipeline)
+target_link_libraries(runUnitTests plProgressMgr)
+target_link_libraries(runUnitTests plResMgr)
+target_link_libraries(runUnitTests plScene)
+target_link_libraries(runUnitTests plSDL)
+target_link_libraries(runUnitTests plSurface)
+target_link_libraries(runUnitTests plTransform)
+target_link_libraries(runUnitTests plUnifiedTime)
+target_link_libraries(runUnitTests plVault)
+target_link_libraries(runUnitTests pnAsyncCore)
+target_link_libraries(runUnitTests pnAsyncCoreExe)
+target_link_libraries(runUnitTests pnEncryption)
+target_link_libraries(runUnitTests pnInputCore)
+target_link_libraries(runUnitTests pnModifier)
+target_link_libraries(runUnitTests pnNetBase)
+target_link_libraries(runUnitTests pnNetCli)
+target_link_libraries(runUnitTests pnNetProtocol)
+target_link_libraries(runUnitTests pnSceneObject)
+
+if(PYTHON_DEBUG_LIBRARY)
+ target_link_libraries(runUnitTests debug ${PYTHON_DEBUG_LIBRARY})
+ target_link_libraries(runUnitTests optimized ${PYTHON_LIBRARY})
+else()
+ target_link_libraries(runUnitTests ${PYTHON_LIBRARY})
+endif()
+target_link_libraries(runUnitTests ${OPENAL_LIBRARY})
+target_link_libraries(runUnitTests ${EXPAT_LIBRARY})
+target_link_libraries(runUnitTests ${JPEG_LIBRARY})
+target_link_libraries(runUnitTests ${PNG_LIBRARY})
+target_link_libraries(runUnitTests ${Speex_LIBRARY})
+target_link_libraries(runUnitTests ${PHYSX_LIBRARIES})
+target_link_libraries(runUnitTests ${DirectX_LIBRARIES})
+if (WIN32)
+ target_link_libraries(runUnitTests Vfw32)
+ target_link_libraries(runUnitTests winmm)
+endif(WIN32)
+
+source_group("Test Sources" FILES ${test_SOURCES})
+source_group("plClient Sources" FILES ${test_plClient_SOURCES})
+
+enable_testing()
+add_test(
+ NAME runUnitTests
+ COMMAND runUnitTests
+)
diff --git a/Tests/testCyMisc.cpp b/Tests/testCyMisc.cpp
new file mode 100644
index 00000000..51899b49
--- /dev/null
+++ b/Tests/testCyMisc.cpp
@@ -0,0 +1,115 @@
+/*==LICENSE==*
+
+CyanWorlds.com Engine - MMOG client, server and tools
+Copyright (C) 2011 Cyan Worlds, Inc.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+You can contact Cyan Worlds, Inc. by email legal@cyan.com
+ or by snail mail at:
+ Cyan Worlds, Inc.
+ 14617 N Newport Hwy
+ Mead, WA 99021
+
+*==LICENSE==*/
+
+#include
+
+#include "gtest/gtest.h"
+
+#include "pfPython/cyMisc.h"
+
+// from zoneinfo/America/Denver version 2011g
+static const time_t dsttransitions[] = {
+ 0x45F3C510, 0x472D7C00,
+ 0x47D3A710, 0x490D5E00,
+ 0x49B38910, 0x4AED4000,
+ 0x4B9CA590, 0x4CD65C80,
+ 0x4D7C8790, 0x4EB63E80,
+ 0x4F5C6990, 0x50962080,
+ 0x513C4B90, 0x52760280,
+ 0x531C2D90, 0x5455E480,
+ 0x54FC0F90, 0x5635C680,
+ 0x56E52C10, 0x581EE300,
+ 0x58C50E10, 0x59FEC500,
+ 0x5AA4F010, 0x5BDEA700,
+ 0x5C84D210, 0x5DBE8900,
+ 0x5E64B410, 0x5F9E6B00,
+ 0x604DD090, 0x61878780,
+ 0x622DB290, 0x63676980,
+ 0x640D9490, 0x65474B80,
+ 0x65ED7690, 0x67272D80,
+ 0x67CD5890, 0x69070F80,
+ 0x69AD3A90, 0x6AE6F180,
+ 0x6B965710, 0x6CD00E00,
+ 0x6D763910, 0x6EAFF000,
+ 0x6F561B10, 0x708FD200,
+ 0x7135FD10, 0x726FB400,
+ 0x7315DF10, 0x744F9600,
+ 0x74FEFB90, 0x7638B280,
+ 0x76DEDD90, 0x78189480,
+ 0x78BEBF90, 0x79F87680,
+ 0x7A9EA190, 0x7BD85880,
+ 0x7C7E8390, 0x7DB83A80,
+ 0x7E5E6590, 0x7F981C80
+};
+
+static ::testing::AssertionResult TmHasHourMinSec(struct tm *tm, int hour, int min, int sec) {
+ if (tm->tm_hour == hour && tm->tm_min == min && tm->tm_sec == sec) {
+ return ::testing::AssertionSuccess();
+ }
+ else {
+ char buf[128];
+ strftime(buf, sizeof(buf), "%a %Y-%m-%d %H:%M:%S", tm);
+ return ::testing::AssertionFailure() << "Actual: " << buf << ", Expected: " << hour << ":" << (::testing::Message() << std::setfill('0') << std::setw(2) << min << ":" << std::setw(2) << sec);
+ // (intermediate Message required because AssertionResult::operator<< will create a new Message for every invocation, losing the manipulators)
+ }
+}
+
+TEST(cyMisc, ConvertGMTtoDni)
+{
+ for (int i = 0; i < sizeof(dsttransitions)/sizeof(dsttransitions[0]); i += 2) {
+ time_t dni1, dni2;
+ struct tm *tm;
+ dni1 = cyMisc::ConvertGMTtoDni(dsttransitions[i] - 1);
+ dni2 = cyMisc::ConvertGMTtoDni(dsttransitions[i]);
+ tm = gmtime(&dni1);
+ EXPECT_TRUE(TmHasHourMinSec(tm, 1, 59, 59)) << "(start before)";
+ tm = gmtime(&dni2);
+ EXPECT_TRUE(TmHasHourMinSec(tm, 3, 0, 0)) << "(start after)";
+
+ dni1 = cyMisc::ConvertGMTtoDni(dsttransitions[i+1] - 1);
+ dni2 = cyMisc::ConvertGMTtoDni(dsttransitions[i+1]);
+ tm = gmtime(&dni1);
+ EXPECT_TRUE(TmHasHourMinSec(tm, 1, 59, 59)) << "(end before)";
+ tm = gmtime(&dni2);
+ EXPECT_TRUE(TmHasHourMinSec(tm, 1, 0, 0)) << "(end after)";
+ }
+}
diff --git a/Tests/testPlUnifiedTime.cpp b/Tests/testPlUnifiedTime.cpp
new file mode 100644
index 00000000..c7d610e9
--- /dev/null
+++ b/Tests/testPlUnifiedTime.cpp
@@ -0,0 +1,71 @@
+/*==LICENSE==*
+
+CyanWorlds.com Engine - MMOG client, server and tools
+Copyright (C) 2011 Cyan Worlds, Inc.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+You can contact Cyan Worlds, Inc. by email legal@cyan.com
+ or by snail mail at:
+ Cyan Worlds, Inc.
+ 14617 N Newport Hwy
+ Mead, WA 99021
+
+*==LICENSE==*/
+
+#include "gtest/gtest.h"
+
+#include "plUnifiedTime/plUnifiedTime.h"
+
+TEST(plUnifiedTime, SetGMTime)
+{
+ // test with two dates, one at the beginning and one in the middle of the year, so that one of them falls into local DST (if present)
+ plUnifiedTime t1;
+ t1.SetGMTime(1984, 1, 24, 18, 27, 43, 123456);
+ EXPECT_EQ(plUnifiedTime::kGmt, t1.GetMode());
+ EXPECT_EQ(1984, t1.GetYear());
+ EXPECT_EQ(1, t1.GetMonth());
+ EXPECT_EQ(24, t1.GetDay());
+ EXPECT_EQ(18, t1.GetHour());
+ EXPECT_EQ(27, t1.GetMinute());
+ EXPECT_EQ(43, t1.GetSecond());
+ EXPECT_EQ(123456, t1.GetMicros());
+
+ plUnifiedTime t2;
+ t2.SetGMTime(2003, 7, 10, 5, 46, 14, 654321);
+ EXPECT_EQ(plUnifiedTime::kGmt, t2.GetMode());
+ EXPECT_EQ(2003, t2.GetYear());
+ EXPECT_EQ(7, t2.GetMonth());
+ EXPECT_EQ(10, t2.GetDay());
+ EXPECT_EQ(5, t2.GetHour());
+ EXPECT_EQ(46, t2.GetMinute());
+ EXPECT_EQ(14, t2.GetSecond());
+ EXPECT_EQ(654321, t2.GetMicros());
+}