Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 60 additions & 12 deletions include/boost/ut.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,29 @@ fixed_string(const CharT (&str)[N]) -> fixed_string<CharT, N - 1>;

struct none {};

namespace detail {
template <typename T>
struct wrapped_type {
using type = T;
};

template <typename>
struct is_wrapped_type : std::false_type {};

template <typename T>
struct is_wrapped_type<wrapped_type<T>> : std::true_type {};
} // namespace detail

template <typename... Ts>
struct type_list {
using types = std::tuple<Ts...>;
static constexpr auto size = sizeof...(Ts);

template <std::size_t I>
requires(I < size)
using get = detail::wrapped_type<std::tuple_element_t<I, types>>;
};

namespace events {
struct run_begin {
int argc{};
Expand Down Expand Up @@ -576,18 +599,18 @@ struct test {
constexpr auto operator()() const { run_impl(static_cast<Test&&>(run), arg); }

private:
static constexpr auto run_impl(Test test, const none&) { test(); }

template <class T>
static constexpr auto run_impl(T test, const TArg& arg)
-> decltype(test(arg), void()) {
test(arg);
}

template <class T>
static constexpr auto run_impl(T test, const TArg&)
-> decltype(test.template operator()<TArg>(), void()) {
test.template operator()<TArg>();
static constexpr auto run_impl(Test test, const TArg& arg) {
if constexpr (std::same_as<TArg, none>) {
test();
} else if constexpr (detail::is_wrapped_type<TArg>::value) {
test.template operator()<typename TArg::type>();
} else if constexpr (std::invocable<Test, TArg>) {
test(arg);
} else if constexpr (requires { test.template operator()<TArg>(); }) {
test.template operator()<TArg>();
} else {
static_assert(false, "Invalid test function definition");
}
}
};
template <class Test, class TArg>
Expand Down Expand Up @@ -2791,6 +2814,31 @@ template <class F, template <class...> class T, class... Ts>
};
}

template <class F, class... Ts>
[[nodiscard]] constexpr auto operator|(const F& f, type_list<Ts...>) {
using curr_type_list = type_list<Ts...>;

constexpr auto unique_name = []<std::size_t I>(std::string_view name) {
using type = curr_type_list::template get<I>::type;
auto type_name = std::string{reflection::type_name<type>()};
return std::string{name} + " (" + type_name + ")";
};

return [f, unique_name](std::string_view type, std::string_view name) {
const auto handler = [&]<std::size_t... I>(std::index_sequence<I...>) {
(detail::on<F>(events::test<F, typename curr_type_list::template get<I>>{
.type = type,
.name = unique_name.template operator()<I>(name),
.tag = {},
.location = {},
.arg = {},
.run = f}),
...);
};
handler(std::index_sequence_for<Ts...>{});
};
}

namespace terse {
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wunused-comparison"
Expand Down
58 changes: 58 additions & 0 deletions test/ut/ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,25 @@ template <class... Ts>
static auto ut::cfg<ut::override, Ts...> = 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;
Expand Down Expand Up @@ -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<non_instantiable_A, non_instantiable_B,
non_instantiable_C, non_instantiable_D>;

"uninstantiated types"_test = []<class T>() {
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<detail::wrapped_type<non_instantiable_A>>(
test_cfg.run_calls[0].arg));
test_assert("uninstantiated types (non_instantiable_B)"sv ==
test_cfg.run_calls[1].name);
void(std::any_cast<detail::wrapped_type<non_instantiable_B>>(
test_cfg.run_calls[1].arg));
test_assert("uninstantiated types (non_instantiable_C)"sv ==
test_cfg.run_calls[2].name);
void(std::any_cast<detail::wrapped_type<non_instantiable_C>>(
test_cfg.run_calls[2].arg));
test_assert("uninstantiated types (non_instantiable_D)"sv ==
test_cfg.run_calls[3].name);
void(std::any_cast<detail::wrapped_type<non_instantiable_D>>(
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{};

Expand Down