354 lines
11 KiB
C++
354 lines
11 KiB
C++
/// Josh Unit Test Library
|
|
|
|
#pragma once
|
|
#include "iostream"
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <source_location>
|
|
#include <jlog/jlog.hpp>
|
|
#include "jlog/io.hpp"
|
|
#include <Colors.hpp>
|
|
|
|
|
|
//#define NDEBUG for disabling assert, but tests still end up passing.
|
|
|
|
// TODO: Move implementation to jtest::detail
|
|
// have this file primarily expose the macros intended for users
|
|
|
|
|
|
namespace jtest {
|
|
class TestResult;
|
|
class TestLogger;
|
|
class Test;
|
|
|
|
class TestConnection;
|
|
class UnitResult;
|
|
class UnitLogger;
|
|
class Unit;
|
|
|
|
|
|
//TestLogger JTestLog;
|
|
//UnitLogger JUnitLog;
|
|
//jlog::Logger JTestLog{"JTEST"};
|
|
|
|
class TestResult : private jlog::LogEntry {
|
|
public:
|
|
TestResult(const std::string& name, bool passed, std::source_location location, jlog::Timestamp ts);
|
|
public:
|
|
std::string Name() { return this->name; };
|
|
bool Passed() { return passed; };
|
|
jlog::Timestamp GetTimeStamp() { return jlog::LogEntry::GetTimeStamp(); }
|
|
std::vector<jlog::token> Tokenize() override;
|
|
private:
|
|
//Test *test;
|
|
std::string name;
|
|
bool passed;
|
|
//jlog::Timestamp timestamp;
|
|
};
|
|
|
|
TestResult::TestResult(const std::string& name, bool passed, std::source_location location, jlog::Timestamp ts)
|
|
: jlog::LogEntry(passed ? Colors::Green : Colors::Red, name, passed ? "PASSED" : "FAILED", location, ts) {
|
|
IncludeTimestamp = true;
|
|
IncludeTrace = true;
|
|
//this->test = &t;
|
|
this->name = name;
|
|
this->passed = passed;
|
|
}
|
|
|
|
std::vector<jlog::token> TestResult::Tokenize() {
|
|
return jlog::LogEntry::Tokenize();
|
|
}
|
|
|
|
class TestLogger : private jlog::Logger {
|
|
public:
|
|
explicit TestLogger() : jlog::Logger("JTEST") {};
|
|
|
|
public:
|
|
void operator()(TestResult tr);
|
|
|
|
void Log(TestResult tr);
|
|
};
|
|
|
|
TestLogger JTestLog;
|
|
|
|
|
|
class Test {
|
|
public:
|
|
explicit Test(const std::string &name, std::function<void()> callback,
|
|
std::source_location location = std::source_location::current()) {
|
|
this->name = name;
|
|
this->callback = callback;
|
|
this->location = location;
|
|
};
|
|
public:
|
|
void operator()() { callback(); }; // <-- This will run our test and catch any exceptions. No it wont
|
|
std::string Name() { return name; };
|
|
|
|
std::source_location Location() { return location; };
|
|
protected:
|
|
std::string name;
|
|
std::function<void()> callback;
|
|
std::source_location location;
|
|
};
|
|
|
|
|
|
void TestLogger::operator()(TestResult tr) {
|
|
return Log(tr);
|
|
}
|
|
|
|
void TestLogger::Log(jtest::TestResult tr) {
|
|
jlog::LogToConsole(tr.Tokenize());
|
|
jlog::LogToConsole("\n");
|
|
}
|
|
|
|
class UnitResult : private jlog::LogEntry {
|
|
public:
|
|
UnitResult(const std::string name, uint64_t testFailCount, uint64_t testPassCount, uint64_t testRunCount, uint64_t health, std::source_location location, jlog::Timestamp ts);
|
|
public:
|
|
std::string Name() { return name; };
|
|
uint64_t TestsFailed() { return testFailCount; };
|
|
uint64_t TestsPassed() { return testPassCount; };
|
|
float Health() { return health; }
|
|
//bool Passed() { return passed; };
|
|
jlog::Timestamp GetTimeStamp() { return jlog::LogEntry::GetTimeStamp(); }
|
|
std::vector<jlog::token> Tokenize() override;
|
|
private:
|
|
//Unit *unit;
|
|
std::string name;
|
|
uint64_t testFailCount;
|
|
uint64_t testPassCount;
|
|
uint64_t testRunCount;
|
|
float health;
|
|
//jlog::Timestamp timestamp;
|
|
};
|
|
|
|
// Yes this looks ugly as fuck
|
|
UnitResult::UnitResult(const std::string name, uint64_t testFailCount, uint64_t testPassCount, uint64_t testRunCount, uint64_t health, std::source_location location, jlog::Timestamp ts)
|
|
: jlog::LogEntry(Colors::Yellow, name, std::format("Passed: {}/{} Failed: {}/{} Health: {}%", testPassCount, testRunCount, testFailCount, testRunCount, health), location, ts)
|
|
{
|
|
//this->unit = &u;
|
|
this->name = name;
|
|
this->testPassCount = testPassCount;
|
|
this->testFailCount = testFailCount;
|
|
this->testRunCount = testRunCount;
|
|
this->health = health;
|
|
}
|
|
|
|
std::vector<jlog::token> UnitResult::Tokenize() {
|
|
return jlog::LogEntry::Tokenize();
|
|
}
|
|
|
|
class UnitLogger : private jlog::Logger {
|
|
public:
|
|
explicit UnitLogger() : jlog::Logger("JTEST") {};
|
|
|
|
public:
|
|
void operator()(UnitResult ur);
|
|
|
|
void Log(UnitResult ur);
|
|
};
|
|
|
|
void UnitLogger::operator()(UnitResult ur) {
|
|
return Log(ur);
|
|
}
|
|
|
|
void UnitLogger::Log(UnitResult ur) {
|
|
jlog::LogToConsole(ur.Tokenize());
|
|
jlog::LogToConsole("\n");
|
|
}
|
|
|
|
UnitLogger JUnitLog;
|
|
|
|
//class TestConnection;
|
|
|
|
class TestConnection {
|
|
private:
|
|
//using delegate = std::function<void()>;
|
|
using delegate = Test;
|
|
public:
|
|
TestConnection(Unit *creator, delegate cb);
|
|
void Invoke();
|
|
delegate GetDelegate();
|
|
private:
|
|
Unit * owner;
|
|
delegate callback;
|
|
bool active = true;
|
|
};
|
|
|
|
TestConnection::TestConnection(Unit *creator, TestConnection::delegate cb) : owner(creator), callback(std::move(cb)) {}
|
|
void TestConnection::Invoke() { callback(); }
|
|
Test TestConnection::GetDelegate() { return callback; }
|
|
|
|
class Unit {
|
|
public:
|
|
using delegate = Test;
|
|
using connection = TestConnection;
|
|
using test_ptr = std::shared_ptr<connection>;
|
|
public:
|
|
Unit(const std::string& name, std::source_location location = std::source_location::current()) {
|
|
this->name = name;
|
|
this->location = location;
|
|
};
|
|
std::string Name() { return name; };
|
|
std::source_location Location() { return location; };
|
|
uint64_t TestCount() { return testCounter; };
|
|
void Invoke();
|
|
void operator()();
|
|
connection Connect(delegate callback);
|
|
connection operator+=(delegate callback);
|
|
private:
|
|
std::string name;
|
|
std::source_location location;
|
|
std::vector<test_ptr> tests;
|
|
uint64_t testCounter = 0;
|
|
};
|
|
|
|
void Unit::Invoke() {
|
|
uint64_t passCounter = 0;
|
|
uint64_t failCounter = 0;
|
|
uint64_t runCounter = 0;
|
|
for (test_ptr &connection_ptr: this->tests) {
|
|
|
|
//connection_ptr->Invoke();
|
|
bool passed = true;
|
|
try { connection_ptr->Invoke(); }
|
|
catch(const std::exception& e)
|
|
{ passed = false; }
|
|
catch(...) // <- Basically covers all exception cases. GTest does something similar
|
|
{ passed = false; }
|
|
|
|
runCounter++;
|
|
Test t = connection_ptr->GetDelegate();
|
|
TestResult r{std::format("{}::{}", name, t.Name()), passed, t.Location(), jlog::Timestamp()};
|
|
|
|
if(!passed) {
|
|
failCounter++;
|
|
//JTestLog(std::format(
|
|
// "{} - FAILED - [{}/{}]",
|
|
// d.Name(), runCounter, testCounter), d.Location());
|
|
} else {
|
|
passCounter++;
|
|
//JTestLog(std::format("{} - FAILED",d.Name(),d.Location()));//, runCounter, testCounter));
|
|
//JTestLog(std::format(
|
|
// "{} - PASSED - [{}/{}]",
|
|
// d.Name(), runCounter, testCounter), d.Location());
|
|
}
|
|
JTestLog(r);
|
|
}
|
|
uint64_t health = float(passCounter)/float(runCounter)*100;
|
|
std::cout << std::to_string(health) << std::endl;
|
|
UnitResult ur{name, failCounter, passCounter, runCounter, health, location, jlog::Timestamp()};
|
|
JUnitLog(ur);
|
|
}
|
|
|
|
void Unit::operator()() { Invoke();}
|
|
|
|
TestConnection Unit::Connect(delegate callback)
|
|
{
|
|
test_ptr retval(new connection(this, callback));
|
|
this->tests.push_back(retval); testCounter++;
|
|
return *retval;
|
|
}
|
|
|
|
TestConnection Unit::operator+=(Unit::delegate callback) { return Connect(callback); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
class Unit {
|
|
public:
|
|
Unit Tests;
|
|
public:
|
|
Unit(const std::string& group_name) { name = group_name; };
|
|
public:
|
|
void operator () () { Tests(); }
|
|
//void Test(const std::string& name, std::function<void()> callback, std::source_location location = std::source_location::current()) = {};
|
|
protected:
|
|
std::string name;
|
|
//std::vector<Test> tests;
|
|
//int rantests;
|
|
//int passedtests;
|
|
//int failedtests;
|
|
};
|
|
*/
|
|
|
|
|
|
|
|
/// Structure to store test meta-data, as tests are initially registered, and ran later.
|
|
struct testdef
|
|
{
|
|
std::string testname;
|
|
std::function<void()> callback;
|
|
std::string file;
|
|
int line;
|
|
bool passed;
|
|
};
|
|
|
|
/// jlog log wrapper for jtest
|
|
void log(std::vector<jlog::token> tokens);
|
|
|
|
/// Generates and returns a sequence of logger tokens pre-formatted to the test data.
|
|
std::vector<jlog::token> log_test_format(const std::string& testname, const std::string& file, int line, bool passed);
|
|
|
|
/// Generates and returns a sequence of logger tokens for the test summary printed at the end of testing.
|
|
std::vector<jlog::token> log_test_tracking_format();
|
|
|
|
/// Registers a test internally.
|
|
void definetest(const std::string& testname, const std::function<void()>& callback, const std::string& file, int line);
|
|
|
|
|
|
// TODO: implement streaming a custom failure message with << operator on check statements
|
|
|
|
/// Raises an exception if the given condition is false, otherwise returns true.
|
|
bool check(bool condition);
|
|
|
|
/// Raises an exception of the given values evaluate to not-equal, otherwise returns true.
|
|
template <typename T>
|
|
bool check_eq(T a, T b) {
|
|
if (a != b)
|
|
throw std::runtime_error("Test check failed!!");
|
|
return true;
|
|
}
|
|
|
|
/// Raises an exception if the given floats are not equal, up to the given epsilon. Otherwise returns true.
|
|
/// @param epsilon The accuracy required to pass the test.
|
|
bool check_float_eq(float a, float b, float epsilon = 1e-3f);
|
|
|
|
/// Raises an exception if the given doubles are not equal, up to the given epsilon. Otherwise returns true.
|
|
/// @param epsilon The accuracy required to pass the test.
|
|
bool check_double_eq(double a, double b, double epsilon = 1e-3f);
|
|
|
|
/// Raises an exception if the given strings are not equal, otherwise returns true.
|
|
bool check_string_eq(const std::string& a, const std::string& b);
|
|
|
|
/// Runs a given test, generates a report, and returns the test result as a boolean.
|
|
bool test(const std::string& testname, const std::function<void()>& callback, const std::string& file, int line);
|
|
|
|
/// Runs all tests that have been registered, and generates a final summary report for the testing suite.
|
|
void run_tests();
|
|
}
|
|
|
|
/// TEST macro to be used by API consumers. Automatically grabs reflection data such as current file, line, function name, etc.
|
|
#define TEST(a, b) jtest::definetest(a, b, __FILE__, __LINE__);
|
|
|
|
/// TODO: Implement check macros
|