xref: /aosp_15_r20/external/cronet/base/test/metrics/histogram_enum_reader.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2018 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_enum_reader.h"
6 
7 #include <optional>
8 
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/path_service.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "third_party/libxml/chromium/xml_reader.h"
15 
16 namespace base {
17 namespace {
18 
19 // This is a helper function to the ReadEnumFromHistogramsXml().
20 // Extracts single enum (with integer values) from histograms.xml.
21 // Expects |reader| to point at given enum.
22 // Returns map { value => label } on success, and nullopt on failure.
ParseEnumFromHistogramsXml(const std::string & enum_name,XmlReader * reader)23 std::optional<HistogramEnumEntryMap> ParseEnumFromHistogramsXml(
24     const std::string& enum_name,
25     XmlReader* reader) {
26   int entries_index = -1;
27 
28   HistogramEnumEntryMap result;
29   bool success = true;
30 
31   while (true) {
32     const std::string node_name = reader->NodeName();
33     if (node_name == "enum" && reader->IsClosingElement())
34       break;
35 
36     if (node_name == "int") {
37       ++entries_index;
38       std::string value_str;
39       std::string label;
40       const bool has_value = reader->NodeAttribute("value", &value_str);
41       const bool has_label = reader->NodeAttribute("label", &label);
42       if (!has_value) {
43         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
44                       << entries_index << ", label='" << label
45                       << "'): No 'value' attribute.";
46         success = false;
47       }
48       if (!has_label) {
49         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
50                       << entries_index << ", value_str='" << value_str
51                       << "'): No 'label' attribute.";
52         success = false;
53       }
54 
55       HistogramBase::Sample value;
56       if (has_value && !StringToInt(value_str, &value)) {
57         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
58                       << entries_index << ", label='" << label
59                       << "', value_str='" << value_str
60                       << "'): 'value' attribute is not integer.";
61         success = false;
62       }
63       if (result.count(value)) {
64         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
65                       << entries_index << ", label='" << label
66                       << "', value_str='" << value_str
67                       << "'): duplicate value '" << value_str
68                       << "' found in enum. The previous one has label='"
69                       << result[value] << "'.";
70         success = false;
71       }
72       if (success)
73         result[value] = label;
74     }
75     // All enum entries are on the same level, so it is enough to iterate
76     // until possible.
77     reader->Next();
78   }
79   if (success)
80     return result;
81   return std::nullopt;
82 }
83 
84 }  // namespace
85 
ReadEnumFromEnumsXml(const std::string & enum_name,const std::optional<std::string> & subdirectory)86 std::optional<HistogramEnumEntryMap> ReadEnumFromEnumsXml(
87     const std::string& enum_name,
88     const std::optional<std::string>& subdirectory) {
89   FilePath src_root;
90   if (!PathService::Get(DIR_SRC_TEST_DATA_ROOT, &src_root)) {
91     ADD_FAILURE() << "Failed to get src root.";
92     return std::nullopt;
93   }
94 
95   base::FilePath enums_xml =
96       src_root.AppendASCII("tools").AppendASCII("metrics").AppendASCII(
97           "histograms");
98   if (subdirectory) {
99     enums_xml =
100         enums_xml.AppendASCII("metadata").AppendASCII(subdirectory.value());
101   }
102   enums_xml = enums_xml.AppendASCII("enums.xml");
103 
104   if (!PathExists(enums_xml)) {
105     ADD_FAILURE() << "enums.xml file does not exist.";
106     return std::nullopt;
107   }
108 
109   XmlReader enums_xml_reader;
110   if (!enums_xml_reader.LoadFile(enums_xml.MaybeAsASCII())) {
111     ADD_FAILURE() << "Failed to load enums.xml";
112     return std::nullopt;
113   }
114 
115   std::optional<HistogramEnumEntryMap> result;
116 
117   // Implement simple depth first search.
118   while (true) {
119     const std::string node_name = enums_xml_reader.NodeName();
120     if (node_name == "enum") {
121       std::string name;
122       if (enums_xml_reader.NodeAttribute("name", &name) && name == enum_name) {
123         if (result.has_value()) {
124           ADD_FAILURE() << "Duplicate enum '" << enum_name
125                         << "' found in enums.xml";
126           return std::nullopt;
127         }
128 
129         const bool got_into_enum = enums_xml_reader.Read();
130         if (!got_into_enum) {
131           ADD_FAILURE() << "Bad enum '" << enum_name
132                         << "' (looks empty) found in enums.xml.";
133           return std::nullopt;
134         }
135 
136         result = ParseEnumFromHistogramsXml(enum_name, &enums_xml_reader);
137         if (!result.has_value()) {
138           ADD_FAILURE() << "Bad enum '" << enum_name
139                         << "' found in histograms.xml (format error).";
140           return std::nullopt;
141         }
142       }
143     }
144     // Go deeper if possible (stops at the closing tag of the deepest node).
145     if (enums_xml_reader.Read())
146       continue;
147 
148     // Try next node on the same level (skips closing tag).
149     if (enums_xml_reader.Next())
150       continue;
151 
152     // Go up until next node on the same level exists.
153     while (enums_xml_reader.Depth() && !enums_xml_reader.SkipToElement()) {
154     }
155 
156     // Reached top. histograms.xml consists of the single top level node
157     // 'histogram-configuration', so this is the end.
158     if (!enums_xml_reader.Depth())
159       break;
160   }
161   return result;
162 }
163 
164 }  // namespace base
165