const_container/test/common_helper/test.hpp

228 lines
7.2 KiB
C++

//
// Created by Patrick Maschek on 08/04/2024.
//
#ifndef CONST_CONTAINER_TEST_HPP_
#define CONST_CONTAINER_TEST_HPP_
#include <any>
#include <array>
#include <concepts>
#include <iostream>
#include <ranges>
#include <tuple>
#include <numeric>
#define TEST_FAIL(msg) ret_val_s { "", ReturnCode::FAILED, msg }
#define TEST_PASS() ret_val_s { "", ReturnCode::PASSED, nullptr }
#define TEST_PASS_MSG(msg) ret_val_s { "", ReturnCode::PASSED, msg }
#define TEST_SKIP() ret_val_s { "", ReturnCode::SKIPPED, nullptr }
enum class EvalFlag { RUNTIME, CONSTEVAL, RUNTIME_CONSTEVAL };
enum class ReturnCode { FAILED = -1, PASSED = 0, SKIPPED = 1, NOT_EVALUATED = 2 };
template<typename Suite>
struct quick_test_def;
inline std::ostream& operator<<(std::ostream& os, ReturnCode rc);
struct ret_val_s {
const char *test_name = "";
ReturnCode val = ReturnCode::FAILED;
const char *msg = "";
};
template<std::size_t Nr>
struct ret_val {
const char * name;
std::array<ret_val_s, Nr> vals;
[[nodiscard]] constexpr inline std::size_t size() const { return vals.size(); }
constexpr inline ret_val_s& operator[](std::size_t i) { return vals[i]; }
constexpr inline const ret_val_s& operator[](std::size_t i) const { return vals[i]; }
};
class test_definition {
public:
[[nodiscard]] virtual constexpr ret_val_s evaluate() const = 0;
[[nodiscard]] virtual const char *name() const = 0;
[[nodiscard]] virtual EvalFlag evalFlag() const = 0;
[[nodiscard]] virtual const ret_val_s &c_res() const = 0;
};
template<std::invocable Func, typename ...Args>
class test_definition_impl : public test_definition {
public:
using FuncType = Func;
static constexpr std::size_t ARG_SIZE = sizeof...(Args);
constexpr test_definition_impl(const char *name, Func func, EvalFlag evalFlag, Args ...args)
: _name(name), _func(func), _evalFlag(evalFlag), _args(std::make_tuple(std::forward(args)...)),
_c_res(_name, ReturnCode::FAILED, "Could not be evaluated at compile time") {
if consteval {
if (evalFlag == EvalFlag::RUNTIME_CONSTEVAL || evalFlag == EvalFlag::CONSTEVAL) {
_c_res = evaluate();
}
}
}
[[nodiscard]] constexpr ret_val_s evaluate() const override {
ret_val_s r = std::apply(_func, _args);
r.test_name = _name;
return r;
}
[[nodiscard]] const char *name() const override { return _name; }
[[nodiscard]] EvalFlag evalFlag() const override { return _evalFlag; }
[[nodiscard]] const ret_val_s &c_res() const override{ return _c_res; }
private:
const char *_name;
EvalFlag _evalFlag;
Func _func;
std::tuple<Args...> _args;
ret_val_s _c_res;
};
template<typename ...TestDefs>
requires (sizeof...(TestDefs) == 0 || (std::derived_from<TestDefs, test_definition> && ...))
class test_suite {
public:
static constexpr std::size_t TEST_NR = sizeof...(TestDefs);
constexpr test_suite(const char *name, std::tuple<TestDefs...> tests) : _name(name), _tests(tests) {}
int run() const {
auto test_arr = expand_test_tuple(_tests, std::make_index_sequence<TEST_NR>());
int num_failed = 0;
std::array<std::array<ReturnCode, 2>, TEST_NR> ret_vals = { { ReturnCode::NOT_EVALUATED } };
for (auto [i, test_ref] : std::ranges::views::enumerate(test_arr)) {
const auto& test = test_ref.get();
std::cout << "Running Test: \"" << test.name() << "\" "
"(Test " << i+1 << "/" << test_arr.size() << ")\n";
if (test.evalFlag() == EvalFlag::RUNTIME || test.evalFlag() == EvalFlag::RUNTIME_CONSTEVAL) {
ret_val_s ret;
std::string ret_exc_str;
try {
ret = test.evaluate();
} catch (std::exception& e) {
ret_exc_str = std::string("Test failed with Exception: \"") + e.what() + "\"";
ret = ret_val_s(test.name(), ReturnCode::FAILED, ret_exc_str.c_str());
}
std::cout << "Result of Runtime Evaluation of Test \"" << ret.test_name << "\": " << ret.val;
if (ret.msg != nullptr) {
std::cout << "\n\t" << ret.msg;
}
std::cout << std::endl;
ret_vals[i][0] = ret.val;
}
if (test.evalFlag() == EvalFlag::CONSTEVAL || test.evalFlag() == EvalFlag::RUNTIME_CONSTEVAL) {
const ret_val_s &ret = test.c_res();
std::cout << "Result of Consteval Evaluation of Test \"" << ret.test_name << "\": " << ret.val;
if (ret.msg != nullptr) {
std::cout << "\n\t" << ret.msg;
}
std::cout << std::endl;
ret_vals[i][1] = ret.val;
}
std::cout << "--------------\n";
}
auto ret_vals_j = ret_vals | std::ranges::views::join;
auto correct = std::ranges::count_if(ret_vals, [](auto&& e) {
return std::ranges::none_of(e, [](auto&& c) { return c == ReturnCode::FAILED; })
&& std::ranges::any_of(e, [](auto&& c) { return c == ReturnCode::PASSED; });
});
auto failed = std::ranges::count_if(ret_vals, [](auto&& e) {
return std::ranges::any_of(e, [](auto&& c) { return c == ReturnCode::FAILED; }); });
auto full_skipped = std::ranges::count_if(ret_vals, [](auto&& e) {
return std::ranges::all_of(e, [](auto&& c) { return c == ReturnCode::SKIPPED || c == ReturnCode::NOT_EVALUATED; }); });
auto part_skipped = std::ranges::count_if(ret_vals, [](auto&& e) {
return std::ranges::any_of(e, [](auto&& c) { return c == ReturnCode::SKIPPED; }); });
std::size_t num_tests = ret_vals.size();
std::cout << "Final Result: " << "\n"
<< correct << "/" << num_tests << " tests evaluated correctly" << "\n"
<< failed << "/" << num_tests << " tests failed" << "\n"
<< full_skipped << "/" << num_tests << " tests skipped" << "\n"
<< part_skipped << "/" << num_tests << " tests have been partially skipped" << "\n";
return -failed;
}
private:
const char *_name;
std::tuple<TestDefs...> _tests;
template<std::size_t ...Is>
static constexpr auto expand_test_tuple(const auto &tests, std::index_sequence<Is...>) {
return std::array {
std::reference_wrapper(
dynamic_cast<const test_definition&>(std::get<Is>(tests))
)... };
}
friend class quick_test_def<test_suite<TestDefs...>>;
};
template<typename Suite>
struct quick_test_def {
Suite current;
template<std::invocable Func, typename ...Args>
constexpr auto operator()(const char *name, Func func, Args... args) {
return operator()(name, func, EvalFlag::RUNTIME, std::forward(args)...);
}
template<std::invocable Func, typename ...Args>
constexpr auto operator()(const char *name, Func func, EvalFlag evalFlag, Args... args) {
auto test = test_definition_impl(name, func, evalFlag, args...);
auto new_suite = test_suite(current._name, std::tuple_cat(current._tests, std::make_tuple(test)));
return quick_test_def<decltype(new_suite)> { new_suite };
}
constexpr operator Suite() {
return current;
}
};
template<typename ...TestDefs>
test_suite(quick_test_def<test_suite<TestDefs...>>) -> test_suite<TestDefs...>;
constexpr auto define_tests(const char *name) {
return quick_test_def { test_suite<>{ name, std::make_tuple() } };
}
inline std::ostream& operator<<(std::ostream& os, ReturnCode rc) {
switch (rc) {
case ReturnCode::FAILED:
return os << "FAILED";
case ReturnCode::PASSED:
return os << "PASSED";
case ReturnCode::SKIPPED:
return os << "SKIPPED";
case ReturnCode::NOT_EVALUATED:
return os << "NOT EVALUATED";
default:
return os;
}
}
#endif //CONST_CONTAINER_TEST_HPP_