Clean Tests
This commit is contained in:
@ -35,17 +35,7 @@ target_link_libraries(IACore PRIVATE
|
||||
OpenSSL::Crypto
|
||||
)
|
||||
|
||||
#if(WIN32)
|
||||
# target_link_libraries(IACore PUBLIC mimalloc-static)
|
||||
#
|
||||
# if(MSVC)
|
||||
# target_link_options(IACore PUBLIC "/INCLUDE:mi_version")
|
||||
# else()
|
||||
# target_link_options(IACore PUBLIC "")
|
||||
# endif()
|
||||
#else()
|
||||
# target_link_libraries(IACore PUBLIC mimalloc)
|
||||
#endif()
|
||||
target_link_libraries(IACore PUBLIC mimalloc-static)
|
||||
|
||||
target_precompile_headers(IACore PUBLIC inc/IACore/PCH.hpp)
|
||||
|
||||
|
||||
@ -17,8 +17,6 @@
|
||||
#include <IACore/IACore.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
// #include <mimalloc-new-delete.h>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
HighResTimePoint g_startTime{};
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
@ -20,63 +20,86 @@
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <exception>
|
||||
# include <exception>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
|
||||
# define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
|
||||
|
||||
// Internal macro to handle the return logic
|
||||
#define __iat_micro_test(call) \
|
||||
if(!(call)) return FALSE
|
||||
# define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return FALSE
|
||||
|
||||
#define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||
#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||
#define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
#define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
# define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||
# define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||
# define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
# define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
|
||||
// Float specific checks (Game dev essential)
|
||||
#define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
# define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
|
||||
#define IAT_UNIT(func) _test_unit([this](){ return this->func(); }, # func)
|
||||
#define IAT_NAMED_UNIT(n, func) _test_unit([this](){ return this->func(); }, n)
|
||||
# define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||
# define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
||||
|
||||
#define IAT_BLOCK(name) class name: public ia::iatest::block
|
||||
# define IAT_BLOCK(name) class name : public ia::iatest::block
|
||||
|
||||
// Concatenation fix for macros
|
||||
#define IAT_BEGIN_BLOCK(_group, _name) class _group##_##_name : public ia::iatest::block { \
|
||||
public: PCCHAR name() CONST OVERRIDE { return #_group "::" #_name; } private:
|
||||
# define IAT_BEGIN_BLOCK(_group, _name) \
|
||||
class _group##_##_name : public ia::iatest::block \
|
||||
{ \
|
||||
public: \
|
||||
PCCHAR name() CONST OVERRIDE \
|
||||
{ \
|
||||
return #_group "::" #_name; \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
#define IAT_END_BLOCK() };
|
||||
# define IAT_END_BLOCK() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define IAT_BEGIN_TEST_LIST() public: VOID declareTests() OVERRIDE {
|
||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
#define IAT_END_TEST_LIST() } private:
|
||||
# define IAT_BEGIN_TEST_LIST() \
|
||||
public: \
|
||||
VOID declareTests() OVERRIDE \
|
||||
{
|
||||
# define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
# define IAT_END_TEST_LIST() \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
namespace ia::iatest
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Type Printing Helper (To show WHAT failed)
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
std::string ToString(CONST T& value) {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
template<typename T> std::string ToString(CONST T &value)
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<T>)
|
||||
{
|
||||
return std::to_string(value);
|
||||
} else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const char*>) {
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const char *>)
|
||||
{
|
||||
return std::string("\"") + value + "\"";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return "{Object}"; // Fallback for complex types
|
||||
}
|
||||
}
|
||||
|
||||
// Specialization for pointers
|
||||
template<typename T>
|
||||
std::string ToString(T* value) {
|
||||
if (value == NULLPTR) return "nullptr";
|
||||
template<typename T> std::string ToString(T *value)
|
||||
{
|
||||
if (value == NULLPTR)
|
||||
return "nullptr";
|
||||
std::stringstream ss;
|
||||
ss << "ptr(" << (void*)value << ")";
|
||||
ss << "ptr(" << (void *) value << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
@ -86,26 +109,30 @@ namespace ia::iatest
|
||||
|
||||
DEFINE_TYPE(functor_t, std::function<BOOL()>);
|
||||
|
||||
struct unit_t {
|
||||
struct unit_t
|
||||
{
|
||||
std::string Name;
|
||||
functor_t Functor;
|
||||
functor_t Functor;
|
||||
};
|
||||
|
||||
class block
|
||||
{
|
||||
public:
|
||||
public:
|
||||
virtual ~block() = default;
|
||||
PURE_VIRTUAL(PCCHAR name() CONST);
|
||||
PURE_VIRTUAL(VOID declareTests());
|
||||
|
||||
std::vector<unit_t>& units() { return m_units; }
|
||||
|
||||
protected:
|
||||
// Generic Equality
|
||||
template<typename T1, typename T2>
|
||||
BOOL _test_eq(IN CONST T1& lhs, IN CONST T2& rhs, IN PCCHAR description)
|
||||
std::vector<unit_t> &units()
|
||||
{
|
||||
if(lhs != rhs) {
|
||||
return m_units;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Generic Equality
|
||||
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
||||
{
|
||||
if (lhs != rhs)
|
||||
{
|
||||
print_fail(description, ToString(lhs), ToString(rhs));
|
||||
return FALSE;
|
||||
}
|
||||
@ -113,10 +140,10 @@ namespace ia::iatest
|
||||
}
|
||||
|
||||
// Generic Inequality
|
||||
template<typename T1, typename T2>
|
||||
BOOL _test_neq(IN CONST T1& lhs, IN CONST T2& rhs, IN PCCHAR description)
|
||||
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
||||
{
|
||||
if(lhs == rhs) {
|
||||
if (lhs == rhs)
|
||||
{
|
||||
print_fail(description, ToString(lhs), "NOT " + ToString(rhs));
|
||||
return FALSE;
|
||||
}
|
||||
@ -124,12 +151,12 @@ namespace ia::iatest
|
||||
}
|
||||
|
||||
// Floating Point Approximation (Epsilon check)
|
||||
template<typename T>
|
||||
BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
|
||||
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
|
||||
T diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001)) { // Default epsilon
|
||||
if (diff > static_cast<T>(0.0001))
|
||||
{ // Default epsilon
|
||||
print_fail(description, ToString(lhs), ToString(rhs));
|
||||
return FALSE;
|
||||
}
|
||||
@ -138,7 +165,8 @@ namespace ia::iatest
|
||||
|
||||
BOOL _test(IN BOOL value, IN PCCHAR description)
|
||||
{
|
||||
if(!value) {
|
||||
if (!value)
|
||||
{
|
||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
|
||||
return FALSE;
|
||||
}
|
||||
@ -147,7 +175,8 @@ namespace ia::iatest
|
||||
|
||||
BOOL _test_not(IN BOOL value, IN PCCHAR description)
|
||||
{
|
||||
if(value) {
|
||||
if (value)
|
||||
{
|
||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
|
||||
return FALSE;
|
||||
}
|
||||
@ -159,8 +188,9 @@ namespace ia::iatest
|
||||
m_units.push_back({name, functor});
|
||||
}
|
||||
|
||||
private:
|
||||
VOID print_fail(PCCHAR desc, std::string v1, std::string v2) {
|
||||
private:
|
||||
VOID print_fail(PCCHAR desc, std::string v1, std::string v2)
|
||||
{
|
||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", desc);
|
||||
printf(__CC_RED " Expected: %s" __CC_DEFAULT "\n", v2.c_str());
|
||||
printf(__CC_RED " Actual: %s" __CC_DEFAULT "\n", v1.c_str());
|
||||
@ -171,91 +201,158 @@ namespace ia::iatest
|
||||
|
||||
template<typename block_class>
|
||||
concept valid_block_class = std::derived_from<block_class, block>;
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Runner
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template<BOOL stopOnFail = false, BOOL isVerbose = false>
|
||||
class runner
|
||||
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
|
||||
{
|
||||
public:
|
||||
runner(){}
|
||||
~runner() { summarize(); }
|
||||
|
||||
public:
|
||||
runner()
|
||||
{
|
||||
}
|
||||
|
||||
~runner()
|
||||
{
|
||||
summarize();
|
||||
}
|
||||
|
||||
template<typename block_class>
|
||||
requires valid_block_class<block_class>
|
||||
requires valid_block_class<block_class>
|
||||
VOID testBlock();
|
||||
|
||||
private:
|
||||
|
||||
private:
|
||||
VOID summarize();
|
||||
|
||||
private:
|
||||
SIZE_T m_testCount { 0 };
|
||||
SIZE_T m_failCount { 0 };
|
||||
SIZE_T m_blockCount { 0 };
|
||||
|
||||
private:
|
||||
SIZE_T m_testCount{0};
|
||||
SIZE_T m_failCount{0};
|
||||
SIZE_T m_blockCount{0};
|
||||
};
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose>
|
||||
template<typename block_class>
|
||||
requires valid_block_class<block_class>
|
||||
requires valid_block_class<block_class>
|
||||
VOID runner<stopOnFail, isVerbose>::testBlock()
|
||||
{
|
||||
m_blockCount++;
|
||||
block_class b;
|
||||
b.declareTests();
|
||||
|
||||
|
||||
printf(__CC_MAGENTA "Testing [%s]..." __CC_DEFAULT "\n", b.name());
|
||||
|
||||
for(auto& v: b.units())
|
||||
|
||||
for (auto &v : b.units())
|
||||
{
|
||||
m_testCount++;
|
||||
if constexpr(isVerbose) {
|
||||
if constexpr (isVerbose)
|
||||
{
|
||||
printf(__CC_YELLOW " Testing %s...\n" __CC_DEFAULT, v.Name.c_str());
|
||||
}
|
||||
|
||||
BOOL result = FALSE;
|
||||
try {
|
||||
try
|
||||
{
|
||||
// Execute the test function
|
||||
result = v.Functor();
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
printf(__CC_RED " CRITICAL EXCEPTION in %s: %s\n" __CC_DEFAULT, v.Name.c_str(), e.what());
|
||||
result = FALSE;
|
||||
}
|
||||
catch (...) {
|
||||
catch (...)
|
||||
{
|
||||
printf(__CC_RED " UNKNOWN CRITICAL EXCEPTION in %s\n" __CC_DEFAULT, v.Name.c_str());
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
if(!result)
|
||||
if (!result)
|
||||
{
|
||||
m_failCount++;
|
||||
if constexpr(stopOnFail) { summarize(); exit(-1); }
|
||||
if constexpr (stopOnFail)
|
||||
{
|
||||
summarize();
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
fputs("\n", stdout);
|
||||
}
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose>
|
||||
VOID runner<stopOnFail, isVerbose>::summarize()
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose> VOID runner<stopOnFail, isVerbose>::summarize()
|
||||
{
|
||||
printf(__CC_GREEN "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n");
|
||||
|
||||
if(!m_failCount) {
|
||||
printf(__CC_GREEN
|
||||
"\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n");
|
||||
|
||||
if (!m_failCount)
|
||||
{
|
||||
printf("\n\tALL TESTS PASSED!\n\n");
|
||||
} else {
|
||||
FLOAT64 successRate = (100.0 * static_cast<FLOAT64>(m_testCount - m_failCount)/static_cast<FLOAT64>(m_testCount));
|
||||
printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount, m_testCount, successRate);
|
||||
}
|
||||
|
||||
printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN "-----------------------------------" __CC_DEFAULT "\n", m_testCount, m_blockCount);
|
||||
else
|
||||
{
|
||||
FLOAT64 successRate =
|
||||
(100.0 * static_cast<FLOAT64>(m_testCount - m_failCount) / static_cast<FLOAT64>(m_testCount));
|
||||
printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount,
|
||||
m_testCount, successRate);
|
||||
}
|
||||
|
||||
printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN
|
||||
"-----------------------------------" __CC_DEFAULT "\n",
|
||||
m_testCount, m_blockCount);
|
||||
}
|
||||
|
||||
template<typename>
|
||||
struct _valid_iatest_runner : std::false_type {};
|
||||
template<typename> struct _valid_iatest_runner : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose>
|
||||
struct _valid_iatest_runner<runner<stopOnFail,isVerbose>> : std::true_type {};
|
||||
}
|
||||
struct _valid_iatest_runner<runner<stopOnFail, isVerbose>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Global Test Registry
|
||||
// -------------------------------------------------------------------------
|
||||
// Standard runner configuration for the single executable
|
||||
using DefaultRunner = runner<false, true>;
|
||||
|
||||
class TestRegistry
|
||||
{
|
||||
public:
|
||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||
|
||||
static std::vector<TestEntry> &GetEntries()
|
||||
{
|
||||
static std::vector<TestEntry> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static int RunAll()
|
||||
{
|
||||
DefaultRunner r;
|
||||
auto &entries = GetEntries();
|
||||
printf(__CC_CYAN "[IATest] Discovered %zu Test Blocks\n\n" __CC_DEFAULT, entries.size());
|
||||
|
||||
for (auto &entry : entries)
|
||||
{
|
||||
entry(r);
|
||||
}
|
||||
// The destructor of 'r' will automatically print the summary
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename BlockType> struct AutoRegister
|
||||
{
|
||||
AutoRegister()
|
||||
{
|
||||
TestRegistry::GetEntries().push_back([](DefaultRunner &r) { r.testBlock<BlockType>(); });
|
||||
}
|
||||
};
|
||||
} // namespace ia::iatest
|
||||
|
||||
// Usage: IAT_REGISTER_ENTRY(Core, Utils)
|
||||
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
|
||||
#endif // __cplusplus
|
||||
Reference in New Issue
Block a user