#include "base/test/metrics/action_variants_reader.h"
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libxml/chromium/xml_reader.h"
namespace base::test {
namespace {
std::optional<ActionVariantsEntryMap> ParseVariants(
std::string_view logging_context,
XmlReader& reader,
std::string_view separator) {
if (reader.IsEmptyElement()) {
return std::nullopt;
}
ActionVariantsEntryMap variants;
const int parent_depth = reader.Depth();
bool success = true;
while (reader.Read() && reader.Depth() > parent_depth) {
if (!reader.IsElement()) {
continue;
}
if (reader.NodeName() != "variant") {
ADD_FAILURE() << "Unexpected node in variants block " << logging_context
<< ": " << reader.NodeName();
success = false;
continue;
}
std::string name;
std::string summary;
if (!reader.NodeAttribute("name", &name) ||
!reader.NodeAttribute("summary", &summary)) {
ADD_FAILURE() << "Variant in " << logging_context
<< " is missing name or summary.";
success = false;
continue;
}
if (name.empty()) {
continue;
}
if (!separator.empty() && name.starts_with(separator)) {
name = name.substr(1);
}
variants[name] = summary;
}
if (!success || variants.empty()) {
return std::nullopt;
}
return variants;
}
bool ParseActionNode(
XmlReader& reader,
std::string_view affected_action,
const std::map<std::string, ActionVariantsEntryMap>& global_variants,
std::vector<ActionVariantsEntryMap>& result,
std::string_view separator) {
std::string name;
if (!reader.NodeAttribute("name", &name)) {
return false;
}
std::string base_name = name;
const size_t brace_pos = name.find('{');
if (brace_pos != std::string::npos) {
base_name = name.substr(0, brace_pos);
}
if (base_name != affected_action) {
return false;
}
if (reader.IsEmptyElement()) {
return false;
}
const int parent_depth = reader.Depth();
while (reader.Read() && reader.Depth() > parent_depth) {
if (!reader.IsElement() || reader.NodeName() != "token") {
continue;
}
std::string variants_name;
if (reader.NodeAttribute("variants", &variants_name)) {
auto it = global_variants.find(variants_name);
if (it == global_variants.end()) {
ADD_FAILURE() << "Variants block not found: " << variants_name;
return false;
}
result.push_back(it->second);
} else {
auto variants = ParseVariants("inline", reader, separator);
if (variants) {
result.push_back(std::move(*variants));
}
}
}
return true;
}
std::vector<ActionVariantsEntryMap> ReadActionVariantsForActionImpl(
XmlReader& reader,
std::string_view affected_action,
std::string_view separator) {
std::vector<ActionVariantsEntryMap> result;
std::map<std::string, ActionVariantsEntryMap> global_variants;
while (reader.Read()) {
if (!reader.IsElement()) {
continue;
}
const std::string node_name = reader.NodeName();
if (node_name == "variants") {
std::string variants_name;
if (reader.NodeAttribute("name", &variants_name)) {
auto variants = ParseVariants(variants_name, reader, separator);
if (variants) {
global_variants[variants_name] = std::move(*variants);
}
}
} else if (node_name == "action") {
if (ParseActionNode(reader, affected_action, global_variants, result,
separator)) {
return result;
}
}
}
return result;
}
}
std::vector<ActionVariantsEntryMap> ReadActionVariantsForAction(
std::string_view action_name,
std::string_view separator) {
base::FilePath actions_xml_path;
CHECK(
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &actions_xml_path));
actions_xml_path = actions_xml_path.AppendASCII("tools")
.AppendASCII("metrics")
.AppendASCII("actions")
.AppendASCII("actions.xml");
std::string xml_string;
if (!base::ReadFileToString(actions_xml_path, &xml_string)) {
ADD_FAILURE() << "Could not read " << actions_xml_path;
return {};
}
XmlReader reader;
if (!reader.Load(xml_string)) {
ADD_FAILURE() << "Failed to load XML from string.";
return {};
}
return ReadActionVariantsForActionImpl(reader, action_name, separator);
}
std::vector<ActionVariantsEntryMap> ReadActionVariantsForActionFromXmlString(
std::string_view xml_string,
std::string_view action_name,
std::string_view separator) {
XmlReader reader;
CHECK(reader.Load(xml_string));
return ReadActionVariantsForActionImpl(reader, action_name, separator);
}
}