xref: /aosp_15_r20/external/cronet/base/test/metrics/histogram_variants_reader.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/test/metrics/histogram_variants_reader.h"
6 
7 #include <map>
8 #include <optional>
9 #include <string>
10 
11 #include "base/base_paths.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/logging.h"
15 #include "base/path_service.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "third_party/libxml/chromium/xml_reader.h"
18 
19 namespace base {
20 
21 namespace {
22 
23 // Extracts single variants block from a histograms.xml.
24 //
25 // Expects |reader| to point at the given <variants> element with the name
26 // `variants_name`.
27 //
28 // Returns map { name => summary } on success, and nullopt on failure.
ParseVariantsFromHistogramsXml(const std::string & variants_name,XmlReader & reader)29 std::optional<HistogramVariantsEntryMap> ParseVariantsFromHistogramsXml(
30     const std::string& variants_name,
31     XmlReader& reader) {
32   HistogramVariantsEntryMap result;
33   bool success = true;
34 
35   while (true) {
36     // Because reader initially points to the start of the <variants> element,
37     // and because <variants> elements are not nested, when the closing tag is
38     // reached, parsing is complete.
39     const std::string node_name = reader.NodeName();
40     if (node_name == "variants" && reader.IsClosingElement()) {
41       break;
42     }
43 
44     if (node_name == "variant") {
45       std::string name;
46       std::string summary;
47       const bool has_name = reader.NodeAttribute("name", &name);
48       const bool has_summary = reader.NodeAttribute("summary", &summary);
49 
50       if (!has_name) {
51         ADD_FAILURE() << "Bad " << variants_name << " variant entry, summary='"
52                       << summary << "'): No 'name' attribute.";
53         success = false;
54       }
55 
56       if (!has_summary) {
57         ADD_FAILURE() << "Bad " << variants_name << " variant entry, name='"
58                       << name << "'): No 'summary' attribute.";
59         success = false;
60       }
61 
62       // Don't check summary here because we want to check for duplicate names,
63       // and if there was a missing summary the function has already failed.
64       if (has_name) {
65         const auto insert_result = result.emplace(name, summary);
66         if (!insert_result.second) {
67           ADD_FAILURE() << "Duplicate entry in " << variants_name
68                         << " variant entry, name='" << name << ')';
69           success = false;
70         }
71       }
72     }
73 
74     // All variant entries are on the same level, so advance to the next
75     // sibling.
76     reader.Next();
77   }
78 
79   return success ? std::make_optional(result) : std::nullopt;
80 }
81 
82 }  // namespace
83 
ReadVariantsFromHistogramsXml(const std::string & variants_name,const std::string & subdirectory,bool from_metadata)84 std::optional<HistogramVariantsEntryMap> ReadVariantsFromHistogramsXml(
85     const std::string& variants_name,
86     const std::string& subdirectory,
87     bool from_metadata) {
88   base::FilePath src_root;
89   if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &src_root)) {
90     ADD_FAILURE() << "Failed to get src root.";
91     return std::nullopt;
92   }
93 
94   base::FilePath path =
95       src_root.AppendASCII("tools").AppendASCII("metrics").AppendASCII(
96           "histograms");
97   if (from_metadata) {
98     path = path.AppendASCII("metadata");
99   }
100   if (!subdirectory.empty()) {
101     path = path.AppendASCII(subdirectory);
102   }
103   path = path.AppendASCII("histograms.xml");
104 
105   if (!base::PathExists(path)) {
106     ADD_FAILURE() << "File does not exist: " << path;
107     return std::nullopt;
108   }
109 
110   XmlReader reader;
111   if (!reader.LoadFile(path.MaybeAsASCII())) {
112     ADD_FAILURE() << "Failed to load " << path;
113     return std::nullopt;
114   }
115 
116   std::optional<HistogramVariantsEntryMap> result;
117 
118   // Implement simple depth first search.
119   while (true) {
120     const std::string node_name = reader.NodeName();
121     if (node_name == "variants") {
122       std::string name;
123       if (reader.NodeAttribute("name", &name) && name == variants_name) {
124         if (result.has_value()) {
125           ADD_FAILURE() << "Duplicate variant '" << variants_name
126                         << "' found in " << path;
127           return std::nullopt;
128         }
129 
130         const bool got_into_variant = reader.Read();
131         if (!got_into_variant) {
132           ADD_FAILURE() << "Bad variant '" << variants_name
133                         << "' (looks empty) found in " << path;
134           return std::nullopt;
135         }
136 
137         result = ParseVariantsFromHistogramsXml(variants_name, reader);
138         if (!result.has_value()) {
139           ADD_FAILURE() << "Bad variant '" << variants_name << "' found in "
140                         << path << " (format error).";
141           return std::nullopt;
142         }
143       }
144     }
145 
146     // Go deeper if possible (stops at the closing tag of the deepest node).
147     if (reader.Read()) {
148       continue;
149     }
150 
151     // Try next node on the same level (skips closing tag).
152     if (reader.Next()) {
153       continue;
154     }
155 
156     // Go up until next node on the same level exists.
157     while (reader.Depth() && !reader.SkipToElement()) {
158     }
159 
160     // Reached top. histograms.xml consists of the single top level node
161     // 'histogram-configuration', so this is the end.
162     if (!reader.Depth()) {
163       break;
164     }
165   }
166 
167   return result;
168 }
169 
170 }  // namespace base
171