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