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