Browse Source

[c] Added spine-c-tests (thanks @jpoag), fixed up CMake build

badlogic 8 years ago
parent
commit
387b0afb80
100 changed files with 3091 additions and 6 deletions
  1. 3 1
      CMakeLists.txt
  2. 3 3
      spine-c/CMakeLists.txt
  3. 2 2
      spine-c/README.md
  4. 58 0
      spine-c/spine-c-unit-tests/CMakeLists.txt
  5. 67 0
      spine-c/spine-c-unit-tests/README.md
  6. 77 0
      spine-c/spine-c-unit-tests/main.cpp
  7. 303 0
      spine-c/spine-c-unit-tests/memory/KMemory.cpp
  8. 189 0
      spine-c/spine-c-unit-tests/memory/KMemory.h
  9. 185 0
      spine-c/spine-c-unit-tests/memory/KString.cpp
  10. 19 0
      spine-c/spine-c-unit-tests/memory/KString.h
  11. 279 0
      spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.cxx
  12. 504 0
      spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.hxx
  13. 30 0
      spine-c/spine-c-unit-tests/teamcity/README.txt
  14. 82 0
      spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.cpp
  15. 83 0
      spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.h
  16. 174 0
      spine-c/spine-c-unit-tests/teamcity/teamcity_messages.cpp
  17. 59 0
      spine-c/spine-c-unit-tests/teamcity/teamcity_messages.h
  18. 48 0
      spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.cpp
  19. 47 0
      spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.h
  20. 123 0
      spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.cpp
  21. 33 0
      spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.h
  22. 25 0
      spine-c/spine-c-unit-tests/tests/EmptyTestFixture.cpp
  23. 26 0
      spine-c/spine-c-unit-tests/tests/EmptyTestFixture.h
  24. 182 0
      spine-c/spine-c-unit-tests/tests/MemoryTestFixture.cpp
  25. 44 0
      spine-c/spine-c-unit-tests/tests/MemoryTestFixture.h
  26. 163 0
      spine-c/spine-c-unit-tests/tests/SpineEventMonitor.cpp
  27. 122 0
      spine-c/spine-c-unit-tests/tests/SpineEventMonitor.h
  28. 26 0
      spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.cpp
  29. 30 0
      spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.h
  30. 26 0
      spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp
  31. 30 0
      spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.h
  32. BIN
      spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/fnr.exe
  33. 17 0
      spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/makeFixture.bat
  34. 32 0
      spine-c/spine-c-unit-tests/tests/TestOptions.h
  35. 0 0
      spine-c/spine-c/include/spine/Animation.h
  36. 0 0
      spine-c/spine-c/include/spine/AnimationState.h
  37. 0 0
      spine-c/spine-c/include/spine/AnimationStateData.h
  38. 0 0
      spine-c/spine-c/include/spine/Atlas.h
  39. 0 0
      spine-c/spine-c/include/spine/AtlasAttachmentLoader.h
  40. 0 0
      spine-c/spine-c/include/spine/Attachment.h
  41. 0 0
      spine-c/spine-c/include/spine/AttachmentLoader.h
  42. 0 0
      spine-c/spine-c/include/spine/Bone.h
  43. 0 0
      spine-c/spine-c/include/spine/BoneData.h
  44. 0 0
      spine-c/spine-c/include/spine/BoundingBoxAttachment.h
  45. 0 0
      spine-c/spine-c/include/spine/Event.h
  46. 0 0
      spine-c/spine-c/include/spine/EventData.h
  47. 0 0
      spine-c/spine-c/include/spine/IkConstraint.h
  48. 0 0
      spine-c/spine-c/include/spine/IkConstraintData.h
  49. 0 0
      spine-c/spine-c/include/spine/MeshAttachment.h
  50. 0 0
      spine-c/spine-c/include/spine/PathAttachment.h
  51. 0 0
      spine-c/spine-c/include/spine/PathConstraint.h
  52. 0 0
      spine-c/spine-c/include/spine/PathConstraintData.h
  53. 0 0
      spine-c/spine-c/include/spine/RegionAttachment.h
  54. 0 0
      spine-c/spine-c/include/spine/Skeleton.h
  55. 0 0
      spine-c/spine-c/include/spine/SkeletonBinary.h
  56. 0 0
      spine-c/spine-c/include/spine/SkeletonBounds.h
  57. 0 0
      spine-c/spine-c/include/spine/SkeletonData.h
  58. 0 0
      spine-c/spine-c/include/spine/SkeletonJson.h
  59. 0 0
      spine-c/spine-c/include/spine/Skin.h
  60. 0 0
      spine-c/spine-c/include/spine/Slot.h
  61. 0 0
      spine-c/spine-c/include/spine/SlotData.h
  62. 0 0
      spine-c/spine-c/include/spine/TransformConstraint.h
  63. 0 0
      spine-c/spine-c/include/spine/TransformConstraintData.h
  64. 0 0
      spine-c/spine-c/include/spine/VertexAttachment.h
  65. 0 0
      spine-c/spine-c/include/spine/extension.h
  66. 0 0
      spine-c/spine-c/include/spine/spine.h
  67. 0 0
      spine-c/spine-c/src/spine/Animation.c
  68. 0 0
      spine-c/spine-c/src/spine/AnimationState.c
  69. 0 0
      spine-c/spine-c/src/spine/AnimationStateData.c
  70. 0 0
      spine-c/spine-c/src/spine/Atlas.c
  71. 0 0
      spine-c/spine-c/src/spine/AtlasAttachmentLoader.c
  72. 0 0
      spine-c/spine-c/src/spine/Attachment.c
  73. 0 0
      spine-c/spine-c/src/spine/AttachmentLoader.c
  74. 0 0
      spine-c/spine-c/src/spine/Bone.c
  75. 0 0
      spine-c/spine-c/src/spine/BoneData.c
  76. 0 0
      spine-c/spine-c/src/spine/BoundingBoxAttachment.c
  77. 0 0
      spine-c/spine-c/src/spine/Event.c
  78. 0 0
      spine-c/spine-c/src/spine/EventData.c
  79. 0 0
      spine-c/spine-c/src/spine/IkConstraint.c
  80. 0 0
      spine-c/spine-c/src/spine/IkConstraintData.c
  81. 0 0
      spine-c/spine-c/src/spine/Json.c
  82. 0 0
      spine-c/spine-c/src/spine/Json.h
  83. 0 0
      spine-c/spine-c/src/spine/MeshAttachment.c
  84. 0 0
      spine-c/spine-c/src/spine/PathAttachment.c
  85. 0 0
      spine-c/spine-c/src/spine/PathConstraint.c
  86. 0 0
      spine-c/spine-c/src/spine/PathConstraintData.c
  87. 0 0
      spine-c/spine-c/src/spine/RegionAttachment.c
  88. 0 0
      spine-c/spine-c/src/spine/Skeleton.c
  89. 0 0
      spine-c/spine-c/src/spine/SkeletonBinary.c
  90. 0 0
      spine-c/spine-c/src/spine/SkeletonBounds.c
  91. 0 0
      spine-c/spine-c/src/spine/SkeletonData.c
  92. 0 0
      spine-c/spine-c/src/spine/SkeletonJson.c
  93. 0 0
      spine-c/spine-c/src/spine/Skin.c
  94. 0 0
      spine-c/spine-c/src/spine/Slot.c
  95. 0 0
      spine-c/spine-c/src/spine/SlotData.c
  96. 0 0
      spine-c/spine-c/src/spine/TransformConstraint.c
  97. 0 0
      spine-c/spine-c/src/spine/TransformConstraintData.c
  98. 0 0
      spine-c/spine-c/src/spine/VertexAttachment.c
  99. 0 0
      spine-c/spine-c/src/spine/extension.c
  100. 0 0
      spine-c/spine-c/src/spine/kvec.h

+ 3 - 1
CMakeLists.txt

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

+ 3 - 3
spine-c/CMakeLists.txt

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

+ 2 - 2
spine-c/README.md

@@ -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 - 0
spine-c/spine-c-unit-tests/CMakeLists.txt

@@ -0,0 +1,58 @@
+cmake_minimum_required(VERSION 2.8.9)
+project(spine_unit_test)
+
+set(CMAKE_INSTALL_PREFIX "./")
+set(CMAKE_VERBOSE_MAKEFILE ON)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS -DKANJI_MEMTRACE -DUSE_CPP11_MUTEX")
+
+#########################################################
+# 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 - 0
spine-c/spine-c-unit-tests/README.md

@@ -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 - 0
spine-c/spine-c-unit-tests/main.cpp

@@ -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 - 0
spine-c/spine-c-unit-tests/memory/KMemory.cpp

@@ -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 - 0
spine-c/spine-c-unit-tests/memory/KMemory.h

@@ -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 - 0
spine-c/spine-c-unit-tests/memory/KString.cpp

@@ -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 - 0
spine-c/spine-c-unit-tests/memory/KString.h

@@ -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 - 0
spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.cxx

@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2003-2004  Pau Arumí & David García
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "MiniCppUnit.hxx"
+
+JetBrains::TeamcityProgressListener gTeamCityListener;
+bool gUseTeamCity = false;
+
+#include <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 - 0
spine-c/spine-c-unit-tests/minicppunit/MiniCppUnit.hxx

@@ -0,0 +1,504 @@
+/*
+ * Copyright (c) 2003-2004  Pau Arum� & David Garc�a
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef MiniCppUnit_hxx
+#define MiniCppUnit_hxx
+
+/**
+ * @mainpage
+ * miniCppUnit 
+ * (C) 2003-2006 Pau Arumi & David Garcia
+ * 
+ * @version 2.5 2006-03-14
+ *   - MS Visual compatibility: SConstruct ccflags, usage example, #ifdefs
+ * @version 2.4 2006-03-14
+ *   - exit test case after first failure
+ *   - double and float comparison with fuzzy equals (using scalable epsilon)
+ *   - have into account not a numbers
+ *   - new ASSERT_EQUALS_EPSILON macro
+ *   - more colors, and disabled when comiled in MS Visual
+ *   - removed catalan location.
+ *   - UsageExample.cxx now uses all macros and features
+ * @version 2.3 2006-02-13 added usage example and SConstruct
+ * @version 2.2 2004-11-28 code in english and tests suites
+ * @version 2.1 2004-11-04 char* especialization
+ * @version 2.0 2004-10-26 TestsFactory
+ * @version 1.0 2003-10-28 initial
+ * 
+ * Example of use:
+ *
+ * @code
+ * #include "MiniCppUnit.hxx"
+ * class MyTests : public TestFixture<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�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 - 0
spine-c/spine-c-unit-tests/teamcity/README.txt

@@ -0,0 +1,30 @@
+CppUnit listener for TeamCity
+-----------------------------
+
+To report your tests result to TeamCity server
+include teamcity_messages.* teamcity_cppunit.*
+to your project and modify "main" function
+as shown in example.cpp
+(around JetBrains::underTeamcity and JetBrains::TeamcityProgressListener)
+
+Technical details
+-----------------
+
+Reporting implemented by writing TeamCity service messages to stdout.
+
+See
+http://www.jetbrains.net/confluence/display/TCD3/Build+Script+Interaction+with+TeamCity
+for more details.
+
+Contact information
+-------------------
+
+Mail to [email protected] 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 - 0
spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.cpp

@@ -0,0 +1,82 @@
+/* Copyright 2011 JetBrains s.r.o.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ * $Revision: 88625 $
+*/
+
+#include <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 - 0
spine-c/spine-c-unit-tests/teamcity/teamcity_cppunit.h

@@ -0,0 +1,83 @@
+/* Copyright 2011 JetBrains s.r.o.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * $Revision: 88625 $
+*/
+
+#pragma once
+
+#include <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);
+	};
+
+}

+ 174 - 0
spine-c/spine-c-unit-tests/teamcity/teamcity_messages.cpp

@@ -0,0 +1,174 @@
+/* Copyright 2011 JetBrains s.r.o.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * $Revision: 88625 $
+*/
+
+#include <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 - 0
spine-c/spine-c-unit-tests/teamcity/teamcity_messages.h

@@ -0,0 +1,59 @@
+/* Copyright 2011 JetBrains s.r.o.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * $Revision: 88625 $
+*/
+
+#ifndef H_TEAMCITY_MESSAGES
+#define H_TEAMCITY_MESSAGES
+
+#include <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 - 0
spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.cpp

@@ -0,0 +1,48 @@
+//////////////////////////////////////////////////////////////////////
+//	filename: 	C_InterfaceTestFixture.cpp
+//	
+//	notes:		There is no C++ interface!
+//
+/////////////////////////////////////////////////////////////////////
+
+#include "CPP_InterfaceTestFixture.h" 
+
+CPP_InterfaceTestFixture::~CPP_InterfaceTestFixture()
+{
+	finalize();
+}
+
+void CPP_InterfaceTestFixture::initialize()
+{
+	// on a Per- Fixture Basis, before Test execution
+}
+
+void CPP_InterfaceTestFixture::finalize()
+{
+	// on a Per- Fixture Basis, after all tests pass/fail
+}
+
+void CPP_InterfaceTestFixture::setUp()
+{
+	// Setup on Per-Test Basis
+}
+
+void CPP_InterfaceTestFixture::tearDown()
+{
+	// Tear Down on Per-Test Basis
+}
+
+void CPP_InterfaceTestFixture::spineboyTestCase()
+{
+	// There is no C++ interface.
+}
+
+void CPP_InterfaceTestFixture::raptorTestCase()
+{
+	// There is no C++ interface.
+}
+
+void CPP_InterfaceTestFixture::goblinsTestCase()
+{
+	// No c++ interface
+}

+ 47 - 0
spine-c/spine-c-unit-tests/tests/CPP_InterfaceTestFixture.h

@@ -0,0 +1,47 @@
+#pragma once 
+//////////////////////////////////////////////////////////////////////
+//	filename: 	C_InterfaceTestFixture.h
+//	
+//	purpose:	Run example animations for regression testing
+//				on "C++" interface to make sure modifications to "C"
+//				interface doesn't cause memory leaks or regression 
+//				errors.
+/////////////////////////////////////////////////////////////////////
+
+#include "MiniCppUnit.hxx"
+#include "TestOptions.h"
+
+class CPP_InterfaceTestFixture : public TestFixture < CPP_InterfaceTestFixture >
+{
+public:
+	TEST_FIXTURE(CPP_InterfaceTestFixture){
+		TEST_CASE(spineboyTestCase);
+		TEST_CASE(raptorTestCase);
+		TEST_CASE(goblinsTestCase);
+
+		initialize();
+	}
+
+	virtual ~CPP_InterfaceTestFixture();
+
+	//////////////////////////////////////////////////////////////////////////
+	// Test Cases
+	//////////////////////////////////////////////////////////////////////////
+public:
+	void	spineboyTestCase();
+	void	raptorTestCase();
+	void	goblinsTestCase();
+
+	//////////////////////////////////////////////////////////////////////////
+	// test fixture setup
+	//////////////////////////////////////////////////////////////////////////
+	void initialize();
+	void finalize();
+public:
+	virtual void setUp();
+	virtual void tearDown();
+
+};
+#if defined(gForceAllTests) || defined(gCPPInterfaceTestFixture)
+REGISTER_FIXTURE(CPP_InterfaceTestFixture);
+#endif

+ 123 - 0
spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.cpp

@@ -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 - 0
spine-c/spine-c-unit-tests/tests/C_InterfaceTestFixture.h

@@ -0,0 +1,33 @@
+#pragma once 
+//////////////////////////////////////////////////////////////////////
+//	filename: 	C_InterfaceTestFixture.h
+//	
+//	purpose:	Run example animations for regression testing
+//				on "C" interface
+/////////////////////////////////////////////////////////////////////
+
+#include "TestOptions.h"
+#include "MiniCppUnit.hxx"
+
+class C_InterfaceTestFixture : public TestFixture<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 - 0
spine-c/spine-c-unit-tests/tests/EmptyTestFixture.cpp

@@ -0,0 +1,25 @@
+#include "EmptyTestFixture.h" 
+
+#include "KMemory.h" // Last include
+
+
+void EmptyTestFixture::setUp()
+{
+}
+
+void EmptyTestFixture::tearDown()
+{
+}
+
+void EmptyTestFixture::emptyTestCase_1()
+{
+	// char* pLeak = new char[256]; // test leak detector
+}
+
+void EmptyTestFixture::emptyTestCase_2()
+{
+}
+
+void EmptyTestFixture::emptyTestCase_3()
+{
+}

+ 26 - 0
spine-c/spine-c-unit-tests/tests/EmptyTestFixture.h

@@ -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 - 0
spine-c/spine-c-unit-tests/tests/MemoryTestFixture.cpp

@@ -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 - 0
spine-c/spine-c-unit-tests/tests/MemoryTestFixture.h

@@ -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 - 0
spine-c/spine-c-unit-tests/tests/SpineEventMonitor.cpp

@@ -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 - 0
spine-c/spine-c-unit-tests/tests/SpineEventMonitor.h

@@ -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
+
+
+*/

+ 26 - 0
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.cpp

@@ -0,0 +1,26 @@
+#include "CPP_InterfaceTestFixture.h" 
+
+CPP_InterfaceTestFixture::~CPP_InterfaceTestFixture()
+{
+	finalize();
+}
+
+void CPP_InterfaceTestFixture::initialize()
+{
+	// on a Per- Fixture Basis, before Test execution
+}
+
+void CPP_InterfaceTestFixture::finalize()
+{
+	// on a Per- Fixture Basis, after all tests pass/fail
+}
+
+void CPP_InterfaceTestFixture::setUp()
+{
+	// Setup on Per-Test Basis
+}
+
+void CPP_InterfaceTestFixture::tearDown()
+{
+	// Tear Down on Per-Test Basis
+}

+ 30 - 0
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/CPP_InterfaceTestFixture.h

@@ -0,0 +1,30 @@
+#pragma once 
+#include "MiniCppUnit.hxx"
+
+class CPP_InterfaceTestFixture : public TestFixture < CPP_InterfaceTestFixture >
+{
+public:
+	TEST_FIXTURE(CPP_InterfaceTestFixture){
+		//TEST_CASE(parseJSON);
+
+		initialize();
+	}
+
+	virtual ~CPP_InterfaceTestFixture();
+
+	//////////////////////////////////////////////////////////////////////////
+	// Test Cases
+	//////////////////////////////////////////////////////////////////////////
+public:
+	// void parseJSON();
+
+	//////////////////////////////////////////////////////////////////////////
+	// test fixture setup
+	//////////////////////////////////////////////////////////////////////////
+	void initialize();
+	void finalize();
+public:
+	virtual void setUp();
+	virtual void tearDown();
+};
+REGISTER_FIXTURE(CPP_InterfaceTestFixture);

+ 26 - 0
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.cpp

@@ -0,0 +1,26 @@
+#include "[[FIXTURE_TYPE]].h" 
+
+[[FIXTURE_TYPE]]::~[[FIXTURE_TYPE]]()
+{
+	finalize();
+}
+
+void [[FIXTURE_TYPE]]::initialize()
+{
+	// on a Per- Fixture Basis, before Test execution
+}
+
+void [[FIXTURE_TYPE]]::finalize()
+{
+	// on a Per- Fixture Basis, after all tests pass/fail
+}
+
+void [[FIXTURE_TYPE]]::setUp()
+{
+	// Setup on Per-Test Basis
+}
+
+void [[FIXTURE_TYPE]]::tearDown()
+{
+	// Tear Down on Per-Test Basis
+}

+ 30 - 0
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/_TestFixture.h

@@ -0,0 +1,30 @@
+#pragma once 
+#include "MiniCppUnit.hxx"
+
+class [[FIXTURE_TYPE]] : public TestFixture < [[FIXTURE_TYPE]] >
+{
+public:
+	TEST_FIXTURE([[FIXTURE_TYPE]]){
+		//TEST_CASE(parseJSON);
+
+		initialize();
+	}
+
+	virtual ~[[FIXTURE_TYPE]]();
+
+	//////////////////////////////////////////////////////////////////////////
+	// Test Cases
+	//////////////////////////////////////////////////////////////////////////
+public:
+	// void parseJSON();
+
+	//////////////////////////////////////////////////////////////////////////
+	// test fixture setup
+	//////////////////////////////////////////////////////////////////////////
+	void initialize();
+	void finalize();
+public:
+	virtual void setUp();
+	virtual void tearDown();
+};
+REGISTER_FIXTURE([[FIXTURE_TYPE]]);

BIN
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/fnr.exe


+ 17 - 0
spine-c/spine-c-unit-tests/tests/TestFixtureGenerator/makeFixture.bat

@@ -0,0 +1,17 @@
+@echo off
+if "%1"=="" goto blank
+
+copy "_TestFixture.cpp" "%1TestFixture.cpp"
+copy "_TestFixture.h" "%1TestFixture.h"
+
+
+fnr --cl --find "[[FIXTURE_TYPE]]" --replace "%1TestFixture" --fileMask "%1TestFixture.cpp" --dir %cd%
+fnr --cl --find "[[FIXTURE_TYPE]]" --replace "%1TestFixture" --fileMask "%1TestFixture.h" --dir %cd%
+
+goto done
+
+:blank
+echo Usage: 
+echo       %~n0 FixtureTypeName
+
+:done

+ 32 - 0
spine-c/spine-c-unit-tests/tests/TestOptions.h

@@ -0,0 +1,32 @@
+#pragma once
+
+//////////////////////////////////////////////////////////////////////////
+// Force all Tests to 'ON.'  Use this for final 'Regression' Testing.
+
+//#define gForceAllTests
+
+//#define TURN_ON_ALL_TESTS // Comment this line out to switch to fast testing only
+
+#ifdef TURN_ON_ALL_TESTS
+//////////////////////////////////////////////////////////////////////////
+// All tests are ON by default, but you can turn off individual tests.
+
+#define gEmptyTestFixture
+#define gCInterfaceTestFixture
+#define gCPPInterfaceTestFixture
+#define gMemoryTestFixture
+
+
+#else
+
+//////////////////////////////////////////////////////////////////////////
+// Slow Tests are disabled by default.  Use this section to turn on
+// Individual tests.
+#define gEmptyTestFixture  // Fast
+
+#define gCInterfaceTestFixture // slow
+#define gCPPInterfaceTestFixture // fast
+
+#define gMemoryTestFixture // medium
+
+#endif

+ 0 - 0
spine-c/include/spine/Animation.h → spine-c/spine-c/include/spine/Animation.h


+ 0 - 0
spine-c/include/spine/AnimationState.h → spine-c/spine-c/include/spine/AnimationState.h


+ 0 - 0
spine-c/include/spine/AnimationStateData.h → spine-c/spine-c/include/spine/AnimationStateData.h


+ 0 - 0
spine-c/include/spine/Atlas.h → spine-c/spine-c/include/spine/Atlas.h


+ 0 - 0
spine-c/include/spine/AtlasAttachmentLoader.h → spine-c/spine-c/include/spine/AtlasAttachmentLoader.h


+ 0 - 0
spine-c/include/spine/Attachment.h → spine-c/spine-c/include/spine/Attachment.h


+ 0 - 0
spine-c/include/spine/AttachmentLoader.h → spine-c/spine-c/include/spine/AttachmentLoader.h


+ 0 - 0
spine-c/include/spine/Bone.h → spine-c/spine-c/include/spine/Bone.h


+ 0 - 0
spine-c/include/spine/BoneData.h → spine-c/spine-c/include/spine/BoneData.h


+ 0 - 0
spine-c/include/spine/BoundingBoxAttachment.h → spine-c/spine-c/include/spine/BoundingBoxAttachment.h


+ 0 - 0
spine-c/include/spine/Event.h → spine-c/spine-c/include/spine/Event.h


+ 0 - 0
spine-c/include/spine/EventData.h → spine-c/spine-c/include/spine/EventData.h


+ 0 - 0
spine-c/include/spine/IkConstraint.h → spine-c/spine-c/include/spine/IkConstraint.h


+ 0 - 0
spine-c/include/spine/IkConstraintData.h → spine-c/spine-c/include/spine/IkConstraintData.h


+ 0 - 0
spine-c/include/spine/MeshAttachment.h → spine-c/spine-c/include/spine/MeshAttachment.h


+ 0 - 0
spine-c/include/spine/PathAttachment.h → spine-c/spine-c/include/spine/PathAttachment.h


+ 0 - 0
spine-c/include/spine/PathConstraint.h → spine-c/spine-c/include/spine/PathConstraint.h


+ 0 - 0
spine-c/include/spine/PathConstraintData.h → spine-c/spine-c/include/spine/PathConstraintData.h


+ 0 - 0
spine-c/include/spine/RegionAttachment.h → spine-c/spine-c/include/spine/RegionAttachment.h


+ 0 - 0
spine-c/include/spine/Skeleton.h → spine-c/spine-c/include/spine/Skeleton.h


+ 0 - 0
spine-c/include/spine/SkeletonBinary.h → spine-c/spine-c/include/spine/SkeletonBinary.h


+ 0 - 0
spine-c/include/spine/SkeletonBounds.h → spine-c/spine-c/include/spine/SkeletonBounds.h


+ 0 - 0
spine-c/include/spine/SkeletonData.h → spine-c/spine-c/include/spine/SkeletonData.h


+ 0 - 0
spine-c/include/spine/SkeletonJson.h → spine-c/spine-c/include/spine/SkeletonJson.h


+ 0 - 0
spine-c/include/spine/Skin.h → spine-c/spine-c/include/spine/Skin.h


+ 0 - 0
spine-c/include/spine/Slot.h → spine-c/spine-c/include/spine/Slot.h


+ 0 - 0
spine-c/include/spine/SlotData.h → spine-c/spine-c/include/spine/SlotData.h


+ 0 - 0
spine-c/include/spine/TransformConstraint.h → spine-c/spine-c/include/spine/TransformConstraint.h


+ 0 - 0
spine-c/include/spine/TransformConstraintData.h → spine-c/spine-c/include/spine/TransformConstraintData.h


+ 0 - 0
spine-c/include/spine/VertexAttachment.h → spine-c/spine-c/include/spine/VertexAttachment.h


+ 0 - 0
spine-c/include/spine/extension.h → spine-c/spine-c/include/spine/extension.h


+ 0 - 0
spine-c/include/spine/spine.h → spine-c/spine-c/include/spine/spine.h


+ 0 - 0
spine-c/src/spine/Animation.c → spine-c/spine-c/src/spine/Animation.c


+ 0 - 0
spine-c/src/spine/AnimationState.c → spine-c/spine-c/src/spine/AnimationState.c


+ 0 - 0
spine-c/src/spine/AnimationStateData.c → spine-c/spine-c/src/spine/AnimationStateData.c


+ 0 - 0
spine-c/src/spine/Atlas.c → spine-c/spine-c/src/spine/Atlas.c


+ 0 - 0
spine-c/src/spine/AtlasAttachmentLoader.c → spine-c/spine-c/src/spine/AtlasAttachmentLoader.c


+ 0 - 0
spine-c/src/spine/Attachment.c → spine-c/spine-c/src/spine/Attachment.c


+ 0 - 0
spine-c/src/spine/AttachmentLoader.c → spine-c/spine-c/src/spine/AttachmentLoader.c


+ 0 - 0
spine-c/src/spine/Bone.c → spine-c/spine-c/src/spine/Bone.c


+ 0 - 0
spine-c/src/spine/BoneData.c → spine-c/spine-c/src/spine/BoneData.c


+ 0 - 0
spine-c/src/spine/BoundingBoxAttachment.c → spine-c/spine-c/src/spine/BoundingBoxAttachment.c


+ 0 - 0
spine-c/src/spine/Event.c → spine-c/spine-c/src/spine/Event.c


+ 0 - 0
spine-c/src/spine/EventData.c → spine-c/spine-c/src/spine/EventData.c


+ 0 - 0
spine-c/src/spine/IkConstraint.c → spine-c/spine-c/src/spine/IkConstraint.c


+ 0 - 0
spine-c/src/spine/IkConstraintData.c → spine-c/spine-c/src/spine/IkConstraintData.c


+ 0 - 0
spine-c/src/spine/Json.c → spine-c/spine-c/src/spine/Json.c


+ 0 - 0
spine-c/src/spine/Json.h → spine-c/spine-c/src/spine/Json.h


+ 0 - 0
spine-c/src/spine/MeshAttachment.c → spine-c/spine-c/src/spine/MeshAttachment.c


+ 0 - 0
spine-c/src/spine/PathAttachment.c → spine-c/spine-c/src/spine/PathAttachment.c


+ 0 - 0
spine-c/src/spine/PathConstraint.c → spine-c/spine-c/src/spine/PathConstraint.c


+ 0 - 0
spine-c/src/spine/PathConstraintData.c → spine-c/spine-c/src/spine/PathConstraintData.c


+ 0 - 0
spine-c/src/spine/RegionAttachment.c → spine-c/spine-c/src/spine/RegionAttachment.c


+ 0 - 0
spine-c/src/spine/Skeleton.c → spine-c/spine-c/src/spine/Skeleton.c


+ 0 - 0
spine-c/src/spine/SkeletonBinary.c → spine-c/spine-c/src/spine/SkeletonBinary.c


+ 0 - 0
spine-c/src/spine/SkeletonBounds.c → spine-c/spine-c/src/spine/SkeletonBounds.c


+ 0 - 0
spine-c/src/spine/SkeletonData.c → spine-c/spine-c/src/spine/SkeletonData.c


+ 0 - 0
spine-c/src/spine/SkeletonJson.c → spine-c/spine-c/src/spine/SkeletonJson.c


+ 0 - 0
spine-c/src/spine/Skin.c → spine-c/spine-c/src/spine/Skin.c


+ 0 - 0
spine-c/src/spine/Slot.c → spine-c/spine-c/src/spine/Slot.c


+ 0 - 0
spine-c/src/spine/SlotData.c → spine-c/spine-c/src/spine/SlotData.c


+ 0 - 0
spine-c/src/spine/TransformConstraint.c → spine-c/spine-c/src/spine/TransformConstraint.c


+ 0 - 0
spine-c/src/spine/TransformConstraintData.c → spine-c/spine-c/src/spine/TransformConstraintData.c


+ 0 - 0
spine-c/src/spine/VertexAttachment.c → spine-c/spine-c/src/spine/VertexAttachment.c


+ 0 - 0
spine-c/src/spine/extension.c → spine-c/spine-c/src/spine/extension.c


+ 0 - 0
spine-c/src/spine/kvec.h → spine-c/spine-c/src/spine/kvec.h


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