Jtest rewritten check changes for more info
This commit is contained in:
@@ -24,15 +24,15 @@ include(cmake/CPM.cmake)
|
||||
file(GLOB_RECURSE jtest_HEADERS "include/jtest/*.h" "include/jtest/*.hpp")
|
||||
file(GLOB_RECURSE jtest_SRC "src/jtest/*.c" "src/jtest/*.cpp")
|
||||
|
||||
# TODO: Fix Unit needing to be included too, it should be an automatically-managed depencency of jlog!!!
|
||||
#CPMAddPackage(
|
||||
# NAME Unit
|
||||
# URL https://git.redacted.cc/josh/Event/archive/Release-6.zip
|
||||
#)
|
||||
# TODO: Fix Event needing to be included too, it should be an automatically-managed depencency of jlog!!!
|
||||
CPMAddPackage(
|
||||
NAME Event
|
||||
URL https://git.redacted.cc/josh/Event/archive/Release-10.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME jlog
|
||||
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-13.zip
|
||||
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-14.zip
|
||||
)
|
||||
|
||||
|
||||
|
28
include/jtest/Test.hpp
Normal file
28
include/jtest/Test.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Josh's Test Library
|
||||
/// A no-frills, straightforward unit testing module in and for Modern C++.
|
||||
/// Created by Joshua O'Leary @ Redacted Software, June 2024
|
||||
/// Maintained by Maxine Hayes @ Redacted Software.
|
||||
/// This work is dedicated to the public domain.
|
||||
/// Contact: josh@redacted.cc, maxi@redacted.cc
|
||||
|
||||
/// @file UnitLogger.hpp
|
||||
/// @desc
|
||||
/// @edit 2024-08-21
|
||||
|
||||
#pragma once
|
||||
#include <jlog/Logger.hpp>
|
||||
|
||||
namespace jtest {
|
||||
class Test {
|
||||
public:
|
||||
explicit Test(const std::string& name, std::function<void()> callback, std::source_location = std::source_location::current());
|
||||
public:
|
||||
void operator()() { callback(); };
|
||||
std::string Name() { return name; };
|
||||
std::source_location Location() { return location; };
|
||||
protected:
|
||||
std::string name;
|
||||
std::function<void()> callback;
|
||||
std::source_location location;
|
||||
};
|
||||
}
|
26
include/jtest/Unit.hpp
Normal file
26
include/jtest/Unit.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <Event.h>
|
||||
#include <EventConnection.h>
|
||||
|
||||
#include <jtest/Test.hpp>
|
||||
#include <jtest/UnitLogger.hpp>
|
||||
|
||||
namespace jtest {
|
||||
class Unit : public BasicEvent<Test>, protected UnitLogger {
|
||||
using event_base = BasicEvent<Test>;
|
||||
public:
|
||||
Unit(const std::string& name);
|
||||
public:
|
||||
std::string Name();
|
||||
void Invoke() override;
|
||||
int TestCount() { return this->listeners.size();}
|
||||
void RunAll()
|
||||
{
|
||||
Invoke();
|
||||
}
|
||||
void RunNext();
|
||||
private:
|
||||
std::string name;
|
||||
};
|
||||
}
|
35
include/jtest/UnitLogger.hpp
Normal file
35
include/jtest/UnitLogger.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/// Josh's Test Library
|
||||
/// A no-frills, straightforward unit testing module in and for Modern C++.
|
||||
/// Created by Joshua O'Leary @ Redacted Software, June 2024
|
||||
/// Maintained by Maxine Hayes @ Redacted Software.
|
||||
/// This work is dedicated to the public domain.
|
||||
/// Contact: josh@redacted.cc, maxi@redacted.cc
|
||||
|
||||
/// @file UnitLogger.hpp
|
||||
/// @desc
|
||||
/// @edit 2024-08-21
|
||||
|
||||
#pragma once
|
||||
#include <jlog/Logger.hpp>
|
||||
#include <jtest/Test.hpp>
|
||||
|
||||
namespace jtest
|
||||
{
|
||||
class UnitLogger : protected jlog::ConsoleLogger {
|
||||
protected:
|
||||
UnitLogger(const std::string& name);
|
||||
protected:
|
||||
void Log(const std::string message);
|
||||
void LogTimestamp(jlog::Timestamp ts =jlog::Timestamp());
|
||||
void LogLocation(std::source_location l);
|
||||
void LogTestResult(Test& t, bool passed, int index, int total);
|
||||
// TODO: I suggest float percentage_passed.
|
||||
void LogUnitResult(int tests_total, int tests_ran, int tests_passed, int tests_failed);
|
||||
protected:
|
||||
Color4 failColor;
|
||||
Color4 passColor;
|
||||
Color4 healthGoodColor;
|
||||
Color4 healthWarningColor;
|
||||
Color4 healthCriticalColor;
|
||||
};
|
||||
}
|
@@ -1,353 +1,91 @@
|
||||
/// Josh Unit Test Library
|
||||
/// Josh's Test Library
|
||||
/// A no-frills, straightforward unit testing module in and for Modern C++.
|
||||
/// Created by Joshua O'Leary @ Redacted Software, June 2024
|
||||
/// Maintained by Maxine Hayes @ Redacted Software.
|
||||
/// This work is dedicated to the public domain.
|
||||
/// Contact: josh@redacted.cc, maxi@redacted.cc
|
||||
|
||||
/// @file jtest.hpp
|
||||
/// @desc
|
||||
/// @edit 2024-08-21
|
||||
|
||||
#pragma once
|
||||
#include "iostream"
|
||||
#include <functional>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <source_location>
|
||||
#include <jlog/jlog.hpp>
|
||||
#include "jlog/io.hpp"
|
||||
#include <Colors.hpp>
|
||||
#include <functional>
|
||||
|
||||
|
||||
//#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)
|
||||
namespace jtest
|
||||
{
|
||||
//this->unit = &u;
|
||||
this->name = name;
|
||||
this->testPassCount = testPassCount;
|
||||
this->testFailCount = testFailCount;
|
||||
this->testRunCount = testRunCount;
|
||||
this->health = health;
|
||||
}
|
||||
class check_fail {};
|
||||
class check_eq_fail : public check_fail {};
|
||||
class check_ne_fail : public check_fail {};
|
||||
|
||||
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)
|
||||
/// Raises an exception if the given condition is false.
|
||||
void check(bool condition)
|
||||
{
|
||||
test_ptr retval(new connection(this, callback));
|
||||
this->tests.push_back(retval); testCounter++;
|
||||
return *retval;
|
||||
if (!condition)
|
||||
throw check_fail();
|
||||
}
|
||||
|
||||
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.
|
||||
/// Raises an exception if the given types do not evaluate as equal.
|
||||
template <typename T>
|
||||
bool check_eq(T a, T b) {
|
||||
void check_eq(T a, T b)
|
||||
{
|
||||
if (a != b)
|
||||
throw std::runtime_error("Test check failed!!");
|
||||
return true;
|
||||
throw check_eq_fail();
|
||||
}
|
||||
|
||||
/// 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();
|
||||
/// Raises an exception if the given types do evaluate as equal.
|
||||
template <typename T>
|
||||
void check_ne(T a, T b)
|
||||
{
|
||||
if (a == b)
|
||||
throw check_ne_fail();
|
||||
}
|
||||
|
||||
/// 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__);
|
||||
/// Raises an exception of the given float values are not equal (up to the given epsilon).
|
||||
void check_float_eq(float a, float b, float epsilon = 1e-3f)
|
||||
{
|
||||
if ( ! (std::abs(a - b) <= epsilon))
|
||||
throw check_eq_fail();
|
||||
}
|
||||
|
||||
/// TODO: Implement check macros
|
||||
void check_float_ne(float a, float b, float epsilon = 1e-3f)
|
||||
{
|
||||
if ( (std::abs(a - b) <= epsilon))
|
||||
throw check_ne_fail();
|
||||
}
|
||||
|
||||
void check_float_eq_exact(float a, float b)
|
||||
{
|
||||
if (a != b)
|
||||
throw check_eq_fail();
|
||||
}
|
||||
|
||||
|
||||
void check_string_eq(std::string a, std::string b)
|
||||
{
|
||||
if (a != b)
|
||||
throw check_eq_fail();
|
||||
}
|
||||
|
||||
void check_string_ne(std::string a, std::string b)
|
||||
{
|
||||
if (a == b)
|
||||
throw check_ne_fail();
|
||||
}
|
||||
void check_throws(std::function<void()> callback);
|
||||
void check_nothrows(std::function<void()> callback);
|
||||
|
||||
// TODO: Implement death check:
|
||||
// Spawn a new process, and execute the callback in that process.
|
||||
// This verifies that a piece of code would cause the process to terminate.
|
||||
void check_death(std::function<void()> callback);
|
||||
|
||||
|
||||
/// Raises an exception, which causes the test to fail.
|
||||
void fail_test()
|
||||
{
|
||||
throw check_fail();
|
||||
}
|
||||
}
|
47
main.cpp
47
main.cpp
@@ -1,33 +1,44 @@
|
||||
// Josh's Test Library
|
||||
// A no-frills, straightforward unit testing module in and for Modern C++.
|
||||
// Created by Joshua O'Leary @ Redacted Software, June 2024
|
||||
// This work is dedicated to the public domain.
|
||||
// Contact: josh@redacted.cc, git.redacted.cc/josh
|
||||
/// Josh's Test Library
|
||||
/// A no-frills, straightforward unit testing module in and for Modern C++.
|
||||
/// Created by Joshua O'Leary @ Redacted Software, June 2024
|
||||
/// Maintained by Maxine Hayes @ Redacted Software.
|
||||
/// This work is dedicated to the public domain.
|
||||
/// Contact: josh@redacted.cc, maxi@redacted.cc
|
||||
|
||||
// TODO: Provide introspection insofar as which assertion check failed.
|
||||
// TODO: Provide alternate checks (google test has specific assertations for handling floats, for example) (Are these actually necessary??)
|
||||
// TODO: Implement log-file-specification-capability in jlog so we can log to test_results.txt specifically.
|
||||
// TODO: Provide benchmarking on test running-time
|
||||
|
||||
#include <jtest/Unit.hpp>
|
||||
#include <jlog/Logger.hpp>
|
||||
#include <jtest/jtest.hpp>
|
||||
#include <cassert>
|
||||
|
||||
//void TestA() { jtest::check("Bruh" == "Bruh"); }
|
||||
//void TestB() { jtest::check(6*6 == 36); }
|
||||
//void TestC() { jtest::check(6+9 == 69); }
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
jtest::Unit u{"jtest-demo"};
|
||||
u += jtest::Test{"check_true", [] () {
|
||||
jtest::check(true);
|
||||
}};
|
||||
u += jtest::Test{"check_false", [] () {
|
||||
jtest::check(false);
|
||||
}};
|
||||
u += jtest::Test{"check floats", [] () {
|
||||
jtest::check_float_eq(5.01, 5.09, 0.1);
|
||||
}};
|
||||
u += jtest::Test{"balls", [] () {
|
||||
//jlog::Debug("ass");
|
||||
}};
|
||||
u += jtest::Test{"balls", [] () {
|
||||
//jlog::Debug("ass");
|
||||
}};
|
||||
u += jtest::Test{"balls", [] () {
|
||||
//jlog::Debug("ass");
|
||||
}};
|
||||
|
||||
jtest::Unit ThisDick{"This Dick"};
|
||||
u += jtest::Test{"dsads", [](){throw("thisdick");}};
|
||||
|
||||
ThisDick += jtest::Test("bawls", []{ printf("FUCK1\n"); });
|
||||
|
||||
ThisDick += jtest::Test("more bawls1", []{ printf("FUCK2\n"); });
|
||||
|
||||
ThisDick += jtest::Test("more bawls2", []{ throw("OH GOD OH FUCK"); });
|
||||
|
||||
ThisDick += jtest::Test("more bawls3", []{ throw("OH GOD OH FUCK2"); });
|
||||
|
||||
ThisDick();
|
||||
u.Invoke();
|
||||
}
|
10
src/jtest/Test.cpp
Normal file
10
src/jtest/Test.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <jtest/Test.hpp>
|
||||
|
||||
namespace jtest
|
||||
{
|
||||
Test::Test(const std::string &name, std::function<void()> callback, std::source_location location) {
|
||||
this->name = name;
|
||||
this->callback = callback;
|
||||
this->location = location;
|
||||
}
|
||||
}
|
51
src/jtest/Unit.cpp
Normal file
51
src/jtest/Unit.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include <jtest/Unit.hpp>
|
||||
|
||||
namespace jtest {
|
||||
Unit::Unit(const std::string& name) : event_base(), UnitLogger(name) { //, //ulog(name, jlog::GlobalLogFile) {
|
||||
//ulog.IncludeLocation(false);
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
std::string Unit::Name() { return name; }
|
||||
|
||||
void Unit::Invoke()
|
||||
{
|
||||
|
||||
|
||||
|
||||
int tests_total = TestCount();
|
||||
int tests_ran = 0;
|
||||
int tests_passed = 0;
|
||||
int tests_failed = 0;
|
||||
|
||||
// TODO: output "Running Test Unit <name> with <N> tests.
|
||||
Log(std::format("Running test unit with {} tests...", tests_total));
|
||||
|
||||
for (event_ptr &connection_ptr: this->listeners) {
|
||||
//ulog(std::format("Running Test <{}> [{}/{}]", connection_ptr->callback.Name(), tcnt, this->listeners.size()));
|
||||
|
||||
bool this_test_passed = false;
|
||||
|
||||
//LogTestResult(connection_ptr->callback, true);
|
||||
//LogTestResult(connection_ptr->callback, false);
|
||||
|
||||
try {
|
||||
connection_ptr->Invoke();
|
||||
tests_passed++;
|
||||
this_test_passed = true;
|
||||
} catch(...) {
|
||||
tests_failed++;
|
||||
this_test_passed = false;
|
||||
}
|
||||
|
||||
tests_ran++;
|
||||
|
||||
// TODO: Suffix with [N/M] test index.
|
||||
LogTestResult(connection_ptr->callback, this_test_passed, tests_ran, tests_total);
|
||||
|
||||
//tests_ran++;
|
||||
}
|
||||
|
||||
LogUnitResult(tests_total, tests_ran, tests_passed, tests_failed);
|
||||
}
|
||||
}
|
119
src/jtest/UnitLogger.cpp
Normal file
119
src/jtest/UnitLogger.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include <jtest/UnitLogger.hpp>
|
||||
|
||||
namespace jtest
|
||||
{
|
||||
UnitLogger::UnitLogger(const std::string& name) : ConsoleLogger(name) {
|
||||
this->timestampColor = Colors::Gray;
|
||||
this->contextColor = Colors::White;
|
||||
this->locationColor = Colors::Gray;
|
||||
this->pointerColor = Colors::White;
|
||||
this->messageColor = Colors::Gray;
|
||||
this->failColor = Colors::Red;
|
||||
this->passColor = Colors::Green;
|
||||
this->healthGoodColor = Colors::Green;
|
||||
this->healthWarningColor = Colors::Yellow;
|
||||
this->healthCriticalColor = Colors::Red;
|
||||
#ifdef WIN32
|
||||
IncludeColor = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UnitLogger::Log(const std::string message) {
|
||||
//LogTimestamp();
|
||||
os << jlog::token(context, "[]", contextColor).Stringer(IncludeColor);
|
||||
os << jlog::token{"*", "", pointerColor}.Stringer(IncludeColor)
|
||||
<< jlog::token(message, "", messageColor).Stringer(IncludeColor) << "\n";
|
||||
}
|
||||
|
||||
void UnitLogger::LogTimestamp(jlog::Timestamp ts) {
|
||||
os << jlog::token{std::format(
|
||||
"{}-{}-{} {}:{}:{}.{}",
|
||||
ts.Year(),
|
||||
ts.Month(),
|
||||
ts.Day(),
|
||||
ts.Hour().count(),
|
||||
ts.Minute().count(),
|
||||
ts.Second().count(),
|
||||
ts.Millisecond().count()), "[]", timestampColor}.Stringer(IncludeColor);
|
||||
}
|
||||
|
||||
void UnitLogger::LogTestResult(Test& t, bool passed, int index, int total) {
|
||||
Color4 stateColor;
|
||||
std::string state;
|
||||
|
||||
if (passed) {
|
||||
stateColor = passColor;
|
||||
state = "PASSED";
|
||||
} else {
|
||||
stateColor = failColor;
|
||||
state = "FAILED";
|
||||
}
|
||||
|
||||
auto l = t.Location();
|
||||
|
||||
const auto tally = jlog::token(std::format("{}/{}", index, total), "[]", stateColor);
|
||||
const auto status = jlog::token(state, "[]", stateColor);
|
||||
const auto testname = jlog::token(std::format("{}::{}", context, t.Name()), "[]", stateColor).Stringer(IncludeColor);
|
||||
const auto source = jlog::token{std::format("{}:{}", l.file_name(), l.line()), "[]", locationColor}.Stringer(IncludeColor);
|
||||
|
||||
os << tally
|
||||
<< status
|
||||
<< testname
|
||||
<< source
|
||||
<< "\n";
|
||||
}
|
||||
void UnitLogger::LogUnitResult(int tests_total, int tests_ran, int tests_passed, int tests_failed) {
|
||||
float tests_ran_percentage = (float)tests_ran / (float)tests_total;
|
||||
float tests_passed_percentage = (float)tests_passed / (float)tests_ran;
|
||||
float tests_failed_percentage = (float)tests_failed / (float)tests_ran;
|
||||
|
||||
Color4 stateColor;
|
||||
std::string state;
|
||||
|
||||
|
||||
float health = tests_passed_percentage * 100;
|
||||
|
||||
// health levels
|
||||
// 100 - PERFECT
|
||||
// 99 - EXCELLENT
|
||||
// 95 - GOOD
|
||||
// 75 - WARNING
|
||||
// 50 - CRITICAL
|
||||
// 25 - DISASTER
|
||||
|
||||
|
||||
if (health >= 100.f)
|
||||
{
|
||||
stateColor = healthGoodColor;
|
||||
state = "PERFECT";
|
||||
} else if (health >= 99.f ) {
|
||||
stateColor = healthGoodColor;
|
||||
state = "EXCELLENT";
|
||||
} else if (health >= 95.f ) {
|
||||
stateColor = healthGoodColor;
|
||||
state = "GOOD";
|
||||
} else if (health >= 75.f) {
|
||||
stateColor = healthWarningColor;
|
||||
state = "OKAY";
|
||||
} else if (health >= 50.f) {
|
||||
stateColor = healthWarningColor;
|
||||
state = "WARNING";
|
||||
} else if (health >= 25.f) {
|
||||
stateColor = healthCriticalColor;
|
||||
state = "CRITICAL";
|
||||
} else {
|
||||
stateColor = healthCriticalColor;
|
||||
state = "DISASTER";
|
||||
}
|
||||
|
||||
// UnitName: X tests ran of Y, W passed, Z failed. Health Score: W / X;
|
||||
|
||||
os << jlog::token(context, "[]", stateColor).Stringer(IncludeColor);
|
||||
os << std::format("Results: {}/{} tests ran, {}/{} passed, {}/{} failed.", tests_ran, tests_total, tests_passed, tests_ran, tests_failed, tests_ran);
|
||||
os << std::format(" Report Card: {:.0f}% ", health);
|
||||
os << jlog::token(state, "[]", stateColor);
|
||||
|
||||
|
||||
|
||||
};
|
||||
}
|
@@ -1,123 +1 @@
|
||||
//
|
||||
// Created by dawsh on 6/16/24.
|
||||
//
|
||||
#include <jtest/jtest.hpp>
|
||||
namespace jtest {
|
||||
|
||||
|
||||
/*
|
||||
// Globals for test tracking
|
||||
std::vector<testdef> testlist;
|
||||
int rantests;
|
||||
int passedtests;
|
||||
int failedtests;
|
||||
|
||||
void log(std::vector<jlog::token> tokens)
|
||||
{
|
||||
std::vector<jlog::token> wtokens;
|
||||
auto head = jlog::token{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content ="JTEST"};
|
||||
|
||||
wtokens.push_back(head);
|
||||
wtokens.insert(wtokens.end(), tokens.begin(), tokens.end());
|
||||
|
||||
jlog::log(wtokens, "test_results.log");
|
||||
}
|
||||
|
||||
std::vector<jlog::token> log_test_format(const std::string &testname, const std::string &file, int line,
|
||||
bool passed) {
|
||||
std::vector<jlog::token> wtokens;
|
||||
|
||||
auto filedata = jlog::token{.content = std::format("{}:{}", file, line)};
|
||||
|
||||
std::vector<jlog::token> teststate;
|
||||
if (passed)
|
||||
{
|
||||
teststate.push_back(jlog::token{.colorCode = jlog::ansi_escape_codes::FG_GREEN, .content = testname});
|
||||
teststate.push_back(jlog::token{.content = "Passed:", .delimiter = ""});
|
||||
} else
|
||||
{
|
||||
teststate.push_back(jlog::token{.colorCode = jlog::ansi_escape_codes::FG_RED, .content = testname});
|
||||
teststate.push_back(jlog::token{.content = "Failed:", .delimiter = ""});
|
||||
}
|
||||
|
||||
auto raninfo = jlog::token{.content = std::format("{}/{}", rantests, testlist.size())};
|
||||
|
||||
wtokens.push_back(filedata);
|
||||
wtokens.insert(wtokens.end(), teststate.begin(), teststate.end());
|
||||
wtokens.push_back(raninfo);
|
||||
|
||||
return wtokens;
|
||||
}
|
||||
|
||||
std::vector<jlog::token> log_test_tracking_format() {
|
||||
auto ran = jlog::token{.content = std::format("Tests Ran: [{}/{}]", rantests, testlist.size()), .delimiter = ""};
|
||||
auto failed = jlog::token{.content = std::format("Failed: [{}/{}]", failedtests, rantests), .delimiter = ""};
|
||||
auto passed = jlog::token{.content = std::format("Passed: [{}/{}]", passedtests, rantests), .delimiter = ""};
|
||||
|
||||
return {ran, failed, passed};
|
||||
}
|
||||
|
||||
void definetest(const std::string &testname, const std::function<void()> &callback, const std::string &file,
|
||||
int line) {
|
||||
testlist.push_back(testdef(testname, callback, file, line));
|
||||
}
|
||||
|
||||
bool check(bool condition) {
|
||||
if (!condition)
|
||||
throw std::runtime_error("Test check failed!!");
|
||||
return condition;
|
||||
}
|
||||
|
||||
bool check_float_eq(float a, float b, float epsilon) {
|
||||
if (std::abs(a - b) > epsilon) {
|
||||
throw std::runtime_error ("Test check failed!!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_double_eq(double a, double b, double epsilon) {
|
||||
if (std::abs(a - b) > epsilon) {
|
||||
throw std::runtime_error ("Test check failed!!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_string_eq(const std::string &a, const std::string &b) {
|
||||
if (a == b) // This is valid and recommended C++2x.
|
||||
throw std::runtime_error("Test check failed!!");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool test(const std::string &testname, const std::function<void()> &callback, const std::string &file, int line) {
|
||||
bool passed = true;
|
||||
|
||||
try { callback(); }
|
||||
catch(const std::exception& e)
|
||||
{ passed = false; }
|
||||
catch(...) // <- Basically covers all exception cases. GTest does something similar
|
||||
{ passed = false; }
|
||||
|
||||
rantests++;
|
||||
|
||||
if (passed) { passedtests++; }
|
||||
else { failedtests++; }
|
||||
|
||||
jtest::log(log_test_format(testname, file, line, passed));
|
||||
|
||||
return passed;
|
||||
|
||||
}
|
||||
|
||||
void run_tests() {
|
||||
|
||||
for (testdef& td : testlist)
|
||||
{
|
||||
td.passed = test(td.testname, td.callback, td.file, td.line);
|
||||
}
|
||||
|
||||
jtest::log(log_test_tracking_format());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user