// --------------------------------------------------------------------------
//
// File
//		Name:    Test.h
//		Purpose: Useful stuff for tests
//		Created: 2003/07/11
//
// --------------------------------------------------------------------------

#ifndef TEST__H
#define TEST__H

#include <cstring>
#include <list>
#include <map>

#include "Configuration.h"

#ifdef WIN32
#define BBACKUPCTL      "..\\..\\bin\\bbackupctl\\bbackupctl.exe"
#define BBACKUPD        "..\\..\\bin\\bbackupd\\bbackupd.exe"
#define BBSTORED        "..\\..\\bin\\bbstored\\bbstored.exe"
#define BBACKUPQUERY    "..\\..\\bin\\bbackupquery\\bbackupquery.exe"
#define BBSTOREACCOUNTS "..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts.exe"
#define TEST_RETURN(actual, expected) TEST_EQUAL(expected, actual);
#define TEST_RETURN_COMMAND(actual, expected, command) TEST_EQUAL_LINE(expected, actual, command);
#else
#define BBACKUPCTL      "../../bin/bbackupctl/bbackupctl"
#define BBACKUPD        "../../bin/bbackupd/bbackupd"
#define BBSTORED        "../../bin/bbstored/bbstored"
#define BBACKUPQUERY    "../../bin/bbackupquery/bbackupquery"
#define BBSTOREACCOUNTS "../../bin/bbstoreaccounts/bbstoreaccounts"
#define TEST_RETURN(actual, expected) TEST_EQUAL((expected << 8), actual);
#define TEST_RETURN_COMMAND(actual, expected, command) TEST_EQUAL_LINE((expected << 8), actual, command);
#endif

#define DEFAULT_BBSTORED_CONFIG_FILE "testfiles/bbstored.conf"
#define DEFAULT_BBACKUPD_CONFIG_FILE "testfiles/bbackupd.conf"
#define DEFAULT_S3_CACHE_DIR "testfiles/bbackupd-cache"

extern int num_failures;
extern int first_fail_line;
extern int num_tests_selected;
extern std::string first_fail_file;
extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args;
extern std::list<std::string> run_only_named_tests;
extern std::string current_test_name;
extern std::map<std::string, std::string> s_test_status;

//! Simplifies calling setUp() with the current function name in each test.
#define SETUP() \
	if (!setUp(__FUNCTION__)) return true; \
	try \
	{ // left open for TEARDOWN()

#define TEARDOWN() \
		return tearDown(); \
	} \
	catch (BoxException &e) \
	{ \
		BOX_NOTICE(__FUNCTION__ << " errored: " << e.what()); \
		num_failures++; \
		tearDown(); \
		s_test_status[__FUNCTION__] = "ERRORED"; \
		return false; \
	}

//! End the current test. Only use within a test function, because it just returns false!
#define FAIL { \
	std::ostringstream os; \
	os << "failed at " << __FUNCTION__ << ":" << __LINE__; \
	s_test_status[current_test_name] = os.str(); \
	return fail(); \
}

#define TEST_FAIL_WITH_MESSAGE(msg) \
{ \
	if (num_failures == 0) \
	{ \
		first_fail_file = __FILE__; \
		first_fail_line = __LINE__; \
	} \
	num_failures++; \
	BOX_ERROR("**** TEST FAILURE: " << msg << " at " << __FILE__ << \
		":" << __LINE__); \
}

#define TEST_ABORT_WITH_MESSAGE(msg) {TEST_FAIL_WITH_MESSAGE(msg); return 1;}

#define TEST_THAT_OR(condition, or_command) \
	if(!(condition)) \
	{ \
		TEST_FAIL_WITH_MESSAGE("Condition [" #condition "] failed"); \
		or_command; \
	}
#define TEST_THAT(condition) TEST_THAT_OR(condition,)
#define TEST_THAT_ABORTONFAIL(condition) {if(!(condition)) TEST_ABORT_WITH_MESSAGE("Condition [" #condition "] failed")}
#define TEST_THAT_THROWONFAIL(condition) \
	TEST_THAT_OR(condition, THROW_EXCEPTION_MESSAGE(CommonException, \
		AssertFailed, "Condition [" #condition "] failed"));

// NOTE: The 0- bit is to allow this to work with stuff which has negative constants for flags (eg ConnectionException)
#define TEST_CHECK_THROWS_AND_OR(statement, excepttype, subtype, and_command, or_command) \
	{ \
		bool didthrow = false; \
		HideExceptionMessageGuard hide; \
		BOX_TRACE("Exception logging disabled at " __FILE__ ":" \
			<< __LINE__); \
		try \
		{ \
			statement; \
		} \
		catch(excepttype &e) \
		{ \
			if(e.GetSubType() != ((unsigned int)excepttype::subtype) \
					&& e.GetSubType() != (unsigned int)(0-excepttype::subtype)) \
			{ \
				throw; \
			} \
			didthrow = true; \
			and_command; \
		} \
		catch(...) \
		{ \
			throw; \
		} \
		if(!didthrow) \
		{ \
			TEST_FAIL_WITH_MESSAGE("Didn't throw exception " #excepttype "(" #subtype ")"); \
			or_command; \
		} \
	}
#define TEST_CHECK_THROWS(statement, excepttype, subtype) \
	TEST_CHECK_THROWS_AND_OR(statement, excepttype, subtype,,)

// utility macro for comparing two strings in a line
#define TEST_EQUAL_OR(_expected, _found, or_command) \
{ \
	std::ostringstream _oss1; \
	_oss1 << _expected; \
	std::string _exp_str = _oss1.str(); \
	\
	std::ostringstream _oss2; \
	_oss2 << _found; \
	std::string _found_str = _oss2.str(); \
	\
	if(_exp_str != _found_str) \
	{ \
		BOX_ERROR("Expected <" << _exp_str << "> but found <" << \
			_found_str << ">"); \
		\
		std::ostringstream _oss3; \
		_oss3 << #_found << " != " << #_expected; \
		\
		TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \
		or_command; \
	} \
}
#define TEST_EQUAL(_expected, _found) \
	TEST_EQUAL_OR(_expected, _found,)

// utility macro for comparing two strings in a line
#define TEST_EQUAL_LINE(_expected, _found, _line) \
{ \
	std::ostringstream _oss1; \
	_oss1 << _expected; \
	std::string _exp_str = _oss1.str(); \
	\
	std::ostringstream _oss2; \
	_oss2 << _found; \
	std::string _found_str = _oss2.str(); \
	\
	if(_exp_str != _found_str) \
	{ \
		std::ostringstream _ossl; \
		_ossl << _line; \
		std::string _line_str = _ossl.str(); \
		printf("Expected <%s> but found <%s> in <%s>\n", \
			_exp_str.c_str(), _found_str.c_str(), _line_str.c_str()); \
		\
		std::ostringstream _oss3; \
		_oss3 << #_found << " != " << #_expected << " in " << _line; \
		\
		TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \
	} \
}

// utility macros for testing a string/output line
#define TEST_LINE(_condition, _line) \
	TEST_THAT(_condition); \
	if (!(_condition)) \
	{ \
		std::ostringstream _ossl; \
		_ossl << _line; \
		std::string _line_str = _ossl.str(); \
		printf("Test failed on <%s>\n", _line_str.c_str()); \
	}

#define TEST_LINE_OR(_condition, _line, _or_command) \
	TEST_LINE(_condition, _line); \
	if(!(_condition)) \
	{ \
		_or_command; \
	}

#define TEST_STARTSWITH(expected, actual) \
	TEST_EQUAL_LINE(expected, actual.substr(0, std::string(expected).size()), actual);

#define TEST_ENDSWITH(expected, actual) \
	TEST_EQUAL_LINE(expected, actual.substr(std::string(actual().size() - std::string(expected).size(), \
		std::string(actual).size()), actual);

//! Sets up (cleans up) test environment at the start of every test.
bool setUp(const char* function_name);

//! Checks account for errors and shuts down daemons at end of every test.
bool tearDown();

//! Like tearDown() but returns false, because a test failure was detected.
bool fail();

//! Report final status of all tests, and return the correct value to test main().
int finish_test_suite();

bool TestFileExists(const char *Filename);
bool TestDirExists(const char *Filename);
bool TestFileNotEmpty(const char *Filename);

// -1 if doesn't exist
int TestGetFileSize(const std::string& Filename);
std::string ConvertPaths(const std::string& rOriginal);
int RunCommand(const std::string& rCommandLine);
bool ServerIsAlive(int pid);
int ReadPidFile(const char *pidFile);

#define TestRemoteProcessMemLeaks(filename) \
	TestRemoteProcessMemLeaksFunc(filename, __FILE__, __LINE__)

void TestRemoteProcessMemLeaksFunc(const char *filename,
	const char* file, int line);

void force_sync();
void wait_for_sync_start();
void wait_for_sync_end();
void sync_and_wait();
void terminate_bbackupd(int pid);

// Wait a given number of seconds for something to complete
void wait_for_operation(int seconds, const char* message);
void safe_sleep(int seconds);
std::auto_ptr<Configuration> load_config_file(const std::string& config_file,
	const ConfigurationVerify& verify);

#ifndef TEST_EXECUTABLE
#	ifdef _MSC_VER
		// Our CMakeFiles compile tests to different executable filenames,
		// e.g. test_common.exe instead of _test.exe.
		#define TEST_EXECUTABLE BOX_MODULE ".exe"
#	else
		#define TEST_EXECUTABLE "./_test"
#	endif
#endif // TEST_EXECUTABLE

#endif // TEST__H
