62 enum class Status { Failure, Error, Skipped };
63 Status status = Status::Failure;
68 Result(std::string message, std::string type, std::string text, Status status = Status::Failure)
69 : status(status), message(std::move(message)), type(std::move(type)), text(std::move(text)) {}
71 [[nodiscard]] std::string status_string()
const {
83 [[nodiscard]] std::string to_xml()
const {
84 return std::format(R
"( <{} message="{}" type="{}">{}</{}>)", status_string(), encode_xml(message),
85 encode_xml(type), encode_xml(text), status_string());
91 std::string classname;
92 std::size_t assertions = 0;
93 std::chrono::duration<double> time;
94 std::list<Result> results;
98 [[nodiscard]] std::string to_xml()
const {
100 std::format(R
"( <testcase name="{}" classname="{}" assertions="{}" time="{:f}" file="{}" line="{}")",
101 encode_xml(name), encode_xml(classname), assertions, time.count(), file, line);
102 if (results.empty()) {
106 auto xml_results = results | std::views::transform([](
const Result& r) {
return r.to_xml(); });
108 std::stringstream ss;
109 ss << start <<
">" << std::endl;
111 ss << std::accumulate(xml_results.begin(), xml_results.end(), std::string{},
112 [](
const std::string& acc,
const std::string& r) { return acc +
"\n" + r; });
114 ss <<
" </testcase>";
122 std::chrono::duration<double> time;
123 std::chrono::time_point<std::chrono::system_clock> timestamp;
125 std::size_t failures;
126 std::list<TestCase> cases;
128 TestSuite(std::string name,
129 std::chrono::duration<double> time,
132 std::chrono::time_point<std::chrono::system_clock> timestamp)
133 : id(get_next_id()), name(std::move(name)), time(time), timestamp(timestamp), tests(tests), failures(failures) {}
135 [[nodiscard]] std::string to_xml()
const {
136 std::string timestamp_str = format_timestamp(timestamp);
138 std::stringstream ss;
140 << std::format(R
"(<testsuite id="{}" name="{}" time="{:f}" timestamp="{}" tests="{}" failures="{}">)", id,
141 encode_xml(name), time.count(), timestamp_str, tests, failures);
143 for (
const TestCase& test_case : cases) {
144 ss << test_case.to_xml() << std::endl;
146 ss <<
" </testsuite>";
150 static size_t get_next_id() {
151 static std::size_t id_counter = 0;
160 std::chrono::duration<double> time{};
161 std::chrono::time_point<std::chrono::system_clock> timestamp;
163 std::list<TestSuite> suites;
165 [[nodiscard]] std::string to_xml()
const {
166 std::stringstream ss;
167 auto timestamp_str = format_timestamp(timestamp);
168 ss << std::format(R
"(<testsuites name="{}" tests="{}" failures="{}" time="{:f}" timestamp="{}">)", encode_xml(name),
169 tests, failures, time.count(), timestamp_str);
172 ss << suite.to_xml() << std::endl;
174 ss <<
"</testsuites>" << std::endl;
180class JUnitXML :
public BaseFormatter {
184 explicit JUnitXML(std::ostream& out_stream = std::cout,
bool color = is_terminal())
185 : BaseFormatter(out_stream, color) {}
189 std::accumulate(test_suites.suites.begin(), test_suites.suites.end(),
size_t{0},
191 test_suites.failures =
192 std::accumulate(test_suites.suites.begin(), test_suites.suites.end(),
size_t{0},
194 test_suites.time = std::ranges::fold_left(test_suites.suites, std::chrono::duration<double>(0),
195 [](
const auto& acc,
const auto& suite) { return acc + suite.time; });
196 test_suites.timestamp =
197 test_suites.suites.empty() ? std::chrono::system_clock::now() : test_suites.suites.front().timestamp;
199 out_stream << std::fixed;
201 out_stream << junit_xml_header << std::endl;
202 out_stream << test_suites.to_xml() << std::endl;
206 void format(
const Description& description)
override {
207 if (test_suites.name.empty()) {
208#ifdef CPPSPEC_SEMIHOSTED
209 std::string file_path = description.get_location().file_name();
211 auto pos = file_path.find_last_of(
"/");
212 if (pos != std::string::npos) {
213 file_path = file_path.substr(pos + 1);
217 pos = file_path.find_last_of(
'.');
218 if (pos != std::string::npos) {
219 file_path = file_path.substr(0, pos);
222 test_suites.name = file_path;
224 std::filesystem::path file_path = description.get_location().file_name();
225 test_suites.name = file_path.stem().string();
231 test_suites.suites.emplace_back(description.get_description(), description.get_runtime(), description.num_tests(),
232 description.num_failures(), description.get_start_time());
235 void format(
const ItBase& it)
override {
236 using namespace std::chrono;
237 std::forward_list<std::string> descriptions;
239 descriptions.push_front(it.get_description());
242 descriptions.push_front(parent->get_description());
245 std::string description =
Util::join(descriptions,
" ");
250 .assertions = it.get_results().size(),
251 .time = it.get_runtime(),
253 .file = it.get_location().file_name(),
254 .line = it.get_location().line(),
257 for (
const Result& result : it.get_results()) {
258 if (result.is_success()) {
261 test_case.results.emplace_back(result.get_location_string() +
": Match failure.", result.get_type(),
262 result.get_message());
265 test_suites.suites.back().cases.push_back(test_case);