Home | History | Annotate | Download | only in browser
      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
      3 // found in the LICENSE file.
      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"
     10 #include "base/prefs/pref_registry_simple.h"
     11 #include "base/prefs/testing_pref_service.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/strings/stringprintf.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "base/values.h"
     16 #include "chrome/browser/about_flags.h"
     17 #include "chrome/browser/pref_service_flags_storage.h"
     18 #include "chrome/common/chrome_switches.h"
     19 #include "chrome/common/pref_names.h"
     20 #include "chrome/grit/chromium_strings.h"
     21 #include "testing/gtest/include/gtest/gtest.h"
     22 #include "third_party/libxml/chromium/libxml_utils.h"
     23 
     24 namespace {
     25 
     26 const char kFlags1[] = "flag1";
     27 const char kFlags2[] = "flag2";
     28 const char kFlags3[] = "flag3";
     29 const char kFlags4[] = "flag4";
     30 const char kFlags5[] = "flag5";
     31 
     32 const char kSwitch1[] = "switch";
     33 const char kSwitch2[] = "switch2";
     34 const char kSwitch3[] = "switch3";
     35 const char kValueForSwitch2[] = "value_for_switch2";
     36 
     37 const char kMultiSwitch1[] = "multi_switch1";
     38 const char kMultiSwitch2[] = "multi_switch2";
     39 const char kValueForMultiSwitch2[] = "value_for_multi_switch2";
     40 
     41 const char kEnableDisableValue1[] = "value1";
     42 const char kEnableDisableValue2[] = "value2";
     43 
     44 typedef base::HistogramBase::Sample Sample;
     45 typedef std::map<std::string, Sample> SwitchToIdMap;
     46 
     47 // This is a helper function to the ReadEnumFromHistogramsXml().
     48 // Extracts single enum (with integer values) from histograms.xml.
     49 // Expects |reader| to point at given enum.
     50 // Returns map { value => label }.
     51 // Returns empty map on error.
     52 std::map<Sample, std::string> ParseEnumFromHistogramsXml(
     53     const std::string& enum_name,
     54     XmlReader* reader) {
     55   int entries_index = -1;
     56 
     57   std::map<Sample, std::string> result;
     58   bool success = true;
     59 
     60   while (true) {
     61     const std::string node_name = reader->NodeName();
     62     if (node_name == "enum" && reader->IsClosingElement())
     63       break;
     64 
     65     if (node_name == "int") {
     66       ++entries_index;
     67       std::string value_str;
     68       std::string label;
     69       const bool has_value = reader->NodeAttribute("value", &value_str);
     70       const bool has_label = reader->NodeAttribute("label", &label);
     71       if (!has_value) {
     72         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
     73                       << entries_index << ", label='" << label
     74                       << "'): No 'value' attribute.";
     75         success = false;
     76       }
     77       if (!has_label) {
     78         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
     79                       << entries_index << ", value_str='" << value_str
     80                       << "'): No 'label' attribute.";
     81         success = false;
     82       }
     83 
     84       Sample value;
     85       if (has_value && !base::StringToInt(value_str, &value)) {
     86         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
     87                       << entries_index << ", label='" << label
     88                       << "', value_str='" << value_str
     89                       << "'): 'value' attribute is not integer.";
     90         success = false;
     91       }
     92       if (result.count(value)) {
     93         ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
     94                       << entries_index << ", label='" << label
     95                       << "', value_str='" << value_str
     96                       << "'): duplicate value '" << value_str
     97                       << "' found in enum. The previous one has label='"
     98                       << result[value] << "'.";
     99         success = false;
    100       }
    101       if (success) {
    102         result[value] = label;
    103       }
    104     }
    105     // All enum entries are on the same level, so it is enough to iterate
    106     // until possible.
    107     reader->Next();
    108   }
    109   return (success ? result : std::map<Sample, std::string>());
    110 }
    111 
    112 // Find and read given enum (with integer values) from histograms.xml.
    113 // |enum_name| - enum name.
    114 // |histograms_xml| - must be loaded histograms.xml file.
    115 //
    116 // Returns map { value => label } so that:
    117 //   <int value="9" label="enable-pinch-virtual-viewport"/>
    118 // becomes:
    119 //   { 9 => "enable-pinch-virtual-viewport" }
    120 // Returns empty map on error.
    121 std::map<Sample, std::string> ReadEnumFromHistogramsXml(
    122     const std::string& enum_name,
    123     XmlReader* histograms_xml) {
    124   std::map<Sample, std::string> login_custom_flags;
    125 
    126   // Implement simple depth first search.
    127   while (true) {
    128     const std::string node_name = histograms_xml->NodeName();
    129     if (node_name == "enum") {
    130       std::string name;
    131       if (histograms_xml->NodeAttribute("name", &name) && name == enum_name) {
    132         if (!login_custom_flags.empty()) {
    133           EXPECT_TRUE(login_custom_flags.empty())
    134               << "Duplicate enum '" << enum_name << "' found in histograms.xml";
    135           return std::map<Sample, std::string>();
    136         }
    137 
    138         const bool got_into_enum = histograms_xml->Read();
    139         if (got_into_enum) {
    140           login_custom_flags =
    141               ParseEnumFromHistogramsXml(enum_name, histograms_xml);
    142           EXPECT_FALSE(login_custom_flags.empty())
    143               << "Bad enum '" << enum_name
    144               << "' found in histograms.xml (format error).";
    145         } else {
    146           EXPECT_TRUE(got_into_enum)
    147               << "Bad enum '" << enum_name
    148               << "' (looks empty) found in histograms.xml.";
    149         }
    150         if (login_custom_flags.empty())
    151           return std::map<Sample, std::string>();
    152       }
    153     }
    154     // Go deeper if possible (stops at the closing tag of the deepest node).
    155     if (histograms_xml->Read())
    156       continue;
    157 
    158     // Try next node on the same level (skips closing tag).
    159     if (histograms_xml->Next())
    160       continue;
    161 
    162     // Go up until next node on the same level exists.
    163     while (histograms_xml->Depth() && !histograms_xml->SkipToElement()) {
    164     }
    165 
    166     // Reached top. histograms.xml consists of the single top level node
    167     // 'histogram-configuration', so this is the end.
    168     if (!histograms_xml->Depth())
    169       break;
    170   }
    171   EXPECT_FALSE(login_custom_flags.empty())
    172       << "Enum '" << enum_name << "' is not found in histograms.xml.";
    173   return login_custom_flags;
    174 }
    175 
    176 std::string FilePathStringTypeToString(const base::FilePath::StringType& path) {
    177 #if defined(OS_WIN)
    178   return base::UTF16ToUTF8(path);
    179 #else
    180   return path;
    181 #endif
    182 }
    183 
    184 std::set<std::string> GetAllSwitchesForTesting() {
    185   std::set<std::string> result;
    186 
    187   size_t num_experiments = 0;
    188   const about_flags::Experiment* experiments =
    189       about_flags::testing::GetExperiments(&num_experiments);
    190 
    191   for (size_t i = 0; i < num_experiments; ++i) {
    192     const about_flags::Experiment& experiment = experiments[i];
    193     if (experiment.type == about_flags::Experiment::SINGLE_VALUE) {
    194       result.insert(experiment.command_line_switch);
    195     } else if (experiment.type == about_flags::Experiment::MULTI_VALUE) {
    196       for (int j = 0; j < experiment.num_choices; ++j) {
    197         result.insert(experiment.choices[j].command_line_switch);
    198       }
    199     } else {
    200       DCHECK_EQ(experiment.type, about_flags::Experiment::ENABLE_DISABLE_VALUE);
    201       result.insert(experiment.command_line_switch);
    202       result.insert(experiment.disable_command_line_switch);
    203     }
    204   }
    205   return result;
    206 }
    207 
    208 }  // anonymous namespace
    209 
    210 namespace about_flags {
    211 
    212 const Experiment::Choice kMultiChoices[] = {
    213   { IDS_PRODUCT_NAME, "", "" },
    214   { IDS_PRODUCT_NAME, kMultiSwitch1, "" },
    215   { IDS_PRODUCT_NAME, kMultiSwitch2, kValueForMultiSwitch2 },
    216 };
    217 
    218 // The experiments that are set for these tests. The 3rd experiment is not
    219 // supported on the current platform, all others are.
    220 static Experiment kExperiments[] = {
    221   {
    222     kFlags1,
    223     IDS_PRODUCT_NAME,
    224     IDS_PRODUCT_NAME,
    225     0,  // Ends up being mapped to the current platform.
    226     Experiment::SINGLE_VALUE,
    227     kSwitch1,
    228     "",
    229     NULL,
    230     NULL,
    231     NULL,
    232     0
    233   },
    234   {
    235     kFlags2,
    236     IDS_PRODUCT_NAME,
    237     IDS_PRODUCT_NAME,
    238     0,  // Ends up being mapped to the current platform.
    239     Experiment::SINGLE_VALUE,
    240     kSwitch2,
    241     kValueForSwitch2,
    242     NULL,
    243     NULL,
    244     NULL,
    245     0
    246   },
    247   {
    248     kFlags3,
    249     IDS_PRODUCT_NAME,
    250     IDS_PRODUCT_NAME,
    251     0,  // This ends up enabling for an OS other than the current.
    252     Experiment::SINGLE_VALUE,
    253     kSwitch3,
    254     "",
    255     NULL,
    256     NULL,
    257     NULL,
    258     0
    259   },
    260   {
    261     kFlags4,
    262     IDS_PRODUCT_NAME,
    263     IDS_PRODUCT_NAME,
    264     0,  // Ends up being mapped to the current platform.
    265     Experiment::MULTI_VALUE,
    266     "",
    267     "",
    268     "",
    269     "",
    270     kMultiChoices,
    271     arraysize(kMultiChoices)
    272   },
    273   {
    274     kFlags5,
    275     IDS_PRODUCT_NAME,
    276     IDS_PRODUCT_NAME,
    277     0,  // Ends up being mapped to the current platform.
    278     Experiment::ENABLE_DISABLE_VALUE,
    279     kSwitch1,
    280     kEnableDisableValue1,
    281     kSwitch2,
    282     kEnableDisableValue2,
    283     NULL,
    284     3
    285   },
    286 };
    287 
    288 class AboutFlagsTest : public ::testing::Test {
    289  protected:
    290   AboutFlagsTest() : flags_storage_(&prefs_) {
    291     prefs_.registry()->RegisterListPref(prefs::kEnabledLabsExperiments);
    292     testing::ClearState();
    293   }
    294 
    295   virtual void SetUp() OVERRIDE {
    296     for (size_t i = 0; i < arraysize(kExperiments); ++i)
    297       kExperiments[i].supported_platforms = GetCurrentPlatform();
    298 
    299     int os_other_than_current = 1;
    300     while (os_other_than_current == GetCurrentPlatform())
    301       os_other_than_current <<= 1;
    302     kExperiments[2].supported_platforms = os_other_than_current;
    303 
    304     testing::SetExperiments(kExperiments, arraysize(kExperiments));
    305   }
    306 
    307   virtual void TearDown() OVERRIDE {
    308     testing::SetExperiments(NULL, 0);
    309   }
    310 
    311   TestingPrefServiceSimple prefs_;
    312   PrefServiceFlagsStorage flags_storage_;
    313 };
    314 
    315 
    316 TEST_F(AboutFlagsTest, NoChangeNoRestart) {
    317   EXPECT_FALSE(IsRestartNeededToCommitChanges());
    318   SetExperimentEnabled(&flags_storage_, kFlags1, false);
    319   EXPECT_FALSE(IsRestartNeededToCommitChanges());
    320 }
    321 
    322 TEST_F(AboutFlagsTest, ChangeNeedsRestart) {
    323   EXPECT_FALSE(IsRestartNeededToCommitChanges());
    324   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    325   EXPECT_TRUE(IsRestartNeededToCommitChanges());
    326 }
    327 
    328 TEST_F(AboutFlagsTest, MultiFlagChangeNeedsRestart) {
    329   const Experiment& experiment = kExperiments[3];
    330   ASSERT_EQ(kFlags4, experiment.internal_name);
    331   EXPECT_FALSE(IsRestartNeededToCommitChanges());
    332   // Enable the 2nd choice of the multi-value.
    333   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(2), true);
    334   EXPECT_TRUE(IsRestartNeededToCommitChanges());
    335   testing::ClearState();
    336   EXPECT_FALSE(IsRestartNeededToCommitChanges());
    337   // Enable the default choice now.
    338   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(0), true);
    339   EXPECT_TRUE(IsRestartNeededToCommitChanges());
    340 }
    341 
    342 TEST_F(AboutFlagsTest, AddTwoFlagsRemoveOne) {
    343   // Add two experiments, check they're there.
    344   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    345   SetExperimentEnabled(&flags_storage_, kFlags2, true);
    346 
    347   const base::ListValue* experiments_list = prefs_.GetList(
    348       prefs::kEnabledLabsExperiments);
    349   ASSERT_TRUE(experiments_list != NULL);
    350 
    351   ASSERT_EQ(2u, experiments_list->GetSize());
    352 
    353   std::string s0;
    354   ASSERT_TRUE(experiments_list->GetString(0, &s0));
    355   std::string s1;
    356   ASSERT_TRUE(experiments_list->GetString(1, &s1));
    357 
    358   EXPECT_TRUE(s0 == kFlags1 || s1 == kFlags1);
    359   EXPECT_TRUE(s0 == kFlags2 || s1 == kFlags2);
    360 
    361   // Remove one experiment, check the other's still around.
    362   SetExperimentEnabled(&flags_storage_, kFlags2, false);
    363 
    364   experiments_list = prefs_.GetList(prefs::kEnabledLabsExperiments);
    365   ASSERT_TRUE(experiments_list != NULL);
    366   ASSERT_EQ(1u, experiments_list->GetSize());
    367   ASSERT_TRUE(experiments_list->GetString(0, &s0));
    368   EXPECT_TRUE(s0 == kFlags1);
    369 }
    370 
    371 TEST_F(AboutFlagsTest, AddTwoFlagsRemoveBoth) {
    372   // Add two experiments, check the pref exists.
    373   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    374   SetExperimentEnabled(&flags_storage_, kFlags2, true);
    375   const base::ListValue* experiments_list = prefs_.GetList(
    376       prefs::kEnabledLabsExperiments);
    377   ASSERT_TRUE(experiments_list != NULL);
    378 
    379   // Remove both, the pref should have been removed completely.
    380   SetExperimentEnabled(&flags_storage_, kFlags1, false);
    381   SetExperimentEnabled(&flags_storage_, kFlags2, false);
    382   experiments_list = prefs_.GetList(prefs::kEnabledLabsExperiments);
    383   EXPECT_TRUE(experiments_list == NULL || experiments_list->GetSize() == 0);
    384 }
    385 
    386 TEST_F(AboutFlagsTest, ConvertFlagsToSwitches) {
    387   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    388 
    389   CommandLine command_line(CommandLine::NO_PROGRAM);
    390   command_line.AppendSwitch("foo");
    391 
    392   EXPECT_TRUE(command_line.HasSwitch("foo"));
    393   EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
    394 
    395   ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    396 
    397   EXPECT_TRUE(command_line.HasSwitch("foo"));
    398   EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
    399   EXPECT_TRUE(command_line.HasSwitch(switches::kFlagSwitchesBegin));
    400   EXPECT_TRUE(command_line.HasSwitch(switches::kFlagSwitchesEnd));
    401 
    402   CommandLine command_line2(CommandLine::NO_PROGRAM);
    403 
    404   ConvertFlagsToSwitches(&flags_storage_, &command_line2, kNoSentinels);
    405 
    406   EXPECT_TRUE(command_line2.HasSwitch(kSwitch1));
    407   EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesBegin));
    408   EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesEnd));
    409 }
    410 
    411 CommandLine::StringType CreateSwitch(const std::string& value) {
    412 #if defined(OS_WIN)
    413   return base::ASCIIToUTF16(value);
    414 #else
    415   return value;
    416 #endif
    417 }
    418 
    419 TEST_F(AboutFlagsTest, CompareSwitchesToCurrentCommandLine) {
    420   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    421 
    422   const std::string kDoubleDash("--");
    423 
    424   CommandLine command_line(CommandLine::NO_PROGRAM);
    425   command_line.AppendSwitch("foo");
    426 
    427   CommandLine new_command_line(CommandLine::NO_PROGRAM);
    428   ConvertFlagsToSwitches(&flags_storage_, &new_command_line, kAddSentinels);
    429 
    430   EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
    431       new_command_line, command_line, NULL));
    432   {
    433     std::set<CommandLine::StringType> difference;
    434     EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
    435         new_command_line, command_line, &difference));
    436     EXPECT_EQ(1U, difference.size());
    437     EXPECT_EQ(1U, difference.count(CreateSwitch(kDoubleDash + kSwitch1)));
    438   }
    439 
    440   ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    441 
    442   EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(
    443       new_command_line, command_line, NULL));
    444   {
    445     std::set<CommandLine::StringType> difference;
    446     EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(
    447         new_command_line, command_line, &difference));
    448     EXPECT_TRUE(difference.empty());
    449   }
    450 
    451   // Now both have flags but different.
    452   SetExperimentEnabled(&flags_storage_, kFlags1, false);
    453   SetExperimentEnabled(&flags_storage_, kFlags2, true);
    454 
    455   CommandLine another_command_line(CommandLine::NO_PROGRAM);
    456   ConvertFlagsToSwitches(&flags_storage_, &another_command_line, kAddSentinels);
    457 
    458   EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
    459       new_command_line, another_command_line, NULL));
    460   {
    461     std::set<CommandLine::StringType> difference;
    462     EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
    463         new_command_line, another_command_line, &difference));
    464     EXPECT_EQ(2U, difference.size());
    465     EXPECT_EQ(1U, difference.count(CreateSwitch(kDoubleDash + kSwitch1)));
    466     EXPECT_EQ(1U,
    467               difference.count(CreateSwitch(kDoubleDash + kSwitch2 + "=" +
    468                                             kValueForSwitch2)));
    469   }
    470 }
    471 
    472 TEST_F(AboutFlagsTest, RemoveFlagSwitches) {
    473   std::map<std::string, CommandLine::StringType> switch_list;
    474   switch_list[kSwitch1] = CommandLine::StringType();
    475   switch_list[switches::kFlagSwitchesBegin] = CommandLine::StringType();
    476   switch_list[switches::kFlagSwitchesEnd] = CommandLine::StringType();
    477   switch_list["foo"] = CommandLine::StringType();
    478 
    479   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    480 
    481   // This shouldn't do anything before ConvertFlagsToSwitches() wasn't called.
    482   RemoveFlagsSwitches(&switch_list);
    483   ASSERT_EQ(4u, switch_list.size());
    484   EXPECT_TRUE(switch_list.find(kSwitch1) != switch_list.end());
    485   EXPECT_TRUE(switch_list.find(switches::kFlagSwitchesBegin) !=
    486               switch_list.end());
    487   EXPECT_TRUE(switch_list.find(switches::kFlagSwitchesEnd) !=
    488               switch_list.end());
    489   EXPECT_TRUE(switch_list.find("foo") != switch_list.end());
    490 
    491   // Call ConvertFlagsToSwitches(), then RemoveFlagsSwitches() again.
    492   CommandLine command_line(CommandLine::NO_PROGRAM);
    493   command_line.AppendSwitch("foo");
    494   ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    495   RemoveFlagsSwitches(&switch_list);
    496 
    497   // Now the about:flags-related switch should have been removed.
    498   ASSERT_EQ(1u, switch_list.size());
    499   EXPECT_TRUE(switch_list.find("foo") != switch_list.end());
    500 }
    501 
    502 // Tests enabling experiments that aren't supported on the current platform.
    503 TEST_F(AboutFlagsTest, PersistAndPrune) {
    504   // Enable experiments 1 and 3.
    505   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    506   SetExperimentEnabled(&flags_storage_, kFlags3, true);
    507   CommandLine command_line(CommandLine::NO_PROGRAM);
    508   EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
    509   EXPECT_FALSE(command_line.HasSwitch(kSwitch3));
    510 
    511   // Convert the flags to switches. Experiment 3 shouldn't be among the switches
    512   // as it is not applicable to the current platform.
    513   ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    514   EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
    515   EXPECT_FALSE(command_line.HasSwitch(kSwitch3));
    516 
    517   // Experiment 3 should show still be persisted in preferences though.
    518   const base::ListValue* experiments_list =
    519       prefs_.GetList(prefs::kEnabledLabsExperiments);
    520   ASSERT_TRUE(experiments_list);
    521   EXPECT_EQ(2U, experiments_list->GetSize());
    522   std::string s0;
    523   ASSERT_TRUE(experiments_list->GetString(0, &s0));
    524   EXPECT_EQ(kFlags1, s0);
    525   std::string s1;
    526   ASSERT_TRUE(experiments_list->GetString(1, &s1));
    527   EXPECT_EQ(kFlags3, s1);
    528 }
    529 
    530 // Tests that switches which should have values get them in the command
    531 // line.
    532 TEST_F(AboutFlagsTest, CheckValues) {
    533   // Enable experiments 1 and 2.
    534   SetExperimentEnabled(&flags_storage_, kFlags1, true);
    535   SetExperimentEnabled(&flags_storage_, kFlags2, true);
    536   CommandLine command_line(CommandLine::NO_PROGRAM);
    537   EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
    538   EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
    539 
    540   // Convert the flags to switches.
    541   ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    542   EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
    543   EXPECT_EQ(std::string(), command_line.GetSwitchValueASCII(kSwitch1));
    544   EXPECT_TRUE(command_line.HasSwitch(kSwitch2));
    545   EXPECT_EQ(std::string(kValueForSwitch2),
    546             command_line.GetSwitchValueASCII(kSwitch2));
    547 
    548   // Confirm that there is no '=' in the command line for simple switches.
    549   std::string switch1_with_equals = std::string("--") +
    550                                     std::string(kSwitch1) +
    551                                     std::string("=");
    552 #if defined(OS_WIN)
    553   EXPECT_EQ(std::wstring::npos,
    554             command_line.GetCommandLineString().find(
    555                 base::ASCIIToWide(switch1_with_equals)));
    556 #else
    557   EXPECT_EQ(std::string::npos,
    558             command_line.GetCommandLineString().find(switch1_with_equals));
    559 #endif
    560 
    561   // And confirm there is a '=' for switches with values.
    562   std::string switch2_with_equals = std::string("--") +
    563                                     std::string(kSwitch2) +
    564                                     std::string("=");
    565 #if defined(OS_WIN)
    566   EXPECT_NE(std::wstring::npos,
    567             command_line.GetCommandLineString().find(
    568                 base::ASCIIToWide(switch2_with_equals)));
    569 #else
    570   EXPECT_NE(std::string::npos,
    571             command_line.GetCommandLineString().find(switch2_with_equals));
    572 #endif
    573 
    574   // And it should persist.
    575   const base::ListValue* experiments_list =
    576       prefs_.GetList(prefs::kEnabledLabsExperiments);
    577   ASSERT_TRUE(experiments_list);
    578   EXPECT_EQ(2U, experiments_list->GetSize());
    579   std::string s0;
    580   ASSERT_TRUE(experiments_list->GetString(0, &s0));
    581   EXPECT_EQ(kFlags1, s0);
    582   std::string s1;
    583   ASSERT_TRUE(experiments_list->GetString(1, &s1));
    584   EXPECT_EQ(kFlags2, s1);
    585 }
    586 
    587 // Tests multi-value type experiments.
    588 TEST_F(AboutFlagsTest, MultiValues) {
    589   const Experiment& experiment = kExperiments[3];
    590   ASSERT_EQ(kFlags4, experiment.internal_name);
    591 
    592   // Initially, the first "deactivated" option of the multi experiment should
    593   // be set.
    594   {
    595     CommandLine command_line(CommandLine::NO_PROGRAM);
    596     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    597     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
    598     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
    599   }
    600 
    601   // Enable the 2nd choice of the multi-value.
    602   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(2), true);
    603   {
    604     CommandLine command_line(CommandLine::NO_PROGRAM);
    605     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    606     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
    607     EXPECT_TRUE(command_line.HasSwitch(kMultiSwitch2));
    608     EXPECT_EQ(std::string(kValueForMultiSwitch2),
    609               command_line.GetSwitchValueASCII(kMultiSwitch2));
    610   }
    611 
    612   // Disable the multi-value experiment.
    613   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(0), true);
    614   {
    615     CommandLine command_line(CommandLine::NO_PROGRAM);
    616     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    617     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
    618     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
    619   }
    620 }
    621 
    622 TEST_F(AboutFlagsTest, EnableDisableValues) {
    623   const Experiment& experiment = kExperiments[4];
    624   ASSERT_EQ(kFlags5, experiment.internal_name);
    625 
    626   // Nothing selected.
    627   {
    628     CommandLine command_line(CommandLine::NO_PROGRAM);
    629     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    630     EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
    631     EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
    632   }
    633 
    634   // "Enable" option selected.
    635   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(1), true);
    636   {
    637     CommandLine command_line(CommandLine::NO_PROGRAM);
    638     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    639     EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
    640     EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
    641     EXPECT_EQ(kEnableDisableValue1, command_line.GetSwitchValueASCII(kSwitch1));
    642   }
    643 
    644   // "Disable" option selected.
    645   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(2), true);
    646   {
    647     CommandLine command_line(CommandLine::NO_PROGRAM);
    648     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    649     EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
    650     EXPECT_TRUE(command_line.HasSwitch(kSwitch2));
    651     EXPECT_EQ(kEnableDisableValue2, command_line.GetSwitchValueASCII(kSwitch2));
    652   }
    653 
    654   // "Default" option selected, same as nothing selected.
    655   SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(0), true);
    656   {
    657     CommandLine command_line(CommandLine::NO_PROGRAM);
    658     ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
    659     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
    660     EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
    661   }
    662 }
    663 
    664 // Makes sure there are no separators in any of the experiment names.
    665 TEST_F(AboutFlagsTest, NoSeparators) {
    666   testing::SetExperiments(NULL, 0);
    667   size_t count;
    668   const Experiment* experiments = testing::GetExperiments(&count);
    669     for (size_t i = 0; i < count; ++i) {
    670     std::string name = experiments->internal_name;
    671     EXPECT_EQ(std::string::npos, name.find(testing::kMultiSeparator)) << i;
    672   }
    673 }
    674 
    675 class AboutFlagsHistogramTest : public ::testing::Test {
    676  protected:
    677   // This is a helper function to check that all IDs in enum LoginCustomFlags in
    678   // histograms.xml are unique.
    679   void SetSwitchToHistogramIdMapping(const std::string& switch_name,
    680                                      const Sample switch_histogram_id,
    681                                      std::map<std::string, Sample>* out_map) {
    682     const std::pair<std::map<std::string, Sample>::iterator, bool> status =
    683         out_map->insert(std::make_pair(switch_name, switch_histogram_id));
    684     if (!status.second) {
    685       EXPECT_TRUE(status.first->second == switch_histogram_id)
    686           << "Duplicate switch '" << switch_name
    687           << "' found in enum 'LoginCustomFlags' in histograms.xml.";
    688     }
    689   }
    690 
    691   // This method generates a hint for the user for what string should be added
    692   // to the enum LoginCustomFlags to make in consistent.
    693   std::string GetHistogramEnumEntryText(const std::string& switch_name,
    694                                         Sample value) {
    695     return base::StringPrintf(
    696         "<int value=\"%d\" label=\"%s\"/>", value, switch_name.c_str());
    697   }
    698 };
    699 
    700 TEST_F(AboutFlagsHistogramTest, CheckHistograms) {
    701   base::FilePath histograms_xml_file_path;
    702   ASSERT_TRUE(
    703       PathService::Get(base::DIR_SOURCE_ROOT, &histograms_xml_file_path));
    704   histograms_xml_file_path = histograms_xml_file_path.AppendASCII("tools")
    705       .AppendASCII("metrics")
    706       .AppendASCII("histograms")
    707       .AppendASCII("histograms.xml");
    708 
    709   XmlReader histograms_xml;
    710   ASSERT_TRUE(histograms_xml.LoadFile(
    711       FilePathStringTypeToString(histograms_xml_file_path.value())));
    712   std::map<Sample, std::string> login_custom_flags =
    713       ReadEnumFromHistogramsXml("LoginCustomFlags", &histograms_xml);
    714   ASSERT_TRUE(login_custom_flags.size())
    715       << "Error reading enum 'LoginCustomFlags' from histograms.xml.";
    716 
    717   // Build reverse map {switch_name => id} from login_custom_flags.
    718   SwitchToIdMap histograms_xml_switches_ids;
    719 
    720   EXPECT_TRUE(login_custom_flags.count(kBadSwitchFormatHistogramId))
    721       << "Entry for UMA ID of incorrect command-line flag is not found in "
    722          "histograms.xml enum LoginCustomFlags. "
    723          "Consider adding entry:\n"
    724       << "  " << GetHistogramEnumEntryText("BAD_FLAG_FORMAT", 0);
    725   // Check that all LoginCustomFlags entries have correct values.
    726   for (std::map<Sample, std::string>::const_iterator it =
    727            login_custom_flags.begin();
    728        it != login_custom_flags.end();
    729        ++it) {
    730     if (it->first == kBadSwitchFormatHistogramId) {
    731       // Add eror value with empty name.
    732       SetSwitchToHistogramIdMapping(
    733           "", it->first, &histograms_xml_switches_ids);
    734       continue;
    735     }
    736     const Sample uma_id = GetSwitchUMAId(it->second);
    737     EXPECT_EQ(uma_id, it->first)
    738         << "histograms.xml enum LoginCustomFlags "
    739            "entry '" << it->second << "' has incorrect value=" << it->first
    740         << ", but " << uma_id << " is expected. Consider changing entry to:\n"
    741         << "  " << GetHistogramEnumEntryText(it->second, uma_id);
    742     SetSwitchToHistogramIdMapping(
    743         it->second, it->first, &histograms_xml_switches_ids);
    744   }
    745 
    746   // Check that all flags in about_flags.cc have entries in login_custom_flags.
    747   std::set<std::string> all_switches = GetAllSwitchesForTesting();
    748   for (std::set<std::string>::const_iterator it = all_switches.begin();
    749        it != all_switches.end();
    750        ++it) {
    751     // Skip empty placeholders.
    752     if (it->empty())
    753       continue;
    754     const Sample uma_id = GetSwitchUMAId(*it);
    755     EXPECT_NE(kBadSwitchFormatHistogramId, uma_id)
    756         << "Command-line switch '" << *it
    757         << "' from about_flags.cc has UMA ID equal to reserved value "
    758            "kBadSwitchFormatHistogramId=" << kBadSwitchFormatHistogramId
    759         << ". Please modify switch name.";
    760     SwitchToIdMap::iterator enum_entry =
    761         histograms_xml_switches_ids.lower_bound(*it);
    762 
    763     // Ignore case here when switch ID is incorrect - it has already been
    764     // reported in the previous loop.
    765     EXPECT_TRUE(enum_entry != histograms_xml_switches_ids.end() &&
    766                 enum_entry->first == *it)
    767         << "histograms.xml enum LoginCustomFlags doesn't contain switch '"
    768         << *it << "' (value=" << uma_id
    769         << " expected). Consider adding entry:\n"
    770         << "  " << GetHistogramEnumEntryText(*it, uma_id);
    771   }
    772 }
    773 
    774 }  // namespace about_flags
    775