diff --git a/.clang-tidy b/.clang-tidy index 44e19644..b6df48c2 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -16,8 +16,11 @@ modernize-*,\ -modernize-pass-by-value,\ -modernize-use-trailing-return-type,\ performance-*,\ +-performance-avoid-endl,\ portability-*,\ readability-*,\ +-readability-identifier-length,\ +-readability-redundant-member-init,\ -*implicit-bool-conversion,\ -*magic-numbers,\ -*named-parameter,\ diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e459284c..d4b15a1f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,10 +21,7 @@ jobs: strategy: matrix: toolset: [ClangCl, v143] - std: [17, 20] - exclude: - - toolset: v143 - std: 17 + std: [20] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/windows_shared.yml b/.github/workflows/windows_shared.yml index 5016881d..adced379 100644 --- a/.github/workflows/windows_shared.yml +++ b/.github/workflows/windows_shared.yml @@ -21,10 +21,7 @@ jobs: strategy: matrix: toolset: [ClangCl, v143] - std: [17, 20] - exclude: - - toolset: v143 - std: 17 + std: [20] steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 047b226c..8b2b60d6 100644 --- a/README.md +++ b/README.md @@ -957,6 +957,17 @@ for (auto i : std::vector{1, 2, 3}) { } | std::tuple{true, 42}; ``` +When using the `operator|` syntax instead of a `for` loop, the test name will automatically +be extended to avoid duplicate names. For example, the test name for the `args and types` test +will be `args and types (true, bool)` for the first parameter and `args and types (42, int)` +for the second parameter. For simple built-in types (integral types and floating point numbers), +the test name will contain the parameter values. For other types, the parameters will simply be +enumerated. For example, if we would extend the test above to use +`std::tuple{true, 42, std::complex{0.5, 1}}`, the test name in the third run would be +`args and types (3rd parameter, std::complex)`. If you want to have the actual value of +a non-integral type included in the test name, you can overload the `format_test_parameter` function. +See the [example on parameterized tests](https://github.com/boost-ext/ut/blob/master/example/parameterized.cpp) +for details. ``` All tests passed (14 asserts in 10 tests) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 3eb23838..db1b067e 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,9 +8,6 @@ find_program(BOOST_UT_MEMORYCHECK_COMMAND valgrind) function(example file target) add_executable(boost_ut_${target} ${file}.cpp) - if(${target} MATCHES "_cpp17$") - set_property(TARGET boost_ut_${target} PROPERTY CXX_STANDARD 17) - endif() if(BOOST_UT_ENABLE_MEMCHECK AND BOOST_UT_MEMORYCHECK_COMMAND) ut_add_custom_command_or_test( TARGET @@ -93,14 +90,3 @@ example(terse terse) example(test _test) example(tmp tmp) example(using using) - -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - example(expect expect_cpp17) - example(test test_cpp17) - example(suite suite_cpp17) - example(tag tag_cpp17) - example(terse terse_cpp17) - example(section section_cpp17) - example(should should_cpp17) - example(skip skip_cpp17) -endif() diff --git a/example/parameterized.cpp b/example/parameterized.cpp index d74e08fa..67b9be43 100644 --- a/example/parameterized.cpp +++ b/example/parameterized.cpp @@ -6,12 +6,28 @@ // http://www.boost.org/LICENSE_1_0.txt) // #include +#include +#include #include #include #include #include #include +namespace boost::inline ext::ut { + +namespace { +template +std::string format_test_parameter(const std::complex& arg, + [[maybe_unused]] const int counter) { + std::ostringstream oss; + oss << arg.real() << '+' << arg.imag() << 'i'; + return oss.str(); +} +} // namespace + +} // namespace boost::inline ext::ut + int main() { using namespace boost::ut; @@ -58,4 +74,19 @@ int main() { expect(42_i == static_cast(arg) or arg); expect(type == type or type == type); } | std::tuple{42, true}; + + // Modifying test names when using alternative syntax + // When using the alternative syntax, the test names are extended based on the + // test parameters (to ensure uniqueness). Here, for simple built-in types, + // the parameter value is printed, while other types are simply enumerated. + // Without the `format_test_parameter` overload above, the test names for the + // test below would be: + // "parameterized test names (42, int)" + // "parameterized test names (true, bool)" + // "parameterized test names (3rd parameter, std::complex)" + // However, since the overload for std::complex is available, the third test name becomes: + // "parameterized test names (1.5+2i, std::complex)" + "parameterized test names"_test = []([[maybe_unused]] TArg arg) { + expect(true); + } | std::tuple{42, true, std::complex{1.5, 2.0}}; } diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 878c6475..02e55f80 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -14,10 +14,6 @@ export import std; #define BOOST_UT_EXPORT #endif -#if __has_include() -#include // and, or, not, ... -#endif - #include #if defined(_MSC_VER) #pragma push_macro("min") @@ -84,6 +80,7 @@ export import std; #include #include #include +#include #include #include #include @@ -238,9 +235,15 @@ template return output; } constexpr auto regex_match(const char* str, const char* pattern) -> bool { - if (*pattern == '\0' && *str == '\0') return true; - if (*pattern == '\0' && *str != '\0') return false; - if (*str == '\0' && *pattern != '\0') return false; + if (*pattern == '\0' && *str == '\0') { + return true; + } + if (*pattern == '\0' && *str != '\0') { + return false; + } + if (*str == '\0' && *pattern != '\0') { + return false; + } if (*pattern == '.') { return regex_match(str + 1, pattern + 1); } @@ -280,7 +283,7 @@ class source_location { namespace detail { template [[nodiscard]] constexpr auto get_template_function_name_use_type() - -> const std::string_view { + -> std::string_view { // for over compiler need over macros #if defined(_MSC_VER) && !defined(__clang__) return {&__FUNCSIG__[0], sizeof(__FUNCSIG__)}; @@ -292,7 +295,7 @@ template // decay allows you to highlight a cleaner name template [[nodiscard]] constexpr auto get_template_function_name_use_decay_type() - -> const std::string_view { + -> std::string_view { return get_template_function_name_use_type>(); } @@ -325,7 +328,7 @@ inline constexpr const std::size_t suffix_length = tail_length - need_length; } // namespace detail template -[[nodiscard]] constexpr auto type_name() -> const std::string_view { +[[nodiscard]] constexpr auto type_name() -> std::string_view { const std::string_view raw_type_name = detail::get_template_function_name_use_type(); const std::size_t end = raw_type_name.length() - detail::suffix_length; @@ -336,7 +339,7 @@ template // decay allows you to highlight a cleaner name template -[[nodiscard]] constexpr auto decay_type_name() -> const std::string_view { +[[nodiscard]] constexpr auto decay_type_name() -> std::string_view { const std::string_view raw_type_name = detail::get_template_function_name_use_decay_type(); const std::size_t end = raw_type_name.length() - detail::suffix_length; @@ -549,8 +552,11 @@ struct fixed_string { CharT _data[N + 1] = {}; constexpr explicit(false) fixed_string(const CharT (&str)[N + 1]) noexcept { - if constexpr (N != 0) - for (std::size_t i = 0; i < N; ++i) _data[i] = str[i]; + if constexpr (N != 0) { + for (std::size_t i = 0; i < N; ++i) { + _data[i] = str[i]; + } + } } [[nodiscard]] constexpr std::size_t size() const noexcept { return N; } @@ -689,7 +695,7 @@ struct log { }; template log(TMsg) -> log; -struct fatal_assertion {}; +struct fatal_assertion : std::exception {}; struct exception { const char* msg{}; [[nodiscard]] auto what() const -> const char* { return msg; } @@ -705,7 +711,7 @@ struct fatal_; struct fatal { template - [[nodiscard]] inline auto operator()(const T& t) const { + [[nodiscard]] auto operator()(const T& t) const { return detail::fatal_{t}; } }; @@ -726,22 +732,22 @@ struct cfg { #endif static inline std::string executable_name = "unknown executable"; - static inline std::string query_pattern = ""; // <- done + static inline std::string query_pattern; // <- done static inline bool invert_query_pattern = false; // <- done - static inline std::string query_regex_pattern = ""; // <- done + static inline std::string query_regex_pattern; // <- done static inline bool show_help = false; // <- done static inline bool show_tests = false; // <- done static inline bool list_tags = false; // <- done static inline bool show_successful_tests = false; // <- done - static inline std::string output_filename = ""; + static inline std::string output_filename; static inline std::string use_reporter = "console"; // <- done - static inline std::string suite_name = ""; + static inline std::string suite_name; static inline bool abort_early = false; // <- done static inline std::size_t abort_after_n_failures = std::numeric_limits::max(); // <- done static inline bool show_duration = false; // <- done static inline std::size_t show_min_duration = 0; - static inline std::string input_filename = ""; + static inline std::string input_filename; static inline bool show_test_names = false; // <- done static inline bool show_reporters = false; // <- done static inline std::string sort_order = "decl"; @@ -807,7 +813,7 @@ struct cfg { std::cout << "version: " << BOOST_UT_VERSION << std::endl; } - static inline void parse_arg_with_fallback(int argc, const char* argv[]) { + static void parse_arg_with_fallback(int argc, const char* argv[]) { if (argc > 0 && argv != nullptr) { cfg::largc = argc; cfg::largv = argv; @@ -815,7 +821,7 @@ struct cfg { parse(cfg::largc, cfg::largv); } - static inline void parse(int argc, const char* argv[]) { + static void parse(int argc, const char* argv[]) { const std::size_t n_args = argc > 0 ? static_cast(argc) : 0U; if (n_args > 0 && argv != nullptr) { executable_name = argv[0]; @@ -918,8 +924,8 @@ template template struct type_ : op { template - [[nodiscard]] constexpr auto operator()(const TOther&) const - -> const type_ { + [[nodiscard]] constexpr auto operator()(const TOther&) const // NOLINT(readability-const-return-type) + -> const type_ { return {}; } [[nodiscard]] constexpr auto operator==(type_) -> bool { return true; } @@ -1025,7 +1031,7 @@ struct eq_ : op { if constexpr (type_traits::has_static_member_object_value_v and type_traits::has_static_member_object_value_v) { - return TLhs::value == TRhs::value; + return lhs.value == rhs.value; } else if constexpr (type_traits::has_static_member_object_epsilon_v< TLhs> and type_traits::has_static_member_object_epsilon_v< @@ -1090,7 +1096,7 @@ struct neq_ : op { if constexpr (type_traits::has_static_member_object_value_v and type_traits::has_static_member_object_value_v) { - return TLhs::value != TRhs::value; + return lhs.value != rhs.value; } else if constexpr (type_traits::has_static_member_object_epsilon_v< TLhs> and type_traits::has_static_member_object_epsilon_v< @@ -1125,7 +1131,7 @@ struct gt_ : op { if constexpr (type_traits::has_static_member_object_value_v and type_traits::has_static_member_object_value_v) { - return TLhs::value > TRhs::value; + return lhs.value > rhs.value; } else { return get(lhs_) > get(rhs_); } @@ -1148,7 +1154,7 @@ struct ge_ : op { if constexpr (type_traits::has_static_member_object_value_v and type_traits::has_static_member_object_value_v) { - return TLhs::value >= TRhs::value; + return lhs.value >= rhs.value; } else { return get(lhs_) >= get(rhs_); } @@ -1171,7 +1177,13 @@ struct lt_ : op { if constexpr (type_traits::has_static_member_object_value_v and type_traits::has_static_member_object_value_v) { +#if defined(_MSC_VER) && !defined(__clang__) + // for some reason, accessing the static member via :: does not compile on MSVC, + // and the next line does not compile on clang, so we have to use the ifdef here + return lhs.value < rhs.value; +#else return TLhs::value < TRhs::value; +#endif } else { return get(lhs_) < get(rhs_); } @@ -1195,7 +1207,7 @@ struct le_ : op { if constexpr (type_traits::has_static_member_object_value_v and type_traits::has_static_member_object_value_v) { - return TLhs::value <= TRhs::value; + return lhs.value <= rhs.value; } else { return get(lhs_) <= get(rhs_); } @@ -1357,7 +1369,7 @@ struct colors { }; class printer { - [[nodiscard]] inline auto color(const bool cond) { + [[nodiscard]] auto color(const bool cond) { return cond ? colors_.pass : colors_.fail; } @@ -1560,7 +1572,7 @@ class reporter { ++asserts_.fail; } - auto on(events::fatal_assertion) -> void {} + auto on(const events::fatal_assertion&) -> void {} auto on(events::summary) -> void { if (tests_.fail or asserts_.fail) { @@ -1610,7 +1622,7 @@ class reporter_junit { using clock_ref = std::chrono::high_resolution_clock; using timePoint = std::chrono::time_point; using timeDiff = std::chrono::milliseconds; - enum class ReportType { CONSOLE, JUNIT } report_type_; + enum class ReportType : std::uint8_t { CONSOLE, JUNIT } report_type_; static constexpr ReportType CONSOLE = ReportType::CONSOLE; static constexpr ReportType JUNIT = ReportType::JUNIT; @@ -1750,8 +1762,8 @@ class reporter_junit { if (report_type_ == CONSOLE) { ss_out_ << "\n"; - ss_out_ << std::string(2 * active_test_.size() - 2, ' '); - ss_out_ << "Running test \"" << test_event.name << "\"... "; + ss_out_ << std::string((2 * active_test_.size()) - 2, ' '); + ss_out_ << "Running " << test_event.type << " \"" << test_event.name << "\"... "; } } @@ -1765,7 +1777,7 @@ class reporter_junit { if (detail::cfg::show_successful_tests) { if (!active_scope_->nested_tests->empty()) { ss_out_ << "\n"; - ss_out_ << std::string(2 * active_test_.size() - 2, ' '); + ss_out_ << std::string((2 * active_test_.size()) - 2, ' '); ss_out_ << "Running test \"" << test_event.name << "\" - "; } ss_out_ << color_.pass << "PASSED" << color_.none; @@ -1794,7 +1806,7 @@ class reporter_junit { active_scope_->status = "SKIPPED"; active_scope_->skipped += 1; if (report_type_ == CONSOLE) { - lcout_ << '\n' << std::string(2 * active_test_.size() - 2, ' '); + lcout_ << '\n' << std::string((2 * active_test_.size()) - 2, ' '); lcout_ << "Running \"" << test_event.name << "\"... "; lcout_ << color_.skip << "SKIPPED" << color_.none; } @@ -1820,7 +1832,7 @@ class reporter_junit { active_scope_->report_string += color_.none; } if (report_type_ == CONSOLE) { - lcout_ << std::string(2 * active_test_.size() - 2, ' '); + lcout_ << std::string((2 * active_test_.size()) - 2, ' '); lcout_ << "Running test \"" << active_test_.top() << "\"... "; lcout_ << color_.fail << "FAILED" << color_.none; print_duration(lcout_); @@ -1867,7 +1879,7 @@ class reporter_junit { } } - auto on(events::fatal_assertion) -> void { active_scope_->fails++; } + auto on(const events::fatal_assertion&) -> void { active_scope_->fails++; } auto on(events::summary) -> void { std::cout.flush(); @@ -1931,7 +1943,8 @@ class reporter_junit { void print_junit_summary(std::ostream& stream) { // aggregate results - size_t n_tests = 0, n_fails = 0; + size_t n_tests = 0; + size_t n_fails = 0; double total_time = 0.0; auto suite_time = [](auto const& suite_result) { std::int64_t time_ms = @@ -1972,7 +1985,7 @@ class reporter_junit { stream << ""; } void print_result(std::ostream& stream, const std::string& suite_name, - std::string indent, const test_result& parent) { + const std::string& indent, const test_result& parent) { for (const auto& [name, result] : *parent.nested_tests) { stream << indent; stream << " +inline std::string format_test_parameter([[maybe_unused]] const TArg& arg, const int counter) { + return std::to_string(counter) + get_ordinal_suffix(counter) + " parameter"; +} + +template + requires (std::integral || std::floating_point) && (!std::same_as) +inline std::string format_test_parameter(const F& arg, [[maybe_unused]] const int counter) { + std::ostringstream oss; + oss << arg; + return oss.str(); +} + +inline std::string format_test_parameter(const bool& arg, [[maybe_unused]] const int counter) { + return arg ? "true" : "false"; +} + namespace operators { [[nodiscard]] constexpr auto operator==(std::string_view lhs, std::string_view rhs) { @@ -2728,7 +2774,8 @@ template [[nodiscard]] inline auto operator/(const detail::tag& lhs, const detail::tag& rhs) { - std::vector tag{}; + std::vector tag; + tag.reserve(lhs.name.size() + rhs.name.size()); for (const auto& name : lhs.name) { tag.push_back(name); } @@ -2738,29 +2785,45 @@ template return detail::tag{tag}; } -template > = 0> +template + requires std::ranges::range [[nodiscard]] constexpr auto operator|(const F& f, const T& t) { return [f, t](const auto name) { - for (const auto& arg : t) { - detail::on(events::test{.type = "test", - .name = std::string{name}, - .tag = {}, - .location = {}, - .arg = arg, - .run = f}); + for (int counter = 1; const auto& arg : t) { + detail::on(events::test{ + .type = "test", + .name = + std::string{name} + " (" + + format_test_parameter(arg, counter) + ")", + .tag = {}, + .location = {}, + .arg = arg, + .run = f}); + ++counter; } }; } template class T, class... Ts, type_traits::requires_t>> = 0> -[[nodiscard]] constexpr auto operator|(const F& f, const T& t) { - return [f, t](const auto name) { +[[nodiscard]] constexpr auto operator|(const F& f, const T& t) +{ + constexpr auto unique_name = [](const auto& name, const TArg& arg, int& counter) { + auto ret = std::string{name} + " ("; + if (std::invocable) { + ret += format_test_parameter(arg, counter) + ", "; + } + ret += std::string(reflection::type_name()) + ")"; + ++counter; + return ret; + }; + + return [f, t, unique_name](const auto name) { + int counter = 1; apply( - [f, name](const auto&... args) { + [f, name, unique_name, &counter](const auto&... args) { (detail::on(events::test{.type = "test", - .name = std::string{name}, + .name = unique_name.template operator()(name, args, counter), .tag = {}, .location = {}, .arg = args, diff --git a/test/ut/ut.cpp b/test/ut/ut.cpp index c59b8abd..24a9a37d 100644 --- a/test/ut/ut.cpp +++ b/test/ut/ut.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -17,12 +18,9 @@ #include #include #include +#include #include -#if __has_include() -#include -#endif - namespace ut = boost::ut; namespace test_has_member_object { @@ -45,6 +43,7 @@ struct public_static_member_object_value { }; struct public_member_function_value { + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) int value() const { return 0; } }; @@ -68,6 +67,7 @@ struct public_static_member_object_epsilon { }; struct public_member_function_epsilon { + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) int epsilon() const { return 0; } }; @@ -77,12 +77,13 @@ struct public_static_member_function_epsilon { } // namespace test_has_member_object -constexpr auto to_string = [](const auto expr) { +constexpr auto to_string = [](const auto& expr) { ut::printer printer{{.none = "", .pass = "", .fail = ""}}; printer << std::boolalpha << expr; return printer.str(); }; +namespace { auto test_assert = [](const bool result, const ut::reflection::source_location& sl = ut::reflection::source_location::current()) { @@ -92,6 +93,7 @@ auto test_assert = std::abort(); } }; +} struct fake_cfg { struct assertion_call { @@ -141,7 +143,7 @@ struct fake_cfg { } template - auto on(ut::events::assertion assertion) -> bool { + auto on(const ut::events::assertion& assertion) -> bool { assertion_calls.push_back({.expr = to_string(assertion.expr), .location = assertion.location, .result = assertion.expr}); @@ -171,7 +173,7 @@ template class fake_calculator { public: auto enter(const T& value) -> void { values_.push_back(value); } - auto name(std::string value) -> void { name_ = value; } + auto name(std::string value) -> void { name_ = std::move(value); } auto add() -> void { result_ = std::accumulate(std::cbegin(values_), std::cend(values_), 0); } @@ -205,6 +207,7 @@ struct test_empty { }; struct test_throw { + // NOLINTNEXTLINE(hicpp-exception-baseclass) auto operator()() -> void { throw 42; } }; @@ -289,13 +292,13 @@ struct test_summary_reporter : ut::reporter { auto count_runs(std::size_t& counter) -> void { runs_counter_ = &counter; } - auto on(ut::events::summary) -> void { + auto on(ut::events::summary) const -> void { if (summary_counter_) { ++*summary_counter_; } } - auto on(ut::events::run_begin) -> void { + auto on(ut::events::run_begin) const -> void { if (runs_counter_) { ++*runs_counter_; } @@ -310,16 +313,20 @@ struct test_summary_runner : ut::runner { }; namespace ns { +namespace { template constexpr auto operator""_i() -> int { return sizeof...(Cs); } -static auto f() -> int { return 0_i; } +auto f() -> int { return 0_i; } +} } // namespace ns -static auto f() -> int { +namespace { +auto f() -> int { using namespace ns; return 42_i; } +} struct custom { int i{}; @@ -353,10 +360,25 @@ struct custom_vec : std::vector { } }; +struct custom_non_printable_type { + int value; +}; + +struct custom_printable_type { + int value; +}; + +// NOLINTBEGIN(misc-use-anonymous-namespace) +static std::string format_test_parameter(const custom_printable_type& value, + [[maybe_unused]] const int counter) { + return "custom_printable_type(" + std::to_string(value.value) + ")"; +} + template static auto ut::cfg = fake_cfg{}; +// NOLINTEND(misc-use-anonymous-namespace) -int main() { +int main() { // NOLINT(readability-function-size) { using namespace ut; using namespace std::literals::string_view_literals; @@ -655,8 +677,8 @@ int main() { { std::stringstream out{}; std::stringstream err{}; - auto old_cout = std::cout.rdbuf(out.rdbuf()); - auto old_cerr = std::cerr.rdbuf(err.rdbuf()); + auto* old_cout = std::cout.rdbuf(out.rdbuf()); + auto* old_cerr = std::cerr.rdbuf(err.rdbuf()); auto reporter = test_reporter{}; @@ -849,7 +871,7 @@ int main() { { test_summary_runner run; run.reporter_.count_summaries(summary_count); - test_assert(false == run.run({.report_errors = true})); + test_assert(!run.run({.report_errors = true})); } test_assert(1 == summary_count); } @@ -859,7 +881,7 @@ int main() { { auto run = test_summary_runner{}; run.reporter_.count_runs(run_count); - test_assert(false == run.run({.report_errors = true})); + test_assert(!run.run({.report_errors = true})); } test_assert(1 == run_count); } @@ -1390,6 +1412,7 @@ int main() { } { + // NOLINTBEGIN(hicpp-exception-baseclass) test_cfg = fake_cfg{}; "exceptions"_test = [] { @@ -1437,7 +1460,9 @@ int main() { "should throw"_test = [] { auto f = [](const auto should_throw) { - if (should_throw) throw 42; + if (should_throw) { + throw 42; + } return 42; }; expect(42_i == f(true)); @@ -1462,6 +1487,7 @@ int main() { test_assert("events::exception message"sv == test_cfg.exception_calls[2]); test_assert("Unknown exception"sv == test_cfg.exception_calls[3]); test_assert(6 == std::size(test_cfg.assertion_calls)); + // NOLINTEND(hicpp-exception-baseclass) } #if __has_include() and __has_include() and not defined(EMSCRIPTEN) @@ -1538,11 +1564,11 @@ int main() { } | std::vector{1, 2, 3}; test_assert(3 == std::size(test_cfg.run_calls)); - test_assert("args vector"sv == test_cfg.run_calls[0].name); + test_assert("args vector (1)"sv == test_cfg.run_calls[0].name); test_assert(1 == std::any_cast(test_cfg.run_calls[0].arg)); - test_assert("args vector"sv == test_cfg.run_calls[1].name); + test_assert("args vector (2)"sv == test_cfg.run_calls[1].name); test_assert(2 == std::any_cast(test_cfg.run_calls[1].arg)); - test_assert("args vector"sv == test_cfg.run_calls[2].name); + test_assert("args vector (3)"sv == test_cfg.run_calls[2].name); test_assert(3 == std::any_cast(test_cfg.run_calls[2].arg)); test_assert(3 == std::size(test_cfg.assertion_calls)); test_assert(test_cfg.assertion_calls[0].result); @@ -1561,9 +1587,9 @@ int main() { } | std::array{99, 11}; test_assert(2 == std::size(test_cfg.run_calls)); - test_assert("args array"sv == test_cfg.run_calls[0].name); + test_assert("args array (99)"sv == test_cfg.run_calls[0].name); test_assert(99 == std::any_cast(test_cfg.run_calls[0].arg)); - test_assert("args array"sv == test_cfg.run_calls[1].name); + test_assert("args array (11)"sv == test_cfg.run_calls[1].name); test_assert(11 == std::any_cast(test_cfg.run_calls[1].arg)); test_assert(2 == std::size(test_cfg.assertion_calls)); test_assert(test_cfg.assertion_calls[0].result); @@ -1580,11 +1606,11 @@ int main() { } | std::string{"str"}; test_assert(3 == std::size(test_cfg.run_calls)); - test_assert("args string"sv == test_cfg.run_calls[0].name); + test_assert("args string (s)"sv == test_cfg.run_calls[0].name); test_assert('s' == std::any_cast(test_cfg.run_calls[0].arg)); - test_assert("args string"sv == test_cfg.run_calls[1].name); + test_assert("args string (t)"sv == test_cfg.run_calls[1].name); test_assert('t' == std::any_cast(test_cfg.run_calls[1].arg)); - test_assert("args string"sv == test_cfg.run_calls[2].name); + test_assert("args string (r)"sv == test_cfg.run_calls[2].name); test_assert('r' == std::any_cast(test_cfg.run_calls[2].arg)); test_assert(3 == std::size(test_cfg.assertion_calls)); test_assert(test_cfg.assertion_calls[0].result); @@ -1608,11 +1634,11 @@ int main() { } | std::map{{'a', 1}, {'b', 2}}; test_assert(2 == std::size(test_cfg.run_calls)); - test_assert("args map"sv == test_cfg.run_calls[0].name); + test_assert("args map (1st parameter)"sv == test_cfg.run_calls[0].name); test_assert( std::pair{'a', 1} == std::any_cast>(test_cfg.run_calls[0].arg)); - test_assert("args map"sv == test_cfg.run_calls[1].name); + test_assert("args map (2nd parameter)"sv == test_cfg.run_calls[1].name); test_assert( std::pair{'b', 2} == std::any_cast>(test_cfg.run_calls[1].arg)); @@ -1627,7 +1653,6 @@ int main() { test_assert("2 > 0" == test_cfg.assertion_calls[3].expr); } -#if not defined(__APPLE__) { test_cfg = fake_cfg{}; @@ -1638,11 +1663,12 @@ int main() { } | std::tuple{}; test_assert(3 == std::size(test_cfg.run_calls)); - test_assert("types"sv == test_cfg.run_calls[0].name); + test_assert("types (bool)"sv == test_cfg.run_calls[0].name); void(std::any_cast(test_cfg.run_calls[0].arg)); - test_assert("types"sv == test_cfg.run_calls[1].name); + test_assert("types (int)"sv == test_cfg.run_calls[1].name); void(std::any_cast(test_cfg.run_calls[1].arg)); - test_assert("types"sv == test_cfg.run_calls[2].name); + // the pointer is printed differently on different platforms, so we only check the prefix + test_assert(test_cfg.run_calls[2].name.starts_with("types (void")); void(std::any_cast(test_cfg.run_calls[2].arg)); test_assert(3 == std::size(test_cfg.assertion_calls)); test_assert("(true or void == bool)" == test_cfg.assertion_calls[0].expr); @@ -1663,9 +1689,9 @@ int main() { } | std::tuple{42, 'x'}; test_assert(2 == std::size(test_cfg.run_calls)); - test_assert("args and types"sv == test_cfg.run_calls[0].name); + test_assert("args and types (42, int)"sv == test_cfg.run_calls[0].name); test_assert(42 == std::any_cast(test_cfg.run_calls[0].arg)); - test_assert("args and types"sv == test_cfg.run_calls[1].name); + test_assert("args and types (x, char)"sv == test_cfg.run_calls[1].name); test_assert('x' == std::any_cast(test_cfg.run_calls[1].arg)); test_assert(4 == std::size(test_cfg.assertion_calls)); test_assert(test_cfg.assertion_calls[0].result); @@ -1679,7 +1705,31 @@ int main() { test_assert("(char == int or char == char)" == test_cfg.assertion_calls[3].expr); } -#endif + + { + test_cfg = fake_cfg{}; + + "parameterized test names"_test = [](const TArg& /*arg*/) { + expect(true); + } | std::tuple { + custom_non_printable_type{0}, std::string{"str"}, "str", "str"sv, + 42.5, false, std::complex{1.5, 2.0}, custom_printable_type{42}}; + + test_assert(8 == std::size(test_cfg.run_calls)); + test_assert(test_cfg.run_calls[0].name.starts_with("parameterized test names (1st parameter, ")); + test_assert(test_cfg.run_calls[0].name.find("custom_non_printable_type") != std::string::npos); + test_assert(test_cfg.run_calls[1].name.starts_with("parameterized test names (2nd parameter, ")); + test_assert(test_cfg.run_calls[1].name.find("string") != std::string::npos); + test_assert(test_cfg.run_calls[2].name.starts_with("parameterized test names (3rd parameter, const char")); + test_assert(test_cfg.run_calls[2].name.find('*') != std::string::npos); + test_assert(test_cfg.run_calls[3].name.starts_with("parameterized test names (4th parameter, ")); + test_assert(test_cfg.run_calls[3].name.find("string_view") != std::string::npos); + test_assert(test_cfg.run_calls[4].name == "parameterized test names (42.5, double)"sv); + test_assert(test_cfg.run_calls[5].name == "parameterized test names (false, bool)"sv); + test_assert(test_cfg.run_calls[6].name.starts_with("parameterized test names (7th parameter, ")); + test_assert(test_cfg.run_calls[6].name.find("std::complex") != std::string::npos); + test_assert(test_cfg.run_calls[7].name.starts_with("parameterized test names (custom_printable_type(42), ")); + } { test_cfg = fake_cfg{}; @@ -1891,7 +1941,7 @@ int main() { calc.enter(value); }; steps.when("I name it '{value}'") = [&](std::string value) { - calc.name(value); + calc.name(std::move(value)); }; steps.when("I press add") = [&] { calc.add(); }; steps.when("I press sub") = [&] { calc.sub(); };