diff --git a/spine-cpp/spine-cpp-unit-tests/CMakeLists.txt b/spine-cpp/spine-cpp-unit-tests/CMakeLists.txt new file mode 100755 index 000000000..e08a7d3ba --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 2.8.9) +project(spine_unit_test) + +set(CMAKE_INSTALL_PREFIX "./") +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS -DKANJI_MEMTRACE -DUSE_CPP11_MUTEX -std=c++11") + +######################################################### +# set includes +######################################################### +include_directories(../spine-cpp/include teamcity minicppunit tests memory) + +######################################################### +# Add Sources +######################################################### +set(MINICPP_SRC + minicppunit/MiniCppUnit.cxx +) + +set(TEAMCITY_SRC + teamcity/teamcity_cppunit.cpp + teamcity/teamcity_messages.cpp +) + +set(TEST_SRC + tests/SpineEventMonitor.cpp + tests/EmptyTestFixture.cpp + tests/C_InterfaceTestFixture.cpp + tests/CPP_InterfaceTestFixture.cpp + tests/MemoryTestFixture.cpp +) + +set(MEMLEAK_SRC + memory/KMemory.cpp + memory/KString.cpp +) + +######################################################### +# setup main project +######################################################### +add_executable(spine_unit_test main.cpp ${MINICPP_SRC} ${TEAMCITY_SRC} ${TEST_SRC} ${MEMLEAK_SRC}) +target_link_libraries(spine_unit_test spine-cpp) + + +######################################################### +# copy resources to build output directory +######################################################### +add_custom_command(TARGET spine_unit_test PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_LIST_DIR}/../../examples/spineboy/export $/testdata/spineboy) + +add_custom_command(TARGET spine_unit_test PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_LIST_DIR}/../../examples/raptor/export $/testdata/raptor) + +add_custom_command(TARGET spine_unit_test PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_LIST_DIR}/../../examples/goblins/export $/testdata/goblins) diff --git a/spine-cpp/spine-cpp-unit-tests/README.md b/spine-cpp/spine-cpp-unit-tests/README.md new file mode 100755 index 000000000..bfe38c489 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/README.md @@ -0,0 +1,67 @@ +# spine-cpp-unit-tests + +The spine-cpp-unit-tests project is to test the [Spine](http://esotericsoftware.com) skeletal animation system. It does not perform rendering. It is primarily used for regression testing and leak detection. It is designed to be run from a Continuous Integration server and to passively verify changes automatically on check-in. + +## Mini CPP Unit Testing +[MiniCppUnit](https://sourceforge.net/p/minicppunit/wiki/Home/) is a minimal unit testing framework similar to JUnit. It is used here to avoid large dependancies. + +Tests are sorted into Suites, Fixtures and Cases. There is one suite, it contains many fixtures and each fixture contains test cases. To turn off a fixture, edit "TestOptions.h". To turn off specific test cases, comment out the TEST_CASE() line in the fixture's header. + +## Memory Leak Detection +This project includes a very minimal memory leak detector. It is based roughly on the leak detector in the [Popcap Framework](https://sourceforge.net/projects/popcapframework/?source=directory), but has been modified over the years. + +## Continuous Integration +The test runner includes the ability to format output messages to signal a CI server. An example interface for [Teamcity](https://www.jetbrains.com/teamcity/) is included. To implement for another server, determine the wireformat for the messages and duplicate/edit the teamcity_messages class. [Teamcity Wire Format](https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity) + +### Trigger +Your CI server should trigger on VCS check-in. + +### CMake Build Step +The first build step for the CI server should be to run CMake on the 'spine-cpp-unit-tests' folder. Follow the usage directions below. + +### Compile Build Step +This build step should not execute if the previous step did not successfully complete. +Depending on the test agent build environment, you should build the output solution or project from the cmake step. Debug is fine. + +### Test Runner Build Step +This build step should not execute if the previous step did not successfully complete. +Again, depending on the test agent build environment, you should have produced an executable. Run this executable. + + +## Usage +Make sure [CMake](https://cmake.org/download/) is installed. + +Create a 'build' directory in the 'spine-cpp-unit-tests' folder. Then switch to that folder and execute cmake: + +mkdir build +cd build +cmake .. + +### Win32 build +msbuild spine_unit_test.sln /t:spine_unit_test /p:Configuration="Debug" /p:Platform="Win32" + + +## Licensing +This Spine Runtime may only be used for personal or internal use, typically to evaluate Spine before purchasing. If you would like to incorporate a Spine Runtime into your applications, distribute software containing a Spine Runtime, or modify a Spine Runtime, then you will need a valid [Spine license](https://esotericsoftware.com/spine-purchase). Please see the [Spine Runtimes Software License](https://github.com/EsotericSoftware/spine-runtimes/blob/master/LICENSE) for detailed information. + +The Spine Runtimes are developed with the intent to be used with data exported from Spine. By purchasing Spine, `Section 2` of the [Spine Software License](https://esotericsoftware.com/files/license.txt) grants the right to create and distribute derivative works of the Spine Runtimes. + +original "walk"": 330 +second "walk": 0d0 + +queue interrupt for original walk +queue start for second walk +drain interrupt and start + +0d0 is interrupted +0d0 is ended + +"run": 0c0 + 0d0 is interrupted + second walk becomes mixingFrom of run + 0c0 is started + + queue is drained + + first walk: 6f0 + second walk: 9c0 diff --git a/spine-cpp/spine-cpp-unit-tests/main.cpp b/spine-cpp/spine-cpp-unit-tests/main.cpp index f4343cab2..3cfa5fcd4 100755 --- a/spine-cpp/spine-cpp-unit-tests/main.cpp +++ b/spine-cpp/spine-cpp-unit-tests/main.cpp @@ -1,7 +1,110 @@ -#include +// SexyKanjiTestSuite.cpp : Defines the entry point for the console application. +// + +#include "MiniCppUnit.hxx" + +#ifdef WIN32 +#include +#else +#include +#endif // WIN32 + +#include +#include "KString.h" +#include + +#include "spine/Extension.h" + +#include "KMemory.h" // last include -int main(int argc, const char * argv[]) +using namespace Spine; + +class KanjiSpineExtension : public DefaultSpineExtension { - std::cout << "Awww yeah, gonna test some stuff here eventually!\n"; - return 0; +public: + static KanjiSpineExtension* getInstance(); + + virtual ~KanjiSpineExtension(); + + virtual void* spineAlloc(size_t size, const char* file, int line); + + virtual void* spineCalloc(size_t num, size_t size, const char* file, int line); + + virtual void* spineRealloc(void* ptr, size_t size, const char* file, int line); + + virtual void spineFree(void* mem); + +protected: + KanjiSpineExtension(); +}; + +KanjiSpineExtension* KanjiSpineExtension::getInstance() +{ + static KanjiSpineExtension ret; + return &ret; } + +KanjiSpineExtension::~KanjiSpineExtension() +{ + // Empty +} + +void* KanjiSpineExtension::spineAlloc(size_t size, const char* file, int line) +{ + return _kanjimalloc(size); +} + +void* KanjiSpineExtension::spineCalloc(size_t num, size_t size, const char* file, int line) +{ + void* ptr = _kanjimalloc(num * size, file, line); + if (ptr) + { + memset(ptr, 0, num * size); + } + + return ptr; +} + +void* KanjiSpineExtension::spineRealloc(void* ptr, size_t size, const char* file, int line) +{ + return _kanjirealloc(ptr, size); +} + +void KanjiSpineExtension::spineFree(void* mem) +{ + _kanjifree(mem); +} + +KanjiSpineExtension::KanjiSpineExtension() : DefaultSpineExtension() +{ + // Empty +} + +int main(int argc, char* argv[]) +{ + SpineExtension::setInstance(KanjiSpineExtension::getInstance()); + + // Start Timing + time_t start_time, end_time; + time(&start_time); + + /* Set working directory to current location for opening test data */ +#ifdef WIN32 + _chdir( GetFileDir(argv[0], false).c_str() ); +#else + chdir(GetFileDir(argv[0], false).c_str()); +#endif + + // Run Test Suite + if(JetBrains::underTeamcity()) gTeamCityListener.startSuite("Spine-CPP Test Suite"); + int ret_val = TestFixtureFactory::theInstance().runTests() ? 0 : -1; + if(JetBrains::underTeamcity()) gTeamCityListener.endSuite("Spine-CPP Test Suite"); + + // End Timing + time(&end_time); + double secs = difftime(end_time,start_time); + printf("\n\n%i minutes and %i seconds of your life taken from you by these tests.\n", ((int)secs) / 60, ((int)secs) % 60); + + return ret_val; +} + diff --git a/spine-cpp/spine-cpp-unit-tests/memory/KMemory.cpp b/spine-cpp/spine-cpp-unit-tests/memory/KMemory.cpp new file mode 100755 index 000000000..a21cbafba --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/memory/KMemory.cpp @@ -0,0 +1,303 @@ +#include +#include +#include +#include +#include + +#include "KString.h" + +#include "KMemory.h" // last include + +/////////////////////////////////////////////////////////////////////////////// +// +// KANJI_DUMP_LEAKED_MEM will print out the memory block that was leaked. +// This is helpful when leaked objects have string identifiers. +// +/////////////////////////////////////////////////////////////////////////////// +//#define KANJI_DUMP_LEAKED_MEM + + +/////////////////////////////////////////////////////////////////////////////// +// +// KANJI_TRACK_MEM_USAGE will print out all memory allocations. +// +/////////////////////////////////////////////////////////////////////////////// +//#define KANJI_TRACK_MEM_USAGE + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Our memory system is thread-safe, but instead of linking massive libraries, +// we attempt to use C++11 std::mutex. +#ifdef USE_CPP11_MUTEX_DISABLED +#include +typedef std::recursive_mutex KSysLock; // rentrant +struct KAutoLock { + KAutoLock(KSysLock& lock) :mLock(lock) { mLock.lock(); } // acquire + ~KAutoLock() { mLock.unlock(); } // release + + KSysLock& mLock; +}; +#else // Fallback to unsafe. don't spawn threads +typedef int KSysLock; +struct KAutoLock { + KAutoLock(KSysLock) {} // acquire + ~KAutoLock() {} // release +}; +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +struct KANJI_ALLOC_INFO +{ + size_t size; + std::string file; + int line; +}; +static bool showLeaks = false; +class KAllocMap : public std::map +{ +public: + KSysLock crit; + static bool allocMapValid; + +public: + KAllocMap() { allocMapValid = true; } + ~KAllocMap() + { + if (showLeaks) + KMemoryDumpUnfreed(); + + allocMapValid = false; + } +}; +bool KAllocMap::allocMapValid = false; +static KAllocMap allocMap; // once this static object destructs, it dumps unfreed memory + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef KANJI_TRACK_MEM_USAGE +void KMemoryDumpUsage(); // forward declaration +class KAllocStat +{ +public: + typedef std::map allocCount; // [size] = count + typedef std::map, allocCount> allocInfo; // [file, line] = allocCount + + allocInfo memInfo; + static bool allocMapValid; + +public: + + KAllocStat() + { + allocMapValid = true; + } + ~KAllocStat() + { + if (showLeaks) + KMemoryDumpUsage(); + + allocMapValid = false; + } + void addTrack(const char* fname, int lnum, int asize) + { + allocCount& info = memInfo[std::pair(fname, lnum)]; + info[asize]++; + } +}; +bool KAllocStat::allocMapValid = false; +static KAllocStat allocStat; +#endif // KANJI_TRACK_MEM_USAGE + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +extern void KMemoryAddTrack( void* addr, size_t asize, const char* fname, int lnum ) +{ + if (!KAllocMap::allocMapValid || asize == 0) + return; + + KAutoLock aCrit(allocMap.crit); + showLeaks = true; + + KANJI_ALLOC_INFO& info = allocMap[addr]; + info.file = fname; + info.line = lnum; + info.size = asize; + +#ifdef KANJI_TRACK_MEM_USAGE + if (KAllocStat::allocMapValid) + allocStat.addTrack(fname, lnum, asize); +#endif +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void KMemoryRemoveTrack(void* addr) +{ + if (!KAllocMap::allocMapValid) + return; + + KAutoLock aCrit(allocMap.crit); + KAllocMap::iterator anItr = allocMap.find(addr); + if (anItr != allocMap.end()) + allocMap.erase(anItr); +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void KMemoryDumpUnfreed() +{ + if (!KAllocMap::allocMapValid) + return; + + KAutoLock aCrit(allocMap.crit); // prevent modification of the map while iterating + + size_t totalSize = 0; + char buf[8192]; + + FILE* f = fopen("mem_leaks.txt", "wt"); + if (!f) + return; + + time_t aTime = time(NULL); + sprintf(buf, "Memory Leak Report for %s\n", asctime(localtime(&aTime))); + fprintf(f, "%s", buf); + KOutputDebug(DEBUGLVL, "\n"); + KOutputDebug(INFOLVL, buf); + for(KAllocMap::iterator i = allocMap.begin(); i != allocMap.end(); ++i) + { + sprintf(buf, "%s(%d) : Leak %u byte%s @0x%08X\n", i->second.file.c_str(), i->second.line, i->second.size, i->second.size > 1 ? "s" : "", (size_t)i->first); + KOutputDebug(ERRORLVL, buf); + fprintf(f, "%s", buf); + +#ifdef KANJI_DUMP_LEAKED_MEM + unsigned char* data = (unsigned char*)i->first; + int count = 0; + char hex_dump[1024]; + char ascii_dump[1024]; + + for (int index = 0; index < i->second.size; index++) + { + unsigned char _c = *data; + + if (count == 0) + sprintf(hex_dump, "\t%02X ", _c); + else + sprintf(hex_dump, "%s%02X ", hex_dump, _c); // technically, this is undefined behavior + + if ((_c < 32) || (_c > 126)) + _c = '.'; + + if (count == 7) + sprintf(ascii_dump, "%s%c ", ascii_dump, _c); + else + sprintf(ascii_dump, "%s%c", count == 0 ? "\t" : ascii_dump, _c); // technically, this is undefined behavior + + + if (++count == 16) + { + count = 0; + sprintf(buf, "%s\t%s\n", hex_dump, ascii_dump); + fprintf(f, buf); + + memset((void*)hex_dump, 0, 1024); + memset((void*)ascii_dump, 0, 1024); + } + + data++; + } + + if (count != 0) + { + fprintf(f, hex_dump); + for (int index = 0; index < 16 - count; index++) + fprintf(f, "\t"); + + fprintf(f, ascii_dump); + + for (int index = 0; index < 16 - count; index++) + fprintf(f, "."); + } + + count = 0; + fprintf(f, "\n\n"); + memset((void*)hex_dump, 0, 1024); + memset((void*)ascii_dump, 0, 1024); + +#endif // KANJI_DUMP_LEAKED_MEM + + totalSize += i->second.size; + } + + ErrorLevel lvl = (totalSize > 0) ? ERRORLVL : INFOLVL; + + sprintf(buf, "-----------------------------------------------------------\n"); + fprintf(f, "%s", buf); + KOutputDebug(lvl, buf); + sprintf(buf, "Total Unfreed: %u bytes (%luKB)\n\n", totalSize, totalSize / 1024); + KOutputDebug(lvl, buf); + fprintf(f, "%s", buf); + fclose(f); +} + +#ifdef KANJI_TRACK_MEM_USAGE +void KMemoryDumpUsage() +{ + if (!KAllocStat::allocMapValid) + return; + + char buf[8192]; + FILE* f = fopen("mem_usage.txt", "wt"); + + time_t aTime = time(NULL); + sprintf(buf, "Memory Usage Report for %s\n", asctime(localtime(&aTime))); + if (f) fprintf(f, "%s", buf); + KOutputDebug("\n"); + KOutputDebug(buf); + + for(KAllocStat::allocInfo::iterator i = allocStat.memInfo.begin(); i != allocStat.memInfo.end(); ++i) + { + int aBytesTotal = 0; + int aCallsTotal = 0; + for (KAllocStat::allocCount::iterator index = i->second.begin(); index != i->second.end(); ++index) + { + aBytesTotal += index->first; + aCallsTotal += index->second; + sprintf(buf, "%s(%d) : %d bytes (%d %s)\n", i->first.first.c_str(), i->first.second, index->first, index->second, index->second == 1 ? "call" : "calls"); + if (f) fprintf(f, "%s", buf); + KOutputDebug(buf); + } + + if (i->second.size() > 1) + { + sprintf(buf, " %s(%d) : %d KB total (%d calls)\n", i->first.first.c_str(), i->first.second, aBytesTotal / 1024, aCallsTotal); + if (f) fprintf(f, "%s", buf); + KOutputDebug(buf); + } + } + if (f) fclose(f); +} +#endif // KANJI_TRACK_MEM_USAGE + +size_t KMemoryAllocated() +{ + if (!KAllocMap::allocMapValid) + return 0; + + KAutoLock aCrit(allocMap.crit); + + size_t size = 0; + for(auto i = allocMap.begin(); i != allocMap.end(); ++i) + { + KANJI_ALLOC_INFO& info = i->second; + size += info.size; + } + return size; +} diff --git a/spine-cpp/spine-cpp-unit-tests/memory/KMemory.h b/spine-cpp/spine-cpp-unit-tests/memory/KMemory.h new file mode 100755 index 000000000..470a5db7d --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/memory/KMemory.h @@ -0,0 +1,189 @@ +#ifndef __KANJIMEMORY_H__ +#define __KANJIMEMORY_H__ + +#include +#include + +#if defined(_DEBUG) && !defined(KANJI_MEMTRACE) +#define KANJI_MEMTRACE +#endif + +#ifdef WIN32 +#pragma warning(disable : 4595) +#endif + +////////////////////////////////////////////////////////////////////////// +// HOW TO USE THIS FILE +// +// In the desired .CPP file (NOT header file), AFTER ALL of your +// #include declarations, do a #include "KMemory.h" or whatever you renamed +// this file to. It's very important that you do it only in the .cpp and +// after every other include file, otherwise it won't compile. The memory leaks +// will appear in a file called mem_leaks.txt and they will also be printed out +// in the output window when the program exits. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef SAFE_DELETE +#define SAFE_DELETE(pPtr) { if(pPtr) delete pPtr; pPtr = 0; } +#endif + +#ifndef SCOPED_AUTO_SAFE_DELETE +template +class ScopedAutoDeletePointerHelper +{ +public: + ScopedAutoDeletePointerHelper(T pPtr) : _pPtr(pPtr) {} + ~ScopedAutoDeletePointerHelper() { SAFE_DELETE(_pPtr); } + + T _pPtr; +}; +#define SCOPED_AUTO_SAFE_DELETE(p) ScopedAutoDeletePointerHelper anAutoDelete##p(p); +#endif + +#ifndef SAFE_DELETE_ARRAY +#define SAFE_DELETE_ARRAY(pPtr) { if(pPtr) delete [] pPtr; pPtr = 0; } +#endif + +extern void KMemoryDumpUnfreed(); +extern size_t KMemoryAllocated(); + +#ifdef WIN32 +#define KMEM_CALLTYPE __cdecl +#else +#define KMEM_CALLTYPE +#endif + +#ifdef __APPLE__ +#define KMEM_THROWSPEC throw(std::bad_alloc) +#define KMEM_THROWS_BADALLOC +#include +#else +#define KMEM_THROWSPEC +#endif + +#if defined(KANJI_MEMTRACE) + +///////////////////////////////////////////// +// DO NOT CALL THESE TWO METHODS DIRECTLY // +///////////////////////////////////////////// + +extern void KMemoryAddTrack(void* addr, size_t asize, const char* fname, int lnum); +extern void KMemoryRemoveTrack(void* addr); + +//Replacement for the standard malloc/free, records size of allocation and the file/line number it was on +inline void* _kanjimalloc (size_t size, const char* file, int line) +{ + void* ptr = (void*)malloc(size); + KMemoryAddTrack(ptr, size, file, line); + return(ptr); +} + +inline void* _kanjimalloc (size_t size) +{ + return _kanjimalloc(size, "", 0); +} + +inline void _kanjifree (void* ptr) +{ + KMemoryRemoveTrack(ptr); + free(ptr); +} + +inline void* _kanjirealloc (void* ptr, size_t size, const char* file, int line) +{ + void* ptr2 = (void*)realloc(ptr, size); + if (ptr2) + { + KMemoryRemoveTrack(ptr); + KMemoryAddTrack(ptr2, size, file, line); + } + return ptr2; +} + +inline void* _kanjirealloc (void* ptr, size_t size) +{ + return _kanjirealloc(ptr, size, "", 0); +} + +#define kanjimalloc(size) _kanjimalloc((size), __FILE__, __LINE__) +#define kanjifree _kanjifree +#define kanjirealloc(ptr, size) _kanjirealloc(ptr, size, __FILE__, __LINE__) + +//Replacement for the standard "new" operator, records size of allocation and the file/line number it was on +inline void* KMEM_CALLTYPE operator new(size_t size, const char* file, int line) +{ + void* ptr = (void*)malloc(size); + KMemoryAddTrack(ptr, size, file, line); + return(ptr); +} + +//Same as above, but for arrays +inline void* KMEM_CALLTYPE operator new[](size_t size, const char* file, int line) +{ + void* ptr = (void*)malloc(size); + KMemoryAddTrack(ptr, size, file, line); + return(ptr); +} + + +// These single argument new operators allow vc6 apps to compile without errors +inline void* KMEM_CALLTYPE operator new(size_t size) KMEM_THROWSPEC +{ + void* ptr = (void*)malloc(size); +#ifdef KMEM_THROWS_BADALLOC + if(!ptr) throw std::bad_alloc(); +#endif + return(ptr); +} + +inline void* KMEM_CALLTYPE operator new[](size_t size) KMEM_THROWSPEC +{ + void* ptr = (void*)malloc(size); +#ifdef KMEM_THROWS_BADALLOC + if(!ptr) throw std::bad_alloc(); +#endif // KMEM_THROWS_BADALLOC + return(ptr); +} + + +//custom delete operators +inline void KMEM_CALLTYPE operator delete(void* p) throw() +{ + KMemoryRemoveTrack(p); + free(p); +} + +inline void KMEM_CALLTYPE operator delete[](void* p) throw() +{ + KMemoryRemoveTrack(p); + free(p); +} + +//needed in case in the constructor of the class we're newing, it throws an exception +inline void KMEM_CALLTYPE operator delete(void* pMem, const char* file, int line) +{ + free(pMem); +} + +inline void KMEM_CALLTYPE operator delete[](void* pMem, const char* file, int line) +{ + free(pMem); +} + +#define KDEBUG_NEW new(__FILE__, __LINE__) +#define new KDEBUG_NEW + +#else // KANJI_MEMTRACE NOT DEFINED + +#define kanjimalloc malloc +#define kanjifree free +#define kanjirealloc realloc + +inline void* _kanjimalloc(size_t size) { return malloc(size); } +inline void _kanjifree(void* ptr) { free(ptr); } +inline void* _kanjirealloc(void* ptr, size_t size) { return realloc(ptr, size); } + +#endif // KANJI_MEMTRACE + +#endif // __KANJIMEMORY_H__ \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/memory/KString.cpp b/spine-cpp/spine-cpp-unit-tests/memory/KString.cpp new file mode 100755 index 000000000..bd7aef664 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/memory/KString.cpp @@ -0,0 +1,185 @@ +#include "KString.h" +#include + +#include "MiniCppUnit.hxx" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// String Helper + +static std::string vasprintf(const char* fmt, va_list argv) +{ + std::string result; + va_list argv_copy; // vsnprintf modifies argv, need copy +#ifndef va_copy + argv_copy = argv; +#else + va_copy(argv_copy, argv); +#endif + + int len = vsnprintf(NULL, 0, fmt, argv_copy); + if (len > 0 && len < 255) + { + // cover 90% of all calls + char str[256] = { 0 }; + int len2 = vsnprintf(str, 255, fmt, argv); + result = str; + } + else if (len > 0) + { + char* str = static_cast(alloca(len + 1)); // alloca on stack, space for null-termination + int len2 = vsnprintf(str, len + 1, fmt, argv); + result = str; + } + return result; +} + + +static void reportWarning(const std::string& warnStr) +{ + if (JetBrains::underTeamcity()) + gTeamCityListener.messages.messageWarning(warnStr); + else + fprintf(stderr, "%s", warnStr.c_str()); +} + +static void reportError(const std::string& errorStr) +{ + if (JetBrains::underTeamcity()) + gTeamCityListener.messages.messageError(errorStr); + else + fprintf(stderr, "%s", errorStr.c_str()); +} + +static void reportInfo(const std::string& infoStr) +{ + if (JetBrains::underTeamcity()) + gTeamCityListener.messages.messageNormal(infoStr); + else + fprintf(stderr, "%s", infoStr.c_str()); +} + +static void reportDebug(const std::string& debugStr) +{ + fprintf(stderr, "%s", debugStr.c_str()); +} + +static void report(ErrorLevel level, const std::string& Str) +{ + switch (level) { + case WARNLVL: reportWarning(Str); break; + case ERRORLVL: reportError(Str); break; + case INFOLVL: reportInfo(Str); break; + case DEBUGLVL: reportDebug(Str); break; + } +} + +void KOutputDebug(ErrorLevel lvl, const char* fmt ...) +{ + va_list argList; + va_start(argList, fmt); + std::string str = vasprintf(fmt, argList); + va_end(argList); + + report(lvl, str); +} + +#define K_MAX(a,b) ((a>b) ? a : b) + +std::string GetFileName(const std::string& thePath, bool noExtension) +{ + int aLastSlash = K_MAX((int)thePath.rfind('\\'), (int)thePath.rfind('/')); + + if (noExtension) + { + int aLastDot = (int)thePath.rfind('.'); + if (aLastDot > aLastSlash) + return thePath.substr(aLastSlash + 1, aLastDot - aLastSlash - 1); + } + + if (aLastSlash == -1) + return thePath; + else + return thePath.substr(aLastSlash + 1); +} + +std::string GetFileDir(const std::string& thePath, bool withSlash) +{ + int aLastSlash = K_MAX((int)thePath.rfind(('\\')), (int)thePath.rfind(('/'))); + + if (aLastSlash == -1) + return (""); + else + { + if (withSlash) + return thePath.substr(0, aLastSlash + 1); + else + return thePath.substr(0, aLastSlash); + } +} + +std::string GetFileExt(const std::string& thePath) +{ + std::string::size_type idx = thePath.find_last_of('.'); + + if (idx != std::string::npos) + return thePath.substr(idx + 1); + + return (""); +} + +/** + * g_ascii_strcasecmp: + * @s1: string to compare with @s2. + * @s2: string to compare with @s1. + * + * Compare two strings, ignoring the case of ASCII characters. + * + * Unlike the BSD strcasecmp() function, this only recognizes standard + * ASCII letters and ignores the locale, treating all non-ASCII + * bytes as if they are not letters. + * + * This function should be used only on strings that are known to be + * in encodings where the bytes corresponding to ASCII letters always + * represent themselves. This includes UTF-8 and the ISO-8859-* + * charsets, but not for instance double-byte encodings like the + * Windows Codepage 932, where the trailing bytes of double-byte + * characters include all ASCII letters. If you compare two CP932 + * strings using this function, you will get false matches. + * + * Return value: an integer less than, equal to, or greater than + * zero if @s1 is found, respectively, to be less than, + * to match, or to be greater than @s2. + **/ +static int g_ascii_compare_caseless(const char* s1, const char* s2) +{ +#define TOUPPER(c) (((c) >= 'a' && (c) <= 'z') ? (c) - 'a' + 'A' : (c)) +#define TOLOWER(c) (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c)) +#define g_return_val_if_fail(expr,val) { if (!(expr)) return (val); } + + int c1, c2; + + g_return_val_if_fail(s1 != NULL, 0); + g_return_val_if_fail(s2 != NULL, 0); + + while (*s1 && *s2) + { + c1 = (int)(unsigned char)TOLOWER(*s1); + c2 = (int)(unsigned char)TOLOWER(*s2); + if (c1 != c2) + return (c1 - c2); + s1++; s2++; + } + + return (((int)(unsigned char)* s1) - ((int)(unsigned char)* s2)); + +#undef g_return_val_if_fail +#undef TOUPPER +#undef TOLOWER +} + + +int CompareNoCase(const std::string & str1, const std::string & str2) +{ + return g_ascii_compare_caseless(str1.c_str(), str2.c_str()); +} diff --git a/spine-cpp/spine-cpp-unit-tests/memory/KString.h b/spine-cpp/spine-cpp-unit-tests/memory/KString.h new file mode 100755 index 000000000..32a27cb44 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/memory/KString.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +// Error reporting with levels similar to Android and are automatically forwarded to Continuous integration server +enum ErrorLevel { + WARNLVL, + ERRORLVL, + INFOLVL, + DEBUGLVL +}; + +extern void KOutputDebug(ErrorLevel lvl, const char* fmt ...); + +extern std::string GetFileName(const std::string& thePath, bool noExtension); +extern std::string GetFileDir(const std::string& thePath, bool withSlash); +extern std::string GetFileExt(const std::string& thePath); + +extern int CompareNoCase(const std::string& str1, const std::string& str2); diff --git a/spine-cpp/spine-cpp-unit-tests/minicppunit/MiniCppUnit.cxx b/spine-cpp/spine-cpp-unit-tests/minicppunit/MiniCppUnit.cxx new file mode 100755 index 000000000..f14e790fb --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/minicppunit/MiniCppUnit.cxx @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2003-2004 Pau Arumí & David García + * + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "MiniCppUnit.hxx" + +JetBrains::TeamcityProgressListener gTeamCityListener; +bool gUseTeamCity = false; + +#include +#include +#include + +#define MIN(a,b) ((a < b) ? a : b) +#define MAX(a,b) ((a > b) ? a : b) + +TestsListener::TestsListener() : _currentTestName(0) +{ + _executed=_failed=_exceptions=0; + gUseTeamCity = JetBrains::underTeamcity(); +} + +TestsListener& TestsListener::theInstance() +{ + static TestsListener instancia; + return instancia; +} + +std::stringstream& TestsListener::errorsLog() +{ + if (_currentTestName) + { + _log << "\n" << errmsgTag_nameOfTest() << (*_currentTestName) << "\n"; + } + return _log; +} + +std::string TestsListener::logString() +{ + std::string aRetornar = _log.str(); + _log.str(""); + return aRetornar; +} +void TestsListener::currentTestName( std::string& name) +{ + _currentTestName = &name; + + if(gUseTeamCity)gTeamCityListener.startTest(name); +} +void TestsListener::testHasRun() // started +{ + std::cout << "."; + theInstance()._executed++; +} + +void TestsListener::testHasPassed() // finished without errors +{ + if(gUseTeamCity) + gTeamCityListener.endTest(*(theInstance()._currentTestName)); +} + +void TestsListener::testHasFailed(const char* reason, const char* file, int line) +{ + if(gUseTeamCity) + { + gTeamCityListener.addFailure(JetBrains::TestFailure(*(theInstance()._currentTestName), "", JetBrains::SourceLine(file, line), reason)); + gTeamCityListener.endTest(*(theInstance()._currentTestName)); + } + + std::cout << "F"; + theInstance()._failed++; + throw TestFailedException(); +} +void TestsListener::testHasThrown() +{ + if(gUseTeamCity) + { + gTeamCityListener.addFailure(JetBrains::TestFailure(*(theInstance()._currentTestName), "", JetBrains::SourceLine(), "Exception")); + gTeamCityListener.endTest(*(theInstance()._currentTestName)); + } + std::cout << "E"; + theInstance()._exceptions++; +} +std::string TestsListener::summary() +{ + std::ostringstream os; + os << "\nSummary:\n" + << Assert::bold() << "\tExecuted Tests: " + << _executed << Assert::normal() << std::endl + << Assert::green() << "\tPassed Tests: " + << (_executed-_failed-_exceptions) + << Assert::normal() << std::endl; + if (_failed > 0) + { + os << Assert::red() << "\tFailed Tests: " + << _failed << Assert::normal() << std::endl; + } + if (_exceptions > 0) + { + os << Assert::yellow() << "\tUnexpected exceptions: " + << _exceptions << Assert::normal() << std::endl; + } + os << std::endl; + return os.str(); +} +bool TestsListener::allTestsPassed() +{ + return !theInstance()._exceptions && !theInstance()._failed; +} + + + +void Assert::assertTrue(char* strExpression, bool expression, + const char* file, int linia) +{ + if (!expression) + { + TestsListener::theInstance().errorsLog() << "\n" + << errmsgTag_testFailedIn() << file + << errmsgTag_inLine() << linia << "\n" + << errmsgTag_failedExpression() + << bold() << strExpression << normal() << "\n"; + TestsListener::theInstance().testHasFailed(strExpression, file, linia); + } +} + +void Assert::assertTrueMissatge(char* strExpression, bool expression, + const char* missatge, const char* file, int linia) +{ + if (!expression) + { + TestsListener::theInstance().errorsLog() << "\n" + << errmsgTag_testFailedIn() << file + << errmsgTag_inLine() << linia << "\n" + << errmsgTag_failedExpression() + << bold() << strExpression << "\n" + << missatge<< normal() << "\n"; + TestsListener::theInstance().testHasFailed(strExpression, file, linia); + } +} + + + +void Assert::assertEquals( const char * expected, const char * result, + const char* file, int linia ) +{ + assertEquals(std::string(expected), std::string(result), + file, linia); + +} +void Assert::assertEquals( const bool& expected, const bool& result, + const char* file, int linia ) +{ + assertEquals( + (expected?"true":"false"), + (result?"true":"false"), + file, linia); +} + +// floating point numbers comparisons taken +// from c/c++ users journal. dec 04 pag 10 +bool isNaN(double x) +{ + bool b1 = (x < 0.0); + bool b2 = (x >= 0.0); + return !(b1 || b2); +} + +double scaledEpsilon(const double& expected, const double& fuzzyEpsilon ) +{ + const double aa = fabs(expected)+1; + return (std::isinf(aa))? fuzzyEpsilon: fuzzyEpsilon * aa; +} +bool fuzzyEquals(double expected, double result, double fuzzyEpsilon) +{ + return (expected==result) || ( fabs(expected-result) <= scaledEpsilon(expected, fuzzyEpsilon) ); +} +void Assert::assertEquals( const double& expected, const double& result, + const char* file, int linia ) +{ + const double fuzzyEpsilon = 0.000001; + assertEqualsEpsilon( expected, result, fuzzyEpsilon, file, linia ); +} + +void Assert::assertEquals( const float& expected, const float& result, + const char* file, int linia ) +{ + assertEquals((double)expected, (double)result, file, linia); +} +void Assert::assertEquals( const long double& expected, const long double& result, + const char* file, int linia ) +{ + assertEquals((double)expected, (double)result, file, linia); +} +void Assert::assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon, + const char* file, int linia ) +{ + if (isNaN(expected) && isNaN(result) ) return; + if (!isNaN(expected) && !isNaN(result) && fuzzyEquals(expected, result, epsilon) ) return; + + std::stringstream anError; + anError + << errmsgTag_testFailedIn() << file + << errmsgTag_inLine() << linia << "\n" + << errmsgTag_expected() + << bold() << expected << normal() << " " + << errmsgTag_butWas() + << bold() << result << normal() << "\n"; + + TestsListener::theInstance().errorsLog() << anError.str(); + + TestsListener::theInstance().testHasFailed(anError.str().c_str(), file, linia); +} + +int Assert::notEqualIndex( const std::string & one, const std::string & other ) +{ + int end = MIN(one.length(), other.length()); + for ( int index = 0; index < end; index++ ) + if (one[index] != other[index] ) + return index; + return end; +} + + +/** + * we overload the assert with string doing colored diffs + * + * MS Visual6 doesn't allow string by reference :-( + */ +void Assert::assertEquals( const std::string expected, const std::string result, + const char* file, int linia ) +{ + if(expected == result) + return; + + int indexDiferent = notEqualIndex(expected, result); + + std::stringstream anError; + anError + << file << ", linia: " << linia << "\n" + << errmsgTag_expected() << "\n" << blue() + << expected.substr(0,indexDiferent) + << green() << expected.substr(indexDiferent) + << normal() << "\n" + << errmsgTag_butWas() << blue() << "\n" + << result.substr(0,indexDiferent) + << red() << result.substr(indexDiferent) + << normal() << std::endl; + + TestsListener::theInstance().errorsLog() << anError.str(); + + TestsListener::theInstance().testHasFailed(anError.str().c_str(), file, linia); +} +void Assert::fail(const char* motiu, const char* file, int linia) +{ + TestsListener::theInstance().errorsLog() << + file << errmsgTag_inLine() << linia << "\n" << + "Reason: " << motiu << "\n"; + + TestsListener::theInstance().testHasFailed(motiu, file, linia); +} + + diff --git a/spine-cpp/spine-cpp-unit-tests/minicppunit/MiniCppUnit.hxx b/spine-cpp/spine-cpp-unit-tests/minicppunit/MiniCppUnit.hxx new file mode 100755 index 000000000..7ce3d8fd6 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/minicppunit/MiniCppUnit.hxx @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2003-2004 Pau Arum� & David Garc�a + * + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef MiniCppUnit_hxx +#define MiniCppUnit_hxx + +/** + * @mainpage + * miniCppUnit + * (C) 2003-2006 Pau Arumi & David Garcia + * + * @version 2.5 2006-03-14 + * - MS Visual compatibility: SConstruct ccflags, usage example, #ifdefs + * @version 2.4 2006-03-14 + * - exit test case after first failure + * - double and float comparison with fuzzy equals (using scalable epsilon) + * - have into account not a numbers + * - new ASSERT_EQUALS_EPSILON macro + * - more colors, and disabled when comiled in MS Visual + * - removed catalan location. + * - UsageExample.cxx now uses all macros and features + * @version 2.3 2006-02-13 added usage example and SConstruct + * @version 2.2 2004-11-28 code in english and tests suites + * @version 2.1 2004-11-04 char* especialization + * @version 2.0 2004-10-26 TestsFactory + * @version 1.0 2003-10-28 initial + * + * Example of use: + * + * @code + * #include "MiniCppUnit.hxx" + * class MyTests : public TestFixture + * { + * public: + * TEST_FIXTURE( MyTests ) + * { + * CAS_DE_TEST( testAddition ); + * // etc + * } + * void testAddition() + * { + * ASSERT_EQUALS( 4, 1+1+2 ); + * } + * // etc + * }; + * + * REGISTER_FIXTURE( MyTests ); + * @endcode + * @code + * int main() + * { + * return TestFixtureFactory::theInstance().runTests() ? 0 : -1; + * } + * @endcode + * Good things: + * + * - it's a tiny framework made up of two or three src files. + * => no need to install as a library + * - object oriented and makes use of several GoF patterns + * - very simple usage. Just needs to learn very few C macros + * - string asserts are simpler to use than cppunit + * - string asserts are enhanced with coloured diffs + * - concrete test classes are totally decoupled via static factory + * => no src file have to include them all. + * - it have test suite hierarchies + * - compatible with non-standard compliant VisualC6 + * (though not necessary good ;) + */ + +#include +#include +#include +#include + +#if _MSC_VER < 1300 +/** necesary for Visual 6 which don't define std::min */ +namespace std +{ + template T min(const T& a, const T& b) { return a < b ? a: b; } +} +#endif + +#include "../teamcity/teamcity_cppunit.h" + +extern JetBrains::TeamcityProgressListener gTeamCityListener; + +/** + * A singleton class. + * Receives tests results and stores messages to the test log + * for later listing. + * It's a singleton for an easy global access from the 'Asserts' + * methods but it is probably asking for a refactoring in order to limit + * access only to TestFixtures + */ +class TestsListener +{ +public: + /** accessor to the global (static) singleton instance */ + static TestsListener& theInstance(); + std::stringstream& errorsLog(); + std::string logString(); + void currentTestName( std::string& name); + static void testHasRun(); + static void testHasPassed(); + static void testHasFailed(const char* reason, const char* file, int line); + static void testHasThrown(); + /** the human readable summary of run tests*/ + std::string summary(); + /** returns wheather all run tests have passed */ + static bool allTestsPassed(); + +private: + static const char* errmsgTag_nameOfTest() { return "Test failed: "; } + + /** constructor private: force the singleton to be wellbehaved ! */ + TestsListener(); + + std::string* _currentTestName; + std::stringstream _log; + unsigned _executed; + unsigned _failed; + unsigned _exceptions; +}; + +class TestFailedException +{ +}; + +/** + * Abstract class with interface that allows run a test. That is runTest + * and name. It is implemented by TestFixture and TestCase + * + * It does the 'Component' role in the 'Composite' patten + **/ +class Test +{ +public: + virtual ~Test(){} + /** run the test: exercice the code and check results*/ + virtual void runTest() = 0; + /** the test human-readable name */ + virtual std::string name() const = 0; +}; + + +/** + * This class is just a placeholder for all assert functions --as static methods. + * It is meant for being used just by the assert macros + */ +class Assert +{ + static const char * errmsgTag_testFailedIn() { return "Test failed in "; } + static const char * errmsgTag_inLine() { return ", line: "; }; + static const char * errmsgTag_failedExpression() { return "Failed expression: "; } + static const char * errmsgTag_expected() { return "Expected: "; } + static const char * errmsgTag_butWas() { return "But was: "; } + +public: +#ifdef _MSC_VER + static const char * blue() { return ""; } + static const char * green() { return ""; } + static const char * red() { return ""; } + static const char * normal() { return ""; } + static const char * bold() { return ""; } + static const char * yellow() { return ""; } +#else + static const char * blue() { return "\033[36;1m"; } + static const char * green() { return "\033[32;1m"; } + static const char * red() { return "\033[31;1m"; } + static const char * normal() { return "\033[0m"; } + static const char * bold() { return "\033[" "1m"; } + static const char * yellow() { return "\033[93;1m"; } +#endif + template + static void assertEquals( const AType& expected, const AType& result, + const char* file="", int linia=0 ) + { + if(expected != result) + { + std::stringstream anError; + + anError + << file << ", linia: " << linia << "\n" + << errmsgTag_expected() << " " << expected << " " + << errmsgTag_butWas() << " " << result << "\n"; + + // TestsListener::theInstance().errorsLog() << anError; + + TestsListener::theInstance().testHasFailed(anError.str().c_str(), file, linia); + } + } + + static void assertTrue(char* strExpression, bool expression, + const char* file="", int linia=0); + + static void assertTrueMissatge(char* strExpression, bool expression, + const char* missatge, const char* file="", int linia=0); + + static void assertEquals( const char * expected, const char * result, + const char* file="", int linia=0 ); + + static void assertEquals( const bool& expected, const bool& result, + const char* file="", int linia=0 ); + + static void assertEquals( const double& expected, const double& result, + const char* file="", int linia=0 ); + + static void assertEquals( const float& expected, const float& result, + const char* file="", int linia=0 ); + + static void assertEquals( const long double& expected, const long double& result, + const char* file="", int linia=0 ); + + static void assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon, + const char* file="", int linia=0 ); + + static int notEqualIndex( const std::string & one, const std::string & other ); + + /** + * we overload the assert with string doing colored diffs + * + * MS Visual6 doesn't allow string by reference :-( + */ + static void assertEquals( const std::string expected, const std::string result, + const char* file="", int linia=0 ); + + static void fail(const char* motiu, const char* file="", int linia=0); + + +}; + +/** + * A TestFixture is a class that contain TestCases --which corresponds to + * ConcreteTestFixture methods-- common objects uder tests, and setUp and + * tearDown methods which are automatically executed before and after each + * test case. + * + * Is the base class of ConcreteFixtures implemented by the framework user + * + * It does the 'Composite' role in the 'Composite' GoF pattern. + * Its composite children are TestCases, which wrapps the test methods. + * + * It is a template class parametrized by ConcreteTestFixture so that it can + * instantiate TestCase objects templatized with this same parameter: it needs the + * concrete class type for calling its non-static methods. + */ +template +class TestFixture : public Test +{ +protected: + + typedef ConcreteTestFixture ConcreteFixture; + typedef void(ConcreteTestFixture::*TestCaseMethod)(); + + /** + * Wrapper for the test methods of concrete TestFixtures. + * + * Makes the 'Leave' role in the 'Composite' GoF pattern because can't be + * be a composition of other tests. + * + * It's also a case of 'Command' pattern because it encapsules in an object + * certain functionality whose execution depends on some deferred entity. + */ + class TestCase : public Test + { + public: + TestCase(ConcreteFixture* parent, TestCaseMethod method, const std::string & name) : + _parent(parent), + _testCaseMethod(method), + _name(name) + { + } + /** calls TestFixture method. setUp and tearDown methods are called by + * its parent TestFixture (in its runTest method). + * it is robust to unexpected exceptions (throw) */ + void runTest() + { + TestsListener::theInstance().testHasRun(); + TestsListener::theInstance().currentTestName(_name); + try + { + (_parent->*_testCaseMethod)(); + TestsListener::theInstance().testHasPassed(); + } + catch( std::exception& error ) + { + TestsListener::theInstance().testHasThrown(); + TestsListener::theInstance().errorsLog() + << "std::exception catched by MiniCppUnit: \n" + << "what() : " + << Assert::yellow() << error.what() + << Assert::normal() << "\n"; + } + catch ( TestFailedException& ) //just for skiping current test case + { + // the assert() calls testHasFailed() + } + catch(...) + { + TestsListener::theInstance().testHasThrown(); + TestsListener::theInstance().errorsLog() + << "non standard exception catched by MiniCppUnit.\n"; + } + } + + /** the TestFixture method hame */ + std::string name() const + { + return _name; + } + + private: + ConcreteFixture* _parent; + TestCaseMethod _testCaseMethod; + std::string _name; + }; + //------------- end of class TestCase ---------------------------- + +private: + + typedef std::list TestCases; + TestCases _testCases; + std::string _name; + + void testsList() const + { + std::cout << "\n+ " << name() << "\n"; + for( TestCases::const_iterator it=_testCases.begin(); + it!=_testCases.end(); it++ ) + std::cout << " - "<< (*it)->name() << "\n"; + } + + +public: + virtual void setUp() {} + virtual void tearDown() {} + + std::string name() const + { + return _name; + }; + + TestFixture(const std::string& name="A text fixture") : _name(name) + { + } + + void afegeixCasDeTest(ConcreteFixture* parent, TestCaseMethod method, const char* name) + { + TestCase* casDeTest = new TestCase(parent, method, _name + "::" + name); + _testCases.push_back( casDeTest ); + } + /** calls each test after setUp and tearDown TestFixture methods */ + void runTest() + { + testsList(); + TestCases::iterator it; + for( it=_testCases.begin(); it!=_testCases.end(); it++) + { + setUp(); + (*it)->runTest(); + tearDown(); + } + } + /** TestCase that wrapps TestFixture methods are dynamically created and owned by + * the TestFixture. So here we clean it up*/ + virtual ~TestFixture() + { + TestCases::iterator it; + for( it =_testCases.begin(); it!=_testCases.end(); it++) + delete (*it); + } +}; + + +/** + * This class is aimed to hold a creator method for each concrete TestFixture + */ +class TestFixtureFactory +{ +private: + /** Well behaved singleton: + * Don't allow instantiation apart from theInstance(), so private ctr.*/ + TestFixtureFactory() + { + } + typedef Test* (*FixtureCreator)(); + std::list _creators; +public: + /** Accessor to the (static) singleton instance */ + static TestFixtureFactory& theInstance() + { + static TestFixtureFactory theFactory; + return theFactory; + } + bool runTests() + { + std::list::iterator it; + for(it=_creators.begin(); it!=_creators.end(); it++) + { + FixtureCreator creator = *it; + Test* test = creator(); + test->runTest(); + delete test; + } + std::string errors = TestsListener::theInstance().logString(); + if (errors!="") std::cout << "\n\nError Details:\n" << errors; + std::cout << TestsListener::theInstance().summary(); + + return TestsListener::theInstance().allTestsPassed(); + } + void addFixtureCreator(FixtureCreator creator) + { + _creators.push_back( creator ); + } + +}; + +/** + * Macro a usar despr�s de cada classe de test + */ +#define REGISTER_FIXTURE( ConcreteTestFixture ) \ +\ +Test* Creador##ConcreteTestFixture() { return new ConcreteTestFixture; } \ +\ +class Registrador##ConcreteTestFixture \ +{ \ +public: \ + Registrador##ConcreteTestFixture() \ + { \ + TestFixtureFactory::theInstance().addFixtureCreator( \ + Creador##ConcreteTestFixture); \ + } \ +}; \ +static Registrador##ConcreteTestFixture estatic##ConcreteTestFixture; + + +/** + * Assert macros to use in test methods. An assert is a test condition + * we want to check. + */ +#define ASSERT_EQUALS( expected, result) \ + Assert::assertEquals( expected, result, __FILE__, __LINE__ ); + +#define ASSERT_EQUALS_EPSILON( expected, result, epsilon) \ + Assert::assertEqualsEpsilon( expected, result, epsilon, __FILE__, __LINE__ ); + +#define ASSERT( exp ) \ + Assert::assertTrue(#exp, exp, __FILE__, __LINE__); + +#define ASSERT_MESSAGE( exp, message ) \ + Assert::assertTrueMissatge(#exp, exp, message, __FILE__, __LINE__); + +#define FAIL( why ) \ + Assert::fail(#why, __FILE__, __LINE__); + +/** + * Macros that allows to write the constructor of the concrete TestFixture. + * What the constructor does is agregate a wrapper for each test case (method) + * As easy to write as this: + * + * @code + * class MyTests : public TestFixture + * { + * public: + * TEST_FIXTURE( MyTests ) + * { + * TEST_CASE( test ); + * // etc + * } + * void test() + * { + * ASSERT_EQUALS( 4, 1+1+2 ); + * } + * @endcode + */ + +#define TEST_FIXTURE( ConcreteFixture ) \ + ConcreteFixture() : TestFixture( #ConcreteFixture ) + +#define TEST_CASE( methodName ) \ + afegeixCasDeTest( this, &ConcreteFixture::methodName, #methodName ); + + + + + +#endif // MiniCppUnit_hxx diff --git a/spine-cpp/spine-cpp-unit-tests/teamcity/README.txt b/spine-cpp/spine-cpp-unit-tests/teamcity/README.txt new file mode 100755 index 000000000..04f7914b9 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/teamcity/README.txt @@ -0,0 +1,30 @@ +CppUnit listener for TeamCity +----------------------------- + +To report your tests result to TeamCity server +include teamcity_messages.* teamcity_cppunit.* +to your project and modify "main" function +as shown in example.cpp +(around JetBrains::underTeamcity and JetBrains::TeamcityProgressListener) + +Technical details +----------------- + +Reporting implemented by writing TeamCity service messages to stdout. + +See +http://www.jetbrains.net/confluence/display/TCD3/Build+Script+Interaction+with+TeamCity +for more details. + +Contact information +------------------- + +Mail to teamcity-feedback@jetbrains.com or see other options at + +http://www.jetbrains.com/support/teamcity + +License +------- + +Apache, version 2.0 +http://www.apache.org/licenses/LICENSE-2.0 diff --git a/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_cppunit.cpp b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_cppunit.cpp new file mode 100755 index 000000000..86f163b9c --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_cppunit.cpp @@ -0,0 +1,82 @@ +/* Copyright 2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $Revision: 88625 $ +*/ + +#include + +#include "teamcity_cppunit.h" + +using namespace std; + +namespace JetBrains { + +TeamcityProgressListener::TeamcityProgressListener() +{ + flowid = getFlowIdFromEnvironment(); +} + +TeamcityProgressListener::TeamcityProgressListener(const std::string& _flowid) +{ + flowid = _flowid; +} + +void TeamcityProgressListener::startTest(const std::string& test) { + messages.testStarted(test, flowid); +} + +static string sourceLine2string(const SourceLine &sline) { + stringstream ss; + + ss << sline.fileName << ":" << sline.lineNumber; + + return ss.str(); +} + +void TeamcityProgressListener::addFailure(const TestFailure &failure) +{ + + string details = failure.details; + + if (failure.sourceLine.isValid()) { + details.append(" at "); + details.append(sourceLine2string(failure.sourceLine)); + details.append("\n"); + } + + messages.testFailed( + failure.testName, + failure.description, + details, + flowid + ); +} + +void TeamcityProgressListener::endTest(const std::string& test) +{ + messages.testFinished(test, -1, flowid); +} + +void TeamcityProgressListener::startSuite(const std::string& test) +{ + messages.suiteStarted(test, flowid); +} + +void TeamcityProgressListener::endSuite(const std::string& test) +{ + messages.suiteFinished(test, flowid); +} + +} diff --git a/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_cppunit.h b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_cppunit.h new file mode 100755 index 000000000..7541a5186 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_cppunit.h @@ -0,0 +1,83 @@ +/* Copyright 2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $Revision: 88625 $ +*/ + +#pragma once + +#include + +#include "teamcity_messages.h" + +namespace JetBrains { + + class SourceLine + { + public: + SourceLine():lineNumber(-1){} + SourceLine(const std::string& theFile, int theLineNum):fileName(theFile),lineNumber(theLineNum){} + ~SourceLine(){} + + std::string fileName; + int lineNumber; + bool isValid() const {return (!fileName.empty() && lineNumber > -1);} + }; + + class TestFailure + { + public: + std::string details; + SourceLine sourceLine; + std::string testName; + std::string description; + public: + TestFailure(){} + ~TestFailure(){} + + TestFailure(const std::string& theTestName, const std::string& theDetails, SourceLine theSourcelLine, const std::string& theDescription) + { + testName = theTestName; + details = theDetails; + sourceLine = theSourcelLine; + description = theDescription; + } + }; + + class TeamcityProgressListener + { + public: + TeamcityMessages messages; + public: + TeamcityProgressListener(const std::string& _flowid); + TeamcityProgressListener(); + ~TeamcityProgressListener(){} + + void startTest(const std::string& test); + void addFailure(const TestFailure &failure); + void endTest(const std::string& test); + void startSuite(const std::string& test); + void endSuite(const std::string& test); + + private: + std::string flowid; + + // Prevents the use of the copy constructor. + TeamcityProgressListener(const TeamcityProgressListener ©); + + // Prevents the use of the copy operator. + void operator =(const TeamcityProgressListener ©); + }; + +} \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_messages.cpp b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_messages.cpp new file mode 100755 index 000000000..6f88d1ee9 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_messages.cpp @@ -0,0 +1,174 @@ +/* Copyright 2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $Revision: 88625 $ +*/ + +#include +#include + +#include "teamcity_messages.h" + +using namespace std; + +namespace JetBrains { + +std::string getFlowIdFromEnvironment() { + const char *flowId = getenv("TEAMCITY_PROCESS_FLOW_ID"); + return flowId == NULL ? "" : flowId; +} + +bool underTeamcity() { + return getenv("TEAMCITY_PROJECT_NAME") != NULL; +} + +TeamcityMessages::TeamcityMessages() +: m_out(&cout) +{} + +void TeamcityMessages::setOutput(ostream &out) { + m_out = &out; +} + +string TeamcityMessages::escape(string s) { + string result; + + for (size_t i = 0; i < s.length(); i++) { + char c = s[i]; + + switch (c) { + case '\n': result.append("|n"); break; + case '\r': result.append("|r"); break; + case '\'': result.append("|'"); break; + case '|': result.append("||"); break; + case ']': result.append("|]"); break; + default: result.append(&c, 1); + } + } + + return result; +} + +void TeamcityMessages::openMsg(const string &name) { + // endl for http://jetbrains.net/tracker/issue/TW-4412 + *m_out << endl << "##teamcity[" << name; +} + +void TeamcityMessages::closeMsg() { + *m_out << "]"; + // endl for http://jetbrains.net/tracker/issue/TW-4412 + *m_out << endl; + m_out->flush(); +} + +void TeamcityMessages::writeProperty(string name, string value) { + *m_out << " " << name << "='" << escape(value) << "'"; +} + +void TeamcityMessages::suiteStarted(string name, string flowid) { + openMsg("testSuiteStarted"); + writeProperty("name", name); + if(flowid.length() > 0) { + writeProperty("flowId", flowid); + } + + closeMsg(); +} + +void TeamcityMessages::suiteFinished(string name, string flowid) { + openMsg("testSuiteFinished"); + writeProperty("name", name); + if(flowid.length() > 0) { + writeProperty("flowId", flowid); + } + + closeMsg(); +} + +void TeamcityMessages::testStarted(string name, string flowid) { + openMsg("testStarted"); + writeProperty("name", name); + if(flowid.length() > 0) { + writeProperty("flowId", flowid); + } + + closeMsg(); +} + +void TeamcityMessages::testFinished(string name, int durationMs, string flowid) { + openMsg("testFinished"); + + writeProperty("name", name); + + if(flowid.length() > 0) { + writeProperty("flowId", flowid); + } + + if(durationMs >= 0) { + stringstream out; + out << durationMs; + writeProperty("duration", out.str()); + } + + closeMsg(); +} + +void TeamcityMessages::testFailed(string name, string message, string details, string flowid) { + openMsg("testFailed"); + writeProperty("name", name); + writeProperty("message", message); + writeProperty("details", details); + if(flowid.length() > 0) { + writeProperty("flowId", flowid); + } + + closeMsg(); +} + +void TeamcityMessages::testIgnored(std::string name, std::string message, string flowid) { + openMsg("testIgnored"); + writeProperty("name", name); + writeProperty("message", message); + if(flowid.length() > 0) { + writeProperty("flowId", flowid); + } + + closeMsg(); +} + +void TeamcityMessages::messageError(const std::string& text) +{ + openMsg("message"); + writeProperty("text", text); + writeProperty("status", "ERROR"); + closeMsg(); +} + +void TeamcityMessages::messageWarning(const std::string& text) +{ + openMsg("message"); + writeProperty("text", text); + writeProperty("status", "WARNING"); + closeMsg(); +} + +void TeamcityMessages::messageNormal(const std::string& text) +{ + openMsg("message"); + writeProperty("text", text); + writeProperty("status", "NORMAL"); + closeMsg(); +} + +} diff --git a/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_messages.h b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_messages.h new file mode 100755 index 000000000..36ad80797 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/teamcity/teamcity_messages.h @@ -0,0 +1,59 @@ +/* Copyright 2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $Revision: 88625 $ +*/ + +#ifndef H_TEAMCITY_MESSAGES +#define H_TEAMCITY_MESSAGES + +#include +#include + +namespace JetBrains { + +std::string getFlowIdFromEnvironment(); +bool underTeamcity(); + +class TeamcityMessages { + std::ostream *m_out; + +protected: + std::string escape(std::string s); + + void openMsg(const std::string &name); + void writeProperty(std::string name, std::string value); + void closeMsg(); + +public: + TeamcityMessages(); + + void setOutput(std::ostream &); + + void suiteStarted(std::string name, std::string flowid = ""); + void suiteFinished(std::string name, std::string flowid = ""); + + void testStarted(std::string name, std::string flowid = ""); + void testFailed(std::string name, std::string message, std::string details, std::string flowid = ""); + void testIgnored(std::string name, std::string message, std::string flowid = ""); + void testFinished(std::string name, int durationMs = -1, std::string flowid = ""); + + void messageError(const std::string& text); + void messageWarning(const std::string& text); + void messageNormal(const std::string& text); +}; + +} + +#endif /* H_TEAMCITY_MESSAGES */ diff --git a/spine-cpp/spine-cpp-unit-tests/tests/CPP_InterfaceTestFixture.cpp b/spine-cpp/spine-cpp-unit-tests/tests/CPP_InterfaceTestFixture.cpp new file mode 100755 index 000000000..dde75c363 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/CPP_InterfaceTestFixture.cpp @@ -0,0 +1,48 @@ +////////////////////////////////////////////////////////////////////// +// filename: C_InterfaceTestFixture.cpp +// +// notes: There is no C++ interface! +// +///////////////////////////////////////////////////////////////////// + +#include "CPP_InterfaceTestFixture.h" + +CPP_InterfaceTestFixture::~CPP_InterfaceTestFixture() +{ + finalize(); +} + +void CPP_InterfaceTestFixture::initialize() +{ + // on a Per- Fixture Basis, before Test execution +} + +void CPP_InterfaceTestFixture::finalize() +{ + // on a Per- Fixture Basis, after all tests pass/fail +} + +void CPP_InterfaceTestFixture::setUp() +{ + // Setup on Per-Test Basis +} + +void CPP_InterfaceTestFixture::tearDown() +{ + // Tear Down on Per-Test Basis +} + +void CPP_InterfaceTestFixture::spineboyTestCase() +{ + // There is no C++ interface. +} + +void CPP_InterfaceTestFixture::raptorTestCase() +{ + // There is no C++ interface. +} + +void CPP_InterfaceTestFixture::goblinsTestCase() +{ + // No c++ interface +} diff --git a/spine-cpp/spine-cpp-unit-tests/tests/CPP_InterfaceTestFixture.h b/spine-cpp/spine-cpp-unit-tests/tests/CPP_InterfaceTestFixture.h new file mode 100755 index 000000000..8dc5fed36 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/CPP_InterfaceTestFixture.h @@ -0,0 +1,47 @@ +#pragma once +////////////////////////////////////////////////////////////////////// +// filename: C_InterfaceTestFixture.h +// +// purpose: Run example animations for regression testing +// on "C++" interface to make sure modifications to "C" +// interface doesn't cause memory leaks or regression +// errors. +///////////////////////////////////////////////////////////////////// + +#include "MiniCppUnit.hxx" +#include "TestOptions.h" + +class CPP_InterfaceTestFixture : public TestFixture < CPP_InterfaceTestFixture > +{ +public: + TEST_FIXTURE(CPP_InterfaceTestFixture){ + TEST_CASE(spineboyTestCase); + TEST_CASE(raptorTestCase); + TEST_CASE(goblinsTestCase); + + initialize(); + } + + virtual ~CPP_InterfaceTestFixture(); + + ////////////////////////////////////////////////////////////////////////// + // Test Cases + ////////////////////////////////////////////////////////////////////////// +public: + void spineboyTestCase(); + void raptorTestCase(); + void goblinsTestCase(); + + ////////////////////////////////////////////////////////////////////////// + // test fixture setup + ////////////////////////////////////////////////////////////////////////// + void initialize(); + void finalize(); +public: + virtual void setUp(); + virtual void tearDown(); + +}; +#if defined(gForceAllTests) || defined(gCPPInterfaceTestFixture) +REGISTER_FIXTURE(CPP_InterfaceTestFixture); +#endif diff --git a/spine-cpp/spine-cpp-unit-tests/tests/C_InterfaceTestFixture.cpp b/spine-cpp/spine-cpp-unit-tests/tests/C_InterfaceTestFixture.cpp new file mode 100755 index 000000000..2c1cb61bc --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/C_InterfaceTestFixture.cpp @@ -0,0 +1,175 @@ +#include "C_InterfaceTestFixture.h" +#include "SpineEventMonitor.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "KMemory.h" // last include + +#define SPINEBOY_JSON "testdata/spineboy/spineboy-ess.json" +#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas" + +#define RAPTOR_JSON "testdata/raptor/raptor-pro.json" +#define RAPTOR_ATLAS "testdata/raptor/raptor.atlas" + +#define GOBLINS_JSON "testdata/goblins/goblins-pro.json" +#define GOBLINS_ATLAS "testdata/goblins/goblins.atlas" + +#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution + +using namespace Spine; + +void C_InterfaceTestFixture::setUp() +{ +} + +void C_InterfaceTestFixture::tearDown() +{ +} + +static SkeletonData* readSkeletonJsonData(const char* filename, Atlas* atlas) +{ + Vector atlasArray; + atlasArray.push_back(atlas); + + SkeletonJson* skeletonJson = NEW(SkeletonJson); + new (skeletonJson) SkeletonJson(atlasArray); + ASSERT(skeletonJson != 0); + + SkeletonData* skeletonData = skeletonJson->readSkeletonDataFile(filename); + ASSERT(skeletonData != 0); + + DESTROY(SkeletonJson, skeletonJson); + + return skeletonData; +} + +typedef std::vector AnimList; + +static size_t enumerateAnimations(AnimList& outList, SkeletonData* skeletonData) +{ + if (skeletonData) + { + for (int n = 0; n < skeletonData->getAnimations().size(); n++) + { + outList.push_back(skeletonData->getAnimations()[n]->getName()); + } + } + + return outList.size(); +} + +class MyTextureLoader : public TextureLoader +{ + virtual void load(AtlasPage& page, std::string path) + { + page.rendererObject = NULL; + page.width = 2048; + page.height = 2048; + } + + virtual void unload(void* texture) + { + // TODO + } +}; + +static void testRunner(const char* jsonName, const char* atlasName) +{ + /////////////////////////////////////////////////////////////////////////// + // Global Animation Information + MyTextureLoader myTextureLoader; + Atlas* atlas = NEW(Atlas); + new (atlas) Atlas(atlasName, myTextureLoader); + ASSERT(atlas != 0); + + SkeletonData* skeletonData = readSkeletonJsonData(jsonName, atlas); + ASSERT(skeletonData != 0); + + AnimationStateData* stateData = NEW(AnimationStateData); + new (stateData) AnimationStateData(skeletonData); + ASSERT(stateData != 0); + stateData->setDefaultMix(0.2f); // force mixing + + /////////////////////////////////////////////////////////////////////////// + // Animation Instance + Skeleton* skeleton = NEW(Skeleton); + new (skeleton) Skeleton(skeletonData); + ASSERT(skeleton != 0); + + AnimationState* state = NEW(AnimationState); + new (state) AnimationState(stateData); + ASSERT(state != 0); + + /////////////////////////////////////////////////////////////////////////// + // Run animation + skeleton->setToSetupPose(); + SpineEventMonitor eventMonitor(state); + + AnimList anims; // Let's chain all the animations together as a test + size_t count = enumerateAnimations(anims, skeletonData); + if (count > 0) + { + state->setAnimation(0, anims[0].c_str(), false); + } + + for (size_t i = 1; i < count; ++i) + { + state->addAnimation(0, anims[i].c_str(), false, 0.0f); + } + + // Run Loop + for (int i = 0; i < MAX_RUN_TIME && eventMonitor.isAnimationPlaying(); ++i) + { + const float timeSlice = 1.0f / 60.0f; + skeleton->update(timeSlice); + state->update(timeSlice); + state->apply(*skeleton); + } + + /////////////////////////////////////////////////////////////////////////// + // Dispose Instance + DESTROY(Skeleton, skeleton); + DESTROY(AnimationState, state); + + /////////////////////////////////////////////////////////////////////////// + // Dispose Global + DESTROY(AnimationStateData, stateData); + DESTROY(SkeletonData, skeletonData); + DESTROY(Atlas, atlas); +} + +void C_InterfaceTestFixture::spineboyTestCase() +{ + testRunner(SPINEBOY_JSON, SPINEBOY_ATLAS); +} + +void C_InterfaceTestFixture::raptorTestCase() +{ + testRunner(RAPTOR_JSON, RAPTOR_ATLAS); +} + +void C_InterfaceTestFixture::goblinsTestCase() +{ + testRunner(GOBLINS_JSON, GOBLINS_ATLAS); +} diff --git a/spine-cpp/spine-cpp-unit-tests/tests/C_InterfaceTestFixture.h b/spine-cpp/spine-cpp-unit-tests/tests/C_InterfaceTestFixture.h new file mode 100755 index 000000000..2f30d2bea --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/C_InterfaceTestFixture.h @@ -0,0 +1,33 @@ +#pragma once +////////////////////////////////////////////////////////////////////// +// filename: C_InterfaceTestFixture.h +// +// purpose: Run example animations for regression testing +// on "C" interface +///////////////////////////////////////////////////////////////////// + +#include "TestOptions.h" +#include "MiniCppUnit.hxx" + +class C_InterfaceTestFixture : public TestFixture +{ +public: + TEST_FIXTURE(C_InterfaceTestFixture) + { + // enable/disable individual tests here + TEST_CASE(spineboyTestCase); + TEST_CASE(raptorTestCase); + TEST_CASE(goblinsTestCase); + } + +public: + virtual void setUp(); + virtual void tearDown(); + + void spineboyTestCase(); + void raptorTestCase(); + void goblinsTestCase(); +}; +#if defined(gForceAllTests) || defined(gCInterfaceTestFixture) +REGISTER_FIXTURE(C_InterfaceTestFixture); +#endif \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/tests/EmptyTestFixture.cpp b/spine-cpp/spine-cpp-unit-tests/tests/EmptyTestFixture.cpp new file mode 100755 index 000000000..d2260b7d5 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/EmptyTestFixture.cpp @@ -0,0 +1,25 @@ +#include "EmptyTestFixture.h" + +#include "KMemory.h" // Last include + + +void EmptyTestFixture::setUp() +{ +} + +void EmptyTestFixture::tearDown() +{ +} + +void EmptyTestFixture::emptyTestCase_1() +{ + // char* pLeak = new char[256]; // test leak detector +} + +void EmptyTestFixture::emptyTestCase_2() +{ +} + +void EmptyTestFixture::emptyTestCase_3() +{ +} diff --git a/spine-cpp/spine-cpp-unit-tests/tests/EmptyTestFixture.h b/spine-cpp/spine-cpp-unit-tests/tests/EmptyTestFixture.h new file mode 100755 index 000000000..c11cfd5f2 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/EmptyTestFixture.h @@ -0,0 +1,26 @@ +#pragma once +#include "TestOptions.h" +#include "MiniCppUnit.hxx" + +class EmptyTestFixture : public TestFixture +{ +public: + TEST_FIXTURE(EmptyTestFixture) + { + // enable/disable individual tests here + TEST_CASE(emptyTestCase_1); + TEST_CASE(emptyTestCase_2); + TEST_CASE(emptyTestCase_3); + } + +public: + virtual void setUp(); + virtual void tearDown(); + + void emptyTestCase_1(); + void emptyTestCase_2(); + void emptyTestCase_3(); +}; +#if defined(gForceAllTests) || defined(gEmptyTestFixture) +REGISTER_FIXTURE(EmptyTestFixture); +#endif \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/tests/MemoryTestFixture.cpp b/spine-cpp/spine-cpp-unit-tests/tests/MemoryTestFixture.cpp new file mode 100755 index 000000000..a59822a43 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/MemoryTestFixture.cpp @@ -0,0 +1,338 @@ +//#include +//#include "MemoryTestFixture.h" +//#include "SpineEventMonitor.h" +// +//#include "KMemory.h" // last include +// +//#define SPINEBOY_JSON "testdata/spineboy/spineboy-ess.json" +//#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas" +// +//#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution +// +//MemoryTestFixture::~MemoryTestFixture() +//{ +// finalize(); +//} +// +//void MemoryTestFixture::initialize() +//{ +// // on a Per- Fixture Basis, before Test execution +//} +// +//void MemoryTestFixture::finalize() +//{ +// // on a Per- Fixture Basis, after all tests pass/fail +//} +// +//void MemoryTestFixture::setUp() +//{ +// // Setup on Per-Test Basis +//} +// +//void MemoryTestFixture::tearDown() +//{ +// // Tear Down on Per-Test Basis +//} +// +// +//////////////////////////////////////////////////////////////////////////// +//// Helper methods +//static spSkeletonData* readSkeletonJsonData(const char* filename, spAtlas* atlas) { +// spSkeletonJson* json = spSkeletonJson_create(atlas); +// ASSERT(json != 0); +// +// spSkeletonData* skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename); +// ASSERT(skeletonData != 0); +// +// spSkeletonJson_dispose(json); +// return skeletonData; +//} +// +//static void LoadSpineboyExample(spAtlas* &atlas, spSkeletonData* &skeletonData, spAnimationStateData* &stateData, spSkeleton* &skeleton, spAnimationState* &state) +//{ +// /////////////////////////////////////////////////////////////////////////// +// // Global Animation Information +// atlas = spAtlas_createFromFile(SPINEBOY_ATLAS, 0); +// ASSERT(atlas != 0); +// +// skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas); +// ASSERT(skeletonData != 0); +// +// stateData = spAnimationStateData_create(skeletonData); +// ASSERT(stateData != 0); +// stateData->defaultMix = 0.4f; // force mixing +// +// /////////////////////////////////////////////////////////////////////////// +// // Animation Instance +// skeleton = spSkeleton_create(skeletonData); +// ASSERT(skeleton != 0); +// +// state = spAnimationState_create(stateData); +// ASSERT(state != 0); +//} +// +//static void DisposeAll(spSkeleton* skeleton, spAnimationState* state, spAnimationStateData* stateData, spSkeletonData* skeletonData, spAtlas* atlas) +//{ +// /////////////////////////////////////////////////////////////////////////// +// // Dispose Instance +// spSkeleton_dispose(skeleton); +// spAnimationState_dispose(state); +// +// /////////////////////////////////////////////////////////////////////////// +// // Dispose Global +// spAnimationStateData_dispose(stateData); +// spSkeletonData_dispose(skeletonData); +// spAtlas_dispose(atlas); +//} +// +// +//////////////////////////////////////////////////////////////////////////// +//// Reproduce Memory leak as described in Issue #776 +//// https://github.com/EsotericSoftware/spine-runtimes/issues/776 +//void MemoryTestFixture::reproduceIssue_776() +//{ +// spAtlas* atlas = 0; +// spSkeletonData* skeletonData = 0; +// spAnimationStateData* stateData = 0; +// spSkeleton* skeleton = 0; +// spAnimationState* state = 0; +// +// ////////////////////////////////////////////////////////////////////////// +// // Initialize Animations +// LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state); +// +// /////////////////////////////////////////////////////////////////////////// +// // Run animation +// spSkeleton_setToSetupPose(skeleton); +// InterruptMonitor eventMonitor(state); +// //eventMonitor.SetDebugLogging(true); +// +// // Interrupt the animation on this specific sequence of spEventType(s) +// eventMonitor +// .AddInterruptEvent(SP_ANIMATION_INTERRUPT, "jump") +// .AddInterruptEvent(SP_ANIMATION_START); +// +// spAnimationState_setAnimationByName(state, 0, "walk", true); +// spAnimationState_addAnimationByName(state, 0, "jump", false, 0.0f); +// spAnimationState_addAnimationByName(state, 0, "run", true, 0.0f); +// spAnimationState_addAnimationByName(state, 0, "jump", false, 3.0f); +// spAnimationState_addAnimationByName(state, 0, "walk", true, 0.0f); +// spAnimationState_addAnimationByName(state, 0, "idle", false, 1.0f); +// +// for (int i = 0; i < MAX_RUN_TIME && eventMonitor.isAnimationPlaying(); ++i) { +// const float timeSlice = 1.0f / 60.0f; +// spSkeleton_update(skeleton, timeSlice); +// spAnimationState_update(state, timeSlice); +// spAnimationState_apply(state, skeleton); +// } +// +// ////////////////////////////////////////////////////////////////////////// +// // Cleanup Animations +// DisposeAll(skeleton, state, stateData, skeletonData, atlas); +//} +// +//void MemoryTestFixture::reproduceIssue_777() +//{ +// spAtlas* atlas = 0; +// spSkeletonData* skeletonData = 0; +// spAnimationStateData* stateData = 0; +// spSkeleton* skeleton = 0; +// spAnimationState* state = 0; +// +// ////////////////////////////////////////////////////////////////////////// +// // Initialize Animations +// LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state); +// +// /////////////////////////////////////////////////////////////////////////// +// // Run animation +// spSkeleton_setToSetupPose(skeleton); +// SpineEventMonitor eventMonitor(state); +// //eventMonitor.SetDebugLogging(true); +// +// // Set Animation and Play for 5 frames +// spAnimationState_setAnimationByName(state, 0, "walk", true); +// for (int i = 0; i < 5; ++i) { +// const float timeSlice = 1.0f / 60.0f; +// spSkeleton_update(skeleton, timeSlice); +// spAnimationState_update(state, timeSlice); +// spAnimationState_apply(state, skeleton); +// } +// +// // Change animation twice in a row +// spAnimationState_setAnimationByName(state, 0, "walk", false); +// spAnimationState_setAnimationByName(state, 0, "run", false); +// +// // run normal update +// for (int i = 0; i < 5; ++i) { +// const float timeSlice = 1.0f / 60.0f; +// spSkeleton_update(skeleton, timeSlice); +// spAnimationState_update(state, timeSlice); +// spAnimationState_apply(state, skeleton); +// } +// +// // Now we'd lose mixingFrom (the first "walk" entry we set above) and should leak +// spAnimationState_setAnimationByName(state, 0, "run", false); +// +// ////////////////////////////////////////////////////////////////////////// +// // Cleanup Animations +// DisposeAll(skeleton, state, stateData, skeletonData, atlas); +//} +// +//spSkeleton* skeleton = 0; +//static void spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event) +//{ +// if (type == SP_ANIMATION_COMPLETE) +// { +// spAnimationState_setAnimationByName(state, 0, "walk", false); +// spAnimationState_update(state, 0); +// spAnimationState_apply(state, skeleton); +// } +//} +// +//void MemoryTestFixture::reproduceIssue_Loop() +//{ +// spAtlas* atlas = 0; +// spSkeletonData* skeletonData = 0; +// spAnimationStateData* stateData = 0; +// spAnimationState* state = 0; +// +// ////////////////////////////////////////////////////////////////////////// +// // Initialize Animations +// LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state); +// +// /////////////////////////////////////////////////////////////////////////// +// +// if (state) +// state->listener = (spAnimationStateListener)&spineAnimStateHandler; +// +// spAnimationState_setAnimationByName(state, 0, "walk", false); +// +// // run normal update +// for (int i = 0; i < 50; ++i) { +// const float timeSlice = 1.0f / 60.0f; +// spSkeleton_update(skeleton, timeSlice); +// spAnimationState_update(state, timeSlice); +// spAnimationState_apply(state, skeleton); +// } +// +// DisposeAll(skeleton, state, stateData, skeletonData, atlas); +//} +// +//void MemoryTestFixture::triangulator() { +// spTriangulator* triangulator = spTriangulator_create(); +// spFloatArray* polygon = spFloatArray_create(16); +// spFloatArray_add(polygon, 0); +// spFloatArray_add(polygon, 0); +// spFloatArray_add(polygon, 100); +// spFloatArray_add(polygon, 0); +// spFloatArray_add(polygon, 100); +// spFloatArray_add(polygon, 100); +// spFloatArray_add(polygon, 0); +// spFloatArray_add(polygon, 100); +// +// spShortArray* triangles = spTriangulator_triangulate(triangulator, polygon); +// ASSERT(triangles->size == 6); +// ASSERT(triangles->items[0] == 3); +// ASSERT(triangles->items[1] == 0); +// ASSERT(triangles->items[2] == 1); +// ASSERT(triangles->items[3] == 3); +// ASSERT(triangles->items[4] == 1); +// ASSERT(triangles->items[5] == 2); +// +// spArrayFloatArray* polys = spTriangulator_decompose(triangulator, polygon, triangles); +// ASSERT(polys->size == 1); +// ASSERT(polys->items[0]->size == 8); +// ASSERT(polys->items[0]->items[0] == 0); +// ASSERT(polys->items[0]->items[1] == 100); +// ASSERT(polys->items[0]->items[2] == 0); +// ASSERT(polys->items[0]->items[3] == 0); +// ASSERT(polys->items[0]->items[4] == 100); +// ASSERT(polys->items[0]->items[5] == 0); +// ASSERT(polys->items[0]->items[6] == 100); +// ASSERT(polys->items[0]->items[7] == 100); +// +// spFloatArray_dispose(polygon); +// spTriangulator_dispose(triangulator); +//} +// +//void MemoryTestFixture::skeletonClipper() { +// spSkeletonClipping* clipping = spSkeletonClipping_create(); +// +// spBoneData* boneData = spBoneData_create(0, "bone", 0); +// spBone* bone = spBone_create(boneData, 0, 0); +// CONST_CAST(float, bone->a) = 1; +// CONST_CAST(float, bone->b) = 0; +// CONST_CAST(float, bone->c) = 0; +// CONST_CAST(float, bone->d) = 1; +// CONST_CAST(float, bone->worldX) = 0; +// CONST_CAST(float, bone->worldY) = 0; +// spSlotData* slotData = spSlotData_create(0, "slot", 0); +// spSlot* slot = spSlot_create(slotData, bone); +// spClippingAttachment* clip = spClippingAttachment_create("clipping"); +// clip->endSlot = slotData; +// clip->super.worldVerticesLength = 4 * 2; +// clip->super.verticesCount = 4; +// clip->super.vertices = MALLOC(float, 4 * 8); +// clip->super.vertices[0] = 0; +// clip->super.vertices[1] = 50; +// clip->super.vertices[2] = 100; +// clip->super.vertices[3] = 50; +// clip->super.vertices[4] = 100; +// clip->super.vertices[5] = 70; +// clip->super.vertices[6] = 0; +// clip->super.vertices[7] = 70; +// +// spSkeletonClipping_clipStart(clipping, slot, clip); +// +// spFloatArray* vertices = spFloatArray_create(16); +// spFloatArray_add(vertices, 0); +// spFloatArray_add(vertices, 0); +// spFloatArray_add(vertices, 100); +// spFloatArray_add(vertices, 0); +// spFloatArray_add(vertices, 50); +// spFloatArray_add(vertices, 150); +// spFloatArray* uvs = spFloatArray_create(16); +// spFloatArray_add(uvs, 0); +// spFloatArray_add(uvs, 0); +// spFloatArray_add(uvs, 1); +// spFloatArray_add(uvs, 0); +// spFloatArray_add(uvs, 0.5f); +// spFloatArray_add(uvs, 1); +// spUnsignedShortArray* indices = spUnsignedShortArray_create(16); +// spUnsignedShortArray_add(indices, 0); +// spUnsignedShortArray_add(indices, 1); +// spUnsignedShortArray_add(indices, 2); +// +// spSkeletonClipping_clipTriangles(clipping, vertices->items, vertices->size, indices->items, indices->size, uvs->items, 2); +// +// float expectedVertices[8] = { 83.333328, 50.000000, 76.666664, 70.000000, 23.333334, 70.000000, 16.666672, 50.000000 }; +// ASSERT(clipping->clippedVertices->size == 8); +// for (int i = 0; i < clipping->clippedVertices->size; i++) { +// ASSERT(ABS(clipping->clippedVertices->items[i] - expectedVertices[i]) < 0.001); +// } +// +// float expectedUVs[8] = { 0.833333f, 0.333333, 0.766667, 0.466667, 0.233333, 0.466667, 0.166667, 0.333333 }; +// ASSERT(clipping->clippedUVs->size == 8); +// for (int i = 0; i < clipping->clippedUVs->size; i++) { +// ASSERT(ABS(clipping->clippedUVs->items[i] - expectedUVs[i]) < 0.001); +// } +// +// short expectedIndices[6] = { 0, 1, 2, 0, 2, 3 }; +// ASSERT(clipping->clippedTriangles->size == 6); +// for (int i = 0; i < clipping->clippedTriangles->size; i++) { +// ASSERT(clipping->clippedTriangles->items[i] == expectedIndices[i]); +// } +// +// spFloatArray_dispose(vertices); +// spFloatArray_dispose(uvs); +// spUnsignedShortArray_dispose(indices); +// +// spSlotData_dispose(slotData); +// spSlot_dispose(slot); +// spBoneData_dispose(boneData); +// spBone_dispose(bone); +// _spClippingAttachment_dispose(SUPER(SUPER(clip))); +// spSkeletonClipping_dispose(clipping); +//} +// +// diff --git a/spine-cpp/spine-cpp-unit-tests/tests/MemoryTestFixture.h b/spine-cpp/spine-cpp-unit-tests/tests/MemoryTestFixture.h new file mode 100755 index 000000000..f45c35c86 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/MemoryTestFixture.h @@ -0,0 +1,50 @@ +////////////////////////////////////////////////////////////////////// +// filename: MemoryTestFixture.h +// +// purpose: Reproduce Memory Error/Leak Bugs to help debug +// and for regression testing +///////////////////////////////////////////////////////////////////// + +#pragma once +#include "MiniCppUnit.hxx" +#include "TestOptions.h" + +class MemoryTestFixture : public TestFixture < MemoryTestFixture > +{ +public: + TEST_FIXTURE(MemoryTestFixture){ + + // Comment out here to disable individual test cases + TEST_CASE(reproduceIssue_776); + TEST_CASE(reproduceIssue_777); + TEST_CASE(reproduceIssue_Loop); + TEST_CASE(triangulator); + TEST_CASE(skeletonClipper); + + initialize(); + } + + virtual ~MemoryTestFixture(); + + ////////////////////////////////////////////////////////////////////////// + // Test Cases + ////////////////////////////////////////////////////////////////////////// +public: + void reproduceIssue_776(); + void reproduceIssue_777(); + void reproduceIssue_Loop(); // http://esotericsoftware.com/forum/spine-c-3-5-animation-jerking-7451 + void triangulator(); + void skeletonClipper(); + + ////////////////////////////////////////////////////////////////////////// + // test fixture setup + ////////////////////////////////////////////////////////////////////////// + void initialize(); + void finalize(); +public: + virtual void setUp(); + virtual void tearDown(); +}; +#if defined(gForceAllTests) || defined(gMemoryTestFixture) +REGISTER_FIXTURE(MemoryTestFixture); +#endif \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/tests/SpineEventMonitor.cpp b/spine-cpp/spine-cpp-unit-tests/tests/SpineEventMonitor.cpp new file mode 100755 index 000000000..9ea3e4d6e --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/SpineEventMonitor.cpp @@ -0,0 +1,176 @@ +#include "SpineEventMonitor.h" + +#include "KString.h" + +#include "KMemory.h" // Last include + +SpineEventMonitor::SpineEventMonitor(AnimationState* _pAnimationState /*= nullptr*/) +{ + bLogging = false; + RegisterListener(_pAnimationState); +} + +SpineEventMonitor::~SpineEventMonitor() +{ + pAnimState = 0; +} + +void SpineEventMonitor::RegisterListener(AnimationState * _pAnimationState) +{ +// if (_pAnimationState) +// { +// _pAnimationState->rendererObject = this; +// _pAnimationState->listener = (spAnimationStateListener)&SpineEventMonitor::spineAnimStateHandler; +// } +// pAnimState = _pAnimationState; +} + +bool SpineEventMonitor::isAnimationPlaying() +{ +// if (pAnimState) +// { +// return spAnimationState_getCurrent(pAnimState, 0) != 0; +// } + return false; +} + +void SpineEventMonitor::spineAnimStateHandler(AnimationState * state, int type, spTrackEntry * entry, spEvent * event) +{ +// if (state && state->rendererObject) +// { +// SpineEventMonitor* pEventMonitor = (SpineEventMonitor*)state->rendererObject; +// pEventMonitor->OnSpineAnimationStateEvent(state, type, entry, event); +// } +} + +void SpineEventMonitor::OnSpineAnimationStateEvent(AnimationState * state, int type, spTrackEntry * trackEntry, spEvent * event) +{ +// const char* eventName = 0; +// if (state == pAnimState) +// { +// // only monitor ours +// switch(type) +// { +// case SP_ANIMATION_START: eventName = "SP_ANIMATION_START"; break; +// case SP_ANIMATION_INTERRUPT: eventName = "SP_ANIMATION_INTERRUPT"; break; +// case SP_ANIMATION_END: eventName = "SP_ANIMATION_END"; break; +// case SP_ANIMATION_COMPLETE: eventName = "SP_ANIMATION_COMPLETE"; break; +// case SP_ANIMATION_DISPOSE: eventName = "SP_ANIMATION_DISPOSE"; break; +// case SP_ANIMATION_EVENT: eventName = "SP_ANIMATION_EVENT"; break; +// default: +// break; +// } +// +// if (bLogging && eventName && trackEntry && trackEntry->animation && trackEntry->animation->name) +// KOutputDebug(DEBUGLVL, "[%s : '%s']\n", eventName, trackEntry->animation->name);//*/ +// } +} + +InterruptMonitor::InterruptMonitor(AnimationState * _pAnimationState): + SpineEventMonitor(_pAnimationState) +{ + bForceInterrupt = false; + mEventStackCursor = 0; // cursor used to track events +} + +bool InterruptMonitor::isAnimationPlaying() +{ + return !bForceInterrupt && SpineEventMonitor::isAnimationPlaying(); +} + +// Stops the animation on any occurance of the spEventType +InterruptMonitor& InterruptMonitor::AddInterruptEvent(int theEventType) +{ + InterruptEvent ev; + ev.mEventType = theEventType; + mEventStack.push_back(ev); + return *this; +} + +// Stops the animation when the [spEventType : 'animationName'] occurs +InterruptMonitor& InterruptMonitor::AddInterruptEvent(int theEventType, const std::string & theAnimationName) +{ + InterruptEvent ev; + ev.mEventType = theEventType; + ev.mAnimName = theAnimationName; + mEventStack.push_back(ev); + return *this; +} + +// stops the first encounter of spEventType on the specified TrackEntry +InterruptMonitor& InterruptMonitor::AddInterruptEvent(int theEventType, spTrackEntry * theTrackEntry) +{ + InterruptEvent ev; + ev.mEventType = theEventType; + ev.mTrackEntry = theTrackEntry; + mEventStack.push_back(ev); + return *this; +} + +// Stops on the first SP_ANIMATION_EVENT with the string payload of 'theEventTriggerName' +InterruptMonitor& InterruptMonitor::AddInterruptEventTrigger(const std::string & theEventTriggerName) +{ + InterruptEvent ev; +// ev.mEventType = SP_ANIMATION_EVENT; + ev.mEventName = theEventTriggerName; + mEventStack.push_back(ev); + return *this; +} + +void InterruptMonitor::OnSpineAnimationStateEvent(AnimationState * state, int type, spTrackEntry * trackEntry, spEvent * event) +{ + SpineEventMonitor::OnSpineAnimationStateEvent(state, type, trackEntry, event); + + if (mEventStackCursor < mEventStack.size()) + { + if (mEventStack[mEventStackCursor].matches(state, type, trackEntry, event)) + { + ++mEventStackCursor; + } + + if (mEventStackCursor >= mEventStack.size()) + { + bForceInterrupt = true; + OnMatchingComplete(); + } + } +} + +inline bool InterruptMonitor::InterruptEvent::matches(AnimationState * state, int type, spTrackEntry * trackEntry, spEvent * event) +{ + // Must match spEventType {SP_ANIMATION_START, SP_ANIMATION_INTERRUPT, SP_ANIMATION_END, SP_ANIMATION_COMPLETE, SP_ANIMATION_DISPOSE, SP_ANIMATION_EVENT } +// if (mEventType == type) +// { +// // Looking for specific TrackEntry by pointer +// if (mTrackEntry != 0) +// { +// return mTrackEntry == trackEntry; +// } +// +// // looking for Animation Track by name +// if (!mAnimName.empty()) +// { +// if (trackEntry && trackEntry->animation && trackEntry->animation->name) +// { +// if (CompareNoCase(trackEntry->animation->name, mAnimName) == 0) +// { +// return true; +// } +// } +// return false; +// } +// +// // looking for Event String Text +// if (!mEventName.empty()) +// { +// if (event && event->stringValue) +// { +// return (CompareNoCase(event->stringValue, mEventName) == 0); +// } +// return false; +// } +// +// return true; // waiting for ANY spEventType that matches +// } + return false; +} diff --git a/spine-cpp/spine-cpp-unit-tests/tests/SpineEventMonitor.h b/spine-cpp/spine-cpp-unit-tests/tests/SpineEventMonitor.h new file mode 100755 index 000000000..df177c03c --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/SpineEventMonitor.h @@ -0,0 +1,126 @@ +////////////////////////////////////////////////////////////////////// +// filename: SpineEventMonitor.h +// +// purpose: Monitor spAnimationState Events +///////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +// forward declarations +typedef struct spAnimationState spAnimationState; +typedef struct spTrackEntry spTrackEntry; +typedef struct spEvent spEvent; + +#include + +using namespace Spine; + +////////////////////////////////////////////////////////////////////// +// class: SpineEventMonitor +// +// purpose: Monitor spAnimationState Events and report when there +// are no more spTrackEntry(s) waiting to play on track 0; +// +// Also allows for debug printing of Events to console. +///////////////////////////////////////////////////////////////////// +class SpineEventMonitor +{ +public: + SpineEventMonitor(AnimationState* _pAnimationState = 0); + virtual ~SpineEventMonitor(); + + void RegisterListener(AnimationState* _pAnimationState); + + void SetDebugLogging(bool val) { bLogging = val; } + bool GetDebugLogging() { return bLogging; } + + virtual bool isAnimationPlaying(); + +protected: + static void spineAnimStateHandler(AnimationState* state, int type, spTrackEntry* entry, spEvent* event); + virtual void OnSpineAnimationStateEvent(AnimationState* state, int type, spTrackEntry* trackEntry, spEvent* event); + +protected: + AnimationState *pAnimState; + bool bLogging; +}; + +////////////////////////////////////////////////////////////////////// +// class: InterruptMonitor +// +// purpose: Allows a programmer to interrupt/stop the updating +// of an animation based on a specific sequence of +// events generated by the animation. +///////////////////////////////////////////////////////////////////// +class InterruptMonitor : public SpineEventMonitor +{ +private: + struct InterruptEvent + { + InterruptEvent() { + mEventType = -1; // invalid + mTrackEntry = 0; + } + + bool matches(AnimationState* state, int type, spTrackEntry* trackEntry, spEvent* event); + + std::string mAnimName; + int mEventType; + spTrackEntry* mTrackEntry; + std::string mEventName; + }; + typedef std::vector InterruptEventStack; + + +public: + InterruptMonitor(AnimationState* _pAnimationState = 0); + ~InterruptMonitor() {} + + virtual bool isAnimationPlaying() override; + +public: + InterruptMonitor& AddInterruptEvent(int theEventType); + InterruptMonitor& AddInterruptEvent(int theEventType, const std::string& theAnimationName); + InterruptMonitor& AddInterruptEvent(int theEventType, spTrackEntry* theTrackEntry); + InterruptMonitor& AddInterruptEventTrigger(const std::string& theEventTriggerName); + +protected: + virtual void OnSpineAnimationStateEvent(AnimationState* state, int type, spTrackEntry* trackEntry, spEvent* event) override; + virtual void OnMatchingComplete() {} + +protected: + bool bForceInterrupt; + InterruptEventStack mEventStack; // must match these events in this order + size_t mEventStackCursor; +}; + +/* + +EXAMPLE +======= + +SpineEventMonitor eventMonitor(state); +eventMonitor.SetDebugLogging(true); + +while(eventMonitor.isAnimationPlaying()){ + // update... +} + + + +EXAMPLE +======= + +InterruptMonitor eventMonitor(state); +eventMonitor.SetDebugLogging(true); + +// Interrupt the animation on this specific sequence of spEventType(s) +eventMonitor + .AddInterruptEvent(SP_ANIMATION_INTERRUPT, "jump") // First, wait for INTERRUPT signal on the 'jump' animation spTrackEntry + .AddInterruptEvent(SP_ANIMATION_START); // Then, stop on any following START signal + + +*/ diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.cpp b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.cpp new file mode 100755 index 000000000..9c2a964a9 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.cpp @@ -0,0 +1,26 @@ +#include "CPP_InterfaceTestFixture.h" + +CPP_InterfaceTestFixture::~CPP_InterfaceTestFixture() +{ + finalize(); +} + +void CPP_InterfaceTestFixture::initialize() +{ + // on a Per- Fixture Basis, before Test execution +} + +void CPP_InterfaceTestFixture::finalize() +{ + // on a Per- Fixture Basis, after all tests pass/fail +} + +void CPP_InterfaceTestFixture::setUp() +{ + // Setup on Per-Test Basis +} + +void CPP_InterfaceTestFixture::tearDown() +{ + // Tear Down on Per-Test Basis +} diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.h b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.h new file mode 100755 index 000000000..5fcb99c79 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.h @@ -0,0 +1,30 @@ +#pragma once +#include "MiniCppUnit.hxx" + +class CPP_InterfaceTestFixture : public TestFixture < CPP_InterfaceTestFixture > +{ +public: + TEST_FIXTURE(CPP_InterfaceTestFixture){ + //TEST_CASE(parseJSON); + + initialize(); + } + + virtual ~CPP_InterfaceTestFixture(); + + ////////////////////////////////////////////////////////////////////////// + // Test Cases + ////////////////////////////////////////////////////////////////////////// +public: + // void parseJSON(); + + ////////////////////////////////////////////////////////////////////////// + // test fixture setup + ////////////////////////////////////////////////////////////////////////// + void initialize(); + void finalize(); +public: + virtual void setUp(); + virtual void tearDown(); +}; +REGISTER_FIXTURE(CPP_InterfaceTestFixture); \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp new file mode 100755 index 000000000..67780f31a --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp @@ -0,0 +1,26 @@ +#include "[[FIXTURE_TYPE]].h" + +[[FIXTURE_TYPE]]::~[[FIXTURE_TYPE]]() +{ + finalize(); +} + +void [[FIXTURE_TYPE]]::initialize() +{ + // on a Per- Fixture Basis, before Test execution +} + +void [[FIXTURE_TYPE]]::finalize() +{ + // on a Per- Fixture Basis, after all tests pass/fail +} + +void [[FIXTURE_TYPE]]::setUp() +{ + // Setup on Per-Test Basis +} + +void [[FIXTURE_TYPE]]::tearDown() +{ + // Tear Down on Per-Test Basis +} diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/_TestFixture.h b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/_TestFixture.h new file mode 100755 index 000000000..3103cda85 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/_TestFixture.h @@ -0,0 +1,30 @@ +#pragma once +#include "MiniCppUnit.hxx" + +class [[FIXTURE_TYPE]] : public TestFixture < [[FIXTURE_TYPE]] > +{ +public: + TEST_FIXTURE([[FIXTURE_TYPE]]){ + //TEST_CASE(parseJSON); + + initialize(); + } + + virtual ~[[FIXTURE_TYPE]](); + + ////////////////////////////////////////////////////////////////////////// + // Test Cases + ////////////////////////////////////////////////////////////////////////// +public: + // void parseJSON(); + + ////////////////////////////////////////////////////////////////////////// + // test fixture setup + ////////////////////////////////////////////////////////////////////////// + void initialize(); + void finalize(); +public: + virtual void setUp(); + virtual void tearDown(); +}; +REGISTER_FIXTURE([[FIXTURE_TYPE]]); \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/fnr.exe b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/fnr.exe new file mode 100755 index 000000000..548fc5d91 Binary files /dev/null and b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/fnr.exe differ diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/makeFixture.bat b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/makeFixture.bat new file mode 100755 index 000000000..634154a29 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/TestFixtureGenerator/makeFixture.bat @@ -0,0 +1,17 @@ +@echo off +if "%1"=="" goto blank + +copy "_TestFixture.cpp" "%1TestFixture.cpp" +copy "_TestFixture.h" "%1TestFixture.h" + + +fnr --cl --find "[[FIXTURE_TYPE]]" --replace "%1TestFixture" --fileMask "%1TestFixture.cpp" --dir %cd% +fnr --cl --find "[[FIXTURE_TYPE]]" --replace "%1TestFixture" --fileMask "%1TestFixture.h" --dir %cd% + +goto done + +:blank +echo Usage: +echo %~n0 FixtureTypeName + +:done \ No newline at end of file diff --git a/spine-cpp/spine-cpp-unit-tests/tests/TestOptions.h b/spine-cpp/spine-cpp-unit-tests/tests/TestOptions.h new file mode 100755 index 000000000..7f00ffbb7 --- /dev/null +++ b/spine-cpp/spine-cpp-unit-tests/tests/TestOptions.h @@ -0,0 +1,32 @@ +#pragma once + +////////////////////////////////////////////////////////////////////////// +// Force all Tests to 'ON.' Use this for final 'Regression' Testing. + +//#define gForceAllTests + +//#define TURN_ON_ALL_TESTS // Comment this line out to switch to fast testing only + +#ifdef TURN_ON_ALL_TESTS +////////////////////////////////////////////////////////////////////////// +// All tests are ON by default, but you can turn off individual tests. + +#define gEmptyTestFixture +#define gCInterfaceTestFixture +#define gCPPInterfaceTestFixture +#define gMemoryTestFixture + + +#else + +////////////////////////////////////////////////////////////////////////// +// Slow Tests are disabled by default. Use this section to turn on +// Individual tests. +#define gEmptyTestFixture // Fast + +#define gCInterfaceTestFixture // slow +#define gCPPInterfaceTestFixture // fast + +#define gMemoryTestFixture // medium + +#endif \ No newline at end of file