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 "
139 << absl::StrFormat("%.2fs", absl::ToDoubleSeconds(duration));
140 }
141 oss << "\n\n";
142
143 for (const auto& result : summary.results) {
144 std::string group_name;
145 if (result.group) {
146 group_name = result.group->name;
147 } else if (result.test) {
148 group_name = result.test->group_name;
149 }
150 std::string test_name = result.test ? result.test->name : "<unnamed>";
151 oss << " [" << OutcomeToString(result.outcome) << "] ";
152 if (!group_name.empty()) {
153 oss << group_name << " :: ";
154 }
155 oss << test_name;
156 if (result.duration > absl::ZeroDuration()) {
157 oss << " (" << absl::StrFormat("%.2fs",
158 absl::ToDoubleSeconds(result.duration))
159 << ")";
160 }
161 oss << "\n";
162 if (!result.message.empty() &&
163 result.outcome != TestCaseOutcome::kPassed) {
164 oss << " " << result.message << "\n";
165 }
166 if (!result.assertions.empty() &&
167 result.outcome != TestCaseOutcome::kPassed) {
168 for (const auto& assertion : result.assertions) {
169 oss << " - " << assertion.description << " : "
170 << (assertion.passed ? "PASS" : "FAIL");
171 if (!assertion.error_message.empty()) {
172 oss << " (" << assertion.error_message << ")";
173 }
174 oss << "\n";
175 }
176 }
177 }
178
179 return oss.str();
180}
181
182absl::StatusOr<std::string> BuildJUnitReport(
183 const TestSuiteRunSummary& summary) {
184 std::ostringstream oss;
185 int total = 0;
186 int passed = 0;
187 int failed = 0;
188 int errors = 0;
189 int skipped = 0;
190 ComputeSummaryCounters(summary, &total, &passed, &failed, &errors, &skipped);
191
192 absl::Time timestamp = summary.started_at;
193 if (timestamp == absl::InfinitePast()) {
194 timestamp = absl::Now();
195 }
196 absl::Duration duration = TotalDuration(summary);
197
198 std::string suite_name =
199 summary.suite ? summary.suite->name : "YAZE GUI Test Suite";
200
201 oss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
202 oss << "<testsuite name=\"" << EscapeXml(suite_name) << "\" tests=\""
203 << total << "\" failures=\"" << failed << "\" errors=\"" << errors
204 << "\" skipped=\"" << skipped << "\" time=\""
205 << absl::StrFormat("%.3f", absl::ToDoubleSeconds(duration))
206 << "\" timestamp=\""
207 << EscapeXml(
208 absl::FormatTime("%Y-%m-%dT%H:%M:%SZ", timestamp,
209 absl::UTCTimeZone()))
210 << "\">\n";
211
212 for (const auto& result : summary.results) {
213 std::string classname;
214 if (result.group) {
215 classname = result.group->name;
216 } else if (result.test) {
217 classname = result.test->group_name;
218 }
219 std::string test_name = result.test ? result.test->name : "Test";
220 oss << " <testcase classname=\"" << EscapeXml(classname)
221 << "\" name=\"" << EscapeXml(test_name) << "\" time=\""
222 << absl::StrFormat("%.3f", absl::ToDoubleSeconds(result.duration))
223 << "\">";
224
225 if (result.outcome == TestCaseOutcome::kFailed) {
226 std::string body = result.message;
227 if (!result.assertions.empty()) {
228 std::vector<std::string> assertion_lines;
229 for (const auto& assertion : result.assertions) {
230 assertion_lines.push_back(
231 absl::StrCat(assertion.description, " => ",
232 assertion.passed ? "PASS" : "FAIL"));
233 }
234 body = absl::StrCat(body, "\n",
235 absl::StrJoin(assertion_lines, "\n"));
236 }
237 oss << "\n <failure message=\"" << EscapeXml(result.message)
238 << "\">" << EscapeXml(body) << "</failure>";
239 if (!result.logs.empty()) {
240 oss << "\n <system-out>" << EscapeXml(JoinLogs(result.logs))
241 << "</system-out>";
242 }
243 oss << "\n </testcase>\n";
244 continue;
245 }
246
247 if (result.outcome == TestCaseOutcome::kError) {
248 std::string detail = result.message;
249 if (!result.logs.empty()) {
250 detail = absl::StrCat(detail, "\n", JoinLogs(result.logs));
251 }
252 oss << "\n <error message=\"" << EscapeXml(result.message)
253 << "\">" << EscapeXml(detail) << "</error>";
254 oss << "\n </testcase>\n";
255 continue;
256 }
257
258 if (!result.logs.empty()) {
259 oss << "\n <system-out>" << EscapeXml(JoinLogs(result.logs))
260 << "</system-out>";
261 }
262 oss << "\n </testcase>\n";
263 }
264
265 oss << "</testsuite>\n";
266 return oss.str();
267}
268
269absl::Status WriteJUnitReport(const TestSuiteRunSummary& summary,
270 const std::string& output_path) {
271 ASSIGN_OR_RETURN(std::string xml, BuildJUnitReport(summary));
272 std::filesystem::path path(output_path);
273 if (path.has_parent_path()) {
274 std::error_code ec;
275 std::filesystem::create_directories(path.parent_path(), ec);
276 if (ec) {
277 return absl::InternalError(
278 absl::StrCat("Failed to create directory for JUnit report: ",
279 ec.message()));
280 }
281 }
282 std::ofstream out(path);
283 if (!out.is_open()) {
284 return absl::InternalError(
285 absl::StrCat("Unable to open JUnit output file '", output_path,
286 "'"));
287 }
288 out << xml;
289 return absl::OkStatus();
290}
291
292} // namespace cli
293} // namespace yaze
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
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)
Main namespace for the application.
const TestSuiteDefinition * suite
Definition test_suite.h:80
std::vector< TestCaseRunResult > results
Definition test_suite.h:81