yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
test_suite_reporter.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <sstream>
6
7#include "absl/status/status.h"
8#include "absl/strings/ascii.h"
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_format.h"
11#include "absl/strings/str_join.h"
12#include "absl/time/clock.h"
13#include "absl/time/time.h"
14#include "util/macro.h"
15
16namespace yaze {
17namespace cli {
18namespace {
19
20std::string OutcomeToString(TestCaseOutcome outcome) {
21 switch (outcome) {
23 return "PASS";
25 return "FAIL";
27 return "ERROR";
29 return "SKIP";
30 }
31 return "UNKNOWN";
32}
33
34std::string EscapeXml(absl::string_view input) {
35 std::string escaped;
36 escaped.reserve(input.size());
37 for (char c : input) {
38 switch (c) {
39 case '&':
40 escaped.append("&amp;");
41 break;
42 case '<':
43 escaped.append("&lt;");
44 break;
45 case '>':
46 escaped.append("&gt;");
47 break;
48 case '\"':
49 escaped.append("&quot;");
50 break;
51 case '\'':
52 escaped.append("&apos;");
53 break;
54 default:
55 escaped.push_back(c);
56 break;
57 }
58 }
59 return escaped;
60}
61
62std::string JoinLogs(const std::vector<std::string>& logs) {
63 if (logs.empty()) {
64 return {};
65 }
66 return absl::StrJoin(logs, "\n");
67}
68
69void ComputeSummaryCounters(const TestSuiteRunSummary& summary, int* total,
70 int* passed, int* failed, int* errors,
71 int* skipped) {
72 *total = static_cast<int>(summary.results.size());
73 *passed = summary.passed;
74 *failed = summary.failed;
75 *errors = summary.errors;
76 *skipped = summary.skipped;
77
78 if (*passed + *failed + *errors + *skipped != *total) {
79 *passed = *failed = *errors = *skipped = 0;
80 for (const auto& result : summary.results) {
81 switch (result.outcome) {
83 ++(*passed);
84 break;
86 ++(*failed);
87 break;
89 ++(*errors);
90 break;
92 ++(*skipped);
93 break;
94 }
95 }
96 }
97}
98
99absl::Duration TotalDuration(const TestSuiteRunSummary& summary) {
100 if (summary.total_duration > absl::ZeroDuration()) {
101 return summary.total_duration;
102 }
103 absl::Duration total = absl::ZeroDuration();
104 for (const auto& result : summary.results) {
105 total += result.duration;
106 }
107 return total;
108}
109
110} // namespace
111
112std::string BuildTextSummary(const TestSuiteRunSummary& summary) {
113 std::ostringstream oss;
114 int total = 0;
115 int passed = 0;
116 int failed = 0;
117 int errors = 0;
118 int skipped = 0;
119 ComputeSummaryCounters(summary, &total, &passed, &failed, &errors, &skipped);
120
121 absl::Time timestamp = summary.started_at;
122 if (timestamp == absl::InfinitePast()) {
123 timestamp = absl::Now();
124 }
125
126 oss << "Suite: "
127 << (summary.suite && !summary.suite->name.empty() ? summary.suite->name
128 : "Unnamed Suite")
129 << "\n";
130 oss << "Started: "
131 << absl::FormatTime("%Y-%m-%d %H:%M:%S", timestamp, absl::UTCTimeZone())
132 << " UTC\n";
133 oss << "Totals: " << total << " (" << passed << " passed, " << failed
134 << " failed, " << errors << " errors, " << skipped << " skipped)";
135
136 absl::Duration duration = TotalDuration(summary);
137 if (duration > absl::ZeroDuration()) {
138 oss << " in " << absl::StrFormat("%.2fs", absl::ToDoubleSeconds(duration));
139 }
140 oss << "\n\n";
141
142 for (const auto& result : summary.results) {
143 std::string group_name;
144 if (result.group) {
145 group_name = result.group->name;
146 } else if (result.test) {
147 group_name = result.test->group_name;
148 }
149 std::string test_name = result.test ? result.test->name : "<unnamed>";
150 oss << " [" << OutcomeToString(result.outcome) << "] ";
151 if (!group_name.empty()) {
152 oss << group_name << " :: ";
153 }
154 oss << test_name;
155 if (result.duration > absl::ZeroDuration()) {
156 oss << " ("
157 << absl::StrFormat("%.2fs", absl::ToDoubleSeconds(result.duration))
158 << ")";
159 }
160 oss << "\n";
161 if (!result.message.empty() && result.outcome != TestCaseOutcome::kPassed) {
162 oss << " " << result.message << "\n";
163 }
164 if (!result.assertions.empty() &&
165 result.outcome != TestCaseOutcome::kPassed) {
166 for (const auto& assertion : result.assertions) {
167 oss << " - " << assertion.description << " : "
168 << (assertion.passed ? "PASS" : "FAIL");
169 if (!assertion.error_message.empty()) {
170 oss << " (" << assertion.error_message << ")";
171 }
172 oss << "\n";
173 }
174 }
175 }
176
177 return oss.str();
178}
179
180absl::StatusOr<std::string> BuildJUnitReport(
181 const TestSuiteRunSummary& summary) {
182 std::ostringstream oss;
183 int total = 0;
184 int passed = 0;
185 int failed = 0;
186 int errors = 0;
187 int skipped = 0;
188 ComputeSummaryCounters(summary, &total, &passed, &failed, &errors, &skipped);
189
190 absl::Time timestamp = summary.started_at;
191 if (timestamp == absl::InfinitePast()) {
192 timestamp = absl::Now();
193 }
194 absl::Duration duration = TotalDuration(summary);
195
196 std::string suite_name =
197 summary.suite ? summary.suite->name : "YAZE GUI Test Suite";
198
199 oss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
200 oss << "<testsuite name=\"" << EscapeXml(suite_name) << "\" tests=\"" << total
201 << "\" failures=\"" << failed << "\" errors=\"" << errors
202 << "\" skipped=\"" << skipped << "\" time=\""
203 << absl::StrFormat("%.3f", absl::ToDoubleSeconds(duration))
204 << "\" timestamp=\""
205 << EscapeXml(absl::FormatTime("%Y-%m-%dT%H:%M:%SZ", timestamp,
206 absl::UTCTimeZone()))
207 << "\">\n";
208
209 for (const auto& result : summary.results) {
210 std::string classname;
211 if (result.group) {
212 classname = result.group->name;
213 } else if (result.test) {
214 classname = result.test->group_name;
215 }
216 std::string test_name = result.test ? result.test->name : "Test";
217 oss << " <testcase classname=\"" << EscapeXml(classname) << "\" name=\""
218 << EscapeXml(test_name) << "\" time=\""
219 << absl::StrFormat("%.3f", absl::ToDoubleSeconds(result.duration))
220 << "\">";
221
222 if (result.outcome == TestCaseOutcome::kFailed) {
223 std::string body = result.message;
224 if (!result.assertions.empty()) {
225 std::vector<std::string> assertion_lines;
226 for (const auto& assertion : result.assertions) {
227 assertion_lines.push_back(
228 absl::StrCat(assertion.description, " => ",
229 assertion.passed ? "PASS" : "FAIL"));
230 }
231 body = absl::StrCat(body, "\n", absl::StrJoin(assertion_lines, "\n"));
232 }
233 oss << "\n <failure message=\"" << EscapeXml(result.message) << "\">"
234 << EscapeXml(body) << "</failure>";
235 if (!result.logs.empty()) {
236 oss << "\n <system-out>" << EscapeXml(JoinLogs(result.logs))
237 << "</system-out>";
238 }
239 oss << "\n </testcase>\n";
240 continue;
241 }
242
243 if (result.outcome == TestCaseOutcome::kError) {
244 std::string detail = result.message;
245 if (!result.logs.empty()) {
246 detail = absl::StrCat(detail, "\n", JoinLogs(result.logs));
247 }
248 oss << "\n <error message=\"" << EscapeXml(result.message) << "\">"
249 << EscapeXml(detail) << "</error>";
250 oss << "\n </testcase>\n";
251 continue;
252 }
253
254 if (!result.logs.empty()) {
255 oss << "\n <system-out>" << EscapeXml(JoinLogs(result.logs))
256 << "</system-out>";
257 }
258 oss << "\n </testcase>\n";
259 }
260
261 oss << "</testsuite>\n";
262 return oss.str();
263}
264
265absl::Status WriteJUnitReport(const TestSuiteRunSummary& summary,
266 const std::string& output_path) {
267 ASSIGN_OR_RETURN(std::string xml, BuildJUnitReport(summary));
268 std::filesystem::path path(output_path);
269 if (path.has_parent_path()) {
270 std::error_code ec;
271 std::filesystem::create_directories(path.parent_path(), ec);
272 if (ec) {
273 return absl::InternalError(absl::StrCat(
274 "Failed to create directory for JUnit report: ", ec.message()));
275 }
276 }
277 std::ofstream out(path);
278 if (!out.is_open()) {
279 return absl::InternalError(
280 absl::StrCat("Unable to open JUnit output file '", output_path, "'"));
281 }
282 out << xml;
283 return absl::OkStatus();
284}
285
286} // namespace cli
287} // namespace yaze
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::Duration TotalDuration(const TestSuiteRunSummary &summary)
void ComputeSummaryCounters(const TestSuiteRunSummary &summary, int *total, int *passed, int *failed, int *errors, int *skipped)
std::string JoinLogs(const std::vector< std::string > &logs)
absl::StatusOr< std::string > BuildJUnitReport(const TestSuiteRunSummary &summary)
std::string BuildTextSummary(const TestSuiteRunSummary &summary)
absl::Status WriteJUnitReport(const TestSuiteRunSummary &summary, const std::string &output_path)
const TestSuiteDefinition * suite
Definition test_suite.h:75
std::vector< TestCaseRunResult > results
Definition test_suite.h:76