The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "test_lib.h"

#define DO_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
#define GIT_MAX_TEST_CASES 64

struct git_test {
	char *name;
	char *message;
	char *failed_pos;
	char *description;
	char *error_message;

	git_testfunc function;
	unsigned failed:1, ran:1;
	jmp_buf *jump;
};

struct git_testsuite {
	char *name;
	int count, fail_count;
	git_test *list[GIT_MAX_TEST_CASES];
};

static void test_free(git_test *t)
{
	if (t) {
		free(t->name);
		free(t->description);
		free(t->failed_pos);
		free(t->message);
		free(t->error_message);
		free(t);
	}
}

static void test_run(git_test *tc)
{
	jmp_buf buf;
	tc->jump = &buf;

	if (setjmp(buf) == 0) {
		tc->ran = 1;
		(tc->function)(tc);
	}

	tc->jump = 0;
}

static git_test *create_test(git_testfunc function)
{
	git_test *t = DO_ALLOC(git_test);

	memset(t, 0x0, sizeof(git_test));
	t->function = function;

	return t;
}

void git_test__init(git_test *t, const char *name, const char *description)
{
	t->name = strdup(name);
	t->description = strdup(description);
}


/*-------------------------------------------------------------------------*
 * Public assert methods
 *-------------------------------------------------------------------------*/

static void fail_test(git_test *tc, const char *file, int line, const char *message)
{
	char buf[1024];
	const char *last_error = git_lasterror();

	snprintf(buf, 1024, "%s:%d", file, line);

	tc->failed = 1;
	tc->message = strdup(message);
	tc->failed_pos = strdup(buf);

	if (last_error)
		tc->error_message = strdup(last_error);

	if (tc->jump != 0)
		longjmp(*(tc->jump), 0);
}

void git_test__fail(git_test *tc, const char *file, int line, const char *message)
{
	fail_test(tc, file, line, message);
}

void git_test__assert(git_test *tc, const char *file, int line, const char *message, int condition)
{
	if (condition == 0)
		fail_test(tc, file, line, message);
}

void git_test__assert_pass(git_test *tc, const char *file, int line, const char *message, int ret_value)
{
	if (ret_value < 0)
		fail_test(tc, file, line, message);
}

/*-------------------------------------------------------------------------*
 * Test Suite
 *-------------------------------------------------------------------------*/

static void testsuite_init(git_testsuite *ts)
{
	ts->count = 0;
	ts->fail_count = 0;
	memset(ts->list, 0, sizeof(ts->list));
}

git_testsuite *git_testsuite_new(const char *name)
{
	git_testsuite *ts = DO_ALLOC(git_testsuite);
	testsuite_init(ts);
	ts->name = strdup(name);
	return ts;
}

static void free_suite(git_testsuite *ts)
{
	unsigned int n;

	for (n = 0; n < GIT_MAX_TEST_CASES; n++)
		if (ts->list[n])
			test_free(ts->list[n]);

	free(ts->name);
	free(ts);
}

void git_testsuite_add(git_testsuite *ts, git_testfunc test)
{
	assert(ts->count < GIT_MAX_TEST_CASES);
	ts->list[ts->count++] = create_test(test);
}

static void print_details(git_testsuite *ts)
{
	int i;
	int failCount = 0;

	if (ts->fail_count == 0) {
		const char *testWord = ts->count == 1 ? "test" : "tests";
		printf("OK (%d %s)\n", ts->count, testWord);
	} else {
		printf("Failed (%d failures):\n", ts->fail_count);

		for (i = 0 ; i < ts->count ; ++i) {
			git_test *tc = ts->list[i];
			if (tc->failed) {
				failCount++;
				printf("  %d) \"%s\" [test %s @ %s]\n\t%s\n",
					failCount, tc->description, tc->name, tc->failed_pos, tc->message);
				if (tc->error_message)
					printf("\tError: %s\n", tc->error_message);
			}
		}
	}
}

int git_testsuite_run(git_testsuite *ts)
{
	int i, fail_count;

	printf("Suite \"%s\": ", ts->name);

	for (i = 0 ; i < ts->count ; ++i) {
		git_test *tc = ts->list[i];

		test_run(tc);
		if (tc->failed) {
			ts->fail_count++;
			putchar('F');
		} else
			putchar('.');

		fflush(stdout);
	}
	printf("\n  ");
	print_details(ts);
	fail_count = ts->fail_count;

	free_suite(ts);
	return fail_count;
}