// // Created by Patrick Maschek on 08/04/2024. // #ifndef CONST_CONTAINER_TEST_HPP_ #define CONST_CONTAINER_TEST_HPP_ #include #include #include #include #include #include #include #define _EXPAND_STR(s) #s #define _EXPAND_MACRO(s) _EXPAND_STR(s) #define _LOCATION " (at " _EXPAND_MACRO(__FILE__) ":" _EXPAND_MACRO(__LINE__) ")" #define ADD_TYPE_HINT(type) template <> constexpr const char* const to_type_hint_str::value = #type #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} #define TEST_FAIL_TYPE(msg, type_hint) ret_val_s { "", ReturnCode::FAILED, msg, to_type_hint_str::value } #define TEST_PASS_TYPE(type_hint) ret_val_s { "", ReturnCode::PASSED, nullptr, to_type_hint_str::value } #define TEST_PASS_MSG_TYPE(msg, type_hint) ret_val_s { "", ReturnCode::PASSED, msg, to_type_hint_str::value } #define TEST_SKIP_TYPE(type_hint) ret_val_s { "", ReturnCode::SKIPPED, nullptr, to_type_hint_str::value } #define ASSERT_TYPE(condition, type) ASSERT_TYPE_MSG(condition, #condition "" _LOCATION, type) #define ASSERT_TYPE_MSG(condition, msg, type) { if (!(condition)) return TEST_FAIL_TYPE(msg, type); } static_assert(true, "") #define ASSERT_TYPE_ALL_EQ(first, last, eq, type) ASSERT_TYPE_MSG(std::all_of(first, last, all_eq_arr_elem_test_func(eq)), "Not all elements in (" #first ", " #last ") equal " #eq "" _LOCATION, type) #define ASSERT_TYPE_C_ARR_EQ(first, last, eq, type) ASSERT_TYPE_MSG(std::equal(first, last, eq, all_eq_arr_arr_test_func()), "Elements in (" #first ", " #last ") and " #eq " differ" _LOCATION, type) #define ASSERT_TYPE_ITER_EQ(first, last, eq, type) ASSERT_TYPE_MSG(std::equal(first, last, (eq).begin(), all_eq_arr_arr_test_func::value_type>()), "Elements in (" #first ", " #last ") and " #eq " differ" _LOCATION, type) #define ASSERT(condition) ASSERT_TYPE(condition, std::nullptr_t) #define ASSERT_MSG(condition, msg) ASSERT_TYPE_MSG(condition, msg, std::nullptr_t) #define ASSERT_ALL_EQ(first, last, eq) ASSERT_TYPE_ALL_EQ(first, last, eq, std::nullptr_t) #define ASSERT_C_ARR_EQ(first, last, eq) ASSERT_TYPE_C_ARR_EQ(first, last, eq, std::nullptr_t) #define ASSERT_ITER_EQ(first, last, eq) ASSERT_TYPE_ITER_EQ(first, last, eq, std::nullptr_t) #define ASSERT_TYPE_THROWS(operation, exception_type, type) if not consteval { try { operation; ASSERT_TYPE_MSG(false, #operation " did not throw " #exception_type _LOCATION, type); } catch (exception_type &e) {} } static_assert(true, "") #define ASSERT_THROWS(operation, exception_type) ASSERT_TYPE_THROWS(operation, exception_type, std::nullptr_t) template constexpr auto all_eq_arr_elem_test_func(T&& eq) { return [=] (auto&& e) constexpr { return e == eq; }; } template constexpr auto all_eq_arr_elem_test_func(const char* eq) { return [=] (auto&& e) constexpr { return std::string_view(e) == eq; }; } template constexpr auto all_eq_arr_arr_test_func() { return [] (auto&& a, auto&& b) constexpr { return a == b; }; } template constexpr auto all_eq_arr_arr_test_func() { return [] (auto&& a, auto&& b) constexpr { return std::string_view(a) == b; }; } 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 static constexpr const char *value = nullptr; }; 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 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 = ""; const char *type_hint = nullptr; }; template struct ret_val { const char * name; std::array 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 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; ret_val_s _c_res; }; template requires (sizeof...(TestDefs) == 0 || (std::derived_from && ...)) class test_suite { public: static constexpr std::size_t TEST_NR = sizeof...(TestDefs); constexpr test_suite(const char *name, std::tuple tests) : _name(name), _tests(tests) {} int run() const { auto test_arr = expand_test_tuple(_tests, std::make_index_sequence()); int num_failed = 0; std::array, 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 << "'"; } 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 << "'"; } 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 _tests; template static constexpr auto expand_test_tuple(const auto &tests, std::index_sequence) { return std::array { std::reference_wrapper( dynamic_cast(std::get(tests)) )... }; } friend class quick_test_def>; }; template struct quick_test_def { Suite current; template constexpr auto operator()(const char *name, Func func, Args... args) { return operator()(name, func, EvalFlag::RUNTIME, std::forward(args)...); } template 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 { new_suite }; } constexpr operator Suite() { return current; } }; template test_suite(quick_test_def>) -> test_suite; 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 constexpr auto _repeat_for_types(auto f) { std::array rets { (f.template operator()())... }; return rets; } template constexpr auto _repeat_for_types_n(auto f) { std::array rets = { [&](std::index_sequence) constexpr { return std::array { f.template operator()()... }; }.template operator()(std::make_index_sequence())... }; // Clion does not accept this as a constant expression // even though it compiles and works as intended //return rets | std::ranges::views::join; std::array arr; auto arr_it = std::begin(arr); for (auto&& rp : rets) { for (ret_val_s& r : rp) { *(arr_it++) = r; } } return arr; } #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(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::initializer_list args) { \ auto creator = [&args] (std::index_sequence<_idx...>) { \ return type { (*std::next(std::begin(args), _idx))... }; \ }; \ return creator(std::make_index_sequence()); \ }).operator()(il) #define OPERATOR_EQ_IL(obj, il, len) \ ([&](std::initializer_list args) { \ auto creator = [&] (std::index_sequence<_idx...>) { \ return obj = { (*std::next(std::begin(args), _idx))... }; \ }; \ return creator(std::make_index_sequence()); \ }).operator()(il) template constexpr typename std::remove_cvref_t&& force_move(T&& obj) { return static_cast&&>(obj); } #endif //CONST_CONTAINER_TEST_HPP_