/*
 * Copyright © 2015 Samsung Electronics Co., Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include "zunitc/zunitc_impl.h"
#include "zunitc/zunitc.h"

#include "zuc_base_logger.h"
#include "zuc_collector.h"
#include "zuc_context.h"
#include "zuc_event_listener.h"
#include "zuc_junit_reporter.h"

#include "shared/config-parser.h"
#include "shared/helpers.h"
#include "shared/zalloc.h"

/*
 * If CLOCK_MONOTONIC is present on the system it will give us reliable
 * results under certain edge conditions that normally require manual
 * admin actions to trigger. If not, CLOCK_REALTIME is a reasonable
 * fallback.
 */
#if _POSIX_MONOTONIC_CLOCK
static const clockid_t TARGET_TIMER = CLOCK_MONOTONIC;
#else
static const clockid_t TARGET_TIMER = CLOCK_REALTIME;
#endif

static char const DISABLED_PREFIX[] = "DISABLED_";

#define MS_PER_SEC 1000L
#define NANO_PER_MS 1000000L

/**
 * Simple single-linked list structure.
 */
struct zuc_slinked {
	void *data;
	struct zuc_slinked *next;
};

static struct zuc_context g_ctx = {
	.case_count = 0,
	.cases = NULL,

	.fatal = false,
	.repeat = 0,
	.random = 0,
	.spawn = true,
	.break_on_failure = false,
	.fds = {-1, -1},

	.listeners = NULL,

	.curr_case = NULL,
	.curr_test = NULL,
};

static char *g_progname = NULL;
static char *g_progbasename = NULL;

typedef int (*comp_pred2)(intptr_t lhs, intptr_t rhs);

static bool
test_has_skip(struct zuc_test *test)
{
	return test->skipped;
}

static bool
test_has_failure(struct zuc_test *test)
{
	return test->fatal || test->failed;
}

bool
zuc_has_skip(void)
{
	return g_ctx.curr_test ?
		test_has_skip(g_ctx.curr_test) : false;
}

bool
zuc_has_failure(void)
{
	return g_ctx.curr_test ?
		test_has_failure(g_ctx.curr_test) : false;
}

void
zuc_set_filter(const char *filter)
{
	g_ctx.filter = strdup(filter);
}

void
zuc_set_repeat(int repeat)
{
	g_ctx.repeat = repeat;
}

void
zuc_set_random(int random)
{
	g_ctx.random = random;
}

void
zuc_set_spawn(bool spawn)
{
	g_ctx.spawn = spawn;
}

void
zuc_set_break_on_failure(bool break_on_failure)
{
	g_ctx.break_on_failure = break_on_failure;
}

void
zuc_set_output_junit(bool enable)
{
	g_ctx.output_junit = enable;
}

const char *
zuc_get_program_name(void)
{
	return g_progname;
}

const char *
zuc_get_program_basename(void)
{
	return g_progbasename;
}

static struct zuc_test *
create_test(int order, zucimpl_test_fn fn, zucimpl_test_fn_f fn_f,
	    char const *case_name, char const *test_name,
	    struct zuc_case *parent)
{
	struct zuc_test *test = zalloc(sizeof(struct zuc_test));
	ZUC_ASSERTG_NOT_NULL(test, out);
	test->order = order;
	test->fn = fn;
	test->fn_f = fn_f;
	test->name = strdup(test_name);
	if ((!fn && !fn_f) ||
	    (strncmp(DISABLED_PREFIX,
		     test_name, sizeof(DISABLED_PREFIX) - 1) == 0))
		test->disabled = 1;

	test->test_case = parent;

out:
	return test;
}

static int
compare_regs(const void *lhs, const void *rhs)
{
	int rc = strcmp((*(struct zuc_registration **)lhs)->tcase,
		      (*(struct zuc_registration **)rhs)->tcase);
	if (rc == 0)
		rc = strcmp((*(struct zuc_registration **)lhs)->test,
		      (*(struct zuc_registration **)rhs)->test);

	return rc;
}

/* gcc-specific markers for auto test case registration: */
extern const struct zuc_registration __start_zuc_tsect;
extern const struct zuc_registration __stop_zuc_tsect;

static void
register_tests(void)
{
	size_t case_count = 0;
	size_t count = &__stop_zuc_tsect - &__start_zuc_tsect;
	size_t i;
	int idx = 0;
	const char *last_name = NULL;
	void **array = zalloc(sizeof(void *) * count);
	ZUC_ASSERT_NOT_NULL(array);
	for (i = 0; i < count; ++i)
		array[i] = (void *)(&__start_zuc_tsect + i);

	qsort(array, count, sizeof(array[0]), compare_regs);

	/* Count transitions to get number of test cases. */
	last_name = NULL;
	for (i = 0; i < count; ++i) {
		const struct zuc_registration *reg =
			(const struct zuc_registration *)array[i];
		if (!last_name || (strcmp(last_name, reg->tcase))) {
			last_name = reg->tcase;
			case_count++;
		}
	}

	/* Create test case data items. */
	struct zuc_case **case_array =
		zalloc(sizeof(struct zuc_case *) * case_count);
	ZUC_ASSERT_NOT_NULL(case_array);
	struct zuc_case *last_case = NULL;
	size_t case_num = 0;
	for (i = 0; i < count; ++i) {
		const struct zuc_registration *reg =
			(const struct zuc_registration *)array[i];
		if (!last_case || (strcmp(last_case->name, reg->tcase))) {
			last_case = zalloc(sizeof(struct zuc_case));
			ZUC_ASSERT_NOT_NULL(last_case);
			last_case->order = count;
			last_case->name = strdup(reg->tcase);
			last_case->fxt = reg->fxt;
			last_case->test_count = i;
			if (case_num > 0) {
				int tcount = i
					- case_array[case_num - 1]->test_count;
				case_array[case_num - 1]->test_count = tcount;
			}
			case_array[case_num++] = last_case;
		}
	}
	case_array[case_count - 1]->test_count = count
		- case_array[case_count - 1]->test_count;

	/* Reserve space for tests per test case. */
	for (case_num = 0; case_num < case_count; ++case_num) {
		case_array[case_num]->tests =
			zalloc(case_array[case_num]->test_count
			       * sizeof(struct zuc_test *));
		ZUC_ASSERT_NOT_NULL(case_array[case_num]->tests);
	}

	last_name = NULL;
	case_num = -1;
	for (i = 0; i < count; ++i) {
		const struct zuc_registration *reg =
			(const struct zuc_registration *)array[i];
		int order = count - (1 + (reg - &__start_zuc_tsect));

		if (!last_name || (strcmp(last_name, reg->tcase))) {
			last_name = reg->tcase;
			case_num++;
			idx = 0;
		}
		if (order < case_array[case_num]->order)
			case_array[case_num]->order = order;
		case_array[case_num]->tests[idx] =
			create_test(order, reg->fn, reg->fn_f,
				    reg->tcase, reg->test,
				    case_array[case_num]);

		if (case_array[case_num]->fxt != reg->fxt)
			printf("%s:%d: error: Mismatched fixtures for '%s'\n",
			       __FILE__, __LINE__, case_array[case_num]->name);

		idx++;
	}
	free(array);

	g_ctx.case_count = case_count;
	g_ctx.cases = case_array;
}

static int
compare_case_order(const void *lhs, const void *rhs)
{
	return (*(struct zuc_case **)lhs)->order
		- (*(struct zuc_case **)rhs)->order;
}

static int
compare_test_order(const void *lhs, const void *rhs)
{
	return (*(struct zuc_test **)lhs)->order
		- (*(struct zuc_test **)rhs)->order;
}

static void
order_cases(int count, struct zuc_case **cases)
{
	int i;
	qsort(cases, count, sizeof(*cases), compare_case_order);
	for (i = 0; i < count; ++i) {
		qsort(cases[i]->tests, cases[i]->test_count,
		      sizeof(*cases[i]->tests), compare_test_order);
	}
}

static void
free_events(struct zuc_event **events)
{
	struct zuc_event *evt = *events;
	*events = NULL;
	while (evt) {
		struct zuc_event *old = evt;
		evt = evt->next;
		free(old->file);
		if (old->valtype == ZUC_VAL_CSTR) {
			free((void *)old->val1);
			free((void *)old->val2);
		}
		free(old->expr1);
		free(old->expr2);
		free(old);
	}
}

static void
free_test(struct zuc_test *test)
{
	free(test->name);
	free_events(&test->events);
	free_events(&test->deferred);
	free(test);
}

static void
free_test_case(struct zuc_case *test_case)
{
	int i;
	free(test_case->name);
	for (i = test_case->test_count - 1; i >= 0; --i) {
		free_test(test_case->tests[i]);
		test_case->tests[i] = NULL;
	}
	free(test_case->tests);
	free(test_case);
}

/**
 * A very simple matching that is compatible with the algorithm used in
 * Google Test.
 *
 * @param wildcard sequence of '?', '*' or normal characters to match.
 * @param str string to check for matching.
 * @return true if the wildcard matches the entire string, false otherwise.
 */
static bool
wildcard_matches(char const *wildcard, char const *str)
{
	switch (*wildcard) {
	case '\0':
		return !*str;
	case '?':
		return *str && wildcard_matches(wildcard + 1, str + 1);
	case '*':
		return (*str && wildcard_matches(wildcard, str + 1))
			|| wildcard_matches(wildcard + 1, str);
	default:
		return (*wildcard == *str)
			&& wildcard_matches(wildcard + 1, str + 1);
	};
}

static char**
segment_str(char *str)
{
	int count = 1;
	char **parts = NULL;
	char *saved = NULL;
	char *tok = NULL;
	int i = 0;
	for (i = 0; str[i]; ++i)
		if (str[i] == ':')
			count++;
	parts = zalloc(sizeof(char*) * (count + 1));
	ZUC_ASSERTG_NOT_NULL(parts, out);
	tok = strtok_r(str, ":", &saved);
	i = 0;
	parts[i++] = tok;
	while (tok) {
		tok = strtok_r(NULL, ":", &saved);
		parts[i++] = tok;
	}
out:
	return parts;
}

static void
filter_cases(int *count, struct zuc_case **cases, char const *filter)
{
	int i = 0;
	int j = 0;
	int num_pos = 0;
	int negative = -1;
	char *buf = strdup(filter);
	char **parts = segment_str(buf);

	for (i = 0; parts[i]; ++i) {
		if (parts[i][0] == '-') {
			parts[i]++;
			negative = i;
			break;
		}
		num_pos++;
	}

	for (i = 0; i < *count; ++i) {
		/* Walk backwards for easier pruning. */
		for (j = cases[i]->test_count - 1; j >= 0; --j) {
			int x;
			bool keep = num_pos == 0;
			char *name = NULL;
			struct zuc_test *test = cases[i]->tests[j];
			if (asprintf(&name, "%s.%s", cases[i]->name,
				     test->name) < 0) {
				printf("%s:%d: error: %d\n", __FILE__, __LINE__,
				       errno);
				exit(EXIT_FAILURE);
			}
			for (x = 0; (x < num_pos) && !keep; ++x)
				keep = wildcard_matches(parts[x], name);
			if (keep && (negative >= 0))
				for (x = negative; parts[x] && keep; ++x)
					keep &= !wildcard_matches(parts[x],
								  name);
			if (!keep) {
				int w;
				free_test(test);
				for (w = j + 1; w < cases[i]->test_count; w++)
					cases[i]->tests[w - 1] =
						cases[i]->tests[w];
				cases[i]->test_count--;
			}

			free(name);
		}
	}
	free(parts);
	parts = NULL;
	free(buf);
	buf = NULL;

	/* Prune any cases with no more tests. */
	for (i = *count - 1; i >= 0; --i) {
		if (cases[i]->test_count < 1) {
			free_test_case(cases[i]);
			for (j = i + 1; j < *count; ++j)
				cases[j - 1] = cases[j];
			cases[*count - 1] = NULL;
			(*count)--;
		}
	}
}

static unsigned int
get_seed_from_time(void)
{
	time_t sec = time(NULL);
	unsigned int seed = (unsigned int) sec % 100000;
	if (seed < 2)
		seed = 2;

	return seed;
}

static void
initialize(void)
{
	static bool init = false;
	if (init)
		return;

	init = true;
	register_tests();
	if (g_ctx.fatal)
		return;

	if (g_ctx.random > 1) {
		g_ctx.seed = g_ctx.random;
	} else if (g_ctx.random == 1) {
		g_ctx.seed = get_seed_from_time();
	}

	if (g_ctx.case_count) {
		order_cases(g_ctx.case_count, g_ctx.cases);
		if (g_ctx.filter && g_ctx.filter[0])
			filter_cases(&g_ctx.case_count, g_ctx.cases,
				     g_ctx.filter);
	}
}

int
zuc_initialize(int *argc, char *argv[], bool *help_flagged)
{
	int rc = EXIT_FAILURE;
	int opt_help = 0;
	int opt_nofork = 0;
	int opt_list = 0;
	int opt_repeat = 0;
	int opt_random = 0;
	int opt_break_on_failure = 0;
	int opt_junit = 0;
	char *opt_filter = NULL;

	char *help_param = NULL;
	int argc_in = *argc;

	const struct weston_option options[] = {
		{ WESTON_OPTION_BOOLEAN, "zuc-nofork", 0, &opt_nofork },
		{ WESTON_OPTION_BOOLEAN, "zuc-list-tests", 0, &opt_list },
		{ WESTON_OPTION_INTEGER, "zuc-repeat", 0, &opt_repeat },
		{ WESTON_OPTION_INTEGER, "zuc-random", 0, &opt_random },
		{ WESTON_OPTION_BOOLEAN, "zuc-break-on-failure", 0,
		  &opt_break_on_failure },
#if ENABLE_JUNIT_XML
		{ WESTON_OPTION_BOOLEAN, "zuc-output-xml", 0, &opt_junit },
#endif
		{ WESTON_OPTION_STRING, "zuc-filter", 0, &opt_filter },
	};

	/*
	 *If a test binary is linked to our libzunitcmain it might want
	 * to access the program 'name' from argv[0]
	 */
	free(g_progname);
	g_progname = NULL;
	free(g_progbasename);
	g_progbasename = NULL;
	if ((*argc > 0) && argv) {
		char *path = NULL;
		char *base = NULL;

		g_progname = strdup(argv[0]);

		/* basename() might modify the input, so needs a writeable
		 * string.
		 * It also may return a statically allocated buffer subject to
		 * being overwritten so needs to be dup'd.
		 */
		path = strdup(g_progname);
		base = basename(path);
		g_progbasename = strdup(base);
		free(path);
	} else {
		g_progname = strdup("");
		printf("%s:%d: warning: No valid argv[0] for initialization\n",
		       __FILE__, __LINE__);
	}


	initialize();
	if (g_ctx.fatal)
		return EXIT_FAILURE;

	if (help_flagged)
		*help_flagged = false;

	{
		/* Help param will be a special case and need restoring. */
		int i = 0;
		char **argv_in = NULL;
		const struct weston_option help_options[] = {
			{ WESTON_OPTION_BOOLEAN, "help", 'h', &opt_help },
		};
		argv_in = zalloc(sizeof(char *) * argc_in);
		if (!argv_in) {
			printf("%s:%d: error: alloc failed.\n",
			       __FILE__, __LINE__);
			return EXIT_FAILURE;
		}
		for (i = 0; i < argc_in; ++i)
			argv_in[i] = argv[i];

		parse_options(help_options, ARRAY_LENGTH(help_options),
			      argc, argv);
		if (*argc < argc_in) {
			for (i = 1; (i < argc_in) && !help_param; ++i) {
				bool found = false;
				int j = 0;
				for (j = 0; (j < *argc) && !found; ++j)
					found = (argv[j] == argv_in[i]);

				if (!found)
					help_param = argv_in[i];
			}
		}
		free(argv_in);
	}

	parse_options(options, ARRAY_LENGTH(options), argc, argv);

	if (help_param && (*argc < argc_in))
		argv[(*argc)++] = help_param;

	if (opt_filter) {
		zuc_set_filter(opt_filter);
		free(opt_filter);
	}

	if (opt_help) {
		printf("Usage: %s [OPTIONS]\n"
		       "  --zuc-break-on-failure\n"
		       "  --zuc-filter=FILTER\n"
		       "  --zuc-list-tests\n"
		       "  --zuc-nofork\n"
#if ENABLE_JUNIT_XML
		       "  --zuc-output-xml\n"
#endif
		       "  --zuc-random=N            [0|1|<seed number>]\n"
		       "  --zuc-repeat=N\n"
		       "  --help\n",
		       argv[0]);
		if (help_flagged)
			*help_flagged = true;
		rc = EXIT_SUCCESS;
	} else if (opt_list) {
		zuc_list_tests();
		rc = EXIT_FAILURE;
	} else {
		zuc_set_repeat(opt_repeat);
		zuc_set_random(opt_random);
		zuc_set_spawn(!opt_nofork);
		zuc_set_break_on_failure(opt_break_on_failure);
		zuc_set_output_junit(opt_junit);
		rc = EXIT_SUCCESS;
	}

	return rc;
}

static void
dispatch_pre_run(struct zuc_context *ctx, int pass_count, int pass_num,
		 int seed, const char *filter)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->pre_run)
			listener->pre_run(listener->data,
					  pass_count,
					  pass_num,
					  seed,
					  filter);
	}
}

static void
dispatch_run_started(struct zuc_context *ctx, int live_case_count,
		     int live_test_count, int disabled_count)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->run_started)
			listener->run_started(listener->data,
					      live_case_count,
					      live_test_count,
					      disabled_count);
	}
}

static void
dispatch_run_ended(struct zuc_context *ctx,
		   int live_case_count, int live_test_count, int total_passed,
		   int total_failed, int total_disabled, long total_elapsed)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->run_ended)
			listener->run_ended(listener->data,
					    ctx->case_count,
					    ctx->cases,
					    live_case_count,
					    live_test_count,
					    total_passed,
					    total_failed,
					    total_disabled,
					    total_elapsed);
	}
}

static void
dispatch_case_started(struct zuc_context *ctx,struct zuc_case *test_case,
		      int live_test_count, int disabled_count)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->case_started)
			listener->case_started(listener->data,
					       test_case,
					       live_test_count,
					       disabled_count);
	}
}

static void
dispatch_case_ended(struct zuc_context *ctx, struct zuc_case *test_case)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->case_ended)
			listener->case_ended(listener->data, test_case);
	}
}

static void
dispatch_test_started(struct zuc_context *ctx, struct zuc_test *test)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->test_started)
			listener->test_started(listener->data, test);
	}
}

static void
dispatch_test_ended(struct zuc_context *ctx, struct zuc_test *test)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->test_ended)
			listener->test_ended(listener->data, test);
	}
}

static void
dispatch_test_disabled(struct zuc_context *ctx, struct zuc_test *test)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->test_disabled)
			listener->test_disabled(listener->data, test);
	}
}

static void
dispatch_check_triggered(struct zuc_context *ctx, char const *file, int line,
			 enum zuc_fail_state state, enum zuc_check_op op,
			 enum zuc_check_valtype valtype,
			 intptr_t val1, intptr_t val2,
			 const char *expr1, const char *expr2)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->check_triggered)
			listener->check_triggered(listener->data,
						  file, line,
						  state, op, valtype,
						  val1, val2,
						  expr1, expr2);
	}
}

static void
dispatch_collect_event(struct zuc_context *ctx, char const *file, int line,
		       const char *expr1)
{
	struct zuc_slinked *curr;
	for (curr = ctx->listeners; curr; curr = curr->next) {
		struct zuc_event_listener *listener = curr->data;
		if (listener->collect_event)
			listener->collect_event(listener->data,
						file, line, expr1);
	}
}

static void
migrate_deferred_events(struct zuc_test *test, bool transferred)
{
	struct zuc_event *evt = test->deferred;
	if (!evt)
		return;

	test->deferred = NULL;
	if (test->events) {
		struct zuc_event *old = test->events;
		while (old->next)
			old = old->next;
		old->next = evt;
	} else {
		test->events = evt;
	}
	while (evt && !transferred) {
		dispatch_check_triggered(&g_ctx,
					 evt->file, evt->line,
					 evt->state, evt->op,
					 evt->valtype,
					 evt->val1, evt->val2,
					 evt->expr1, evt->expr2);
		evt = evt->next;
	}
}

static void
mark_single_failed(struct zuc_test *test, enum zuc_fail_state state)
{
	switch (state) {
	case ZUC_CHECK_OK:
		/* no internal state to change */
		break;
	case ZUC_CHECK_SKIP:
		if (test)
			test->skipped = 1;
		break;
	case ZUC_CHECK_FAIL:
		if (test)
			test->failed = 1;
		break;
	case ZUC_CHECK_ERROR:
	case ZUC_CHECK_FATAL:
		if (test)
			test->fatal = 1;
		break;
	}

	if (g_ctx.break_on_failure)
		raise(SIGABRT);

}

static void
mark_failed(struct zuc_test *test, enum zuc_fail_state state)
{
	if (!test && g_ctx.curr_test)
		test = g_ctx.curr_test;

	if (test) {
		mark_single_failed(test, state);
	} else if (g_ctx.curr_case) {
		/* In setup or tear-down of test suite */
		int i;
		for (i = 0; i < g_ctx.curr_case->test_count; ++i)
			mark_single_failed(g_ctx.curr_case->tests[i], state);
	}
	if ((state == ZUC_CHECK_FATAL) || (state == ZUC_CHECK_ERROR))
		g_ctx.fatal = true;
}

void
zuc_attach_event(struct zuc_test *test, struct zuc_event *event,
		 enum zuc_event_type event_type, bool transferred)
{
	if (!test) {
		/*
		 * consider adding events directly to the case.
		 * would be for use during per-suite setup and teardown.
		 */
		printf("%s:%d: error: No current test.\n", __FILE__, __LINE__);
	} else if (event_type == ZUC_EVENT_DEFERRED) {
		if (test->deferred) {
			struct zuc_event *curr = test->deferred;
			while (curr->next)
				curr = curr->next;
			curr->next = event;
		} else {
			test->deferred = event;
		}
	} else {
		if (test)
			migrate_deferred_events(test, transferred);

		if (test->events) {
			struct zuc_event *curr = test->events;
			while (curr->next)
				curr = curr->next;
			curr->next = event;
		} else {
			test->events = event;
		}
		mark_failed(test, event->state);
	}
}

void
zuc_add_event_listener(struct zuc_event_listener *event_listener)
{
	if (!event_listener) /* ensure null entries are not added */
		return;

	if (!g_ctx.listeners) {
		g_ctx.listeners = zalloc(sizeof(struct zuc_slinked));
		ZUC_ASSERT_NOT_NULL(g_ctx.listeners);
		g_ctx.listeners->data = event_listener;
	} else {
		struct zuc_slinked *curr = g_ctx.listeners;
		while (curr->next)
			curr = curr->next;
		curr->next = zalloc(sizeof(struct zuc_slinked));
		ZUC_ASSERT_NOT_NULL(curr->next);
		curr->next->data = event_listener;
	}
}


void
zuc_cleanup(void)
{
	int i;

	free(g_ctx.filter);
	g_ctx.filter = 0;
	for (i = 0; i < 2; ++i)
		if (g_ctx.fds[i] != -1) {
			close(g_ctx.fds[i]);
			g_ctx.fds[i] = -1;
		}

	if (g_ctx.listeners) {
		struct zuc_slinked *curr = g_ctx.listeners;
		while (curr) {
			struct zuc_slinked *old = curr;
			struct zuc_event_listener *listener = curr->data;
			if (listener->destroy)
				listener->destroy(listener->data);
			free(listener);
			curr = curr->next;
			free(old);
		}
		g_ctx.listeners = NULL;
	}

	for (i = g_ctx.case_count - 1; i >= 0; --i) {
		free_test_case(g_ctx.cases[i]);
		g_ctx.cases[i] = NULL;
	}
	free(g_ctx.cases);
	g_ctx.cases = NULL;

	free(g_progname);
	g_progname = NULL;
	free(g_progbasename);
	g_progbasename = NULL;
}

static void
shuffle_cases(int count, struct zuc_case **cases,
		   unsigned int seed)
{
	int i;
	unsigned int rseed = seed;
	for (i = 0; i < count; ++i) {
		int j;
		for (j = cases[i]->test_count - 1; j > 0 ; --j) {
			int val = rand_r(&rseed);
			int b = ((val / (double)RAND_MAX) * j + 0.5);
			if (j != b) {
				struct zuc_test *tmp = cases[i]->tests[j];
				cases[i]->tests[j] = cases[i]->tests[b];
				cases[i]->tests[b] = tmp;
			}
		}
	}

	for (i = count - 1; i > 0; --i) {
		int val = rand_r(&rseed);
		int j = ((val / (double)RAND_MAX) * i + 0.5);

		if (i != j) {
			struct zuc_case *tmp = cases[i];
			cases[i] = cases[j];
			cases[j] = tmp;
		}
	}
}

void
zuc_list_tests(void)
{
	int i;
	int j;
	initialize();
	if (g_ctx.fatal)
		return;
	for (i = 0; i < g_ctx.case_count; ++i) {
		printf("%s.\n", g_ctx.cases[i]->name);
		for (j = 0; j < g_ctx.cases[i]->test_count; ++j) {
			printf("  %s\n", g_ctx.cases[i]->tests[j]->name);
		}
	}
}

static void
spawn_test(struct zuc_test *test, void *test_data,
	   void (*cleanup_fn)(void *data), void *cleanup_data)
{
	pid_t pid = -1;

	if (!test || (!test->fn && !test->fn_f))
		return;

	if (pipe2(g_ctx.fds, O_CLOEXEC)) {
		printf("%s:%d: error: Unable to create pipe: %d\n",
		       __FILE__, __LINE__, errno);
		mark_failed(test, ZUC_CHECK_ERROR);
		return;
	}

	fflush(NULL); /* important. avoid duplication of output */
	pid = fork();
	switch (pid) {
	case -1: /* Error forking */
		printf("%s:%d: error: Problem with fork: %d\n",
		       __FILE__, __LINE__, errno);
		mark_failed(test, ZUC_CHECK_ERROR);
		close(g_ctx.fds[0]);
		g_ctx.fds[0] = -1;
		close(g_ctx.fds[1]);
		g_ctx.fds[1] = -1;
		break;
	case 0: { /* child */
		int rc = EXIT_SUCCESS;
		close(g_ctx.fds[0]);
		g_ctx.fds[0] = -1;

		if (test->fn_f)
			test->fn_f(test_data);
		else
			test->fn();

		if (test_has_failure(test))
			rc = EXIT_FAILURE;
		else if (test_has_skip(test))
			rc = ZUC_EXIT_SKIP;

		/* Avoid confusing memory tools like valgrind */
		if (cleanup_fn)
			cleanup_fn(cleanup_data);

		zuc_cleanup();
		exit(rc);
		break;
	}
	default: { /* parent */
		ssize_t rc = 0;
		siginfo_t info = {};

		close(g_ctx.fds[1]);
		g_ctx.fds[1] = -1;

		do {
			rc = zuc_process_message(g_ctx.curr_test,
						 g_ctx.fds[0]);
		} while (rc > 0);
		close(g_ctx.fds[0]);
		g_ctx.fds[0] = -1;

		if (waitid(P_ALL, 0, &info, WEXITED)) {
			printf("%s:%d: error: waitid failed. (%d)\n",
			       __FILE__, __LINE__, errno);
			mark_failed(test, ZUC_CHECK_ERROR);
		} else {
			switch (info.si_code) {
			case CLD_EXITED: {
				int exit_code = info.si_status;
				switch(exit_code) {
				case EXIT_SUCCESS:
					break;
				case ZUC_EXIT_SKIP:
					if (!test_has_skip(g_ctx.curr_test) &&
					    !test_has_failure(g_ctx.curr_test))
						ZUC_SKIP("Child exited SKIP");
					break;
				default:
					/* unexpected failure */
					if (!test_has_failure(g_ctx.curr_test))
						ZUC_ASSERT_EQ(0, exit_code);
				}
				break;
			}
			case CLD_KILLED:
			case CLD_DUMPED:
				printf("%s:%d: error: signaled: %d\n",
				       __FILE__, __LINE__, info.si_status);
				mark_failed(test, ZUC_CHECK_ERROR);
				break;
			}
		}
	}
	}
}

static void
run_single_test(struct zuc_test *test,const struct zuc_fixture *fxt,
		void *case_data, bool spawn)
{
	long elapsed = 0;
	struct timespec begin;
	struct timespec end;
	void *test_data = NULL;
	void *cleanup_data = NULL;
	void (*cleanup_fn)(void *data) = NULL;
	memset(&begin, 0, sizeof(begin));
	memset(&end, 0, sizeof(end));

	g_ctx.curr_test = test;
	dispatch_test_started(&g_ctx, test);

	cleanup_fn = fxt ? fxt->tear_down : NULL;
	cleanup_data = NULL;

	if (fxt && fxt->set_up) {
		test_data = fxt->set_up(case_data);
		cleanup_data = test_data;
	} else {
		test_data = case_data;
	}

	clock_gettime(TARGET_TIMER, &begin);

	/* Need to re-check these, as fixtures might have changed test state. */
	if (!test->fatal && !test->skipped) {
		if (spawn) {
			spawn_test(test, test_data,
				   cleanup_fn, cleanup_data);
		} else {
			if (test->fn_f)
				test->fn_f(test_data);
			else
				test->fn();
		}
	}

	clock_gettime(TARGET_TIMER, &end);

	elapsed = (end.tv_sec - begin.tv_sec) * MS_PER_SEC;
	if (end.tv_sec != begin.tv_sec) {
		elapsed -= (begin.tv_nsec) / NANO_PER_MS;
		elapsed += (end.tv_nsec) / NANO_PER_MS;
	} else {
		elapsed += (end.tv_nsec - begin.tv_nsec) / NANO_PER_MS;
	}
	test->elapsed = elapsed;

	if (cleanup_fn)
		cleanup_fn(cleanup_data);

	if (test->deferred) {
		if (test_has_failure(test))
			migrate_deferred_events(test, false);
		else
			free_events(&test->deferred);
	}

	dispatch_test_ended(&g_ctx, test);

	g_ctx.curr_test = NULL;
}

static void
run_single_case(struct zuc_case *test_case)
{
	int count_live = test_case->test_count - test_case->disabled;
	g_ctx.curr_case = test_case;
	if (count_live) {
		int i = 0;
		const struct zuc_fixture *fxt = test_case->fxt;
		void *case_data = fxt ? (void *)fxt->data : NULL;

		dispatch_case_started(&g_ctx, test_case,
				      count_live, test_case->disabled);

		if (fxt && fxt->set_up_test_case)
			case_data = fxt->set_up_test_case(fxt->data);

		for (i = 0; i < test_case->test_count; ++i) {
			struct zuc_test *curr = test_case->tests[i];
			if (curr->disabled) {
				dispatch_test_disabled(&g_ctx, curr);
			} else {
				run_single_test(curr, fxt, case_data,
						g_ctx.spawn);
				if (curr->skipped)
					test_case->skipped++;
				if (curr->failed)
					test_case->failed++;
				if (curr->fatal)
					test_case->fatal++;
				if (!curr->failed && !curr->fatal)
					test_case->passed++;
				test_case->elapsed += curr->elapsed;
			}
		}

		if (fxt && fxt->tear_down_test_case)
			fxt->tear_down_test_case(case_data);

		dispatch_case_ended(&g_ctx, test_case);
	}
	g_ctx.curr_case = NULL;
}

static void
reset_test_values(struct zuc_case **cases, int case_count)
{
	int i;
	for (i = 0; i < case_count; ++i) {
		int j;
		cases[i]->disabled = 0;
		cases[i]->skipped = 0;
		cases[i]->failed = 0;
		cases[i]->fatal = 0;
		cases[i]->passed = 0;
		cases[i]->elapsed = 0;
		for (j = 0; j < cases[i]->test_count; ++j) {
			struct zuc_test *test = cases[i]->tests[j];
			if (test->disabled)
				cases[i]->disabled++;
			test->skipped = 0;
			test->failed = 0;
			test->fatal = 0;
			test->elapsed = 0;

			free_events(&test->events);
			free_events(&test->deferred);
		}
	}
}

static int
run_single_pass(void)
{
	long total_elapsed = 0;
	int total_passed = 0;
	int total_failed = 0;
	int total_skipped = 0;
	int live_case_count = 0;
	int live_test_count = 0;
	int disabled_test_count = 0;
	int i;

	reset_test_values(g_ctx.cases, g_ctx.case_count);
	for (i = 0; i <  g_ctx.case_count; ++i) {
		int live = g_ctx.cases[i]->test_count
			- g_ctx.cases[i]->disabled;
		if (live) {
			live_test_count += live;
			live_case_count++;
		}
		if (g_ctx.cases[i]->disabled)
			disabled_test_count++;
	}

	dispatch_run_started(&g_ctx, live_case_count, live_test_count,
			     disabled_test_count);

	for (i = 0; i <  g_ctx.case_count; ++i) {
		run_single_case(g_ctx.cases[i]);
		total_failed += g_ctx.cases[i]->test_count
			- (g_ctx.cases[i]->passed + g_ctx.cases[i]->disabled);
		total_passed += g_ctx.cases[i]->passed;
		total_elapsed += g_ctx.cases[i]->elapsed;
		total_skipped += g_ctx.cases[i]->skipped;
	}

	dispatch_run_ended(&g_ctx, live_case_count, live_test_count,
			   total_passed, total_failed, disabled_test_count,
			   total_elapsed);

	if (total_failed)
		return EXIT_FAILURE;
	else if (total_skipped)
		return ZUC_EXIT_SKIP;
	else
		return EXIT_SUCCESS;
}

int
zucimpl_run_tests(void)
{
	int rc = EXIT_SUCCESS;
	int i;
	int limit = g_ctx.repeat > 0 ? g_ctx.repeat : 1;

	initialize();
	if (g_ctx.fatal)
		return EXIT_FAILURE;

	if (g_ctx.listeners == NULL) {
		zuc_add_event_listener(zuc_collector_create(&(g_ctx.fds[1])));
		zuc_add_event_listener(zuc_base_logger_create());
		if (g_ctx.output_junit)
			zuc_add_event_listener(zuc_junit_reporter_create());
	}

	if (g_ctx.case_count < 1) {
		printf("%s:%d: error: Setup error: test tree is empty\n",
		       __FILE__, __LINE__);
		rc = EXIT_FAILURE;
	}

	for (i = 0; (i < limit) && (g_ctx.case_count > 0); ++i) {
		int pass_code = EXIT_SUCCESS;
		dispatch_pre_run(&g_ctx, limit, i + 1,
				 (g_ctx.random > 0) ? g_ctx.seed : 0,
				 g_ctx.filter);

		order_cases(g_ctx.case_count, g_ctx.cases);
		if (g_ctx.random > 0)
			shuffle_cases(g_ctx.case_count, g_ctx.cases,
				      g_ctx.seed);

		pass_code = run_single_pass();
		if (pass_code == EXIT_FAILURE)
			rc = EXIT_FAILURE;
		else if ((pass_code == ZUC_EXIT_SKIP) && (rc == EXIT_SUCCESS))
			rc = ZUC_EXIT_SKIP;

		g_ctx.seed++;
	}

	return rc;
}

int
zucimpl_tracepoint(char const *file, int line, char const *fmt, ...)
{
	int rc = -1;
	va_list argp;
	char *msg = NULL;


	va_start(argp, fmt);
	rc = vasprintf(&msg, fmt, argp);
	if (rc == -1) {
		msg = NULL;
	}
	va_end(argp);

	dispatch_collect_event(&g_ctx,
			       file, line,
			       msg);

	free(msg);

	return rc;
}

void
zucimpl_terminate(char const *file, int line,
		  bool fail, bool fatal, const char *msg)
{
	enum zuc_fail_state state = ZUC_CHECK_SKIP;
	int level = 2;
	if (fail && fatal) {
		state =  ZUC_CHECK_FATAL;
		level = 0;
	} else if (fail && !fatal) {
		state = ZUC_CHECK_FAIL;
		level = 0;
	}

	mark_failed(g_ctx.curr_test, state);

	if ((state != ZUC_CHECK_OK) && g_ctx.curr_test)
		migrate_deferred_events(g_ctx.curr_test, false);

	dispatch_check_triggered(&g_ctx,
				 file, line,
				 state,
				 ZUC_OP_TERMINATE, ZUC_VAL_INT,
				 level, 0,
				 msg, "");
}

static void
validate_types(enum zuc_check_op op, enum zuc_check_valtype valtype)
{
	bool is_valid = true;

	switch (op) {
	case ZUC_OP_NULL:
	case ZUC_OP_NOT_NULL:
		is_valid = is_valid && (valtype == ZUC_VAL_PTR);
		break;
	default:
		; /* all rest OK */
	}

	switch (valtype) {
	case ZUC_VAL_CSTR:
		is_valid = is_valid && ((op == ZUC_OP_EQ) || (op == ZUC_OP_NE));
		break;
	default:
		; /* all rest OK */
	}

	if (!is_valid)
		printf("%s:%d: warning: Unexpected op+type %d/%d.\n",
		       __FILE__, __LINE__, op, valtype);
}

static int
pred2_unknown(intptr_t lhs, intptr_t rhs)
{
	return 0;
}

static int
pred2_true(intptr_t lhs, intptr_t rhs)
{
	return lhs;
}

static int
pred2_false(intptr_t lhs, intptr_t rhs)
{
	return !lhs;
}

static int
pred2_eq(intptr_t lhs, intptr_t rhs)
{
	return lhs == rhs;
}

static int
pred2_streq(intptr_t lhs, intptr_t rhs)
{
	int status = 0;
	const char *lhptr = (const char *)lhs;
	const char *rhptr = (const char *)rhs;

	if (!lhptr && !rhptr)
		status = 1;
	else if (lhptr && rhptr)
		status = strcmp(lhptr, rhptr) == 0;

	return status;
}

static int
pred2_ne(intptr_t lhs, intptr_t rhs)
{
	return lhs != rhs;
}

static int
pred2_strne(intptr_t lhs, intptr_t rhs)
{
	int status = 0;
	const char *lhptr = (const char *)lhs;
	const char *rhptr = (const char *)rhs;

	if (lhptr != rhptr) {
		if (!lhptr || !rhptr)
			status = 1;
		else
			status = strcmp(lhptr, rhptr) != 0;
	}

	return status;
}

static int
pred2_ge(intptr_t lhs, intptr_t rhs)
{
	return lhs >= rhs;
}

static int
pred2_gt(intptr_t lhs, intptr_t rhs)
{
	return lhs > rhs;
}

static int
pred2_le(intptr_t lhs, intptr_t rhs)
{
	return lhs <= rhs;
}

static int
pred2_lt(intptr_t lhs, intptr_t rhs)
{
	return lhs < rhs;
}

static comp_pred2
get_pred2(enum zuc_check_op op, enum zuc_check_valtype valtype)
{
	switch (op) {
	case ZUC_OP_TRUE:
		return pred2_true;
		break;
	case ZUC_OP_FALSE:
		return pred2_false;
		break;
	case ZUC_OP_NULL:
		return pred2_false;
		break;
	case ZUC_OP_NOT_NULL:
		return pred2_true;
		break;
	case ZUC_OP_EQ:
		if (valtype == ZUC_VAL_CSTR)
			return pred2_streq;
		else
			return pred2_eq;
		break;
	case ZUC_OP_NE:
		if (valtype == ZUC_VAL_CSTR)
			return pred2_strne;
		else
			return pred2_ne;
		break;
	case ZUC_OP_GE:
		return pred2_ge;
		break;
	case ZUC_OP_GT:
		return pred2_gt;
		break;
	case ZUC_OP_LE:
		return pred2_le;
		break;
	case ZUC_OP_LT:
		return pred2_lt;
		break;
	default:
		return pred2_unknown;
	}
}

int
zucimpl_expect_pred2(char const *file, int line,
		     enum zuc_check_op op, enum zuc_check_valtype valtype,
		     bool fatal,
		     intptr_t lhs, intptr_t rhs,
		     const char *lhs_str, const char* rhs_str)
{
	enum zuc_fail_state state = fatal ?  ZUC_CHECK_FATAL : ZUC_CHECK_FAIL;
	comp_pred2 pred = get_pred2(op, valtype);
	int failed = !pred(lhs, rhs);

	validate_types(op, valtype);

	if (failed) {
		mark_failed(g_ctx.curr_test, state);

		if (g_ctx.curr_test)
			migrate_deferred_events(g_ctx.curr_test, false);

		dispatch_check_triggered(&g_ctx,
					 file, line,
					 fatal ? ZUC_CHECK_FATAL
					 : ZUC_CHECK_FAIL,
					 op, valtype,
					 lhs, rhs,
					 lhs_str, rhs_str);
	}
	return failed;
}
