150 lines
3.6 KiB
C++
150 lines
3.6 KiB
C++
#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()>;
|
|
|
|
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<std::string_view, std::vector<Suite>> suites;
|
|
};
|
|
|
|
template <fixed_string suite_name>
|
|
struct suite {
|
|
template <typename Suite>
|
|
suite(Suite suite) {
|
|
static_assert(std::convertible_to<Suite, Runner::Suite>, "Suite must be stateless!");
|
|
Runner::instance().add_suite(suite_name, suite);
|
|
}
|
|
};
|
|
|
|
struct test {
|
|
test(std::string_view name) : name(name) {}
|
|
|
|
template <typename Test>
|
|
void operator= (Test&& test) {
|
|
Runner::instance().on_test(name, std::forward<Test>(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 <typename TExpr>
|
|
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<TExpr>) {
|
|
auto result = expr();
|
|
if(!static_cast<bool>(result)) {
|
|
failed = true;
|
|
|
|
/// TODO: use pretty print, if the expression is too long.
|
|
expression = std::format("{}", expr);
|
|
}
|
|
} else {
|
|
if(!static_cast<bool>(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 <typename TExpr>
|
|
constexpr decltype(auto) operator% (const TExpr& expr) const {
|
|
return expr;
|
|
}
|
|
};
|
|
|
|
inline that_t that;
|
|
|
|
} // namespace clice::testing
|