light/modules/test/public/test.hpp
light7734 4976773218
Some checks reported errors
continuous-integration/drone/push Build was killed
feat(test): minor additions
2025-09-30 14:04:38 +03:30

354 lines
7.1 KiB
C++

#pragma once
#include <concepts>
#include <format>
#include <regex>
#include <test/expects.hpp>
namespace lt::test {
namespace details {
class Registry
{
public:
enum class ExecutionPolicy : uint8_t
{
normal,
stats,
};
struct Options
{
bool stop_on_fail = false;
ExecutionPolicy execution_policy = ExecutionPolicy::normal;
std::string suite_regex;
std::string case_regex;
};
using FuzzFunction = int32_t (*)(const uint8_t *, size_t);
using SuiteFunction = void (*)();
static void register_suite(SuiteFunction suite)
{
instance().m_suites.emplace_back(suite, "");
}
static void register_fuzz_harness(FuzzFunction suite)
{
if (instance().m_fuzz_harness)
{
throw std::logic_error {
"Attempting to register fuzz harness while one is already registered",
};
}
instance().m_fuzz_harness = suite;
}
static auto run_all(Options options) -> uint32_t
{
instance().m_options = std::move(options);
return instance().run_all_impl();
}
static auto process_fuzz_input(const uint8_t *data, size_t size) -> int32_t
{
if (!instance().m_fuzz_harness)
{
throw std::logic_error {
"Attempting to process fuzz input with no active harness",
};
}
return instance().m_fuzz_harness(data, size);
}
static void set_last_suite_name(const char *name)
{
instance().m_suites.back().second = name;
}
static void increment_total_suite_count()
{
++instance().m_total_suite_count;
}
static void increment_matched_suite_count()
{
++instance().m_matched_suite_count;
}
static void increment_skipped_suite_count()
{
++instance().m_skipped_suite_count;
}
static void increment_passed_suite_count()
{
++instance().m_passed_suite_count;
}
static void increment_failed_suite_count()
{
++instance().m_failed_suite_count;
}
static void increment_total_case_count()
{
++instance().m_total_case_count;
}
static void increment_matched_case_count()
{
++instance().m_matched_case_count;
}
static void increment_skipped_case_count()
{
++instance().m_skipped_case_count;
}
static void increment_passed_case_count()
{
++instance().m_passed_case_count;
}
static void increment_failed_case_count()
{
++instance().m_failed_case_count;
}
[[nodiscard]] static auto should_return_on_failure() -> bool
{
return instance().m_options.stop_on_fail;
}
[[nodiscard]] static auto get_options() -> const Options &
{
return instance().m_options;
}
[[nodiscard]] static auto get_case_regex() -> const std::regex &
{
return instance().m_case_regex;
}
private:
auto run_all_impl() -> uint32_t
{
print_options();
m_case_regex = std::regex(m_options.case_regex);
const auto regex = std::regex(m_options.suite_regex);
for (auto &[suite, name] : m_suites)
{
try
{
if (std::regex_search(name, regex))
{
suite();
increment_matched_suite_count();
}
else
{
increment_skipped_suite_count();
}
increment_total_suite_count();
}
catch (const std::exception &exp)
{
if (m_options.stop_on_fail)
{
std::println("Quitting due to options.stop_on_fail == true");
break;
}
std::println("Uncaught exception when running suite:");
std::println("\twhat: {}", exp.what());
break;
}
}
switch (m_options.execution_policy)
{
case ExecutionPolicy::normal:
{
std::cout << "[-------STATS------]\n"
<< "suites:\n"
<< "\ttotal: " << m_total_suite_count << '\n'
<< "\tpassed: " << m_passed_suite_count << '\n'
<< "\tfailed: " << m_failed_suite_count << '\n'
<< "\tmatched: " << m_matched_suite_count << '\n'
<< "\tskipped: " << m_skipped_suite_count << '\n'
<< "tests:\n"
<< "\ttotal: " << m_total_case_count << '\n'
<< "\tpassed: " << m_passed_case_count << '\n'
<< "\tfailed: " << m_failed_case_count << '\n'
<< "\tmatched: " << m_matched_case_count << '\n'
<< "\tskipped: " << m_skipped_case_count << '\n';
std::cout << "________________________________________________________________\n\n\n";
return m_failed_case_count;
}
case ExecutionPolicy::stats:
{
std::println("[-------STATS------]");
std::println("Total suite count: {}", m_total_suite_count);
std::println("Total test count: {}", m_total_case_count);
std::println("________________________________________________________________");
return EXIT_SUCCESS;
}
}
}
void print_options()
{
std::println("stop-on-failure: {}", m_options.stop_on_fail);
}
Registry()
{
std::cout << "________________________________________________________________\n";
}
[[nodiscard]] static auto instance() -> Registry &
{
static auto registry = Registry {};
return registry;
}
Options m_options {};
std::vector<std::pair<SuiteFunction, const char *>> m_suites;
FuzzFunction m_fuzz_harness {};
uint32_t m_total_case_count {};
uint32_t m_passed_case_count {};
uint32_t m_failed_case_count {};
uint32_t m_matched_case_count {};
uint32_t m_skipped_case_count {};
uint32_t m_total_suite_count {};
uint32_t m_passed_suite_count {};
uint32_t m_failed_suite_count {};
uint32_t m_matched_suite_count {};
uint32_t m_skipped_suite_count {};
std::regex m_case_regex;
};
} // namespace details
class Case
{
public:
Case(std::string_view name): m_name(name)
{
}
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
auto operator=(std::invocable auto test) -> void
{
using details::Registry;
using enum Registry::ExecutionPolicy;
switch (Registry::get_options().execution_policy)
{
case normal: run_normal(std::move(test)); break;
case stats: Registry::increment_total_case_count(); break;
}
}
private:
void run_normal(std::invocable auto test)
{
using details::Registry;
Registry::increment_total_case_count();
// NOLINTNEXTLINE
if (!std::regex_search(m_name.data(), Registry::get_case_regex()))
{
Registry::increment_skipped_case_count();
return;
}
Registry::increment_matched_case_count();
std::cout << "[Running-----------] --> ";
std::cout << m_name << '\n';
try
{
test();
}
catch (const std::exception &exp)
{
std::cout << exp.what() << "\n";
std::cout << "[-----------FAIL !!]" << "\n\n";
Registry::increment_failed_case_count();
if (Registry::should_return_on_failure())
{
throw;
}
return;
}
Registry::increment_passed_case_count();
std::cout << "[--------SUCCESS :D]" << "\n\n";
}
std::string_view m_name;
};
struct TestSuite
{
template<typename TSuite>
TestSuite(TSuite body)
{
std::println("CONSTRUCTOR");
#ifndef LIGHT_SKIP_TESTS
details::Registry::register_suite(+body);
#endif
}
TestSuite() = default;
};
struct TestFuzzHarness
{
template<class TestFuzzHarnessBody>
constexpr TestFuzzHarness(TestFuzzHarnessBody body)
{
#ifndef LIGHT_SKIP_FUZZ_TESTS
details::Registry::register_fuzz_harness(+body);
#endif
}
};
using Suite = const TestSuite;
using FuzzHarness = const TestFuzzHarness;
} // namespace lt::test
inline auto operator""_suite(const char *name, size_t size) -> lt::test::TestSuite
{
lt::test::details::Registry::set_last_suite_name(name);
return {};
}