#pragma once #include "TExpr.h" #include "LocationChain.h" #include "Support/JSON.h" #include "Support/Format.h" #include "Support/Compare.h" #include "Support/FileSystem.h" #include "Support/FixedString.h" #include "llvm/ADT/FunctionExtras.h" namespace clice::testing { struct may_failure; class Runner { public: static Runner& instance(); using Suite = void (*)(); using Test = llvm::unique_function; void add_suite(std::string_view name, Suite suite); void on_test(std::string_view name, Test test, bool skipped); /// Current test is failed, continue to execute the next test in the suite. void fail(const may_failure& failure); bool fatal_error_occured() { return curr_fatal; } /// Run all test suites. int run_tests(); private: Runner() = default; Runner(const Runner&) = delete; Runner(Runner&&) = delete; private: bool curr_failed = false; bool skipped = false; bool curr_fatal = false; /// Whether all tests in this test suite are skipped. bool all_skipped = true; std::string curr_suite_name; std::uint32_t curr_tests_count = 0; std::uint32_t curr_failed_tests_count = 0; std::uint32_t total_tests_count = 0; std::uint32_t total_suites_count = 0; std::uint32_t total_failed_tests_count = 0; std::chrono::milliseconds curr_test_duration; std::chrono::milliseconds total_test_duration; std::unordered_map> suites; }; template struct suite { template suite(Suite suite) { static_assert(std::convertible_to, "Suite must be stateless!"); Runner::instance().add_suite(suite_name, suite); } }; struct test { test(std::string_view name) : name(name) {} template void operator= (Test&& test) { Runner::instance().on_test(name, std::forward(test), skipped); } bool skipped = false; std::string name; }; struct may_failure { bool failed = false; bool fatal = false; std::string expression; std::source_location location; std::string message; may_failure& operator<< (std::string message) { this->message += std::move(message); return *this; } ~may_failure() { Runner::instance().fail(*this); } }; constexpr inline struct { template may_failure operator() (const TExpr& expr, std::source_location location = std::source_location::current()) const { bool failed = false; std::string expression = "false"; if constexpr(is_expr_v) { auto result = expr(); if(!static_cast(result)) { failed = true; /// TODO: use pretty print, if the expression is too long. expression = std::format("{}", expr); } } else { if(!static_cast(expr)) { failed = true; } } return may_failure{failed, false, expression, location}; } } expect; constexpr inline struct { test&& operator/ (test&& test) const { test.skipped = true; return std::move(test); } } skip; constexpr inline struct { may_failure&& operator/ (may_failure&& failure) const { if(failure.failed) { failure.fatal = true; } return std::move(failure); } } fatal; struct that_t { template constexpr decltype(auto) operator% (const TExpr& expr) const { return expr; } }; inline that_t that; } // namespace clice::testing