OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 #include <stdint.h> |
| 6 |
| 7 #include "base/files/file_path.h" |
| 8 #include "base/memory/scoped_vector.h" |
| 9 #include "base/path_service.h" |
5 #include "base/prefs/pref_registry_simple.h" | 10 #include "base/prefs/pref_registry_simple.h" |
6 #include "base/prefs/testing_pref_service.h" | 11 #include "base/prefs/testing_pref_service.h" |
7 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" |
| 13 #include "base/strings/stringprintf.h" |
8 #include "base/strings/utf_string_conversions.h" | 14 #include "base/strings/utf_string_conversions.h" |
9 #include "base/values.h" | 15 #include "base/values.h" |
10 #include "chrome/browser/about_flags.h" | 16 #include "chrome/browser/about_flags.h" |
11 #include "chrome/browser/pref_service_flags_storage.h" | 17 #include "chrome/browser/pref_service_flags_storage.h" |
12 #include "chrome/common/chrome_switches.h" | 18 #include "chrome/common/chrome_switches.h" |
13 #include "chrome/common/pref_names.h" | 19 #include "chrome/common/pref_names.h" |
14 #include "grit/chromium_strings.h" | 20 #include "grit/chromium_strings.h" |
15 #include "testing/gtest/include/gtest/gtest.h" | 21 #include "testing/gtest/include/gtest/gtest.h" |
| 22 #include "third_party/libxml/chromium/libxml_utils.h" |
| 23 |
| 24 namespace { |
16 | 25 |
17 const char kFlags1[] = "flag1"; | 26 const char kFlags1[] = "flag1"; |
18 const char kFlags2[] = "flag2"; | 27 const char kFlags2[] = "flag2"; |
19 const char kFlags3[] = "flag3"; | 28 const char kFlags3[] = "flag3"; |
20 const char kFlags4[] = "flag4"; | 29 const char kFlags4[] = "flag4"; |
21 const char kFlags5[] = "flag5"; | 30 const char kFlags5[] = "flag5"; |
22 | 31 |
23 const char kSwitch1[] = "switch"; | 32 const char kSwitch1[] = "switch"; |
24 const char kSwitch2[] = "switch2"; | 33 const char kSwitch2[] = "switch2"; |
25 const char kSwitch3[] = "switch3"; | 34 const char kSwitch3[] = "switch3"; |
26 const char kValueForSwitch2[] = "value_for_switch2"; | 35 const char kValueForSwitch2[] = "value_for_switch2"; |
27 | 36 |
28 const char kMultiSwitch1[] = "multi_switch1"; | 37 const char kMultiSwitch1[] = "multi_switch1"; |
29 const char kMultiSwitch2[] = "multi_switch2"; | 38 const char kMultiSwitch2[] = "multi_switch2"; |
30 const char kValueForMultiSwitch2[] = "value_for_multi_switch2"; | 39 const char kValueForMultiSwitch2[] = "value_for_multi_switch2"; |
31 | 40 |
32 const char kEnableDisableValue1[] = "value1"; | 41 const char kEnableDisableValue1[] = "value1"; |
33 const char kEnableDisableValue2[] = "value2"; | 42 const char kEnableDisableValue2[] = "value2"; |
34 | 43 |
| 44 typedef std::map<std::string, uint32_t> SwitchToIdMap; |
| 45 |
| 46 // This is a helper function to the ReadEnumFromHistogramsXml(). |
| 47 // Extracts single enum (with integer values) from histograms.xml. |
| 48 // Expects |reader| to point at given enum. |
| 49 // Returns map { value => label }. |
| 50 // Returns empty map on error. |
| 51 std::map<uint32_t, std::string> ParseEnumFromHistogramsXml( |
| 52 const std::string& enum_name, |
| 53 XmlReader* reader) { |
| 54 int entries_index = -1; |
| 55 |
| 56 std::map<uint32_t, std::string> result; |
| 57 bool success = true; |
| 58 |
| 59 while (true) { |
| 60 const std::string node_name = reader->NodeName(); |
| 61 if (node_name == "enum" && reader->IsClosingElement()) |
| 62 break; |
| 63 |
| 64 if (node_name == "int") { |
| 65 ++entries_index; |
| 66 std::string value_str; |
| 67 std::string label; |
| 68 const bool has_value = reader->NodeAttribute("value", &value_str); |
| 69 const bool has_label = reader->NodeAttribute("label", &label); |
| 70 if (!has_value) { |
| 71 ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index " |
| 72 << entries_index << ", label='" << label |
| 73 << "'): No 'value' attribute."; |
| 74 success = false; |
| 75 } |
| 76 if (!has_label) { |
| 77 ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index " |
| 78 << entries_index << ", value_str='" << value_str |
| 79 << "'): No 'label' attribute."; |
| 80 success = false; |
| 81 } |
| 82 |
| 83 uint32_t value; |
| 84 if (has_value && !base::StringToUint(value_str, &value)) { |
| 85 ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index " |
| 86 << entries_index << ", label='" << label |
| 87 << "', value_str='" << value_str |
| 88 << "'): 'value' attribute is not integer."; |
| 89 success = false; |
| 90 } |
| 91 if (result.count(value)) { |
| 92 ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index " |
| 93 << entries_index << ", label='" << label |
| 94 << "', value_str='" << value_str |
| 95 << "'): duplicate value '" << value_str |
| 96 << "' found in enum. The previous one has label='" |
| 97 << result[value] << "'."; |
| 98 success = false; |
| 99 } |
| 100 if (success) { |
| 101 result[value] = label; |
| 102 } |
| 103 } |
| 104 // All enum entries are on the same level, so it is enough to iterate |
| 105 // until possible. |
| 106 reader->Next(); |
| 107 } |
| 108 return (success ? result : std::map<uint32_t, std::string>()); |
| 109 } |
| 110 |
| 111 // Find and read given enum (with integer values) from histograms.xml. |
| 112 // |enum_name| - enum name. |
| 113 // |histograms_xml| - must be loaded histograms.xml file. |
| 114 // |
| 115 // Returns map { value => label } so that: |
| 116 // <int value="9" label="enable-pinch-virtual-viewport"/> |
| 117 // becomes: |
| 118 // { 9 => "enable-pinch-virtual-viewport" } |
| 119 // Returns empty map on error. |
| 120 std::map<uint32_t, std::string> ReadEnumFromHistogramsXml( |
| 121 const std::string& enum_name, |
| 122 XmlReader* histograms_xml) { |
| 123 std::map<uint32_t, std::string> login_custom_flags; |
| 124 |
| 125 // Implement simple depth first search. |
| 126 while (true) { |
| 127 const std::string node_name = histograms_xml->NodeName(); |
| 128 if (node_name == "enum") { |
| 129 std::string name; |
| 130 if (histograms_xml->NodeAttribute("name", &name) && name == enum_name) { |
| 131 if (!login_custom_flags.empty()) { |
| 132 EXPECT_TRUE(login_custom_flags.empty()) |
| 133 << "Duplicate enum '" << enum_name << "' found in histograms.xml"; |
| 134 return std::map<uint32_t, std::string>(); |
| 135 } |
| 136 |
| 137 const bool got_into_enum = histograms_xml->Read(); |
| 138 if (got_into_enum) { |
| 139 login_custom_flags = |
| 140 ParseEnumFromHistogramsXml(enum_name, histograms_xml); |
| 141 EXPECT_FALSE(login_custom_flags.empty()) |
| 142 << "Bad enum '" << enum_name |
| 143 << "' found in histograms.xml (format error)."; |
| 144 } else { |
| 145 EXPECT_TRUE(got_into_enum) |
| 146 << "Bad enum '" << enum_name |
| 147 << "' (looks empty) found in histograms.xml."; |
| 148 } |
| 149 if (login_custom_flags.empty()) |
| 150 return std::map<uint32_t, std::string>(); |
| 151 } |
| 152 } |
| 153 // Go deeper if possible (stops at the closing tag of the deepest node). |
| 154 if (histograms_xml->Read()) |
| 155 continue; |
| 156 |
| 157 // Try next node on the same level (skips closing tag). |
| 158 if (histograms_xml->Next()) |
| 159 continue; |
| 160 |
| 161 // Go up until next node on the same level exists. |
| 162 while (histograms_xml->Depth() && !histograms_xml->SkipToElement()) { |
| 163 } |
| 164 |
| 165 // Reached top. histograms.xml consists of the single top level node |
| 166 // 'histogram-configuration', so this is the end. |
| 167 if (!histograms_xml->Depth()) |
| 168 break; |
| 169 } |
| 170 EXPECT_FALSE(login_custom_flags.empty()) |
| 171 << "Enum '" << enum_name << "' is not found in histograms.xml."; |
| 172 return login_custom_flags; |
| 173 } |
| 174 |
| 175 std::string FilePathStringTypeToString(const base::FilePath::StringType& path) { |
| 176 #if defined(OS_WIN) |
| 177 return base::UTF16ToUTF8(path); |
| 178 #else |
| 179 return path; |
| 180 #endif |
| 181 } |
| 182 |
| 183 std::set<std::string> GetAllSwitchesForTesting() { |
| 184 std::set<std::string> result; |
| 185 |
| 186 size_t num_experiments = 0; |
| 187 const about_flags::Experiment* experiments = |
| 188 about_flags::testing::GetExperiments(&num_experiments); |
| 189 |
| 190 for (size_t i = 0; i < num_experiments; ++i) { |
| 191 const about_flags::Experiment& experiment = experiments[i]; |
| 192 if (experiment.type == about_flags::Experiment::SINGLE_VALUE) { |
| 193 result.insert(experiment.command_line_switch); |
| 194 } else if (experiment.type == about_flags::Experiment::MULTI_VALUE) { |
| 195 for (int j = 0; j < experiment.num_choices; ++j) { |
| 196 result.insert(experiment.choices[j].command_line_switch); |
| 197 } |
| 198 } else { |
| 199 DCHECK_EQ(experiment.type, about_flags::Experiment::ENABLE_DISABLE_VALUE); |
| 200 result.insert(experiment.command_line_switch); |
| 201 result.insert(experiment.disable_command_line_switch); |
| 202 } |
| 203 } |
| 204 return result; |
| 205 } |
| 206 |
| 207 } // anonymous namespace |
| 208 |
35 namespace about_flags { | 209 namespace about_flags { |
36 | 210 |
37 const Experiment::Choice kMultiChoices[] = { | 211 const Experiment::Choice kMultiChoices[] = { |
38 { IDS_PRODUCT_NAME, "", "" }, | 212 { IDS_PRODUCT_NAME, "", "" }, |
39 { IDS_PRODUCT_NAME, kMultiSwitch1, "" }, | 213 { IDS_PRODUCT_NAME, kMultiSwitch1, "" }, |
40 { IDS_PRODUCT_NAME, kMultiSwitch2, kValueForMultiSwitch2 }, | 214 { IDS_PRODUCT_NAME, kMultiSwitch2, kValueForMultiSwitch2 }, |
41 }; | 215 }; |
42 | 216 |
43 // The experiments that are set for these tests. The 3rd experiment is not | 217 // The experiments that are set for these tests. The 3rd experiment is not |
44 // supported on the current platform, all others are. | 218 // supported on the current platform, all others are. |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
226 | 400 |
227 CommandLine command_line2(CommandLine::NO_PROGRAM); | 401 CommandLine command_line2(CommandLine::NO_PROGRAM); |
228 | 402 |
229 ConvertFlagsToSwitches(&flags_storage_, &command_line2, kNoSentinels); | 403 ConvertFlagsToSwitches(&flags_storage_, &command_line2, kNoSentinels); |
230 | 404 |
231 EXPECT_TRUE(command_line2.HasSwitch(kSwitch1)); | 405 EXPECT_TRUE(command_line2.HasSwitch(kSwitch1)); |
232 EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesBegin)); | 406 EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesBegin)); |
233 EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesEnd)); | 407 EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesEnd)); |
234 } | 408 } |
235 | 409 |
| 410 CommandLine::StringType CreateSwitch(const std::string& value) { |
| 411 #if defined(OS_WIN) |
| 412 return base::ASCIIToUTF16(value); |
| 413 #else |
| 414 return value; |
| 415 #endif |
| 416 } |
| 417 |
236 TEST_F(AboutFlagsTest, CompareSwitchesToCurrentCommandLine) { | 418 TEST_F(AboutFlagsTest, CompareSwitchesToCurrentCommandLine) { |
237 SetExperimentEnabled(&flags_storage_, kFlags1, true); | 419 SetExperimentEnabled(&flags_storage_, kFlags1, true); |
238 | 420 |
| 421 const std::string kDoubleDash("--"); |
| 422 |
239 CommandLine command_line(CommandLine::NO_PROGRAM); | 423 CommandLine command_line(CommandLine::NO_PROGRAM); |
240 command_line.AppendSwitch("foo"); | 424 command_line.AppendSwitch("foo"); |
241 | 425 |
242 CommandLine new_command_line(CommandLine::NO_PROGRAM); | 426 CommandLine new_command_line(CommandLine::NO_PROGRAM); |
243 ConvertFlagsToSwitches(&flags_storage_, &new_command_line, kAddSentinels); | 427 ConvertFlagsToSwitches(&flags_storage_, &new_command_line, kAddSentinels); |
244 | 428 |
245 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(new_command_line, | 429 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine( |
246 command_line)); | 430 new_command_line, command_line, NULL)); |
| 431 { |
| 432 std::set<CommandLine::StringType> difference; |
| 433 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine( |
| 434 new_command_line, command_line, &difference)); |
| 435 EXPECT_EQ(1U, difference.size()); |
| 436 EXPECT_EQ(1U, difference.count(CreateSwitch(kDoubleDash + kSwitch1))); |
| 437 } |
247 | 438 |
248 ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels); | 439 ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels); |
249 | 440 |
250 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(new_command_line, | 441 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine( |
251 command_line)); | 442 new_command_line, command_line, NULL)); |
| 443 { |
| 444 std::set<CommandLine::StringType> difference; |
| 445 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine( |
| 446 new_command_line, command_line, &difference)); |
| 447 EXPECT_TRUE(difference.empty()); |
| 448 } |
252 | 449 |
253 // Now both have flags but different. | 450 // Now both have flags but different. |
254 SetExperimentEnabled(&flags_storage_, kFlags1, false); | 451 SetExperimentEnabled(&flags_storage_, kFlags1, false); |
255 SetExperimentEnabled(&flags_storage_, kFlags2, true); | 452 SetExperimentEnabled(&flags_storage_, kFlags2, true); |
256 | 453 |
257 CommandLine another_command_line(CommandLine::NO_PROGRAM); | 454 CommandLine another_command_line(CommandLine::NO_PROGRAM); |
258 ConvertFlagsToSwitches(&flags_storage_, &another_command_line, kAddSentinels); | 455 ConvertFlagsToSwitches(&flags_storage_, &another_command_line, kAddSentinels); |
259 | 456 |
260 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(new_command_line, | 457 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine( |
261 another_command_line)); | 458 new_command_line, another_command_line, NULL)); |
| 459 { |
| 460 std::set<CommandLine::StringType> difference; |
| 461 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine( |
| 462 new_command_line, another_command_line, &difference)); |
| 463 EXPECT_EQ(2U, difference.size()); |
| 464 EXPECT_EQ(1U, difference.count(CreateSwitch(kDoubleDash + kSwitch1))); |
| 465 EXPECT_EQ(1U, |
| 466 difference.count(CreateSwitch(kDoubleDash + kSwitch2 + "=" + |
| 467 kValueForSwitch2))); |
| 468 } |
262 } | 469 } |
263 | 470 |
264 TEST_F(AboutFlagsTest, RemoveFlagSwitches) { | 471 TEST_F(AboutFlagsTest, RemoveFlagSwitches) { |
265 std::map<std::string, CommandLine::StringType> switch_list; | 472 std::map<std::string, CommandLine::StringType> switch_list; |
266 switch_list[kSwitch1] = CommandLine::StringType(); | 473 switch_list[kSwitch1] = CommandLine::StringType(); |
267 switch_list[switches::kFlagSwitchesBegin] = CommandLine::StringType(); | 474 switch_list[switches::kFlagSwitchesBegin] = CommandLine::StringType(); |
268 switch_list[switches::kFlagSwitchesEnd] = CommandLine::StringType(); | 475 switch_list[switches::kFlagSwitchesEnd] = CommandLine::StringType(); |
269 switch_list["foo"] = CommandLine::StringType(); | 476 switch_list["foo"] = CommandLine::StringType(); |
270 | 477 |
271 SetExperimentEnabled(&flags_storage_, kFlags1, true); | 478 SetExperimentEnabled(&flags_storage_, kFlags1, true); |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
457 TEST_F(AboutFlagsTest, NoSeparators) { | 664 TEST_F(AboutFlagsTest, NoSeparators) { |
458 testing::SetExperiments(NULL, 0); | 665 testing::SetExperiments(NULL, 0); |
459 size_t count; | 666 size_t count; |
460 const Experiment* experiments = testing::GetExperiments(&count); | 667 const Experiment* experiments = testing::GetExperiments(&count); |
461 for (size_t i = 0; i < count; ++i) { | 668 for (size_t i = 0; i < count; ++i) { |
462 std::string name = experiments->internal_name; | 669 std::string name = experiments->internal_name; |
463 EXPECT_EQ(std::string::npos, name.find(testing::kMultiSeparator)) << i; | 670 EXPECT_EQ(std::string::npos, name.find(testing::kMultiSeparator)) << i; |
464 } | 671 } |
465 } | 672 } |
466 | 673 |
| 674 class AboutFlagsHistogramTest : public ::testing::Test { |
| 675 protected: |
| 676 // This is a helper function to check that all IDs in enum LoginCustomFlags in |
| 677 // histograms.xml are unique. |
| 678 void SetSwitchToHistogramIdMapping(const std::string& switch_name, |
| 679 const uint32_t switch_histogram_id, |
| 680 std::map<std::string, uint32_t>* out_map) { |
| 681 const std::pair<std::map<std::string, uint32_t>::iterator, bool> status = |
| 682 out_map->insert(std::make_pair(switch_name, switch_histogram_id)); |
| 683 if (!status.second) { |
| 684 EXPECT_TRUE(status.first->second == switch_histogram_id) |
| 685 << "Duplicate switch '" << switch_name |
| 686 << "' found in enum 'LoginCustomFlags' in histograms.xml."; |
| 687 } |
| 688 } |
| 689 |
| 690 // This method generates a hint for the user for what string should be added |
| 691 // to the enum LoginCustomFlags to make in consistent. |
| 692 std::string GetHistogramEnumEntryText(const std::string& switch_name, |
| 693 uint32_t value) { |
| 694 return base::StringPrintf( |
| 695 "<int value=\"%u\" label=\"%s\"/>", value, switch_name.c_str()); |
| 696 } |
| 697 }; |
| 698 |
| 699 TEST_F(AboutFlagsHistogramTest, CheckHistograms) { |
| 700 base::FilePath histograms_xml_file_path; |
| 701 ASSERT_TRUE( |
| 702 PathService::Get(base::DIR_SOURCE_ROOT, &histograms_xml_file_path)); |
| 703 histograms_xml_file_path = histograms_xml_file_path.AppendASCII("tools") |
| 704 .AppendASCII("metrics") |
| 705 .AppendASCII("histograms") |
| 706 .AppendASCII("histograms.xml"); |
| 707 |
| 708 XmlReader histograms_xml; |
| 709 ASSERT_TRUE(histograms_xml.LoadFile( |
| 710 FilePathStringTypeToString(histograms_xml_file_path.value()))); |
| 711 std::map<uint32_t, std::string> login_custom_flags = |
| 712 ReadEnumFromHistogramsXml("LoginCustomFlags", &histograms_xml); |
| 713 ASSERT_TRUE(login_custom_flags.size()) |
| 714 << "Error reading enum 'LoginCustomFlags' from histograms.xml."; |
| 715 |
| 716 // Build reverse map {switch_name => id} from login_custom_flags. |
| 717 SwitchToIdMap histograms_xml_switches_ids; |
| 718 |
| 719 EXPECT_TRUE(login_custom_flags.count(kBadSwitchFormatHistogramId)) |
| 720 << "Entry for UMA ID of incorrect command-line flag is not found in " |
| 721 "histograms.xml enum LoginCustomFlags. " |
| 722 "Consider adding entry:\n" |
| 723 << " " << GetHistogramEnumEntryText("BAD_FLAG_FORMAT", 0); |
| 724 // Check that all LoginCustomFlags entries have correct values. |
| 725 for (std::map<uint32_t, std::string>::const_iterator it = |
| 726 login_custom_flags.begin(); |
| 727 it != login_custom_flags.end(); |
| 728 ++it) { |
| 729 if (it->first == kBadSwitchFormatHistogramId) { |
| 730 // Add eror value with empty name. |
| 731 SetSwitchToHistogramIdMapping( |
| 732 "", it->first, &histograms_xml_switches_ids); |
| 733 continue; |
| 734 } |
| 735 const uint32_t uma_id = GetSwitchUMAId(it->second); |
| 736 EXPECT_EQ(uma_id, it->first) |
| 737 << "histograms.xml enum LoginCustomFlags " |
| 738 "entry '" << it->second << "' has incorrect value=" << it->first |
| 739 << ", but " << uma_id << " is expected. Consider changing entry to:\n" |
| 740 << " " << GetHistogramEnumEntryText(it->second, uma_id); |
| 741 SetSwitchToHistogramIdMapping( |
| 742 it->second, it->first, &histograms_xml_switches_ids); |
| 743 } |
| 744 |
| 745 // Check that all flags in about_flags.cc have entries in login_custom_flags. |
| 746 std::set<std::string> all_switches = GetAllSwitchesForTesting(); |
| 747 for (std::set<std::string>::const_iterator it = all_switches.begin(); |
| 748 it != all_switches.end(); |
| 749 ++it) { |
| 750 // Skip empty placeholders. |
| 751 if (it->empty()) |
| 752 continue; |
| 753 const uint32_t uma_id = GetSwitchUMAId(*it); |
| 754 EXPECT_NE(kBadSwitchFormatHistogramId, uma_id) |
| 755 << "Command-line switch '" << *it |
| 756 << "' from about_flags.cc has UMA ID equal to reserved value " |
| 757 "kBadSwitchFormatHistogramId=" << kBadSwitchFormatHistogramId |
| 758 << ". Please modify switch name."; |
| 759 SwitchToIdMap::iterator enum_entry = |
| 760 histograms_xml_switches_ids.lower_bound(*it); |
| 761 |
| 762 // Ignore case here when switch ID is incorrect - it has already been |
| 763 // reported in the previous loop. |
| 764 EXPECT_TRUE(enum_entry != histograms_xml_switches_ids.end() && |
| 765 enum_entry->first == *it) |
| 766 << "histograms.xml enum LoginCustomFlags doesn't contain switch '" |
| 767 << *it << "' (value=" << uma_id |
| 768 << " expected). Consider adding entry:\n" |
| 769 << " " << GetHistogramEnumEntryText(*it, uma_id); |
| 770 } |
| 771 } |
| 772 |
467 } // namespace about_flags | 773 } // namespace about_flags |
OLD | NEW |