1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/about_flags.h"
#include <stddef.h>
#include <map>
#include <set>
#include <string>
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/format_macros.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/flags_ui/feature_entry.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libxml/chromium/libxml_utils.h"
namespace about_flags {
namespace {
typedef base::HistogramBase::Sample Sample;
typedef std::map<std::string, Sample> SwitchToIdMap;
// This is a helper function to the ReadEnumFromHistogramsXml().
// Extracts single enum (with integer values) from histograms.xml.
// Expects |reader| to point at given enum.
// Returns map { value => label }.
// Returns empty map on error.
std::map<Sample, std::string> ParseEnumFromHistogramsXml(
const std::string& enum_name,
XmlReader* reader) {
int entries_index = -1;
std::map<Sample, std::string> result;
bool success = true;
while (true) {
const std::string node_name = reader->NodeName();
if (node_name == "enum" && reader->IsClosingElement())
break;
if (node_name == "int") {
++entries_index;
std::string value_str;
std::string label;
const bool has_value = reader->NodeAttribute("value", &value_str);
const bool has_label = reader->NodeAttribute("label", &label);
if (!has_value) {
ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
<< entries_index << ", label='" << label
<< "'): No 'value' attribute.";
success = false;
}
if (!has_label) {
ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
<< entries_index << ", value_str='" << value_str
<< "'): No 'label' attribute.";
success = false;
}
Sample value;
if (has_value && !base::StringToInt(value_str, &value)) {
ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
<< entries_index << ", label='" << label
<< "', value_str='" << value_str
<< "'): 'value' attribute is not integer.";
success = false;
}
if (result.count(value)) {
ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
<< entries_index << ", label='" << label
<< "', value_str='" << value_str
<< "'): duplicate value '" << value_str
<< "' found in enum. The previous one has label='"
<< result[value] << "'.";
success = false;
}
if (success) {
result[value] = label;
}
}
// All enum entries are on the same level, so it is enough to iterate
// until possible.
reader->Next();
}
return (success ? result : std::map<Sample, std::string>());
}
// Find and read given enum (with integer values) from histograms.xml.
// |enum_name| - enum name.
// |histograms_xml| - must be loaded histograms.xml file.
//
// Returns map { value => label } so that:
// <int value="9" label="enable-pinch-virtual-viewport"/>
// becomes:
// { 9 => "enable-pinch-virtual-viewport" }
// Returns empty map on error.
std::map<Sample, std::string> ReadEnumFromHistogramsXml(
const std::string& enum_name,
XmlReader* histograms_xml) {
std::map<Sample, std::string> login_custom_flags;
// Implement simple depth first search.
while (true) {
const std::string node_name = histograms_xml->NodeName();
if (node_name == "enum") {
std::string name;
if (histograms_xml->NodeAttribute("name", &name) && name == enum_name) {
if (!login_custom_flags.empty()) {
EXPECT_TRUE(login_custom_flags.empty())
<< "Duplicate enum '" << enum_name << "' found in histograms.xml";
return std::map<Sample, std::string>();
}
const bool got_into_enum = histograms_xml->Read();
if (got_into_enum) {
login_custom_flags =
ParseEnumFromHistogramsXml(enum_name, histograms_xml);
EXPECT_FALSE(login_custom_flags.empty())
<< "Bad enum '" << enum_name
<< "' found in histograms.xml (format error).";
} else {
EXPECT_TRUE(got_into_enum)
<< "Bad enum '" << enum_name
<< "' (looks empty) found in histograms.xml.";
}
if (login_custom_flags.empty())
return std::map<Sample, std::string>();
}
}
// Go deeper if possible (stops at the closing tag of the deepest node).
if (histograms_xml->Read())
continue;
// Try next node on the same level (skips closing tag).
if (histograms_xml->Next())
continue;
// Go up until next node on the same level exists.
while (histograms_xml->Depth() && !histograms_xml->SkipToElement()) {
}
// Reached top. histograms.xml consists of the single top level node
// 'histogram-configuration', so this is the end.
if (!histograms_xml->Depth())
break;
}
EXPECT_FALSE(login_custom_flags.empty())
<< "Enum '" << enum_name << "' is not found in histograms.xml.";
return login_custom_flags;
}
std::string FilePathStringTypeToString(const base::FilePath::StringType& path) {
#if defined(OS_WIN)
return base::UTF16ToUTF8(path);
#else
return path;
#endif
}
// Get all associated switches corresponding to defined about_flags.cc entries.
// Does not include information about FEATURE_VALUE entries.
std::set<std::string> GetAllSwitchesForTesting() {
std::set<std::string> result;
size_t num_entries = 0;
const flags_ui::FeatureEntry* entries =
testing::GetFeatureEntries(&num_entries);
for (size_t i = 0; i < num_entries; ++i) {
const flags_ui::FeatureEntry& entry = entries[i];
switch (entry.type) {
case flags_ui::FeatureEntry::SINGLE_VALUE:
case flags_ui::FeatureEntry::SINGLE_DISABLE_VALUE:
result.insert(entry.command_line_switch);
break;
case flags_ui::FeatureEntry::MULTI_VALUE:
for (int j = 0; j < entry.num_choices; ++j) {
result.insert(entry.choices[j].command_line_switch);
}
break;
case flags_ui::FeatureEntry::ENABLE_DISABLE_VALUE:
result.insert(entry.command_line_switch);
result.insert(entry.disable_command_line_switch);
break;
case flags_ui::FeatureEntry::FEATURE_VALUE:
break;
}
}
return result;
}
} // anonymous namespace
// Makes sure there are no separators in any of the entry names.
TEST(AboutFlagsTest, NoSeparators) {
size_t count;
const flags_ui::FeatureEntry* entries = testing::GetFeatureEntries(&count);
for (size_t i = 0; i < count; ++i) {
std::string name = entries[i].internal_name;
EXPECT_EQ(std::string::npos, name.find(flags_ui::testing::kMultiSeparator))
<< i;
}
}
class AboutFlagsHistogramTest : public ::testing::Test {
protected:
// This is a helper function to check that all IDs in enum LoginCustomFlags in
// histograms.xml are unique.
void SetSwitchToHistogramIdMapping(const std::string& switch_name,
const Sample switch_histogram_id,
std::map<std::string, Sample>* out_map) {
const std::pair<std::map<std::string, Sample>::iterator, bool> status =
out_map->insert(std::make_pair(switch_name, switch_histogram_id));
if (!status.second) {
EXPECT_TRUE(status.first->second == switch_histogram_id)
<< "Duplicate switch '" << switch_name
<< "' found in enum 'LoginCustomFlags' in histograms.xml.";
}
}
// This method generates a hint for the user for what string should be added
// to the enum LoginCustomFlags to make in consistent.
std::string GetHistogramEnumEntryText(const std::string& switch_name,
Sample value) {
return base::StringPrintf(
"<int value=\"%d\" label=\"%s\"/>", value, switch_name.c_str());
}
};
TEST_F(AboutFlagsHistogramTest, CheckHistograms) {
base::FilePath histograms_xml_file_path;
ASSERT_TRUE(
PathService::Get(base::DIR_SOURCE_ROOT, &histograms_xml_file_path));
histograms_xml_file_path = histograms_xml_file_path.AppendASCII("tools")
.AppendASCII("metrics")
.AppendASCII("histograms")
.AppendASCII("histograms.xml");
XmlReader histograms_xml;
ASSERT_TRUE(histograms_xml.LoadFile(
FilePathStringTypeToString(histograms_xml_file_path.value())));
std::map<Sample, std::string> login_custom_flags =
ReadEnumFromHistogramsXml("LoginCustomFlags", &histograms_xml);
ASSERT_TRUE(login_custom_flags.size())
<< "Error reading enum 'LoginCustomFlags' from histograms.xml.";
// Build reverse map {switch_name => id} from login_custom_flags.
SwitchToIdMap histograms_xml_switches_ids;
EXPECT_TRUE(login_custom_flags.count(testing::kBadSwitchFormatHistogramId))
<< "Entry for UMA ID of incorrect command-line flag is not found in "
"histograms.xml enum LoginCustomFlags. "
"Consider adding entry:\n"
<< " " << GetHistogramEnumEntryText("BAD_FLAG_FORMAT", 0);
// Check that all LoginCustomFlags entries have correct values.
for (const auto& entry : login_custom_flags) {
if (entry.first == testing::kBadSwitchFormatHistogramId) {
// Add error value with empty name.
SetSwitchToHistogramIdMapping(std::string(), entry.first,
&histograms_xml_switches_ids);
continue;
}
const Sample uma_id = GetSwitchUMAId(entry.second);
EXPECT_EQ(uma_id, entry.first)
<< "histograms.xml enum LoginCustomFlags "
"entry '" << entry.second << "' has incorrect value=" << entry.first
<< ", but " << uma_id << " is expected. Consider changing entry to:\n"
<< " " << GetHistogramEnumEntryText(entry.second, uma_id);
SetSwitchToHistogramIdMapping(entry.second, entry.first,
&histograms_xml_switches_ids);
}
// Check that all flags in about_flags.cc have entries in login_custom_flags.
std::set<std::string> all_switches = GetAllSwitchesForTesting();
for (const std::string& flag : all_switches) {
// Skip empty placeholders.
if (flag.empty())
continue;
const Sample uma_id = GetSwitchUMAId(flag);
EXPECT_NE(testing::kBadSwitchFormatHistogramId, uma_id)
<< "Command-line switch '" << flag
<< "' from about_flags.cc has UMA ID equal to reserved value "
"kBadSwitchFormatHistogramId="
<< testing::kBadSwitchFormatHistogramId
<< ". Please modify switch name.";
SwitchToIdMap::iterator enum_entry =
histograms_xml_switches_ids.lower_bound(flag);
// Ignore case here when switch ID is incorrect - it has already been
// reported in the previous loop.
EXPECT_TRUE(enum_entry != histograms_xml_switches_ids.end() &&
enum_entry->first == flag)
<< "histograms.xml enum LoginCustomFlags doesn't contain switch '"
<< flag << "' (value=" << uma_id
<< " expected). Consider adding entry:\n"
<< " " << GetHistogramEnumEntryText(flag, uma_id);
}
}
} // namespace about_flags
|