#include "cli/service/testing/test_suite_writer.h" #include #include #include #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/string_view.h" namespace yaze { namespace cli { namespace { std::string Indent(int count) { return std::string(count, ' '); } std::string QuoteYaml(absl::string_view value) { std::string escaped(value); absl::StrReplaceAll({{"\\", "\\\\"}, {"\"", "\\\""}}, &escaped); return absl::StrCat("\"", escaped, "\""); } void AppendLine(std::string* out, int indent, absl::string_view line) { out->append(Indent(indent)); out->append(line.data(), line.size()); out->append("\n"); } void AppendScalar(std::string* out, int indent, absl::string_view key, absl::string_view value, bool quote) { out->append(Indent(indent)); out->append(key.data(), key.size()); out->append(":"); if (!value.empty()) { out->append(" "); if (quote) { out->append(QuoteYaml(value)); } else { out->append(value.data(), value.size()); } } out->append("\n"); } std::string FormatDuration(int seconds) { if (seconds <= 0) { return "0s"; } if (seconds % 60 == 0) { return absl::StrCat(seconds / 60, "m"); } return absl::StrCat(seconds, "s"); } std::string FormatBool(bool value) { return value ? "true" : "false"; } std::string JoinQuotedList(const std::vector& values) { if (values.empty()) { return "[]"; } std::vector quoted; quoted.reserve(values.size()); for (const auto& v : values) { quoted.push_back(QuoteYaml(v)); } return absl::StrCat("[", absl::StrJoin(quoted, ", "), "]"); } } // namespace std::string BuildTestSuiteYaml(const TestSuiteDefinition& suite) { std::string output; if (!suite.name.empty()) { AppendScalar(&output, 0, "name", suite.name, /*quote=*/true); } else { AppendScalar(&output, 0, "name", "Unnamed Suite", /*quote=*/true); } if (!suite.description.empty()) { AppendScalar(&output, 0, "description", suite.description, /*quote=*/true); } if (!suite.version.empty()) { AppendScalar(&output, 0, "version", suite.version, /*quote=*/true); } AppendLine(&output, 0, "config:"); AppendScalar(&output, 2, "timeout_per_test", FormatDuration(suite.config.timeout_seconds), /*quote=*/false); AppendScalar(&output, 2, "retry_on_failure", absl::StrCat(suite.config.retry_on_failure), /*quote=*/false); AppendScalar(&output, 2, "parallel_execution", FormatBool(suite.config.parallel_execution), /*quote=*/false); AppendLine(&output, 0, "test_groups:"); for (size_t i = 0; i < suite.groups.size(); ++i) { const TestGroupDefinition& group = suite.groups[i]; AppendLine(&output, 2, "- name: " + QuoteYaml(group.name)); if (!group.description.empty()) { AppendScalar(&output, 4, "description", group.description, /*quote=*/true); } if (!group.depends_on.empty()) { AppendScalar(&output, 4, "depends_on", JoinQuotedList(group.depends_on), /*quote=*/false); } AppendLine(&output, 4, "tests:"); for (const TestCaseDefinition& test : group.tests) { AppendLine(&output, 6, "- path: " + QuoteYaml(test.script_path)); if (!test.name.empty() && test.name != test.script_path) { AppendScalar(&output, 8, "name", test.name, /*quote=*/true); } if (!test.description.empty()) { AppendScalar(&output, 8, "description", test.description, /*quote=*/true); } if (!test.tags.empty()) { AppendScalar(&output, 8, "tags", JoinQuotedList(test.tags), /*quote=*/false); } if (!test.parameters.empty()) { AppendLine(&output, 8, "parameters:"); for (const auto& [key, value] : test.parameters) { AppendScalar(&output, 10, key, value, /*quote=*/true); } } } if (!group.tests.empty() && i + 1 < suite.groups.size()) { output.append("\n"); } } return output; } absl::Status WriteTestSuiteToFile(const TestSuiteDefinition& suite, const std::string& path, bool overwrite) { std::filesystem::path output_path(path); std::error_code ec; if (!overwrite && std::filesystem::exists(output_path, ec)) { if (!ec) { return absl::AlreadyExistsError( absl::StrCat("Test suite file already exists: ", path)); } } std::filesystem::path parent = output_path.parent_path(); if (!parent.empty()) { std::filesystem::create_directories(parent, ec); if (ec) { return absl::InternalError(absl::StrCat( "Failed to create directories for ", path, ": ", ec.message())); } } std::ofstream stream(output_path, std::ios::out | std::ios::trunc); if (!stream.is_open()) { return absl::InternalError( absl::StrCat("Failed to open file for writing: ", path)); } std::string yaml = BuildTestSuiteYaml(suite); stream << yaml; stream.close(); if (!stream) { return absl::InternalError( absl::StrCat("Failed to write test suite to ", path)); } return absl::OkStatus(); } } // namespace cli } // namespace yaze