From 70bf9d8223d0760af353404d697e49bc7a788cb0 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:51:42 +0800 Subject: [PATCH] Preserve tags on parameterized tests --- include/boost/ut.hpp | 16 +++++++--- test/ut/ut.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 8869d9fb..5634de7d 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -2284,7 +2284,11 @@ struct test { (!std::convertible_to) constexpr auto operator=(Test _test) { - return _test(type, name); + if constexpr (requires { _test(type, name, tag); }) { + return _test(type, name, tag); + } else { + return _test(type, name); + } } }; @@ -2734,13 +2738,14 @@ template template requires std::ranges::range [[nodiscard]] constexpr auto operator|(const F& f, const T& t) { - return [f, t](std::string_view type, std::string_view name) { + return [f, t](std::string_view type, std::string_view name, + const std::vector& tag = {}) { for (int counter = 1; const auto& arg : t) { detail::on(events::test{ .type = type, .name = std::string{name} + " (" + format_test_parameter(arg, counter) + ")", - .tag = {}, + .tag = tag, .location = {}, .arg = arg, .run = f}); @@ -2763,14 +2768,15 @@ template class T, class... Ts> return ret; }; - return [f, t, unique_name](std::string_view type, std::string_view name) { + return [f, t, unique_name](std::string_view type, std::string_view name, + const std::vector& tag = {}) { int counter = 1; apply( [=, &counter](const auto&... args) { (detail::on(events::test{ .type = type, .name = unique_name.template operator()(name, args, counter), - .tag = {}, + .tag = tag, .location = {}, .arg = args, .run = f}), diff --git a/test/ut/ut.cpp b/test/ut/ut.cpp index 4111ef66..45393eb3 100644 --- a/test/ut/ut.cpp +++ b/test/ut/ut.cpp @@ -104,6 +104,7 @@ struct fake_cfg { struct test_call { std::string_view type{}; std::string name{}; + std::vector tag{}; ut::reflection::source_location location{}; std::any arg{}; }; @@ -120,6 +121,7 @@ struct fake_cfg { if (std::empty(test_filter) or std::string_view{test.name} == test_filter) { run_calls.push_back({.type = test.type, .name = test.name, + .tag = test.tag, .location = test.location, .arg = test.arg}); try { @@ -906,6 +908,76 @@ int main() { // NOLINT(readability-function-size) test_assert(0 == test_cfg.fatal_assertion_calls); } + { + test_cfg = fake_cfg{}; + int run_count{}; + + skip / "skipped range"_test = [&](auto) { ++run_count; } | + std::vector{1, 2}; + skip / "skipped tuple"_test = [&](auto) { ++run_count; } | + std::tuple{42, 'x'}; + skip / "skipped types"_test = [&]() { ++run_count; } | + std::tuple{}; + + test_assert(0 == run_count); + test_assert(std::empty(test_cfg.run_calls)); + test_assert(6 == std::size(test_cfg.skip_calls)); + test_assert("skipped range (1)"sv == test_cfg.skip_calls[0].name); + test_assert("skipped range (2)"sv == test_cfg.skip_calls[1].name); + test_assert("skipped tuple (42, int)"sv == test_cfg.skip_calls[2].name); + test_assert("skipped tuple (x, char)"sv == test_cfg.skip_calls[3].name); + test_assert("skipped types (int)"sv == test_cfg.skip_calls[4].name); + test_assert("skipped types (char)"sv == test_cfg.skip_calls[5].name); + } + + { + test_cfg = fake_cfg{}; + int run_count{}; + + tag("tag1") / tag("tag2") / "tagged range"_test = [&](auto) { + ++run_count; + } | std::array{1, 2}; + tag("tag1") / tag("tag2") / "tagged tuple"_test = [&](auto) { + ++run_count; + } | std::tuple{42, 'x'}; + tag("tag1") / "empty range"_test = [&](auto) { ++run_count; } | + std::vector{}; + tag("tag1") / "empty tuple"_test = [&]() { ++run_count; } | + std::tuple<>{}; + + test_assert(4 == run_count); + test_assert(4 == std::size(test_cfg.run_calls)); + for (const auto& call : test_cfg.run_calls) { + test_assert((std::vector{"tag1", "tag2"} == call.tag)); + } + test_assert(std::empty(test_cfg.skip_calls)); + } + + { + test_cfg = fake_cfg{}; + std::string_view callback_type; + std::string_view callback_name; + + "callback"_test = [&](std::string_view type, std::string_view name) { + callback_type = type; + callback_name = name; + }; + + test_assert("test"sv == callback_type); + test_assert("callback"sv == callback_name); + + struct ref_qualified_callback { + int& calls; + + void operator()(std::string_view, std::string_view) const { ++calls; } + void operator()(std::string_view, std::string_view, + const std::vector&) && {} + }; + int callback_calls{}; + "ref-qualified callback"_test = ref_qualified_callback{callback_calls}; + test_assert(1 == callback_calls); + } + { test_cfg = fake_cfg{};