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