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