583 lines
20 KiB
C++
583 lines
20 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 _EXPAND_STR(s) #s
|
|
#define _EXPAND_MACRO(s) _EXPAND_STR(s)
|
|
#define _LOCATION " (at " _EXPAND_MACRO(__FILE__) ":" _EXPAND_MACRO(__LINE__) ")"
|
|
|
|
template<typename T, std::size_t N>
|
|
consteval std::size_t c_arr_len(T (&arr)[N]) { return N; }
|
|
|
|
constexpr auto make_copy(auto& value) -> std::remove_cvref_t<decltype(value)> { return value; }
|
|
|
|
#define ADD_TYPE_HINT(type) template <> constexpr const char* const to_type_hint_str::value<type> = #type
|
|
|
|
#define TEST_FAIL(msg, ...) _ret_val_from_ctx<__VA_ARGS__>(ReturnCode::FAILED, msg)
|
|
#define TEST_PASS(...) _ret_val_from_ctx<__VA_ARGS__>(ReturnCode::PASSED)
|
|
#define TEST_PASS_MSG(msg, ...) _ret_val_from_ctx<__VA_ARGS__>(ReturnCode::PASSED, msg)
|
|
#define TEST_SKIP(...) _ret_val_from_ctx<__VA_ARGS__>(ReturnCode::SKIPPED)
|
|
#define TEST_SKIP_MSG(msg, ...) _ret_val_from_ctx<__VA_ARGS__>(ReturnCode::SKIPPED, msg)
|
|
|
|
#define ASSERT(condition, ...) ASSERT_MSG((condition), "Condition (" #condition ") evaluated to false" _LOCATION, __VA_ARGS__)
|
|
#define ASSERT_MSG(condition, msg, ...) { if (!(condition)) return _ret_val_from_ctx<__VA_ARGS__>(ReturnCode::FAILED, msg); } static_assert(true, "")
|
|
#define ASSERT_THROWS(operation, exception_type, ...) if not consteval { try { operation; ASSERT_MSG(false, #operation " did not throw " #exception_type _LOCATION, __VA_ARGS__); } catch (exception_type &e) {} } static_assert(true, "")
|
|
#define ASSERT_NOTHROW(operation, exception_type, ...) if not consteval { try { operation; } catch (exception_type &e) { ASSERT_MSG(false, #operation " threw " #exception_type _LOCATION, __VA_ARGS__); } } static_assert(true, "")
|
|
|
|
template<typename T>
|
|
concept StringLike = std::is_convertible_v<T, std::string_view>;
|
|
|
|
template<typename T, typename U>
|
|
concept not_base_type_same_as = !std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<U>>;
|
|
|
|
template<std::ranges::range R, StringLike T>
|
|
constexpr bool all_equal_to(R r, T t) {
|
|
auto rt = std::ranges::views::repeat(std::string_view(t), std::ranges::size(r));
|
|
return std::ranges::equal(r, rt);
|
|
}
|
|
|
|
template<std::ranges::range R, typename T>
|
|
constexpr bool all_equal_to(R r, T t) {
|
|
auto rt = std::ranges::views::repeat(t, std::ranges::size(r));
|
|
return std::ranges::equal(r, rt);
|
|
}
|
|
|
|
template<std::input_iterator I, std::sentinel_for<I> S, StringLike T>
|
|
constexpr bool all_equal_to(I first, S last, T t) {
|
|
auto r1 = std::ranges::subrange(first, last);
|
|
auto r2 = std::ranges::views::repeat(std::string_view(t), std::ranges::distance(first, last));
|
|
return std::ranges::equal(r1, r2);
|
|
}
|
|
|
|
template<std::input_iterator I, std::sentinel_for<I> S, typename T>
|
|
constexpr bool all_equal_to(I first, S last, T t) {
|
|
auto r1 = std::ranges::subrange(first, last);
|
|
auto r2 = std::ranges::views::repeat(t, std::ranges::distance(first, last));
|
|
return std::ranges::equal(r1, r2);
|
|
}
|
|
|
|
template<std::ranges::range R1, std::ranges::range R2>
|
|
requires StringLike<std::ranges::range_value_t<R1>> && StringLike<std::ranges::range_value_t<R2>>
|
|
constexpr bool ranges_equal(R1 r1, R2 r2) {
|
|
auto r1t = r1 | std::ranges::views::transform([](auto&& e) constexpr { return std::string_view(e); });
|
|
auto r2t = r2 | std::ranges::views::transform([](auto&& e) constexpr { return std::string_view(e); });
|
|
return std::ranges::equal(r1t, r2t);
|
|
}
|
|
|
|
template<std::ranges::range R1, std::ranges::range R2>
|
|
constexpr bool ranges_equal(R1 r1, R2 r2) {
|
|
return std::ranges::equal(r1, r2);
|
|
}
|
|
|
|
template<std::input_iterator I, std::sentinel_for<I> S, std::ranges::range R>
|
|
requires StringLike<std::ranges::range_value_t<R>>
|
|
constexpr bool equal_to(I first, S last, R r) {
|
|
auto r1 = std::ranges::subrange(first, last);
|
|
auto r2 = r | std::ranges::views::transform([](auto&& e) constexpr { return std::string_view(e); });
|
|
return std::ranges::equal(r1, r2);
|
|
}
|
|
|
|
template<std::input_iterator I, std::sentinel_for<I> S, std::ranges::range R>
|
|
constexpr bool equal_to(I first, S last, R r) {
|
|
return std::ranges::equal(std::ranges::subrange(first, last), r);
|
|
}
|
|
|
|
template<std::input_iterator I1, std::sentinel_for<I1> S1, std::input_iterator I2>
|
|
requires StringLike<typename std::iterator_traits<I2>::value_type>
|
|
constexpr bool equal_to(I1 first1, S1 last1, I2 first2) {
|
|
auto r1 = std::ranges::subrange(first1, last1);
|
|
auto r2 = std::ranges::subrange(first2, std::next(first2, std::distance(first1, last1)))
|
|
| std::ranges::views::transform([](auto&& e) constexpr { return std::string_view(e); });
|
|
return std::ranges::equal(r1, r2);
|
|
}
|
|
|
|
template<std::input_iterator I1, std::sentinel_for<I1> S1, std::input_iterator I2>
|
|
constexpr bool equal_to(I1 first1, S1 last1, I2 first2) {
|
|
auto r1 = std::ranges::subrange(first1, last1);
|
|
auto r2 = std::ranges::subrange(first2, std::next(first2, std::distance(first1, last1)));
|
|
return std::ranges::equal(r1, r2);
|
|
}
|
|
|
|
enum class EvalFlag { RUNTIME, CONSTEVAL, RUNTIME_CONSTEVAL };
|
|
enum class ReturnCode { FAILED = -1, PASSED = 0, SKIPPED = 1, NOT_EVALUATED = 2 };
|
|
|
|
|
|
struct to_type_hint_str {
|
|
template<typename T>
|
|
static constexpr const char *value = nullptr;
|
|
};
|
|
|
|
template<typename T>
|
|
constexpr const char *to_type_hint_str_v = to_type_hint_str::value<T>;
|
|
|
|
ADD_TYPE_HINT(bool);
|
|
ADD_TYPE_HINT(int);
|
|
ADD_TYPE_HINT(char);
|
|
ADD_TYPE_HINT(const char *);
|
|
ADD_TYPE_HINT(float);
|
|
ADD_TYPE_HINT(double);
|
|
|
|
template<typename Suite>
|
|
struct quick_test_def;
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, ReturnCode rc);
|
|
|
|
|
|
template<std::size_t N>
|
|
struct tstring {
|
|
char value[N];
|
|
|
|
constexpr tstring(const char (&str)[N]) { std::copy_n(str, N, value); }
|
|
|
|
constexpr operator const char *() { return value; }
|
|
};
|
|
|
|
|
|
struct ret_val_s {
|
|
const char *test_name = "";
|
|
ReturnCode val = ReturnCode::FAILED;
|
|
const char *msg = nullptr;
|
|
const char *type_hint = nullptr;
|
|
std::size_t param_nr = -1;
|
|
};
|
|
|
|
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]; }
|
|
};
|
|
|
|
struct ctx_base {};
|
|
|
|
template<typename T, std::size_t Nr>
|
|
struct ctx_tn : ctx_base {
|
|
using type = T;
|
|
constexpr static std::size_t nr = Nr;
|
|
};
|
|
|
|
template<typename T>
|
|
struct ctx_t : ctx_base {
|
|
using type = T;
|
|
};
|
|
|
|
template<std::size_t Nr>
|
|
struct ctx_n : ctx_base {
|
|
constexpr static std::size_t nr = Nr;
|
|
};
|
|
|
|
template<typename ctx>
|
|
concept require_context = requires {
|
|
std::is_base_of_v<ctx_base, ctx>;
|
|
};
|
|
|
|
template<typename T> auto create_ctx() { return ctx_t<T>{}; }
|
|
template<std::size_t Nr> auto create_ctx() { return ctx_n<Nr>{}; }
|
|
template<typename T, std::size_t Nr> auto create_ctx() { return ctx_tn<T, Nr>{}; }
|
|
|
|
#define CONTEXT(...) decltype(create_ctx<__VA_ARGS__>())
|
|
|
|
|
|
struct empty_param {};
|
|
|
|
struct test_common_params {
|
|
template<tstring var_name>
|
|
constexpr static auto value = empty_param{};
|
|
};
|
|
|
|
template<require_context ctx>
|
|
struct test_context_params {
|
|
using context = ctx;
|
|
|
|
template<tstring var_name>
|
|
constexpr static auto value = empty_param{};
|
|
};
|
|
|
|
template<typename ctx, tstring var_name>
|
|
concept param_is_defined = require_context<ctx> && requires {
|
|
test_context_params<ctx>::template value<var_name>;
|
|
{ test_context_params<ctx>::template value<var_name> } -> not_base_type_same_as<empty_param>;
|
|
};
|
|
|
|
template<typename ctx, tstring var_name>
|
|
concept param_is_defined_number_context = require_context<ctx> && requires {
|
|
test_context_params<CONTEXT(ctx::nr)>::template value<var_name>;
|
|
{ test_context_params<CONTEXT(ctx::nr)>::template value<var_name> } -> not_base_type_same_as<empty_param>;
|
|
};
|
|
|
|
template<typename ctx, tstring var_name>
|
|
concept param_is_defined_type_context = require_context<ctx> && requires {
|
|
test_context_params<CONTEXT(ctx::type)>::template value<var_name>;
|
|
{ test_context_params<CONTEXT(ctx::type)>::template value<var_name> } -> not_base_type_same_as<empty_param>;
|
|
};
|
|
|
|
template<tstring var_name>
|
|
concept param_is_defined_common = requires {
|
|
test_common_params::value<var_name>;
|
|
{ test_common_params::value<var_name> } -> not_base_type_same_as<empty_param>;
|
|
};
|
|
|
|
|
|
template<tstring name>
|
|
requires param_is_defined_common<name>
|
|
constexpr auto& get_test_param() {
|
|
return test_common_params::value<name>;
|
|
}
|
|
|
|
template<require_context ctx, tstring name>
|
|
requires param_is_defined<ctx, name>
|
|
constexpr auto& get_test_param() {
|
|
return test_context_params<ctx>::template value<name>;
|
|
}
|
|
|
|
template<require_context ctx, tstring name>
|
|
requires (!param_is_defined<ctx, name>)
|
|
&& param_is_defined_number_context<ctx, name>
|
|
&& param_is_defined_type_context<ctx, name>
|
|
constexpr void get_test_param() {
|
|
throw std::invalid_argument(std::string_view("Could not resolve ambiguous parameter name ") + name);
|
|
}
|
|
|
|
template<require_context ctx, tstring name>
|
|
requires (!param_is_defined<ctx, name>)
|
|
&& param_is_defined_number_context<ctx, name>
|
|
&& (!param_is_defined_type_context<ctx, name>)
|
|
constexpr auto& get_test_param() {
|
|
return test_context_params<CONTEXT(ctx::nr)>::template value<name>;
|
|
}
|
|
|
|
template<require_context ctx, tstring name>
|
|
requires (!param_is_defined<ctx, name>)
|
|
&& (!param_is_defined_number_context<ctx, name>)
|
|
&& param_is_defined_type_context<ctx, name>
|
|
constexpr auto& get_test_param() {
|
|
return test_context_params<CONTEXT(ctx::type)>::template value<name>;
|
|
}
|
|
|
|
template<require_context ctx, tstring name>
|
|
requires (!param_is_defined<ctx, name>) && param_is_defined_common<name>
|
|
constexpr auto& get_test_param() {
|
|
return test_common_params::value<name>;
|
|
}
|
|
|
|
template<require_context ctx, tstring name>
|
|
constexpr void get_test_param() {
|
|
throw std::invalid_argument(std::string("Parameter ") + name.value + " could not be found in context");
|
|
}
|
|
|
|
template<tstring name>
|
|
constexpr void get_test_param() {
|
|
throw std::invalid_argument(std::string("Parameter ") + name.value + " could not be found");
|
|
}
|
|
|
|
|
|
#define CONTEXT_PARAM(type, name, ...) template<> template<> constexpr type test_context_params<CONTEXT(__VA_ARGS__)>::value< #name >
|
|
#define CONTEXT_PARAM_ARR(type, name, ...) template<> template<> constexpr type test_context_params<CONTEXT(__VA_ARGS__)>::value< #name >[]
|
|
|
|
template<typename ctx>
|
|
requires std::is_same_v<ctx, ctx_tn<typename ctx::type, ctx::nr>>
|
|
constexpr ret_val_s _ret_val_from_ctx(ReturnCode ret, const char *msg = nullptr) {
|
|
return ret_val_s {"", ret, msg, to_type_hint_str_v<typename ctx::type>, ctx::nr };
|
|
}
|
|
template<typename ctx>
|
|
requires std::is_same_v<ctx, ctx_t<typename ctx::type>>
|
|
constexpr ret_val_s _ret_val_from_ctx(ReturnCode ret, const char *msg = nullptr) {
|
|
return ret_val_s {"", ret, msg, to_type_hint_str_v<typename ctx::type> };
|
|
}
|
|
template<typename ctx>
|
|
requires std::is_same_v<ctx, ctx_n<ctx::nr>>
|
|
constexpr ret_val_s _ret_val_from_ctx(ReturnCode ret, const char *msg = nullptr) {
|
|
return ret_val_s {"", ret, msg, nullptr, ctx::nr };
|
|
}
|
|
template<typename ctx = void>
|
|
constexpr ret_val_s _ret_val_from_ctx(ReturnCode ret, const char *msg = nullptr) {
|
|
return ret_val_s {"", ret, msg };
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
};
|
|
|
|
class _has_been_const_evaluated_test : public test_definition {
|
|
public:
|
|
constexpr _has_been_const_evaluated_test()
|
|
: _res(_name, ReturnCode::FAILED, "Could not be evaluated at compile time") {
|
|
if consteval {
|
|
_res = ret_val_s { _name, ReturnCode::PASSED };
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr ret_val_s evaluate() const override { return _res; }
|
|
|
|
[[nodiscard]] const char *name() const override { return _name; }
|
|
[[nodiscard]] EvalFlag evalFlag() const override { return EvalFlag::CONSTEVAL; }
|
|
[[nodiscard]] const ret_val_s &c_res() const override { return _res; }
|
|
|
|
private:
|
|
const char *_name = "Constant evaluation check";
|
|
ret_val_s _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), _c_test(_has_been_const_evaluated_test()) {}
|
|
|
|
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;
|
|
}
|
|
if (ret.type_hint != nullptr) {
|
|
std::cout << "\n\t" << "with type '" << ret.type_hint << "'";
|
|
}
|
|
if (ret.param_nr != -1) {
|
|
std::cout << "\n\t" << "with parameter param_nr: " << ret.param_nr;
|
|
}
|
|
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;
|
|
}
|
|
if (ret.type_hint != nullptr) {
|
|
std::cout << "\n\t" << "with type '" << ret.type_hint << "'";
|
|
}
|
|
if (ret.param_nr != -1) {
|
|
std::cout << "\n\t" << "with parameter param_nr: " << ret.param_nr;
|
|
}
|
|
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"
|
|
<< (_c_test.c_res().val == ReturnCode::PASSED ? "Results of constant evaluation are valid" : "---Results of constant evaluation are invalid---") << "\n";
|
|
|
|
return -failed;
|
|
}
|
|
|
|
private:
|
|
const char *_name;
|
|
std::tuple<TestDefs...> _tests;
|
|
_has_been_const_evaluated_test _c_test;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
template <typename ...Ts>
|
|
constexpr auto _repeat_for_types(auto f) {
|
|
std::array<ret_val_s, sizeof...(Ts)> rets { (f.template operator()<Ts, CONTEXT(Ts)>())... };
|
|
return rets;
|
|
}
|
|
|
|
template <std::size_t N, typename ...Ts>
|
|
constexpr auto _repeat_for_types_n(auto f) {
|
|
std::array rets = {
|
|
[&]<typename T, std::size_t ...Ns>(std::index_sequence<Ns...>) constexpr {
|
|
return std::array {
|
|
(f.template operator()<T, Ns, CONTEXT(T, Ns)>())...
|
|
};
|
|
}.template operator()<Ts>(std::make_index_sequence<N>())...
|
|
};
|
|
return rets | std::ranges::views::join;
|
|
}
|
|
|
|
#define REPEAT_FOR_TYPES(func, ...) { \
|
|
auto r = _repeat_for_types<__VA_ARGS__>(func); \
|
|
auto it = std::ranges::find_if(r, [](auto&& e) constexpr { return e.val == ReturnCode::FAILED; }); \
|
|
if (it != std::ranges::end(r)) { \
|
|
return *it; \
|
|
} \
|
|
} static_assert(true, "")
|
|
|
|
#define REPEAT_FOR_TYPES_N(func, N, ...) { \
|
|
auto r = _repeat_for_types_n<N, __VA_ARGS__>(func); \
|
|
auto it = std::ranges::find_if(r, [](auto&& e) constexpr { return e.val == ReturnCode::FAILED; }); \
|
|
if (it != std::ranges::end(r)) { \
|
|
return *it; \
|
|
} \
|
|
} static_assert(true, "")
|
|
|
|
#define CREATE_FROM_IL(type, il, len) \
|
|
([]<std::size_t _N, typename ArgType>(std::initializer_list<ArgType> args) { \
|
|
auto creator = [&args] <std::size_t ..._idx> (std::index_sequence<_idx...>) { \
|
|
return type { (*std::next(std::begin(args), _idx))... }; \
|
|
}; \
|
|
return creator(std::make_index_sequence<_N>()); \
|
|
}).template operator()<len>(il)
|
|
|
|
#define OPERATOR_EQ_IL(obj, il, len) \
|
|
([&]<std::size_t N, typename ArgType>(std::initializer_list<ArgType> args) { \
|
|
auto creator = [&] <std::size_t ..._idx> (std::index_sequence<_idx...>) { \
|
|
return obj = { (*std::next(std::begin(args), _idx))... }; \
|
|
}; \
|
|
return creator(std::make_index_sequence<N>()); \
|
|
}).operator()<len>(il)
|
|
|
|
template<typename T>
|
|
constexpr typename std::remove_cvref_t<T>&& force_move(T&& obj) {
|
|
return static_cast<std::remove_cvref_t<T>&&>(obj);
|
|
}
|
|
|
|
#endif //CONST_CONTAINER_TEST_HPP_
|