// 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 #include #include #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 "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 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 ParseEnumFromHistogramsXml( const std::string& enum_name, XmlReader* reader) { int entries_index = -1; std::map 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()); } // 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: // // becomes: // { 9 => "enable-pinch-virtual-viewport" } // Returns empty map on error. std::map ReadEnumFromHistogramsXml( const std::string& enum_name, XmlReader* histograms_xml) { std::map 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(); } 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(); } } // 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 GetAllSwitchesForTesting() { std::set 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* out_map) { const std::pair::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( "", 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 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 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