mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
143069bace
@ -20,4 +20,6 @@ endif()
|
||||
|
||||
if((${SPINE_COCOS2D_X}) OR (${CMAKE_CURRENT_BINARY_DIR} MATCHES "spine-cocos2dx"))
|
||||
add_subdirectory(spine-cocos2dx)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(spine-c/spine-c-unit-tests)
|
||||
@ -1,9 +1,9 @@
|
||||
include_directories(include)
|
||||
file(GLOB INCLUDES "include/**/*.h")
|
||||
file(GLOB SOURCES "src/**/*.c" "src/**/*.cpp")
|
||||
file(GLOB INCLUDES "spine-c/include/**/*.h")
|
||||
file(GLOB SOURCES "spine-c/src/**/*.c" "spine-c/src/**/*.cpp")
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c89 -pedantic")
|
||||
add_library(spine-c STATIC ${SOURCES} ${INCLUDES})
|
||||
target_include_directories(spine-c PUBLIC include)
|
||||
target_include_directories(spine-c PUBLIC spine-c/include)
|
||||
install(TARGETS spine-c DESTINATION dist/lib)
|
||||
install(FILES ${INCLUDES} DESTINATION dist/include)
|
||||
@ -21,7 +21,7 @@ spine-c supports all Spine features.
|
||||
1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it [as a zip](https://github.com/EsotericSoftware/spine-runtimes/archive/master.zip).
|
||||
1. Open the `spine-c.sln` Visual C++ 2010 Express project file. For other IDEs, you will need to create a new project and import the source.
|
||||
|
||||
Alternatively, the contents of the `spine-c/src` and `spine-c/include` directories can be copied into your project. Be sure your header search is configured to find the contents of the `spine-c/include` directory. Note that the includes use `spine/Xxx.h`, so the `spine` directory cannot be omitted when copying the files.
|
||||
Alternatively, the contents of the `spine-c/spine-c/src` and `spine-c/spine-c/include` directories can be copied into your project. Be sure your header search is configured to find the contents of the `spine-c/spine-c/include` directory. Note that the includes use `spine/Xxx.h`, so the `spine` directory cannot be omitted when copying the files.
|
||||
|
||||
If `SPINE_SHORT_NAMES` is defined, the `sp` prefix for all structs and functions is optional. Only use this if the spine-c names won't cause a conflict.
|
||||
|
||||
@ -39,7 +39,7 @@ For example, `AtlasAttachmentLoader` is typically used to load attachments when
|
||||
|
||||
[spine-sfml](https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-sfml/src/spine/spine-sfml.cpp#L39) serves as a simple example of extending spine-c.
|
||||
|
||||
spine-c uses an OOP style of programming where each "class" is made up of a struct and a number of functions prefixed with the struct name. More detals about how this works are available in [extension.h](https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-c/include/spine/extension.h#L2). This mechanism allows you to provide your own implementations for `spAttachmentLoader`, `spAttachment` and `spTimeline`, if necessary.
|
||||
spine-c uses an OOP style of programming where each "class" is made up of a struct and a number of functions prefixed with the struct name. More detals about how this works are available in [extension.h](https://github.com/EsotericSoftware/spine-runtimes/blob/master/spine-c/spine-c/include/spine/extension.h#L2). This mechanism allows you to provide your own implementations for `spAttachmentLoader`, `spAttachment` and `spTimeline`, if necessary.
|
||||
|
||||
## Runtimes extending spine-c
|
||||
|
||||
|
||||
58
spine-c/spine-c-unit-tests/CMakeLists.txt
Executable file
58
spine-c/spine-c-unit-tests/CMakeLists.txt
Executable file
@ -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")
|
||||
|
||||
#########################################################
|
||||
# set includes
|
||||
#########################################################
|
||||
include_directories(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-c)
|
||||
|
||||
|
||||
#########################################################
|
||||
# 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 $<TARGET_FILE_DIR:spine_unit_test>/testdata/spineboy)
|
||||
|
||||
add_custom_command(TARGET spine_unit_test PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../examples/raptor/export $<TARGET_FILE_DIR:spine_unit_test>/testdata/raptor)
|
||||
|
||||
add_custom_command(TARGET spine_unit_test PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../examples/goblins/export $<TARGET_FILE_DIR:spine_unit_test>/testdata/goblins)
|
||||
67
spine-c/spine-c-unit-tests/README.md
Executable file
67
spine-c/spine-c-unit-tests/README.md
Executable file
@ -0,0 +1,67 @@
|
||||
# spine-c-unit-tests
|
||||
|
||||
The spine-c-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-c-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-c-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
|
||||
77
spine-c/spine-c-unit-tests/main.cpp
Executable file
77
spine-c/spine-c-unit-tests/main.cpp
Executable file
@ -0,0 +1,77 @@
|
||||
// SexyKanjiTestSuite.cpp : Defines the entry point for the console application.
|
||||
//
|
||||
|
||||
#include "MiniCppUnit.hxx"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif // WIN32
|
||||
|
||||
#include <ctime>
|
||||
#include "KString.h"
|
||||
|
||||
#include "spine/extension.h"
|
||||
#include "spine/spine.h"
|
||||
|
||||
#include "KMemory.h" // last include
|
||||
|
||||
void RegisterMemoryLeakDetector()
|
||||
{
|
||||
// Register our malloc and free functions to track memory leaks
|
||||
#ifdef KANJI_MEMTRACE
|
||||
_setDebugMalloc(_kanjimalloc);
|
||||
#endif
|
||||
_setMalloc(_kanjimalloc);
|
||||
_setFree(_kanjifree);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
RegisterMemoryLeakDetector();
|
||||
|
||||
// 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-C Test Suite");
|
||||
int ret_val = TestFixtureFactory::theInstance().runTests() ? 0 : -1;
|
||||
if(JetBrains::underTeamcity()) gTeamCityListener.endSuite("Spine-C 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);
|
||||
|
||||
spAnimationState_disposeStatics(); // Fix for #775
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" { // probably unnecessary
|
||||
|
||||
void _spAtlasPage_createTexture(spAtlasPage* self, const char* path) {
|
||||
self->rendererObject = nullptr;
|
||||
self->width = 2048;
|
||||
self->height = 2048;
|
||||
}
|
||||
|
||||
void _spAtlasPage_disposeTexture(spAtlasPage* self) {
|
||||
}
|
||||
|
||||
char* _spUtil_readFile(const char* path, int* length) {
|
||||
return _readFile(path, length);
|
||||
}
|
||||
}
|
||||
303
spine-c/spine-c-unit-tests/memory/KMemory.cpp
Executable file
303
spine-c/spine-c-unit-tests/memory/KMemory.cpp
Executable file
@ -0,0 +1,303 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
|
||||
#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
|
||||
#include <mutex>
|
||||
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<void*, KANJI_ALLOC_INFO>
|
||||
{
|
||||
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<int, int> allocCount; // [size] = count
|
||||
typedef std::map<std::pair<std::string, int>, 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<std::string, int>(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;
|
||||
}
|
||||
189
spine-c/spine-c-unit-tests/memory/KMemory.h
Executable file
189
spine-c/spine-c-unit-tests/memory/KMemory.h
Executable file
@ -0,0 +1,189 @@
|
||||
#ifndef __KANJIMEMORY_H__
|
||||
#define __KANJIMEMORY_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 <typename T>
|
||||
class ScopedAutoDeletePointerHelper
|
||||
{
|
||||
public:
|
||||
ScopedAutoDeletePointerHelper(T pPtr) : _pPtr(pPtr) {}
|
||||
~ScopedAutoDeletePointerHelper() { SAFE_DELETE(_pPtr); }
|
||||
|
||||
T _pPtr;
|
||||
};
|
||||
#define SCOPED_AUTO_SAFE_DELETE(p) ScopedAutoDeletePointerHelper<decltype(p)> 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 <new>
|
||||
#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__
|
||||
185
spine-c/spine-c-unit-tests/memory/KString.cpp
Executable file
185
spine-c/spine-c-unit-tests/memory/KString.cpp
Executable file
@ -0,0 +1,185 @@
|
||||
#include "KString.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#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<char*>(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());
|
||||
}
|
||||
19
spine-c/spine-c-unit-tests/memory/KString.h
Executable file
19
spine-c/spine-c-unit-tests/memory/KString.h
Executable file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
// 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);
|
||||
279
spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.cxx
Executable file
279
spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.cxx
Executable file
@ -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 <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
504
spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.hxx
Executable file
504
spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.hxx
Executable file
@ -0,0 +1,504 @@
|
||||
/*
|
||||
* Copyright (c) 2003-2004 Pau Arum<EFBFBD> & David Garc<EFBFBD>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<MyTests>
|
||||
* {
|
||||
* 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 <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
|
||||
#if _MSC_VER < 1300
|
||||
/** necesary for Visual 6 which don't define std::min */
|
||||
namespace std
|
||||
{
|
||||
template<typename T> 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<typename AType>
|
||||
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 <typename ConcreteTestFixture>
|
||||
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<Test*> 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<FixtureCreator> _creators;
|
||||
public:
|
||||
/** Accessor to the (static) singleton instance */
|
||||
static TestFixtureFactory& theInstance()
|
||||
{
|
||||
static TestFixtureFactory theFactory;
|
||||
return theFactory;
|
||||
}
|
||||
bool runTests()
|
||||
{
|
||||
std::list<FixtureCreator>::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<EFBFBD>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<MyTests>
|
||||
* {
|
||||
* public:
|
||||
* TEST_FIXTURE( MyTests )
|
||||
* {
|
||||
* TEST_CASE( test );
|
||||
* // etc
|
||||
* }
|
||||
* void test()
|
||||
* {
|
||||
* ASSERT_EQUALS( 4, 1+1+2 );
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#define TEST_FIXTURE( ConcreteFixture ) \
|
||||
ConcreteFixture() : TestFixture<ConcreteFixture>( #ConcreteFixture )
|
||||
|
||||
#define TEST_CASE( methodName ) \
|
||||
afegeixCasDeTest( this, &ConcreteFixture::methodName, #methodName );
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // MiniCppUnit_hxx
|
||||
30
spine-c/spine-c-unit-tests/teamcity/README.txt
Executable file
30
spine-c/spine-c-unit-tests/teamcity/README.txt
Executable file
@ -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
|
||||
82
spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.cpp
Executable file
82
spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.cpp
Executable file
@ -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 <sstream>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
83
spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.h
Executable file
83
spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.h
Executable file
@ -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 <string>
|
||||
|
||||
#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 ©);
|
||||
};
|
||||
|
||||
}
|
||||
174
spine-c/spine-c-unit-tests/teamcity/teamcity_messages.cpp
Executable file
174
spine-c/spine-c-unit-tests/teamcity/teamcity_messages.cpp
Executable file
@ -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 <stdlib.h>
|
||||
#include <sstream>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
}
|
||||
59
spine-c/spine-c-unit-tests/teamcity/teamcity_messages.h
Executable file
59
spine-c/spine-c-unit-tests/teamcity/teamcity_messages.h
Executable file
@ -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 <string>
|
||||
#include <iostream>
|
||||
|
||||
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 */
|
||||
48
spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.cpp
Executable file
48
spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.cpp
Executable file
@ -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
|
||||
}
|
||||
47
spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.h
Executable file
47
spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.h
Executable file
@ -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
|
||||
123
spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.cpp
Executable file
123
spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.cpp
Executable file
@ -0,0 +1,123 @@
|
||||
#include "C_InterfaceTestFixture.h"
|
||||
#include "SpineEventMonitor.h"
|
||||
|
||||
#include "spine/spine.h"
|
||||
#include <vector>
|
||||
|
||||
#include "KMemory.h" // last include
|
||||
|
||||
#define SPINEBOY_JSON "testdata/spineboy/spineboy.json"
|
||||
#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas"
|
||||
|
||||
#define RAPTOR_JSON "testdata/raptor/raptor.json"
|
||||
#define RAPTOR_ATLAS "testdata/raptor/raptor.atlas"
|
||||
|
||||
#define GOBLINS_JSON "testdata/goblins/goblins.json"
|
||||
#define GOBLINS_ATLAS "testdata/goblins/goblins.atlas"
|
||||
|
||||
#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution
|
||||
|
||||
void C_InterfaceTestFixture::setUp()
|
||||
{
|
||||
}
|
||||
|
||||
void C_InterfaceTestFixture::tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
static spSkeletonData* readSkeletonJsonData(const char* filename, spAtlas* atlas) {
|
||||
spSkeletonJson* json = spSkeletonJson_create(atlas);
|
||||
ASSERT(json != nullptr);
|
||||
|
||||
spSkeletonData* skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename);
|
||||
ASSERT(skeletonData != nullptr);
|
||||
|
||||
spSkeletonJson_dispose(json);
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
typedef std::vector<std::string> AnimList;
|
||||
|
||||
static size_t enumerateAnimations(AnimList& outList, spSkeletonData* skeletonData)
|
||||
{
|
||||
if (skeletonData){
|
||||
|
||||
for (int n = 0; n < skeletonData->animationsCount; n++)
|
||||
outList.push_back(skeletonData->animations[n]->name);
|
||||
}
|
||||
|
||||
return outList.size();
|
||||
}
|
||||
|
||||
static void testRunner(const char* jsonName, const char* atlasName)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Global Animation Information
|
||||
spAtlas* atlas = spAtlas_createFromFile(atlasName, 0);
|
||||
ASSERT(atlas != nullptr);
|
||||
|
||||
spSkeletonData* skeletonData = readSkeletonJsonData(jsonName, atlas);
|
||||
ASSERT(skeletonData != nullptr);
|
||||
|
||||
spAnimationStateData* stateData = spAnimationStateData_create(skeletonData);
|
||||
ASSERT(stateData != nullptr);
|
||||
stateData->defaultMix = 0.2f; // force mixing
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Animation Instance
|
||||
spSkeleton* skeleton = spSkeleton_create(skeletonData);
|
||||
ASSERT(skeleton != nullptr);
|
||||
|
||||
spAnimationState* state = spAnimationState_create(stateData);
|
||||
ASSERT(state != nullptr);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Run animation
|
||||
spSkeleton_setToSetupPose(skeleton);
|
||||
SpineEventMonitor eventMonitor(state);
|
||||
// eventMonitor.SetDebugLogging(true);
|
||||
|
||||
|
||||
AnimList anims; // Let's chain all the animations together as a test
|
||||
size_t count = enumerateAnimations(anims, skeletonData);
|
||||
if (count > 0) spAnimationState_setAnimationByName(state, 0, anims[0].c_str(), false);
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
spAnimationState_addAnimationByName(state, 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;
|
||||
spSkeleton_update(skeleton, timeSlice);
|
||||
spAnimationState_update(state, timeSlice);
|
||||
spAnimationState_apply(state, skeleton);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Dispose Instance
|
||||
spSkeleton_dispose(skeleton);
|
||||
spAnimationState_dispose(state);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Dispose Global
|
||||
spAnimationStateData_dispose(stateData);
|
||||
spSkeletonData_dispose(skeletonData);
|
||||
spAtlas_dispose(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);
|
||||
}
|
||||
33
spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.h
Executable file
33
spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.h
Executable file
@ -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<C_InterfaceTestFixture>
|
||||
{
|
||||
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
|
||||
25
spine-c/spine-c-unit-tests/tests/EmptyTestFixture.cpp
Executable file
25
spine-c/spine-c-unit-tests/tests/EmptyTestFixture.cpp
Executable file
@ -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()
|
||||
{
|
||||
}
|
||||
26
spine-c/spine-c-unit-tests/tests/EmptyTestFixture.h
Executable file
26
spine-c/spine-c-unit-tests/tests/EmptyTestFixture.h
Executable file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "TestOptions.h"
|
||||
#include "MiniCppUnit.hxx"
|
||||
|
||||
class EmptyTestFixture : public TestFixture<EmptyTestFixture>
|
||||
{
|
||||
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
|
||||
182
spine-c/spine-c-unit-tests/tests/MemoryTestFixture.cpp
Executable file
182
spine-c/spine-c-unit-tests/tests/MemoryTestFixture.cpp
Executable file
@ -0,0 +1,182 @@
|
||||
#include "MemoryTestFixture.h"
|
||||
#include "SpineEventMonitor.h"
|
||||
|
||||
#include "spine/spine.h"
|
||||
|
||||
#include "KMemory.h" // last include
|
||||
|
||||
#define SPINEBOY_JSON "testdata/spineboy/spineboy.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 != nullptr);
|
||||
|
||||
spSkeletonData* skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename);
|
||||
ASSERT(skeletonData != nullptr);
|
||||
|
||||
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 != nullptr);
|
||||
|
||||
skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas);
|
||||
ASSERT(skeletonData != nullptr);
|
||||
|
||||
stateData = spAnimationStateData_create(skeletonData);
|
||||
ASSERT(stateData != nullptr);
|
||||
stateData->defaultMix = 0.4f; // force mixing
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Animation Instance
|
||||
skeleton = spSkeleton_create(skeletonData);
|
||||
ASSERT(skeleton != nullptr);
|
||||
|
||||
state = spAnimationState_create(stateData);
|
||||
ASSERT(state != nullptr);
|
||||
}
|
||||
|
||||
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 = nullptr;
|
||||
spSkeletonData* skeletonData = nullptr;
|
||||
spAnimationStateData* stateData = nullptr;
|
||||
spSkeleton* skeleton = nullptr;
|
||||
spAnimationState* state = nullptr;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// 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 = nullptr;
|
||||
spSkeletonData* skeletonData = nullptr;
|
||||
spAnimationStateData* stateData = nullptr;
|
||||
spSkeleton* skeleton = nullptr;
|
||||
spAnimationState* state = nullptr;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
44
spine-c/spine-c-unit-tests/tests/MemoryTestFixture.h
Executable file
44
spine-c/spine-c-unit-tests/tests/MemoryTestFixture.h
Executable file
@ -0,0 +1,44 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// 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);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
virtual ~MemoryTestFixture();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Test Cases
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
public:
|
||||
void reproduceIssue_776();
|
||||
void reproduceIssue_777();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// test fixture setup
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void initialize();
|
||||
void finalize();
|
||||
public:
|
||||
virtual void setUp();
|
||||
virtual void tearDown();
|
||||
};
|
||||
#if defined(gForceAllTests) || defined(gMemoryTestFixture)
|
||||
REGISTER_FIXTURE(MemoryTestFixture);
|
||||
#endif
|
||||
163
spine-c/spine-c-unit-tests/tests/SpineEventMonitor.cpp
Executable file
163
spine-c/spine-c-unit-tests/tests/SpineEventMonitor.cpp
Executable file
@ -0,0 +1,163 @@
|
||||
#include "SpineEventMonitor.h"
|
||||
|
||||
#include "spine/spine.h"
|
||||
#include "KString.h"
|
||||
|
||||
#include "KMemory.h" // Last include
|
||||
|
||||
SpineEventMonitor::SpineEventMonitor(spAnimationState* _pAnimationState /*= nullptr*/)
|
||||
{
|
||||
bLogging = false;
|
||||
RegisterListener(_pAnimationState);
|
||||
}
|
||||
|
||||
SpineEventMonitor::~SpineEventMonitor()
|
||||
{
|
||||
pAnimState = nullptr;
|
||||
}
|
||||
|
||||
void SpineEventMonitor::RegisterListener(spAnimationState * _pAnimationState)
|
||||
{
|
||||
if (_pAnimationState) {
|
||||
_pAnimationState->rendererObject = this;
|
||||
_pAnimationState->listener = (spAnimationStateListener)&SpineEventMonitor::spineAnimStateHandler;
|
||||
}
|
||||
pAnimState = _pAnimationState;
|
||||
}
|
||||
|
||||
bool SpineEventMonitor::isAnimationPlaying()
|
||||
{
|
||||
if (pAnimState)
|
||||
return spAnimationState_getCurrent(pAnimState, 0) != nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpineEventMonitor::spineAnimStateHandler(spAnimationState * 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(spAnimationState * state, int type, spTrackEntry * trackEntry, spEvent * event)
|
||||
{
|
||||
const char* eventName = nullptr;
|
||||
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(spAnimationState * _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(spAnimationState * 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(spAnimationState * 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 != nullptr) {
|
||||
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;
|
||||
}
|
||||
122
spine-c/spine-c-unit-tests/tests/SpineEventMonitor.h
Executable file
122
spine-c/spine-c-unit-tests/tests/SpineEventMonitor.h
Executable file
@ -0,0 +1,122 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// filename: SpineEventMonitor.h
|
||||
//
|
||||
// purpose: Monitor spAnimationState Events
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// forward declarations
|
||||
typedef struct spAnimationState spAnimationState;
|
||||
typedef struct spTrackEntry spTrackEntry;
|
||||
typedef struct spEvent spEvent;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// 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(spAnimationState* _pAnimationState = nullptr);
|
||||
virtual ~SpineEventMonitor();
|
||||
|
||||
void RegisterListener(spAnimationState* _pAnimationState);
|
||||
|
||||
void SetDebugLogging(bool val) { bLogging = val; }
|
||||
bool GetDebugLogging() { return bLogging; }
|
||||
|
||||
virtual bool isAnimationPlaying();
|
||||
|
||||
protected:
|
||||
static void spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event);
|
||||
virtual void OnSpineAnimationStateEvent(spAnimationState* state, int type, spTrackEntry* trackEntry, spEvent* event);
|
||||
|
||||
protected:
|
||||
spAnimationState *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 = nullptr;
|
||||
}
|
||||
|
||||
bool matches(spAnimationState* state, int type, spTrackEntry* trackEntry, spEvent* event);
|
||||
|
||||
std::string mAnimName;
|
||||
int mEventType;
|
||||
spTrackEntry* mTrackEntry;
|
||||
std::string mEventName;
|
||||
};
|
||||
typedef std::vector<InterruptEvent> InterruptEventStack;
|
||||
|
||||
|
||||
public:
|
||||
InterruptMonitor(spAnimationState* _pAnimationState = nullptr);
|
||||
~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(spAnimationState* 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
|
||||
|
||||
|
||||
*/
|
||||
@ -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
|
||||
}
|
||||
@ -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);
|
||||
26
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp
Executable file
26
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp
Executable file
@ -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
|
||||
}
|
||||
30
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.h
Executable file
30
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.h
Executable file
@ -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]]);
|
||||
BIN
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/fnr.exe
Executable file
BIN
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/fnr.exe
Executable file
Binary file not shown.
17
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/makeFixture.bat
Executable file
17
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/makeFixture.bat
Executable file
@ -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
|
||||
32
spine-c/spine-c-unit-tests/tests/TestOptions.h
Executable file
32
spine-c/spine-c-unit-tests/tests/TestOptions.h
Executable file
@ -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
|
||||
@ -183,13 +183,13 @@ void _spAnimationState_disposeTrackEntry (spTrackEntry* entry) {
|
||||
void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntry* entry) {
|
||||
while (entry) {
|
||||
spTrackEntry* next = entry->next;
|
||||
_spAnimationState_disposeTrackEntry(entry);
|
||||
spTrackEntry* from = entry->mixingFrom;
|
||||
while (from) {
|
||||
spTrackEntry* nextFrom = from->mixingFrom;
|
||||
_spAnimationState_disposeTrackEntry(from);
|
||||
from = nextFrom;
|
||||
}
|
||||
_spAnimationState_disposeTrackEntry(entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user