/*
 * TestToolsLib.cpp
 * ----------------
 * Purpose: Unit test framework for libopenmpt.
 * Notes  : Currently somewhat unreadable :/
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "TestToolsLib.h"


#ifdef ENABLE_TESTS
#ifndef MODPLUG_TRACKER


#include "mpt/string/utility.hpp"

#include <exception>
#include <iostream>

#include <cstdlib>


OPENMPT_NAMESPACE_BEGIN


namespace Test {



void mpt_test_reporter::case_run(const mpt::source_location& loc)
{
	std::cout << "TEST..: " << MPT_AFORMAT("{}({}):")(loc.file_name() ? loc.file_name() : "", loc.line()) << ": " << std::endl;
}

void mpt_test_reporter::case_run(const mpt::source_location& loc, const char* text_e)
{
	std::cout << "TEST..: " << MPT_AFORMAT("{}({}): {}")(loc.file_name() ? loc.file_name() : "", loc.line(), text_e) << ": " << std::endl;
}

void mpt_test_reporter::case_run(const mpt::source_location& loc, const char* text_ex, const char* text_e)
{
	if(text_ex)
	{
		std::cout << "TEST..: " << MPT_AFORMAT("{}({}): {} throws {}")(loc.file_name() ? loc.file_name() : "", loc.line(), text_e, text_ex) << ": " << std::endl;
	} else
	{
		std::cout << "TEST..: " << MPT_AFORMAT("{}({}): {} throws")(loc.file_name() ? loc.file_name() : "", loc.line(), text_e) << ": " << std::endl;
	}
}

void mpt_test_reporter::case_run(const mpt::source_location& loc, const char* text_a, const char* text_cmp, const char* text_b)
{
	std::cout << "TEST..: " << MPT_AFORMAT("{}({}): {} {} {}")(loc.file_name() ? loc.file_name() : "", loc.line(), text_a, text_cmp, text_b) << ": " << std::endl;
}

void mpt_test_reporter::case_result(const mpt::source_location& loc, const mpt::test::result& result)
{
	MPT_UNUSED(loc);
	if(std::holds_alternative<mpt::test::result_success>(result.info))
	{
		std::cout << "RESULT: PASS" << std::endl;
	} else if(std::holds_alternative<mpt::test::result_failure>(result.info))
	{
		fail_count++;
		std::cout << "RESULT: FAIL" << std::endl;
		std::cout.flush();
		std::cerr << "FAIL: " << "FAILURE: " << std::get<mpt::test::result_failure>(result.info).text << std::endl;
		std::cerr.flush();
	} else if(std::holds_alternative<mpt::test::result_unexpected_exception>(result.info))
	{
		fail_count++;
		std::cout << "RESULT: FAIL" << std::endl;
		std::cout.flush();
		std::cerr << "FAIL: " << "UNEXPECTED EXCEPTION: " << std::get<mpt::test::result_unexpected_exception>(result.info).text << std::endl;
		std::cerr.flush();
	} else
	{
		fail_count++;
		std::cout << "RESULT: FAIL" << std::endl;
		std::cout.flush();
		std::cerr << "FAIL: " << "UNKOWN" << std::endl;
		std::cerr.flush();
	}
}



int fail_count = 0;


static std::string remove_newlines(std::string str)
{
	return mpt::replace(mpt::replace(str, std::string("\n"), std::string(" ")), std::string("\r"), std::string(" "));
}


Testcase::Testcase(Fatality fatality, Verbosity verbosity, const char * const desc, const mpt::source_location &loc)
	: fatality(fatality)
	, verbosity(verbosity)
	, desc(desc)
	, loc(loc)
{
	return;
}


std::string Testcase::AsString() const
{
	return MPT_AFORMAT("{}({}): {}")(loc.file_name() ? loc.file_name() : "", loc.line(), remove_newlines(desc));
}


void Testcase::ShowStart() const
{
	switch(verbosity)
	{
		case VerbosityQuiet:
			break;
		case VerbosityNormal:
			std::cout << "TEST..: " << AsString() << ": " << std::endl;
			break;
		case VerbosityVerbose:
			std::cout << "TEST..: " << AsString() << ": " << std::endl;
			break;
	}
}


void Testcase::ShowProgress(const char * text) const
{
	switch(verbosity)
	{
		case VerbosityQuiet:
			break;
		case VerbosityNormal:
			break;
		case VerbosityVerbose:
			std::cout << "TEST..: " << AsString() << ": " << text << std::endl;
			break;
	}
}


void Testcase::ShowPass() const
{
	switch(verbosity)
	{
		case VerbosityQuiet:
			break;
		case VerbosityNormal:
			std::cout << "RESULT: PASS" << std::endl;
			break;
		case VerbosityVerbose:
			std::cout << "PASS..: " << AsString() << std::endl;
			break;
	}
}


void Testcase::ShowFail(bool exception, const char * const text) const
{
	switch(verbosity)
	{
		case VerbosityQuiet:
			break;
		case VerbosityNormal:
			std::cout << "RESULT: FAIL" << std::endl;
			break;
		case VerbosityVerbose:
			std::cout << "FAIL..: " << AsString() << std::endl;
			break;
	}
	std::cout.flush();
	if(!exception)
	{
		if(!text || (text && std::string(text).empty()))
		{
			std::cerr << "FAIL: " << AsString() << std::endl;
		} else
		{
			std::cerr << "FAIL: " << AsString() << " : " << text << std::endl;
		}
	} else
	{
		if(!text || (text && std::string(text).empty()))
		{
			std::cerr << "FAIL: " << AsString() << " EXCEPTION!" << std::endl;
		} else
		{
			std::cerr << "FAIL: " << AsString() << " EXCEPTION: " << text << std::endl;
		}
	}
	std::cerr.flush();
}


void Testcase::ReportPassed()
{
	ShowPass();
}


void Testcase::ReportFailed()
{
	fail_count++;
	ReportException();
}


void Testcase::ReportException()
{
	try
	{
		throw; // get the exception
	} catch(TestFailed & e)
	{
		ShowFail(false, e.values.c_str());
		if(fatality == FatalityStop)
		{
			throw; // rethrow
		}
	} catch(std::exception & e)
	{
		ShowFail(true, e.what());
		throw; // rethrow
	} catch(...)
	{
		ShowFail(true);
		throw; // rethrow
	}
}


} // namespace Test


#if defined(MPT_ASSERT_HANDLER_NEEDED)

MPT_NOINLINE void AssertHandler(const mpt::source_location &loc, const char *expr, const char *msg)
{
	Test::fail_count++;
	if(msg)
	{
		mpt::log::GlobalLogger().SendLogMessage(loc, LogError, "ASSERT",
			U_("ASSERTION FAILED: ") + mpt::ToUnicode(mpt::Charset::ASCII, msg) + U_(" (") + mpt::ToUnicode(mpt::Charset::ASCII, expr) + U_(")")
			);
	} else
	{
		mpt::log::GlobalLogger().SendLogMessage(loc, LogError, "ASSERT",
			U_("ASSERTION FAILED: ") + mpt::ToUnicode(mpt::Charset::ASCII, expr)
			);
	}
	#if defined(MPT_BUILD_FATAL_ASSERTS)
		std::abort();
	#endif // MPT_BUILD_FATAL_ASSERTS
}

#endif // MPT_ASSERT_HANDLER_NEEDED


OPENMPT_NAMESPACE_END


#endif // !MODPLUG_TRACKER
#endif // ENABLE_TESTS
