static void testRunner(const char* jsonName, const char* atlasName)
{
	///////////////////////////////////////////////////////////////////////////
	// Global Animation Information
	spAtlas* atlas = spAtlas_createFromFile(atlasName, 0);
	ASSERT(atlas != 0);

	spSkeletonData* skeletonData = readSkeletonJsonData(jsonName, atlas);
	ASSERT(skeletonData != 0);

	spAnimationStateData* stateData = spAnimationStateData_create(skeletonData);
	ASSERT(stateData != 0);
	stateData->defaultMix = 0.2f; // force mixing

	///////////////////////////////////////////////////////////////////////////
	// Animation Instance 
	spSkeleton* skeleton = spSkeleton_create(skeletonData);
	ASSERT(skeleton != 0);

	spAnimationState* state = spAnimationState_create(stateData);
	ASSERT(state != 0);


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