Merge branch 'spine-c-unit-tests'

This commit is contained in:
badlogic 2016-11-28 16:20:58 +01:00
commit 2e6d84aa25
108 changed files with 3603 additions and 626 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View 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)

View 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

View 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);
}
}

View 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;
}

View 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__

View 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());
}

View 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);

View 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);
}

View 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

View 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

View 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);
}
}

View 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 &copy);
// Prevents the use of the copy operator.
void operator =(const TeamcityProgressListener &copy);
};
}

View 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();
}
}

View 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 */

View 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
}

View 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

View 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);
}

View 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

View 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()
{
}

View 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

View 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);
}

View 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

View 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;
}

View 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
*/

View File

@ -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
}

View File

@ -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);

View 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
}

View 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]]);

View 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

View 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

Some files were not shown because too many files have changed in this diff Show More