From d22e0516db1023181fdf7071dafdb576ea39a7b2 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:21:59 +0800 Subject: [PATCH 01/10] remove test_result.passed to improve the efficiency --- include/boost/ut.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 3db532d3..641ec633 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1605,7 +1605,6 @@ class reporter_junit { std::size_t n_tests = 0LU; std::size_t fail_tests = 0LU; std::size_t assertions = 0LU; - std::size_t passed = 0LU; std::size_t skipped = 0LU; std::size_t fails = 0LU; std::string report_string{}; @@ -1664,7 +1663,6 @@ class reporter_junit { active_scope_->n_tests += old_scope->n_tests; active_scope_->fail_tests += old_scope->fail_tests; active_scope_->assertions += old_scope->assertions; - active_scope_->passed += old_scope->passed; active_scope_->skipped += old_scope->skipped; active_scope_->fails += old_scope->fails; return; @@ -1819,7 +1817,6 @@ class reporter_junit { template auto on(events::assertion_pass) -> void { active_scope_->assertions++; - active_scope_->passed++; } template @@ -1901,7 +1898,7 @@ class reporter_junit { << (suite_result.fail_tests > 0 ? color_.fail : color_.none) << suite_result.fail_tests << " failed" << color_.none << '\n' << "asserts: " << (suite_result.assertions) << " | " - << suite_result.passed << " passed" + << (suite_result.assertions - suite_result.fails) << " passed" << " | " << color_.fail << suite_result.fails << " failed" << color_.none; //std::cerr << std::endl; From 598e9485201f380f726daa426dff542f38da4bc0 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:06:48 +0800 Subject: [PATCH 02/10] introduce enum class StatusType to reduce string copy --- include/boost/ut.hpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 641ec633..9d529f95 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1593,13 +1593,19 @@ class reporter_junit { enum class ReportType : std::uint8_t { CONSOLE, JUNIT } report_type_; static constexpr ReportType CONSOLE = ReportType::CONSOLE; static constexpr ReportType JUNIT = ReportType::JUNIT; - + enum class StatusType : std::uint8_t { PASSED, FAILED, SKIPPED, UNDEFINED }; + static constexpr StatusType PASSED = StatusType::PASSED; + static constexpr StatusType FAILED = StatusType::FAILED; + static constexpr StatusType SKIPPED = StatusType::SKIPPED; + static constexpr StatusType UNDEFINED = StatusType::UNDEFINED; + inline static const std::string statusStrings[] = { "PASSED", "FAILED", "SKIPPED", "UNDEFINED" }; + struct test_result { test_result* parent = nullptr; std::string class_name; std::string suite_name; std::string test_name; - std::string status = "STARTED"; + StatusType status = UNDEFINED; timePoint run_start = clock_ref::now(); timePoint run_stop = clock_ref::now(); std::size_t n_tests = 0LU; @@ -1647,11 +1653,10 @@ class reporter_junit { void pop_scope(std::string_view test_name_sv) { const std::string test_name(test_name_sv); active_scope_->run_stop = clock_ref::now(); - if (active_scope_->skipped) { - active_scope_->status = "SKIPPED"; - } else { - active_scope_->status = active_scope_->fails > 0 ? "FAILED" : "PASSED"; - } + active_scope_->status = + active_scope_->skipped + ? SKIPPED + : (active_scope_->fails > 0 ? FAILED : PASSED); if (active_test_.top() == test_name) { active_test_.pop(); auto old_scope = active_scope_; @@ -1773,7 +1778,7 @@ class reporter_junit { ss_out_.clear(); if (!active_scope_->nested_tests->contains(std::string(test_event.name))) { check_for_scope(test_event.name); - active_scope_->status = "SKIPPED"; + active_scope_->status = SKIPPED; active_scope_->skipped += 1; if (report_type_ == CONSOLE) { lcout_ << '\n' << std::string((2 * active_test_.size()) - 2, ' '); @@ -1973,7 +1978,7 @@ class reporter_junit { result.run_stop - result.run_start) .count(); stream << " time=\"" << (static_cast(time_ms) / 1000.0) << "\""; - stream << " status=\"" << result.status << '\"'; + stream << " status=\"" << statusStrings[(int)result.status] << '\"'; if (result.report_string.empty() && result.nested_tests->empty()) { stream << " />\n"; } else if (!result.nested_tests->empty()) { From 0f7fe925be2961c5ae1c98c8b680f0b5e56534c2 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:20:56 +0800 Subject: [PATCH 03/10] remove test_result.class_name and test_result.suite_name --- include/boost/ut.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 9d529f95..8f7c32c6 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1602,8 +1602,6 @@ class reporter_junit { struct test_result { test_result* parent = nullptr; - std::string class_name; - std::string suite_name; std::string test_name; StatusType status = UNDEFINED; timePoint run_start = clock_ref::now(); @@ -1637,8 +1635,7 @@ class reporter_junit { const std::string str_name(test_name); active_test_.push(str_name); const auto [iter, inserted] = active_scope_->nested_tests->try_emplace( - str_name, test_result{active_scope_, detail::cfg::executable_name, - active_suite_, str_name}); + str_name, test_result{active_scope_, str_name}); active_scope_ = &active_scope_->nested_tests->at(str_name); if (active_test_.size() == 1) { reset_printer(); @@ -1967,7 +1964,7 @@ class reporter_junit { const std::string& indent, const test_result& parent) { for (const auto& [name, result] : *parent.nested_tests) { stream << indent; - stream << " Date: Mon, 12 Jan 2026 13:17:48 +0800 Subject: [PATCH 04/10] improve test_result and the process to get summary --- include/boost/ut.hpp | 303 ++++++++++++++++++++----------------------- 1 file changed, 138 insertions(+), 165 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 8f7c32c6..ead795f5 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1585,8 +1585,6 @@ class reporter { template class reporter_junit { - template - using map = std::unordered_map; using clock_ref = std::chrono::high_resolution_clock; using timePoint = std::chrono::time_point; using timeDiff = std::chrono::milliseconds; @@ -1599,10 +1597,10 @@ class reporter_junit { static constexpr StatusType SKIPPED = StatusType::SKIPPED; static constexpr StatusType UNDEFINED = StatusType::UNDEFINED; inline static const std::string statusStrings[] = { "PASSED", "FAILED", "SKIPPED", "UNDEFINED" }; - + struct test_result { - test_result* parent = nullptr; std::string test_name; + test_result* parent = nullptr; StatusType status = UNDEFINED; timePoint run_start = clock_ref::now(); timePoint run_stop = clock_ref::now(); @@ -1612,14 +1610,23 @@ class reporter_junit { std::size_t skipped = 0LU; std::size_t fails = 0LU; std::string report_string{}; - std::unique_ptr> nested_tests = - std::make_unique>(); + std::vector> children; + + explicit test_result(std::string name, test_result* p = nullptr) + : test_name(std::move(name)), parent(p) {} + test_result(const test_result&) = delete; + test_result& operator=(const test_result&) = delete; + test_result(test_result&&) noexcept = default; + test_result& operator=(test_result&&) noexcept = default; + test_result& add_child(std::string name) { + children.emplace_back(std::make_unique(std::move(name), this)); + return *children.back(); + } }; + inline static int layer_ = 0; colors color_{}; - map results_; - std::string active_suite_{"global"}; - test_result* active_scope_ = &results_[active_suite_]; - std::stack active_test_{}; + std::vector> suites_results_; + test_result* current_node_ = nullptr; std::streambuf* cout_save = std::cout.rdbuf(); std::ostream lcout_; @@ -1631,59 +1638,44 @@ class reporter_junit { ss_out_.clear(); } - void check_for_scope(std::string_view test_name) { - const std::string str_name(test_name); - active_test_.push(str_name); - const auto [iter, inserted] = active_scope_->nested_tests->try_emplace( - str_name, test_result{active_scope_, str_name}); - active_scope_ = &active_scope_->nested_tests->at(str_name); - if (active_test_.size() == 1) { + void add_node(std::string node_name) { + if (current_node_->parent == nullptr) { reset_printer(); } - active_scope_->run_start = clock_ref::now(); - if (!inserted) { - std::cout << "WARNING test '" << str_name << "' for test suite '" - << active_suite_ << "' already present\n"; - } - } - - void pop_scope(std::string_view test_name_sv) { - const std::string test_name(test_name_sv); - active_scope_->run_stop = clock_ref::now(); - active_scope_->status = - active_scope_->skipped - ? SKIPPED - : (active_scope_->fails > 0 ? FAILED : PASSED); - if (active_test_.top() == test_name) { - active_test_.pop(); - auto old_scope = active_scope_; - if (active_scope_->parent != nullptr) { - active_scope_ = active_scope_->parent; - } else { - active_scope_ = &results_[std::string{"global"}]; - } - active_scope_->n_tests += old_scope->n_tests; - active_scope_->fail_tests += old_scope->fail_tests; - active_scope_->assertions += old_scope->assertions; - active_scope_->skipped += old_scope->skipped; - active_scope_->fails += old_scope->fails; - return; + layer_++; + current_node_ = ¤t_node_->add_child(node_name); + } + + void count_result() { + current_node_->run_stop = clock_ref::now(); + current_node_->status = + current_node_->skipped + ? SKIPPED : (current_node_->fails > 0 ? FAILED : PASSED); + auto parent = current_node_->parent; + if (parent != nullptr) { + parent->n_tests += current_node_->n_tests; + parent->fail_tests += current_node_->fail_tests; + parent->assertions += current_node_->assertions; + parent->skipped += current_node_->skipped; + parent->fails += current_node_->fails; } - std::stringstream ss("runner returned from test w/o signaling: "); - ss << "not popping because '" << active_test_.top() << "' differs from '" - << test_name << "'" << std::endl; -#if defined(__cpp_exceptions) - throw std::logic_error(ss.str()); -#else - std::abort(); -#endif + current_node_ = parent; + layer_--; + } + + inline std::string getLeadingSpace() { + return layer_ > 0 ? std::format("\n{}", std::string(2 * (layer_ - 1), ' ')) + : "\n"; } public: constexpr auto operator=(TPrinter printer) { printer_ = static_cast(printer); } - reporter_junit() : lcout_(std::cout.rdbuf()) {} + reporter_junit() : lcout_(std::cout.rdbuf()) { + suites_results_.emplace_back(std::make_unique("global")); + current_node_ = suites_results_.front().get(); + } ~reporter_junit() { std::cout.rdbuf(cout_save); } auto on(events::run_begin run) { @@ -1709,58 +1701,45 @@ class reporter_junit { } auto on(events::suite_begin suite) -> void { - while (active_test_.size() > 0) { - pop_scope(active_test_.top()); - } - active_suite_ = suite.name; - active_scope_ = &results_[active_suite_]; + suites_results_.emplace_back(std::make_unique((std::string)suite.name)); + current_node_ = suites_results_.back().get(); } auto on(events::suite_end) -> void { - while (active_test_.size() > 0) { - pop_scope(active_test_.top()); - } - active_suite_ = "global"; - active_scope_ = &results_[active_suite_]; + current_node_ = suites_results_.front().get(); } auto on(events::test_begin test_event) -> void { // starts outermost test - check_for_scope(test_event.name); - + add_node((std::string)test_event.name); if (report_type_ == CONSOLE) { - ss_out_ << "\n"; - ss_out_ << std::string((2 * active_test_.size()) - 2, ' '); + ss_out_ << getLeadingSpace(); ss_out_ << "Running " << test_event.type << " \"" << test_event.name << "\"... "; } } auto on(events::test_end test_event) -> void { - active_scope_->report_string += ss_out_.str(); - if (active_scope_->fails > 0) { - if (report_type_ == CONSOLE) { + current_node_->report_string += ss_out_.str(); + if (report_type_ == CONSOLE) { + if (current_node_->fails > 0) { lcout_ << ss_out_.str(); } - } else { - if (report_type_ == CONSOLE) { - 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_ << "Running test \"" << test_event.name << "\" - "; - } - ss_out_ << color_.pass << "PASSED" << color_.none; - print_duration(ss_out_); - lcout_ << ss_out_.str(); + else if (detail::cfg::show_successful_tests) { + if (!current_node_->children.empty()) { + ss_out_ << getLeadingSpace(); + ss_out_ << "Running test \"" << test_event.name << "\" ... "; } + ss_out_ << color_.pass << "PASSED" << color_.none; + print_duration(ss_out_); + lcout_ << ss_out_.str(); } } reset_printer(); - active_scope_->n_tests = 1LU; - if (active_scope_->fails > 0 || active_scope_->fail_tests > 0) { - active_scope_->fail_tests = 1LU; + current_node_->n_tests = 1LU; + if (current_node_->fails > 0 || current_node_->fail_tests > 0) { + current_node_->fail_tests = 1LU; } - pop_scope(test_event.name); + count_result(); } auto on(events::test_run test_event) -> void { // starts nested test @@ -1773,18 +1752,16 @@ class reporter_junit { auto on(events::test_skip test_event) -> void { ss_out_.clear(); - if (!active_scope_->nested_tests->contains(std::string(test_event.name))) { - check_for_scope(test_event.name); - active_scope_->status = SKIPPED; - active_scope_->skipped += 1; - if (report_type_ == CONSOLE) { - lcout_ << '\n' << std::string((2 * active_test_.size()) - 2, ' '); - lcout_ << "Running \"" << test_event.name << "\"... "; - lcout_ << color_.skip << "SKIPPED" << color_.none; - } - reset_printer(); - pop_scope(test_event.name); + add_node((std::string)test_event.name); + current_node_->status = SKIPPED; + current_node_->skipped += 1; + if (report_type_ == CONSOLE) { + lcout_ << getLeadingSpace(); + lcout_ << "Running \"" << test_event.name << "\"... "; + lcout_ << color_.skip << "SKIPPED" << color_.none; } + reset_printer(); + count_result(); } template @@ -1793,32 +1770,30 @@ class reporter_junit { } auto on(events::exception exception) -> void { - active_scope_->fails++; - if (!active_test_.empty()) { - active_scope_->report_string += color_.fail; - active_scope_->report_string += "Unexpected exception with message:\n"; - active_scope_->report_string += exception.what(); - active_scope_->report_string += color_.none; - } + current_node_->fails++; + current_node_->report_string += color_.fail; + current_node_->report_string += "Unexpected exception with message:\n"; + current_node_->report_string += exception.what(); + current_node_->report_string += color_.none; if (report_type_ == CONSOLE) { - lcout_ << std::string((2 * active_test_.size()) - 2, ' '); - lcout_ << "Running test \"" << active_test_.top() << "\"... "; + lcout_ << getLeadingSpace(); + lcout_ << "Running test \"" << current_node_->test_name << "\"... "; lcout_ << color_.fail << "FAILED" << color_.none; print_duration(lcout_); lcout_ << '\n'; - lcout_ << active_scope_->report_string << '\n'; + lcout_ << current_node_->report_string << '\n'; } if (detail::cfg::abort_early || - active_scope_->fails >= detail::cfg::abort_after_n_failures) { - std::cerr << "early abort for test : " << active_test_.top() << "after "; - std::cerr << active_scope_->fails << " failures total." << std::endl; + current_node_->fails >= detail::cfg::abort_after_n_failures) { + std::cerr << "early abort for test : " << current_node_->test_name << "after "; + std::cerr << current_node_->fails << " failures total." << std::endl; std::exit(-1); } } template auto on(events::assertion_pass) -> void { - active_scope_->assertions++; + current_node_->assertions++; } template @@ -1826,10 +1801,7 @@ class reporter_junit { TPrinter ss{}; ss << ss_out_.str(); if (report_type_ == CONSOLE) { - ss << "\n"; - if (active_test_.size()) { - ss << std::string((2 * active_test_.size()) - 2, ' '); - } + ss << getLeadingSpace(); ss << color_.fail << "FAILED " << color_.none; print_duration(ss); } @@ -1838,22 +1810,22 @@ class reporter_junit { ss << color_.fail << " - test condition: "; ss << '[' << std::boolalpha << assertion.expr; ss << color_.fail << ']' << color_.none; - active_scope_->report_string += ss.str(); - active_scope_->fails++; - active_scope_->assertions++; + current_node_->report_string += ss.str(); + current_node_->fails++; + current_node_->assertions++; reset_printer(); if (report_type_ == CONSOLE) { lcout_ << ss.str(); } if (detail::cfg::abort_early || - active_scope_->fails >= detail::cfg::abort_after_n_failures) { - std::cerr << "early abort for test : " << active_test_.top() << "after "; - std::cerr << active_scope_->fails << " failures total." << std::endl; + current_node_->fails >= detail::cfg::abort_after_n_failures) { + std::cerr << "early abort for test : " << current_node_->test_name << "after "; + std::cerr << current_node_->fails << " failures total." << std::endl; std::exit(-1); } } - auto on(const events::fatal_assertion&) -> void { /*active_scope_->fails++;*/ } + auto on(const events::fatal_assertion&) -> void {} auto on(events::summary) -> void { std::cout.flush(); @@ -1879,7 +1851,7 @@ class reporter_junit { if (detail::cfg::show_duration) { std::int64_t time_ms = std::chrono::duration_cast( - active_scope_->run_stop - active_scope_->run_start) + current_node_->run_stop - current_node_->run_start) .count(); // rounded to nearest ms double time_s = static_cast(time_ms) / 1000.0; @@ -1889,28 +1861,27 @@ class reporter_junit { void print_console_summary(std::ostream& out_stream, std::ostream& err_stream) { - - for (const auto& [suite_name, suite_result] : results_) { - if (suite_result.fails) { + for (const auto& suite_result : suites_results_) { + if (suite_result->fails) { err_stream << "\n========================================================" "=======================\n" - << "Suite " << suite_name << '\n' - << "tests: " << (suite_result.n_tests) << " | " - << (suite_result.fail_tests > 0 ? color_.fail : color_.none) - << suite_result.fail_tests << " failed" << color_.none << '\n' - << "asserts: " << (suite_result.assertions) << " | " - << (suite_result.assertions - suite_result.fails) << " passed" - << " | " << color_.fail << suite_result.fails << " failed" + << "Suite " << suite_result->test_name << '\n' + << "tests: " << (suite_result->n_tests) << " | " + << (suite_result->fail_tests > 0 ? color_.fail : color_.none) + << suite_result->fail_tests << " failed" << color_.none << '\n' + << "asserts: " << (suite_result->assertions) << " | " + << (suite_result->assertions - suite_result->fails) << " passed" + << " | " << color_.fail << suite_result->fails << " failed" << color_.none; - //std::cerr << std::endl; - } else if (suite_result.assertions || suite_result.n_tests || suite_result.skipped) { - out_stream << color_.pass << "\nSuite '" << suite_name + } else if (suite_result->assertions || suite_result->n_tests || + suite_result->skipped) { + out_stream << color_.pass << "\nSuite '" << suite_result->test_name << "': all tests passed" << color_.none << " (" - << suite_result.assertions << " asserts in " - << suite_result.n_tests << " tests)"; - if (suite_result.skipped) { - std::cout << "; " << suite_result.skipped << " tests skipped"; + << suite_result->assertions << " asserts in " + << suite_result->n_tests << " tests)"; + if (suite_result->skipped) { + std::cout << "; " << suite_result->skipped << " tests skipped"; } std::cout.flush(); } @@ -1925,13 +1896,13 @@ class reporter_junit { auto suite_time = [](auto const& suite_result) { std::int64_t time_ms = std::chrono::duration_cast( - suite_result.run_stop - suite_result.run_start) + suite_result->run_stop - suite_result->run_start) .count(); return static_cast(time_ms) / 1000.0; }; - for (const auto& [suite_name, suite_result] : results_) { - n_tests += suite_result.assertions; - n_fails += suite_result.fails; + for (const auto& suite_result : suites_results_) { + n_tests += suite_result->assertions; + n_fails += suite_result->fails; total_time += suite_time(suite_result); } @@ -1944,48 +1915,50 @@ class reporter_junit { stream << " time=\"" << total_time << '\"'; stream << ">\n"; - for (const auto& [suite_name, suite_result] : results_) { + for (const auto& suite_result : suites_results_) { stream << "test_name << '\"'; + stream << " tests=\"" << suite_result->assertions << '\"'; + stream << " errors=\"" << suite_result->fails << '\"'; + stream << " failures=\"" << suite_result->fails << '\"'; + stream << " skipped=\"" << suite_result->skipped << '\"'; stream << " time=\"" << suite_time(suite_result) << '\"'; stream << " version=\"" << BOOST_UT_VERSION << "\">\n"; - print_result(stream, suite_name, " ", suite_result); + print_result(stream, suite_result->test_name, " ", *suite_result); stream << "\n"; stream.flush(); } stream << ""; } void print_result(std::ostream& stream, const std::string& suite_name, - const std::string& indent, const test_result& parent) { - for (const auto& [name, result] : *parent.nested_tests) { + const std::string& indent, const test_result& test_node) { + for (const auto& child_result : test_node.children) { stream << indent; stream << "test_name << '\"'; + stream << " tests=\"" << child_result->assertions << '\"'; + stream << " errors=\"" << child_result->fails << '\"'; + stream << " failures=\"" << child_result->fails << '\"'; + stream << " skipped=\"" << child_result->skipped << '\"'; std::int64_t time_ms = std::chrono::duration_cast( - result.run_stop - result.run_start) + child_result->run_stop - child_result->run_start) .count(); stream << " time=\"" << (static_cast(time_ms) / 1000.0) << "\""; - stream << " status=\"" << statusStrings[(int)result.status] << '\"'; - if (result.report_string.empty() && result.nested_tests->empty()) { + stream << " status=\"" << statusStrings[(int)child_result->status] + << '\"'; + if (child_result->report_string.empty() && + child_result->children.empty()) { stream << " />\n"; - } else if (!result.nested_tests->empty()) { + } else if (!child_result->children.empty()) { stream << " />\n"; - print_result(stream, suite_name, indent + " ", result); + print_result(stream, suite_name, indent + " ", *child_result); stream << indent << "\n"; - } else if (!result.report_string.empty()) { + } else if (!child_result->report_string.empty()) { stream << ">\n"; stream << indent << indent << "\n"; - stream << result.report_string << "\n"; + stream << child_result->report_string << "\n"; stream << indent << indent << "\n"; stream << indent << "\n"; } From 0d2bd8657b2f1ce6df0278d4d3c1897c0e10a643 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:32:28 +0800 Subject: [PATCH 05/10] add get_duration() and improve the format --- include/boost/ut.hpp | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index ead795f5..7380d915 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1729,7 +1729,7 @@ class reporter_junit { ss_out_ << getLeadingSpace(); ss_out_ << "Running test \"" << test_event.name << "\" ... "; } - ss_out_ << color_.pass << "PASSED" << color_.none; + ss_out_ << color_.pass << "PASSED " << color_.none; print_duration(ss_out_); lcout_ << ss_out_.str(); } @@ -1778,7 +1778,7 @@ class reporter_junit { if (report_type_ == CONSOLE) { lcout_ << getLeadingSpace(); lcout_ << "Running test \"" << current_node_->test_name << "\"... "; - lcout_ << color_.fail << "FAILED" << color_.none; + lcout_ << color_.fail << "FAILED " << color_.none; print_duration(lcout_); lcout_ << '\n'; lcout_ << current_node_->report_string << '\n'; @@ -1847,15 +1847,17 @@ class reporter_junit { } protected: - void print_duration(auto& printer) const noexcept { + inline double get_duration(test_result* test_node) const { + std::int64_t time_ms = + std::chrono::duration_cast( + test_node->run_stop - test_node->run_start) + .count(); + return static_cast(time_ms) / 1000.0; + } + + inline void print_duration(auto& printer) const noexcept { if (detail::cfg::show_duration) { - std::int64_t time_ms = - std::chrono::duration_cast( - current_node_->run_stop - current_node_->run_start) - .count(); - // rounded to nearest ms - double time_s = static_cast(time_ms) / 1000.0; - printer << " after " << time_s << " seconds"; + printer << "after " << get_duration(current_node_) << " seconds "; } } @@ -1893,17 +1895,10 @@ class reporter_junit { 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 = - std::chrono::duration_cast( - suite_result->run_stop - suite_result->run_start) - .count(); - return static_cast(time_ms) / 1000.0; - }; for (const auto& suite_result : suites_results_) { n_tests += suite_result->assertions; n_fails += suite_result->fails; - total_time += suite_time(suite_result); + total_time += get_duration(suite_result.get()); } // mock junit output: @@ -1923,7 +1918,7 @@ class reporter_junit { stream << " errors=\"" << suite_result->fails << '\"'; stream << " failures=\"" << suite_result->fails << '\"'; stream << " skipped=\"" << suite_result->skipped << '\"'; - stream << " time=\"" << suite_time(suite_result) << '\"'; + stream << " time=\"" << get_duration(suite_result.get()) << '\"'; stream << " version=\"" << BOOST_UT_VERSION << "\">\n"; print_result(stream, suite_result->test_name, " ", *suite_result); stream << "\n"; @@ -1941,11 +1936,7 @@ class reporter_junit { stream << " errors=\"" << child_result->fails << '\"'; stream << " failures=\"" << child_result->fails << '\"'; stream << " skipped=\"" << child_result->skipped << '\"'; - std::int64_t time_ms = - std::chrono::duration_cast( - child_result->run_stop - child_result->run_start) - .count(); - stream << " time=\"" << (static_cast(time_ms) / 1000.0) << "\""; + stream << " time=\"" << get_duration(child_result.get()) << "\""; stream << " status=\"" << statusStrings[(int)child_result->status] << '\"'; if (child_result->report_string.empty() && From db0907fb7b3efd7c0dd18627378e9b8a9496ed5c Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:40:21 +0800 Subject: [PATCH 06/10] modify the order of StatusType --- include/boost/ut.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 7380d915..22ea8a35 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1591,12 +1591,12 @@ class reporter_junit { enum class ReportType : std::uint8_t { CONSOLE, JUNIT } report_type_; static constexpr ReportType CONSOLE = ReportType::CONSOLE; static constexpr ReportType JUNIT = ReportType::JUNIT; - enum class StatusType : std::uint8_t { PASSED, FAILED, SKIPPED, UNDEFINED }; + enum class StatusType : std::uint8_t { UNDEFINED, PASSED, FAILED, SKIPPED }; + static constexpr StatusType UNDEFINED = StatusType::UNDEFINED; static constexpr StatusType PASSED = StatusType::PASSED; static constexpr StatusType FAILED = StatusType::FAILED; static constexpr StatusType SKIPPED = StatusType::SKIPPED; - static constexpr StatusType UNDEFINED = StatusType::UNDEFINED; - inline static const std::string statusStrings[] = { "PASSED", "FAILED", "SKIPPED", "UNDEFINED" }; + inline static const std::string statusStrings[] = { "UNDEFINED", "FAILED", "SKIPPED", "PASSED" }; struct test_result { std::string test_name; From 532b3d6f13e2936b6598566fb59ae109f2ef47da Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:46:57 +0800 Subject: [PATCH 07/10] correct the status judgement --- include/boost/ut.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 22ea8a35..8521f34e 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1649,8 +1649,8 @@ class reporter_junit { void count_result() { current_node_->run_stop = clock_ref::now(); current_node_->status = - current_node_->skipped - ? SKIPPED : (current_node_->fails > 0 ? FAILED : PASSED); + current_node_->fails > 0 + ? FAILED : (current_node_->skipped ? SKIPPED : PASSED); auto parent = current_node_->parent; if (parent != nullptr) { parent->n_tests += current_node_->n_tests; From a9b190a585b8045d3ed58cf100584cbfc3993944 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:52:19 +0800 Subject: [PATCH 08/10] mark the color of skipped messages --- include/boost/ut.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index 8521f34e..f9dec3f6 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1878,12 +1878,14 @@ class reporter_junit { << color_.none; } else if (suite_result->assertions || suite_result->n_tests || suite_result->skipped) { - out_stream << color_.pass << "\nSuite '" << suite_result->test_name - << "': all tests passed" << color_.none << " (" - << suite_result->assertions << " asserts in " - << suite_result->n_tests << " tests)"; + out_stream + << color_.pass << "\nSuite '" << suite_result->test_name + << "': all tests passed" << color_.none << " (" + << suite_result->assertions << " asserts in " + << suite_result->n_tests << " tests)"; if (suite_result->skipped) { - std::cout << "; " << suite_result->skipped << " tests skipped"; + std::cout << "; " << color_.skip << suite_result->skipped + << " tests skipped" << color_.none; } std::cout.flush(); } From 7933178d1b4276d223938e75eb627fc6d43fd126 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:21:52 +0800 Subject: [PATCH 09/10] show skipped tests with failed messages --- include/boost/ut.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index f9dec3f6..d16c56ff 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1883,12 +1883,12 @@ class reporter_junit { << "': all tests passed" << color_.none << " (" << suite_result->assertions << " asserts in " << suite_result->n_tests << " tests)"; - if (suite_result->skipped) { - std::cout << "; " << color_.skip << suite_result->skipped - << " tests skipped" << color_.none; - } - std::cout.flush(); } + if (suite_result->skipped) { + std::cout << "; " << color_.skip << suite_result->skipped + << " tests skipped" << color_.none; + } + std::cout.flush(); } } From 2543cef00774bf71b0ba42fcf0f0bfac2f0a6816 Mon Sep 17 00:00:00 2001 From: "Otto C." <12378062+ILer32@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:04:00 +0800 Subject: [PATCH 10/10] update getLeadingSpace() --- include/boost/ut.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/ut.hpp b/include/boost/ut.hpp index d16c56ff..6a433fd9 100644 --- a/include/boost/ut.hpp +++ b/include/boost/ut.hpp @@ -1664,7 +1664,7 @@ class reporter_junit { } inline std::string getLeadingSpace() { - return layer_ > 0 ? std::format("\n{}", std::string(2 * (layer_ - 1), ' ')) + return layer_ > 0 ? "\n" + std::string(2 * (layer_ - 1), ' ') : "\n"; }