26 Commits

Author SHA1 Message Date
1c39b8eda6 Re-enable color output on Windows. 2025-06-04 14:01:50 -05:00
bae26cf80a Migrate jlog to latest. 2024-12-25 16:36:23 -05:00
76a384b892 Fix for latest jlog
Also remove having to include event directly as it gets passed up from jlog.
2024-10-10 12:08:25 -04:00
e3b69e7aa9 Implement check_throws() and check_nothrows() 2024-09-16 13:39:18 -04:00
50cd639923 Update CMakeLists.txt
windoze fix
2024-09-14 22:00:45 -04:00
fae8e82ba8 fix cmake_minimum_required using rebitch 2024-08-26 19:52:14 -04:00
Redacted
381a366846 Update CMakeLists.txt 2024-08-22 12:03:07 -04:00
babf4fead2 Add missing newline 2024-08-21 19:28:56 -04:00
maxine
c03ac76535 Merge pull request 'rewrite' (#14) from rewrite into main
Reviewed-on: #14
2024-08-21 18:38:20 -04:00
d6cc9631b4 Jtest rewritten check changes for more info 2024-08-21 18:38:46 -04:00
66ea6e4557 Pass/Fail coloring, Unit health, TestResult, TestLogger, UnitResult, UnitLogger. 2024-08-15 16:40:17 -04:00
42106670e5 Start on rewrite 2024-08-15 10:47:06 -04:00
Redacted
03795a2f3a Update CMakeLists.txt 2024-06-28 18:10:04 -04:00
5e8c85feea Updated to use jlog Prerelease-11. Cleaned up custom logger to utilized jlog Prerelease-11 features. 2024-06-28 13:26:22 -04:00
4ea05e09b7 Wrote a log wrapper "jtest::log". Made test results log to "test_results.log" file. Did some cleanup while I was at it too. 2024-06-28 10:55:16 -04:00
b57cc68b6f Basically got exception handling down. Still working on assertion handling. 2024-06-27 12:13:27 -04:00
Redacted
8c9bb12834 Update CMakeLists.txt 2024-06-26 23:28:53 -04:00
875166c6b6 Nice structural refactor part 1 2024-06-26 12:28:55 -04:00
eea8e5547f Implement check_string_eq 2024-06-26 12:16:14 -04:00
20ed600b89 Implement check variants v1 2024-06-26 12:13:19 -04:00
75eb8f52d9 Merge remote-tracking branch 'origin/main' 2024-06-25 11:05:37 -04:00
b8df31dd50 Wrote log formatters for jtest. Cleaned up test running code. 2024-06-25 11:05:14 -04:00
21cc5313f2 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	include/jtest/jtest.hpp
2024-06-25 10:28:00 -04:00
ab57cd5774 Fix testdef structure 2024-06-25 10:27:52 -04:00
e04b742954 Fixed a bug that caused the testname to not be printed. 2024-06-24 18:23:49 -07:00
6954f51793 Fix CMakeLists.txt to propagate dependencies 2024-06-24 15:09:12 -04:00
10 changed files with 398 additions and 133 deletions

View File

@@ -1,6 +1,6 @@
# 2024 Josh O'Leary @ Redacted Software
cmake_minimum_required(VERSION 3.18...3.25)
cmake_minimum_required(VERSION 3.18...3.27)
project(
jtest
VERSION 1.0
@@ -24,20 +24,11 @@ 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 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-6.zip
)
CPMAddPackage(
NAME jlog
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-5.zip
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-19.zip
)
include_directories(${Event_SOURCE_DIR}/include)
include_directories(${jlog_SOURCE_DIR}/include)
if (UNIX)
add_library(jtest SHARED ${jtest_SRC})
endif()
@@ -46,6 +37,10 @@ if (WIN32)
add_library(jtest STATIC ${jtest_SRC})
endif()
target_include_directories(jtest PUBLIC ${Event_SOURCE_DIR}/include)
target_include_directories(jtest PUBLIC ${jlog_SOURCE_DIR}/include)
target_include_directories(jtest PUBLIC ${PROJECT_SOURCE_DIR}/include)
@@ -64,4 +59,4 @@ target_link_libraries(jtest PUBLIC Event jlog)
add_executable(TestSuiteDemo main.cpp)
# link the new library target with the binary target
target_link_libraries(TestSuiteDemo PUBLIC jtest)
target_link_libraries(TestSuiteDemo PUBLIC jtest)

28
include/jtest/Test.hpp Normal file
View 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;
};
}

25
include/jtest/Unit.hpp Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <Event.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;
};
}

View 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;
};
}

View File

@@ -1,105 +1,108 @@
/// 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 <functional>
#include <string>
#include <vector>
#include <jlog/jlog.hpp>
#include <functional>
namespace jtest {
// Requirements
//
// Can't we just store a struct in a global vector per test?
// -maxine
struct testdef
{
const std::string& testname;
const std::function<void()>& callback;
const std::string file; // <- & is not needed here -maxine
int line;
bool passed;
class check_fail {
};
// Globals for test tracking
std::vector<testdef> testlist;
int rantests;
int passedtests;
int failedtests;
class check_eq_fail : public check_fail {
};
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));
}
class check_ne_fail : public check_fail {
};
bool check(bool condition) {
/// Raises an exception if the given condition is false.
void check(bool condition) {
if (!condition)
throw std::runtime_error("Test check failed!!");
return condition;
throw check_fail();
}
bool test(const std::string& testname, const std::function<void()>& callback, const std::string& file, int line)
/// Raises an exception if the given types do not evaluate as equal.
template<typename T>
void check_eq(T a, T b) {
if (a != b)
throw check_eq_fail();
}
/// 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();
}
/// 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();
}
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)
{
try {
callback();
} catch(const std::exception& e) {
rantests++;
failedtests++;
jlog::log({
{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content ="JTEST"},
{.content = std::format("{}:{}", file, line)},
{.colorCode = jlog::ansi_escape_codes::FG_RED, .content = testname},
{.content = "Failed:", .delimiter = ""},
{.content = std::format("{}/{}", rantests, testlist.size())},
});
return false;
throw check_fail();
} catch(...)
{
// Do nothing, the callback threw as expected...
}
rantests++;
passedtests++;
jlog::log({
{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content ="JTEST"},
{.content = std::format("{}:{}", file, line)},
{.colorCode = jlog::ansi_escape_codes::FG_GREEN, .content = testname},
{.content = "Passed:", .delimiter = ""},
{.content = std::format("{}/{}", rantests, testlist.size())},
});
return true;
}
// Storing a global vector with all the tests should allow us to loop through all the tests
// We can also possibly do more tracing and allow other fancy features.
// -maxine
void run_tests() {
//int i;
//for (int i = 1; const testdef& td : testlist)
for (testdef& td : testlist)
void check_nothrows(std::function<void()> callback)
{
try {
callback();
} catch(...)
{
td.passed = test(td.testname, td.callback, td.file, td.line);
//i++;
}
jlog::log({
//{.content = std::format("{}:{}", file, line)},
{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content = "JTEST"},
{.content = std::format("Tests Ran: [{}/{}] Failed: [{}/{}] Passed: [{}/{}]", rantests, testlist.size(), failedtests, rantests, passedtests, rantests), .delimiter = ""},
});
//USINFO(std::format("Tests Ran: [{}/{}] Failed: [{}/{}] Passed: [{}/{}]", rantests, testlist.size(), failedtests, rantests, passedtests, rantests))
if (passedtests == rantests)
{
USINFO("All tests passed congratulations! Do you wanna cookie?");
jlog::log({
//{.content = std::format("{}:{}", file, line)},
{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content = "JTEST"},
{.colorCode = jlog::ansi_escape_codes::FG_GREEN, .content = "All tests passed congratulations! Do you wanna cookie?", .delimiter = ""},
});
throw check_fail();
}
}
}
// 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)
{
check_throws(callback);
}
//#define TEST(a, b) jtest::test(a, b, __FILE__, __LINE__);
// Same definition as before essentially, but points to a different function which adds the test to the global vector.
// -maxine
#define TEST(a, b) jtest::definetest(a, b, __FILE__, __LINE__);
/// Raises an exception, which causes the test to fail.
void fail_test()
{
throw check_fail();
}
}

View File

@@ -1,41 +1,45 @@
//
// Created by dawsh on 6/16/24.
//
#include <cassert>
#include "include/jtest/jtest.hpp"
/// 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>
int main(int argc, char** argv)
{
mcolor::windowsSaneify();
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");
}};
u += jtest::Test{"dsads", [](){throw("thisdick");}};
TEST("Test1", []{
jtest::check(2+2 == 4);
});
TEST("Test2", [] {
//jtest::check(2+2 == 5);
jtest::check(2+2 == 4);
});
TEST("Test3", []
{
jtest::check(6+9 == 69);
//jtest::check(2+2 == 4);
});
/*
TEST("LMAO");
TEST("KEKERINO")
TEST(":)")
*/
// Doesn't actually do anything yet
jtest::run_tests();
u.Invoke();
}

10
src/jtest/Test.cpp Normal file
View 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
View 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 (EventPtr &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);
}
}

120
src/jtest/UnitLogger.cpp Normal file
View File

@@ -0,0 +1,120 @@
#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);
os << "\n";
};
}

View File

@@ -1,7 +1 @@
//
// Created by dawsh on 6/16/24.
//
#include <jtest/jtest.hpp>