From 875166c6b6bb8a22211418ba7fe213a1a596c7ce Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 26 Jun 2024 12:28:55 -0400 Subject: [PATCH] Nice structural refactor part 1 --- include/jtest/jtest.hpp | 175 +++++++--------------------------------- src/jtest/jtest.cpp | 113 +++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 150 deletions(-) diff --git a/include/jtest/jtest.hpp b/include/jtest/jtest.hpp index 9c3b2be..776635a 100644 --- a/include/jtest/jtest.hpp +++ b/include/jtest/jtest.hpp @@ -11,81 +11,33 @@ namespace jtest { - // Can't we just store a struct in a global vector per test? - // -maxine + /// Structure to store test meta-data, as tests are initially registered, and ran later. struct testdef { std::string testname; std::function callback; - std::string file; // <- & is not needed here -maxine + std::string file; int line; bool passed; }; - // Globals for test tracking - std::vector testlist; - int rantests; - int passedtests; - int failedtests; - std::vector log_test_format(const std::string& testname, const std::string& file, int line, bool passed) - { - std::vector wtokens; + /// Generates and returns a sequence of logger tokens pre-formatted to the test data. + std::vector log_test_format(const std::string& testname, const std::string& file, int line, bool passed); - auto head = jlog::token{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content ="JTEST"}; + /// Generates and returns a sequence of logger tokens for the test summary printed at the end of testing. + std::vector log_test_tracking_format(); - auto filedata = jlog::token{.content = std::format("{}:{}", file, line)}; + /// Registers a test internally. + void definetest(const std::string& testname, const std::function& callback, const std::string& file, int line); - std::vector 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())}; + // TODO: implement streaming a custom failure message with << operator on check statements - wtokens.push_back(head); - wtokens.push_back(filedata); - wtokens.insert(wtokens.end(), teststate.begin(), teststate.end()); - wtokens.push_back(raninfo); - - return wtokens; - } - - std::vector log_test_tracking_format() - { - auto head = jlog::token{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content = "JTEST"}; - auto tracking = jlog::token{.content = std::format("Tests Ran: [{}/{}] Failed: [{}/{}] Passed: [{}/{}]", - rantests, - testlist.size(), - failedtests, - rantests, - passedtests, - rantests), - .delimiter = ""}; - return {head, tracking}; - } - - void definetest(const std::string& testname, const std::function& callback, const std::string& file, int line) - { - testlist.push_back(testdef(testname, callback, file, line)); - } - - // TODO: Implement check variants - // TODO: implement streaming a custom failure message with << operator - // i.e. : check(my_cond) << "The condition is not true!" - - bool check(bool condition) { - if (!condition) - throw std::runtime_error("Test check failed!!"); - return condition; - } + /// 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 bool check_eq(T a, T b) { if (a != b) @@ -93,98 +45,25 @@ namespace jtest { return true; } - bool check_float_eq(float a, float b, float epsilon = 1e-3f) { - if (std::abs(a - b) > epsilon) { - 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); - bool check_double_eq(double a, double b, double epsilon = 1e-3f) { - if (std::abs(a - b) > epsilon) { - throw std::runtime_error ("Test check failed!!"); - } - return true; - } + /// 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); - 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; - } + /// 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); - bool test(const std::string& testname, const std::function& callback, const std::string& file, int line) - { - bool passed = true; - - try - { - callback(); - } catch(const std::exception& e) - { - passed = false; - } - - rantests++; - - if (passed) - { - passedtests++; - } else - { - failedtests++; - } - - jlog::log(log_test_format(testname, file, line, passed)); - - return passed; - /* - try { - callback(); - } catch(const std::exception& e) { - rantests++; - failedtests++; - jlog::log(log_test_format(testname, file, line, false)); - return false; - } - - rantests++; - passedtests++; - jlog::log(log_test_format(testname, file, line, true)); - 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) - { - td.passed = test(td.testname, td.callback, td.file, td.line); - //i++; - } - - jlog::log(log_test_tracking_format()); - - /* - 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 = ""}, - }); - } - */ - } + /// Runs a given test, generates a report, and returns the test result as a boolean. + bool test(const std::string& testname, const std::function& 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(); } -//#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 +/// 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 diff --git a/src/jtest/jtest.cpp b/src/jtest/jtest.cpp index 412b99e..929e554 100644 --- a/src/jtest/jtest.cpp +++ b/src/jtest/jtest.cpp @@ -1,7 +1,116 @@ // // Created by dawsh on 6/16/24. // - - +#include +namespace jtest { + + // Globals for test tracking + std::vector testlist; + int rantests; + int passedtests; + int failedtests; + + std::vector log_test_format(const std::string &testname, const std::string &file, int line, + bool passed) { + std::vector wtokens; + + auto head = jlog::token{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content ="JTEST"}; + + auto filedata = jlog::token{.content = std::format("{}:{}", file, line)}; + + std::vector 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(head); + wtokens.push_back(filedata); + wtokens.insert(wtokens.end(), teststate.begin(), teststate.end()); + wtokens.push_back(raninfo); + + return wtokens; + } + + std::vector log_test_tracking_format() { + auto head = jlog::token{.colorCode = jlog::ansi_escape_codes::FG_WHITE, .content = "JTEST"}; + auto tracking = jlog::token{.content = std::format("Tests Ran: [{}/{}] Failed: [{}/{}] Passed: [{}/{}]", + rantests, + testlist.size(), + failedtests, + rantests, + passedtests, + rantests), + .delimiter = ""}; + return {head, tracking}; + } + + void definetest(const std::string &testname, const std::function &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 &callback, const std::string &file, int line) { + bool passed = true; + + try { callback(); } + catch(const std::exception& e) + { passed = false; } + + rantests++; + + if (passed) { passedtests++; } + else { failedtests++; } + + jlog::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); + + } + + jlog::log(log_test_tracking_format()); + } +}