/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */
 
#include "support.h"
#include <libsyncml/sml_error_internals.h>
#include <libxml/parser.h>

#ifndef THREAD_SAFE_CHECK
GStaticMutex __libsyncml_check_mutex = G_STATIC_MUTEX_INIT;
#endif

/* FIXME: These settings should be controlled by the test environment.
 * FIXME: The hard coding of the environment creates an unreal test scenario.
 * FIXME: The build host environment must be changed appropriately.
 */
void configure_environment()
{
	/* Several libraries do things which confuse debuggers and
	 * memory checkers like Valgrind. Therefore it is often
	 * possible to configure such libraries via environment
	 * variables to use a more conventional behaviour.
	 *
	 * If the user already set an environment variable
	 * then this variable is not touched.
	 */

	/* The glib uses some memory caching mechanisms for performance
	 * enhancements which confuses memory checkers like Valgrind.
	 * Therefore it is necessary to deactivate these mechanisms for
	 * testing.
	 */
	if (!g_setenv("G_SLICE", "always-malloc", FALSE))
		g_warning("G_SLICE is already set.");
	if (!g_setenv("G_DEBUG", "gc-friendly", FALSE))
		g_warning("G_DEBUG is already set.");

	/* The check library usually forks test suites to safely survive
	 * things like segmentation faults and to have a clean separated
	 * address space for every test suite. This behaviour makes it
	 * difficult for memory checks like Valgrind to detect real
	 * problems correctly. Usually you get tons of false positives.
	 * Therefore forking is disabled.
	 */
	if (!g_setenv("CK_FORK", "no", FALSE))
		g_warning("CK_FORK is already set.");

	xmlInitParser();
}

void cleanup_environment()
{
	xmlCleanupParser();
}

char *olddir = NULL;

char *setup_testbed(char *fkt_name)
{
	char *testbed = NULL;
	setuid(65534);
	
	if (!g_thread_supported ()) g_thread_init (NULL);

#ifndef THREAD_SAFE_CHECK
	/* unlock static mutex lock if locked from earlier error */
	g_static_mutex_trylock(&__libsyncml_check_mutex);
	g_static_mutex_unlock(&__libsyncml_check_mutex);
#endif
	
	if (fkt_name) {
		testbed = g_strdup_printf("%s/testbed.XXXXXX", g_get_tmp_dir());
		if (mkdtemp(testbed)) {
			sml_fail_unless(FALSE, "Cannot setup testbed at %s.", testbed);
		}
		
		char *command = NULL;
		if (fkt_name) {
			command = g_strdup_printf("cp -R %s%sdata/%s/* %s", g_getenv("srcdir") ? g_getenv("srcdir") : "", g_getenv("srcdir") ? "/" : "", fkt_name, testbed);
			if (system(command)) {
				sml_fail_unless(FALSE, "Cannot execute command \"%s\".", command);
			}
			g_free(command);
		}
		
		olddir = g_get_current_dir();
		if (chdir(testbed)) {
			sml_fail_unless(FALSE, "Cannot change to testbed directory %s.", testbed);
		}
		
		smlTrace(TRACE_INTERNAL, "%s: Seting up %s at %s", __func__, VA_STRING(fkt_name), VA_STRING(testbed));
	}
	//printf(".");
	//fflush(NULL);
	return testbed;
}

void destroy_testbed(char *path)
{
	char *command = g_strdup_printf("rm -rf %s", path);
	if (olddir && chdir(olddir)) {
		sml_fail_unless(FALSE, "Cannot change to old directory \"%s\".", olddir);
	}
	if (system(command)) {
		sml_fail_unless(FALSE, "Cannot execute command \"%s\".", command);
	}
	g_free(command);
	smlTrace(TRACE_INTERNAL, "%s: Tearing down %s", __func__, VA_STRING(path));
	g_free(path);
}

void create_case(Suite *s, const char *name, TFun function)
{
	TCase *tc_new = tcase_create(name);
	tcase_set_timeout(tc_new, 60);
	tcase_add_test(tc_new, function);
	suite_add_tcase (s, tc_new);
}

SmlParser *start_parser(const char *data, SmlError **error)
{
	CHECK_ERROR_REF
	setup_testbed(NULL);
	
	SmlParser *parser = smlParserNew(SML_MIMETYPE_XML, 0, error);
	sml_fail_unless(parser != NULL, "%s", smlErrorPrint(error));
	sml_fail_unless(*error == NULL, "%s", smlErrorPrint(error));
	
	if (!smlParserStart(parser, data, strlen(data), error)) {
		sml_fail_unless(*error != NULL, "The parser start failed but the error object was not set.");
		smlParserFree(parser);
		return NULL;
	}
	
	return parser;
}

int sml_sleep(int64_t nanoseconds)
{
	long seconds = nanoseconds / 1000000000;
	nanoseconds = nanoseconds % 1000000000;
	struct timespec timestr;
	timestr.tv_sec  = seconds;
	timestr.tv_nsec = nanoseconds;
	return nanosleep(&timestr, NULL);
}

/* create test case stuff */

static void sml_testsuite_all(Suite *s, struct sml_testcase_s *tc)
{
	unsigned int i;
	for (i=0; tc[i].name; i++)
		create_case(s, tc[i].name, tc[i].func);
}

static unsigned int sml_testsuite_selected(
			Suite *s,
			int argc, char **argv,
			struct sml_testcase_s *tc)
{
	int i, j, n=0;
	/* Also argv[0]! for symlink-ed calls */
	for (i=0; argc > i; i++) {
		for (j=0; tc[j].name; j++) {
			if (strcmp(g_basename(argv[i]), tc[j].name))
					continue;

			create_case(s, tc[j].name, tc[j].func);
			n++;
		}
	}

	return n;
}

int sml_testsuite(
		int argc,
		char **argv,
		const char *unittest,
		struct sml_testcase_s *tc)
{
	configure_environment();

	int nf;
	Suite *s = suite_create(unittest);
	SRunner *sr;

	/* check_env(); */

	if (!sml_testsuite_selected(s, argc, argv, tc))
		sml_testsuite_all(s, tc);

	sr = srunner_create(s);
	srunner_run_all(sr, CK_VERBOSE);
	nf = srunner_ntests_failed(sr);
	srunner_free(sr);

	cleanup_environment();

	return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

