yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
test_suite_writer.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <system_error>
6
7#include "absl/status/status.h"
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_join.h"
10#include "absl/strings/str_replace.h"
11#include "absl/strings/string_view.h"
12
13namespace yaze {
14namespace cli {
15namespace {
16
17std::string Indent(int count) {
18 return std::string(count, ' ');
19}
20
21std::string QuoteYaml(absl::string_view value) {
22 std::string escaped(value);
23 absl::StrReplaceAll({{"\\", "\\\\"}, {"\"", "\\\""}}, &escaped);
24 return absl::StrCat("\"", escaped, "\"");
25}
26
27void AppendLine(std::string* out, int indent, absl::string_view line) {
28 out->append(Indent(indent));
29 out->append(line.data(), line.size());
30 out->append("\n");
31}
32
33void AppendScalar(std::string* out, int indent, absl::string_view key,
34 absl::string_view value, bool quote) {
35 out->append(Indent(indent));
36 out->append(key.data(), key.size());
37 out->append(":");
38 if (!value.empty()) {
39 out->append(" ");
40 if (quote) {
41 out->append(QuoteYaml(value));
42 } else {
43 out->append(value.data(), value.size());
44 }
45 }
46 out->append("\n");
47}
48
49std::string FormatDuration(int seconds) {
50 if (seconds <= 0) {
51 return "0s";
52 }
53 if (seconds % 60 == 0) {
54 return absl::StrCat(seconds / 60, "m");
55 }
56 return absl::StrCat(seconds, "s");
57}
58
59std::string FormatBool(bool value) {
60 return value ? "true" : "false";
61}
62
63std::string JoinQuotedList(const std::vector<std::string>& values) {
64 if (values.empty()) {
65 return "[]";
66 }
67 std::vector<std::string> quoted;
68 quoted.reserve(values.size());
69 for (const auto& v : values) {
70 quoted.push_back(QuoteYaml(v));
71 }
72 return absl::StrCat("[", absl::StrJoin(quoted, ", "), "]");
73}
74
75} // namespace
76
77std::string BuildTestSuiteYaml(const TestSuiteDefinition& suite) {
78 std::string output;
79
80 if (!suite.name.empty()) {
81 AppendScalar(&output, 0, "name", suite.name, /*quote=*/true);
82 } else {
83 AppendScalar(&output, 0, "name", "Unnamed Suite", /*quote=*/true);
84 }
85 if (!suite.description.empty()) {
86 AppendScalar(&output, 0, "description", suite.description,
87 /*quote=*/true);
88 }
89 if (!suite.version.empty()) {
90 AppendScalar(&output, 0, "version", suite.version, /*quote=*/true);
91 }
92
93 AppendLine(&output, 0, "config:");
94 AppendScalar(&output, 2, "timeout_per_test",
95 FormatDuration(suite.config.timeout_seconds),
96 /*quote=*/false);
97 AppendScalar(&output, 2, "retry_on_failure",
98 absl::StrCat(suite.config.retry_on_failure),
99 /*quote=*/false);
100 AppendScalar(&output, 2, "parallel_execution",
101 FormatBool(suite.config.parallel_execution),
102 /*quote=*/false);
103
104 AppendLine(&output, 0, "test_groups:");
105 for (size_t i = 0; i < suite.groups.size(); ++i) {
106 const TestGroupDefinition& group = suite.groups[i];
107 AppendLine(&output, 2, "- name: " + QuoteYaml(group.name));
108 if (!group.description.empty()) {
109 AppendScalar(&output, 4, "description", group.description,
110 /*quote=*/true);
111 }
112 if (!group.depends_on.empty()) {
113 AppendScalar(&output, 4, "depends_on", JoinQuotedList(group.depends_on),
114 /*quote=*/false);
115 }
116
117 AppendLine(&output, 4, "tests:");
118 for (const TestCaseDefinition& test : group.tests) {
119 AppendLine(&output, 6, "- path: " + QuoteYaml(test.script_path));
120 if (!test.name.empty() && test.name != test.script_path) {
121 AppendScalar(&output, 8, "name", test.name, /*quote=*/true);
122 }
123 if (!test.description.empty()) {
124 AppendScalar(&output, 8, "description", test.description,
125 /*quote=*/true);
126 }
127 if (!test.tags.empty()) {
128 AppendScalar(&output, 8, "tags", JoinQuotedList(test.tags),
129 /*quote=*/false);
130 }
131 if (!test.parameters.empty()) {
132 AppendLine(&output, 8, "parameters:");
133 for (const auto& [key, value] : test.parameters) {
134 AppendScalar(&output, 10, key, value, /*quote=*/true);
135 }
136 }
137 }
138
139 if (!group.tests.empty() && i + 1 < suite.groups.size()) {
140 output.append("\n");
141 }
142 }
143
144 return output;
145}
146
148 const std::string& path, bool overwrite) {
149 std::filesystem::path output_path(path);
150 std::error_code ec;
151 if (!overwrite && std::filesystem::exists(output_path, ec)) {
152 if (!ec) {
153 return absl::AlreadyExistsError(
154 absl::StrCat("Test suite file already exists: ", path));
155 }
156 }
157
158 std::filesystem::path parent = output_path.parent_path();
159 if (!parent.empty()) {
160 std::filesystem::create_directories(parent, ec);
161 if (ec) {
162 return absl::InternalError(absl::StrCat(
163 "Failed to create directories for ", path, ": ", ec.message()));
164 }
165 }
166
167 std::ofstream stream(output_path, std::ios::out | std::ios::trunc);
168 if (!stream.is_open()) {
169 return absl::InternalError(
170 absl::StrCat("Failed to open file for writing: ", path));
171 }
172
173 std::string yaml = BuildTestSuiteYaml(suite);
174 stream << yaml;
175 stream.close();
176
177 if (!stream) {
178 return absl::InternalError(
179 absl::StrCat("Failed to write test suite to ", path));
180 }
181 return absl::OkStatus();
182}
183
184} // namespace cli
185} // namespace yaze
void AppendLine(std::string *out, int indent, absl::string_view line)
std::string JoinQuotedList(const std::vector< std::string > &values)
void AppendScalar(std::string *out, int indent, absl::string_view key, absl::string_view value, bool quote)
std::string BuildTestSuiteYaml(const TestSuiteDefinition &suite)
absl::Status WriteTestSuiteToFile(const TestSuiteDefinition &suite, const std::string &path, bool overwrite)
std::vector< TestCaseDefinition > tests
Definition test_suite.h:35
std::vector< std::string > depends_on
Definition test_suite.h:34
std::vector< TestGroupDefinition > groups
Definition test_suite.h:43