diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 991f4fd5..18dc1cca 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -543,6 +543,29 @@ fixed_string(const CharT (&str)[N]) -> fixed_string; struct none {}; +namespace detail { +template +struct wrapped_type { + using type = T; +}; + +template +struct is_wrapped_type : std::false_type {}; + +template +struct is_wrapped_type> : std::true_type {}; +} // namespace detail + +template +struct type_list { + using types = std::tuple; + static constexpr auto size = sizeof...(Ts); + + template + requires(I < size) + using get = detail::wrapped_type>; +}; + namespace events { struct run_begin { int argc{}; @@ -576,18 +599,18 @@ struct test { constexpr auto operator()() const { run_impl(static_cast(run), arg); } private: - static constexpr auto run_impl(Test test, const none&) { test(); } - - template - static constexpr auto run_impl(T test, const TArg& arg) - -> decltype(test(arg), void()) { - test(arg); - } - - template - static constexpr auto run_impl(T test, const TArg&) - -> decltype(test.template operator()(), void()) { - test.template operator()(); + static constexpr auto run_impl(Test test, const TArg& arg) { + if constexpr (std::same_as) { + test(); + } else if constexpr (detail::is_wrapped_type::value) { + test.template operator()(); + } else if constexpr (std::invocable) { + test(arg); + } else if constexpr (requires { test.template operator()(); }) { + test.template operator()(); + } else { + static_assert(false, "Invalid test function definition"); + } } }; template @@ -2791,6 +2814,31 @@ template class T, class... Ts> }; } +template +[[nodiscard]] constexpr auto operator|(const F& f, type_list) { + using curr_type_list = type_list; + + constexpr auto unique_name = [](std::string_view name) { + using type = curr_type_list::template get::type; + auto type_name = std::string{reflection::type_name()}; + return std::string{name} + " (" + type_name + ")"; + }; + + return [f, unique_name](std::string_view type, std::string_view name) { + const auto handler = [&](std::index_sequence) { + (detail::on(events::test>{ + .type = type, + .name = unique_name.template operator()(name), + .tag = {}, + .location = {}, + .arg = {}, + .run = f}), + ...); + }; + handler(std::index_sequence_for{}); + }; +} + namespace terse { #if defined(__clang__) #pragma clang diagnostic ignored "-Wunused-comparison" diff --git a/test/ut/ut.cpp b/test/ut/ut.cpp index a1546630..52c70e37 100644 --- a/test/ut/ut.cpp +++ b/test/ut/ut.cpp @@ -377,6 +377,25 @@ template static auto ut::cfg = fake_cfg{}; // NOLINTEND(misc-use-anonymous-namespace) +// used for type_list check +#define DEFINE_NON_INSTANTIABLE_TYPE(Name, Value) \ + struct Name { \ + Name() = delete; \ + Name(Name&&) = delete; \ + Name(const Name&) = delete; \ + Name& operator=(Name&&) = delete; \ + Name& operator=(const Name&) = delete; \ + \ + static bool value() { return Value; } \ + } + +DEFINE_NON_INSTANTIABLE_TYPE(non_instantiable_A, true); +DEFINE_NON_INSTANTIABLE_TYPE(non_instantiable_B, true); +DEFINE_NON_INSTANTIABLE_TYPE(non_instantiable_C, false); +DEFINE_NON_INSTANTIABLE_TYPE(non_instantiable_D, true); + +#undef DEFINE_NON_INSTANTIABLE_TYPE + int main() { // NOLINT(readability-function-size) { using namespace ut; @@ -1656,6 +1675,45 @@ int main() { // NOLINT(readability-function-size) test_assert(test_cfg.assertion_calls[2].result); } + { + test_cfg = fake_cfg{}; + + using types = type_list; + + "uninstantiated types"_test = []() { + expect(T::value()) << "all static member function should return true"; + } | types{}; + + test_assert(4 == std::size(test_cfg.run_calls)); + test_assert("uninstantiated types (non_instantiable_A)"sv == + test_cfg.run_calls[0].name); + void(std::any_cast>( + test_cfg.run_calls[0].arg)); + test_assert("uninstantiated types (non_instantiable_B)"sv == + test_cfg.run_calls[1].name); + void(std::any_cast>( + test_cfg.run_calls[1].arg)); + test_assert("uninstantiated types (non_instantiable_C)"sv == + test_cfg.run_calls[2].name); + void(std::any_cast>( + test_cfg.run_calls[2].arg)); + test_assert("uninstantiated types (non_instantiable_D)"sv == + test_cfg.run_calls[3].name); + void(std::any_cast>( + test_cfg.run_calls[3].arg)); + + test_assert(4 == std::size(test_cfg.assertion_calls)); + test_assert("true"sv == test_cfg.assertion_calls[0].expr); + test_assert(test_cfg.assertion_calls[0].result); + test_assert("true"sv == test_cfg.assertion_calls[1].expr); + test_assert(test_cfg.assertion_calls[1].result); + test_assert("false"sv == test_cfg.assertion_calls[2].expr); + test_assert(not test_cfg.assertion_calls[2].result); + test_assert("true"sv == test_cfg.assertion_calls[3].expr); + test_assert(test_cfg.assertion_calls[3].result); + } + { test_cfg = fake_cfg{};