#pragma once #include #include #include #include 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> 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 TestSuite(TSuite body) { #ifndef LIGHT_SKIP_TESTS details::Registry::register_suite(+body); #endif } TestSuite() = default; }; struct TestFuzzHarness { template 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 {}; }