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.
5 #include "chrome/browser/about_flags.h"
10 #include "base/files/file_path.h"
11 #include "base/memory/scoped_vector.h"
12 #include "base/path_service.h"
13 #include "base/prefs/pref_registry_simple.h"
14 #include "base/prefs/testing_pref_service.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/browser/pref_service_flags_storage.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/pref_names.h"
22 #include "chrome/grit/chromium_strings.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "third_party/libxml/chromium/libxml_utils.h"
26 namespace about_flags
{
30 const char kFlags1
[] = "flag1";
31 const char kFlags2
[] = "flag2";
32 const char kFlags3
[] = "flag3";
33 const char kFlags4
[] = "flag4";
34 const char kFlags5
[] = "flag5";
35 const char kFlags6
[] = "flag6";
37 const char kSwitch1
[] = "switch";
38 const char kSwitch2
[] = "switch2";
39 const char kSwitch3
[] = "switch3";
40 const char kSwitch6
[] = "switch6";
41 const char kValueForSwitch2
[] = "value_for_switch2";
43 const char kMultiSwitch1
[] = "multi_switch1";
44 const char kMultiSwitch2
[] = "multi_switch2";
45 const char kValueForMultiSwitch2
[] = "value_for_multi_switch2";
47 const char kEnableDisableValue1
[] = "value1";
48 const char kEnableDisableValue2
[] = "value2";
50 typedef base::HistogramBase::Sample Sample
;
51 typedef std::map
<std::string
, Sample
> SwitchToIdMap
;
53 // This is a helper function to the ReadEnumFromHistogramsXml().
54 // Extracts single enum (with integer values) from histograms.xml.
55 // Expects |reader| to point at given enum.
56 // Returns map { value => label }.
57 // Returns empty map on error.
58 std::map
<Sample
, std::string
> ParseEnumFromHistogramsXml(
59 const std::string
& enum_name
,
61 int entries_index
= -1;
63 std::map
<Sample
, std::string
> result
;
67 const std::string node_name
= reader
->NodeName();
68 if (node_name
== "enum" && reader
->IsClosingElement())
71 if (node_name
== "int") {
73 std::string value_str
;
75 const bool has_value
= reader
->NodeAttribute("value", &value_str
);
76 const bool has_label
= reader
->NodeAttribute("label", &label
);
78 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
79 << entries_index
<< ", label='" << label
80 << "'): No 'value' attribute.";
84 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
85 << entries_index
<< ", value_str='" << value_str
86 << "'): No 'label' attribute.";
91 if (has_value
&& !base::StringToInt(value_str
, &value
)) {
92 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
93 << entries_index
<< ", label='" << label
94 << "', value_str='" << value_str
95 << "'): 'value' attribute is not integer.";
98 if (result
.count(value
)) {
99 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
100 << entries_index
<< ", label='" << label
101 << "', value_str='" << value_str
102 << "'): duplicate value '" << value_str
103 << "' found in enum. The previous one has label='"
104 << result
[value
] << "'.";
108 result
[value
] = label
;
111 // All enum entries are on the same level, so it is enough to iterate
115 return (success
? result
: std::map
<Sample
, std::string
>());
118 // Find and read given enum (with integer values) from histograms.xml.
119 // |enum_name| - enum name.
120 // |histograms_xml| - must be loaded histograms.xml file.
122 // Returns map { value => label } so that:
123 // <int value="9" label="enable-pinch-virtual-viewport"/>
125 // { 9 => "enable-pinch-virtual-viewport" }
126 // Returns empty map on error.
127 std::map
<Sample
, std::string
> ReadEnumFromHistogramsXml(
128 const std::string
& enum_name
,
129 XmlReader
* histograms_xml
) {
130 std::map
<Sample
, std::string
> login_custom_flags
;
132 // Implement simple depth first search.
134 const std::string node_name
= histograms_xml
->NodeName();
135 if (node_name
== "enum") {
137 if (histograms_xml
->NodeAttribute("name", &name
) && name
== enum_name
) {
138 if (!login_custom_flags
.empty()) {
139 EXPECT_TRUE(login_custom_flags
.empty())
140 << "Duplicate enum '" << enum_name
<< "' found in histograms.xml";
141 return std::map
<Sample
, std::string
>();
144 const bool got_into_enum
= histograms_xml
->Read();
147 ParseEnumFromHistogramsXml(enum_name
, histograms_xml
);
148 EXPECT_FALSE(login_custom_flags
.empty())
149 << "Bad enum '" << enum_name
150 << "' found in histograms.xml (format error).";
152 EXPECT_TRUE(got_into_enum
)
153 << "Bad enum '" << enum_name
154 << "' (looks empty) found in histograms.xml.";
156 if (login_custom_flags
.empty())
157 return std::map
<Sample
, std::string
>();
160 // Go deeper if possible (stops at the closing tag of the deepest node).
161 if (histograms_xml
->Read())
164 // Try next node on the same level (skips closing tag).
165 if (histograms_xml
->Next())
168 // Go up until next node on the same level exists.
169 while (histograms_xml
->Depth() && !histograms_xml
->SkipToElement()) {
172 // Reached top. histograms.xml consists of the single top level node
173 // 'histogram-configuration', so this is the end.
174 if (!histograms_xml
->Depth())
177 EXPECT_FALSE(login_custom_flags
.empty())
178 << "Enum '" << enum_name
<< "' is not found in histograms.xml.";
179 return login_custom_flags
;
182 std::string
FilePathStringTypeToString(const base::FilePath::StringType
& path
) {
184 return base::UTF16ToUTF8(path
);
190 std::set
<std::string
> GetAllSwitchesForTesting() {
191 std::set
<std::string
> result
;
193 size_t num_experiments
= 0;
194 const Experiment
* experiments
=
195 testing::GetExperiments(&num_experiments
);
197 for (size_t i
= 0; i
< num_experiments
; ++i
) {
198 const Experiment
& experiment
= experiments
[i
];
199 if (experiment
.type
== Experiment::SINGLE_VALUE
||
200 experiment
.type
== Experiment::SINGLE_DISABLE_VALUE
) {
201 result
.insert(experiment
.command_line_switch
);
202 } else if (experiment
.type
== Experiment::MULTI_VALUE
) {
203 for (int j
= 0; j
< experiment
.num_choices
; ++j
) {
204 result
.insert(experiment
.choices
[j
].command_line_switch
);
207 DCHECK_EQ(experiment
.type
, Experiment::ENABLE_DISABLE_VALUE
);
208 result
.insert(experiment
.command_line_switch
);
209 result
.insert(experiment
.disable_command_line_switch
);
215 } // anonymous namespace
217 const Experiment::Choice kMultiChoices
[] = {
218 { IDS_PRODUCT_NAME
, "", "" },
219 { IDS_PRODUCT_NAME
, kMultiSwitch1
, "" },
220 { IDS_PRODUCT_NAME
, kMultiSwitch2
, kValueForMultiSwitch2
},
223 // The experiments that are set for these tests. The 3rd experiment is not
224 // supported on the current platform, all others are.
225 static Experiment kExperiments
[] = {
230 0, // Ends up being mapped to the current platform.
231 Experiment::SINGLE_VALUE
,
243 0, // Ends up being mapped to the current platform.
244 Experiment::SINGLE_VALUE
,
256 0, // This ends up enabling for an OS other than the current.
257 Experiment::SINGLE_VALUE
,
269 0, // Ends up being mapped to the current platform.
270 Experiment::MULTI_VALUE
,
276 arraysize(kMultiChoices
)
282 0, // Ends up being mapped to the current platform.
283 Experiment::ENABLE_DISABLE_VALUE
,
285 kEnableDisableValue1
,
287 kEnableDisableValue2
,
296 Experiment::SINGLE_DISABLE_VALUE
,
306 class AboutFlagsTest
: public ::testing::Test
{
308 AboutFlagsTest() : flags_storage_(&prefs_
) {
309 prefs_
.registry()->RegisterListPref(prefs::kEnabledLabsExperiments
);
310 testing::ClearState();
313 void SetUp() override
{
314 for (size_t i
= 0; i
< arraysize(kExperiments
); ++i
)
315 kExperiments
[i
].supported_platforms
= GetCurrentPlatform();
317 int os_other_than_current
= 1;
318 while (os_other_than_current
== GetCurrentPlatform())
319 os_other_than_current
<<= 1;
320 kExperiments
[2].supported_platforms
= os_other_than_current
;
322 testing::SetExperiments(kExperiments
, arraysize(kExperiments
));
325 void TearDown() override
{ testing::SetExperiments(NULL
, 0); }
327 TestingPrefServiceSimple prefs_
;
328 PrefServiceFlagsStorage flags_storage_
;
332 TEST_F(AboutFlagsTest
, NoChangeNoRestart
) {
333 EXPECT_FALSE(IsRestartNeededToCommitChanges());
334 SetExperimentEnabled(&flags_storage_
, kFlags1
, false);
335 EXPECT_FALSE(IsRestartNeededToCommitChanges());
337 // kFlags6 is enabled by default, so enabling should not require a restart.
338 SetExperimentEnabled(&flags_storage_
, kFlags6
, true);
339 EXPECT_FALSE(IsRestartNeededToCommitChanges());
342 TEST_F(AboutFlagsTest
, ChangeNeedsRestart
) {
343 EXPECT_FALSE(IsRestartNeededToCommitChanges());
344 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
345 EXPECT_TRUE(IsRestartNeededToCommitChanges());
348 // Tests that disabling a default enabled experiment requires a restart.
349 TEST_F(AboutFlagsTest
, DisableChangeNeedsRestart
) {
350 EXPECT_FALSE(IsRestartNeededToCommitChanges());
351 SetExperimentEnabled(&flags_storage_
, kFlags6
, false);
352 EXPECT_TRUE(IsRestartNeededToCommitChanges());
355 TEST_F(AboutFlagsTest
, MultiFlagChangeNeedsRestart
) {
356 const Experiment
& experiment
= kExperiments
[3];
357 ASSERT_EQ(kFlags4
, experiment
.internal_name
);
358 EXPECT_FALSE(IsRestartNeededToCommitChanges());
359 // Enable the 2nd choice of the multi-value.
360 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(2), true);
361 EXPECT_TRUE(IsRestartNeededToCommitChanges());
362 testing::ClearState();
363 EXPECT_FALSE(IsRestartNeededToCommitChanges());
364 // Enable the default choice now.
365 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(0), true);
366 EXPECT_TRUE(IsRestartNeededToCommitChanges());
369 TEST_F(AboutFlagsTest
, AddTwoFlagsRemoveOne
) {
370 // Add two experiments, check they're there.
371 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
372 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
374 const base::ListValue
* experiments_list
= prefs_
.GetList(
375 prefs::kEnabledLabsExperiments
);
376 ASSERT_TRUE(experiments_list
!= NULL
);
378 ASSERT_EQ(2u, experiments_list
->GetSize());
381 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
383 ASSERT_TRUE(experiments_list
->GetString(1, &s1
));
385 EXPECT_TRUE(s0
== kFlags1
|| s1
== kFlags1
);
386 EXPECT_TRUE(s0
== kFlags2
|| s1
== kFlags2
);
388 // Remove one experiment, check the other's still around.
389 SetExperimentEnabled(&flags_storage_
, kFlags2
, false);
391 experiments_list
= prefs_
.GetList(prefs::kEnabledLabsExperiments
);
392 ASSERT_TRUE(experiments_list
!= NULL
);
393 ASSERT_EQ(1u, experiments_list
->GetSize());
394 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
395 EXPECT_TRUE(s0
== kFlags1
);
398 TEST_F(AboutFlagsTest
, AddTwoFlagsRemoveBoth
) {
399 // Add two experiments, check the pref exists.
400 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
401 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
402 const base::ListValue
* experiments_list
= prefs_
.GetList(
403 prefs::kEnabledLabsExperiments
);
404 ASSERT_TRUE(experiments_list
!= NULL
);
406 // Remove both, the pref should have been removed completely.
407 SetExperimentEnabled(&flags_storage_
, kFlags1
, false);
408 SetExperimentEnabled(&flags_storage_
, kFlags2
, false);
409 experiments_list
= prefs_
.GetList(prefs::kEnabledLabsExperiments
);
410 EXPECT_TRUE(experiments_list
== NULL
|| experiments_list
->GetSize() == 0);
413 TEST_F(AboutFlagsTest
, ConvertFlagsToSwitches
) {
414 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
416 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
417 command_line
.AppendSwitch("foo");
419 EXPECT_TRUE(command_line
.HasSwitch("foo"));
420 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
422 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
424 EXPECT_TRUE(command_line
.HasSwitch("foo"));
425 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
426 EXPECT_TRUE(command_line
.HasSwitch(switches::kFlagSwitchesBegin
));
427 EXPECT_TRUE(command_line
.HasSwitch(switches::kFlagSwitchesEnd
));
429 base::CommandLine
command_line2(base::CommandLine::NO_PROGRAM
);
431 ConvertFlagsToSwitches(&flags_storage_
, &command_line2
, kNoSentinels
);
433 EXPECT_TRUE(command_line2
.HasSwitch(kSwitch1
));
434 EXPECT_FALSE(command_line2
.HasSwitch(switches::kFlagSwitchesBegin
));
435 EXPECT_FALSE(command_line2
.HasSwitch(switches::kFlagSwitchesEnd
));
438 base::CommandLine::StringType
CreateSwitch(const std::string
& value
) {
440 return base::ASCIIToUTF16(value
);
446 TEST_F(AboutFlagsTest
, CompareSwitchesToCurrentCommandLine
) {
447 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
449 const std::string
kDoubleDash("--");
451 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
452 command_line
.AppendSwitch("foo");
454 base::CommandLine
new_command_line(base::CommandLine::NO_PROGRAM
);
455 ConvertFlagsToSwitches(&flags_storage_
, &new_command_line
, kAddSentinels
);
457 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
458 new_command_line
, command_line
, NULL
));
460 std::set
<base::CommandLine::StringType
> difference
;
461 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
462 new_command_line
, command_line
, &difference
));
463 EXPECT_EQ(1U, difference
.size());
464 EXPECT_EQ(1U, difference
.count(CreateSwitch(kDoubleDash
+ kSwitch1
)));
467 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
469 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(
470 new_command_line
, command_line
, NULL
));
472 std::set
<base::CommandLine::StringType
> difference
;
473 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(
474 new_command_line
, command_line
, &difference
));
475 EXPECT_TRUE(difference
.empty());
478 // Now both have flags but different.
479 SetExperimentEnabled(&flags_storage_
, kFlags1
, false);
480 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
482 base::CommandLine
another_command_line(base::CommandLine::NO_PROGRAM
);
483 ConvertFlagsToSwitches(&flags_storage_
, &another_command_line
, kAddSentinels
);
485 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
486 new_command_line
, another_command_line
, NULL
));
488 std::set
<base::CommandLine::StringType
> difference
;
489 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
490 new_command_line
, another_command_line
, &difference
));
491 EXPECT_EQ(2U, difference
.size());
492 EXPECT_EQ(1U, difference
.count(CreateSwitch(kDoubleDash
+ kSwitch1
)));
494 difference
.count(CreateSwitch(kDoubleDash
+ kSwitch2
+ "=" +
499 TEST_F(AboutFlagsTest
, RemoveFlagSwitches
) {
500 std::map
<std::string
, base::CommandLine::StringType
> switch_list
;
501 switch_list
[kSwitch1
] = base::CommandLine::StringType();
502 switch_list
[switches::kFlagSwitchesBegin
] = base::CommandLine::StringType();
503 switch_list
[switches::kFlagSwitchesEnd
] = base::CommandLine::StringType();
504 switch_list
["foo"] = base::CommandLine::StringType();
506 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
508 // This shouldn't do anything before ConvertFlagsToSwitches() wasn't called.
509 RemoveFlagsSwitches(&switch_list
);
510 ASSERT_EQ(4u, switch_list
.size());
511 EXPECT_TRUE(switch_list
.find(kSwitch1
) != switch_list
.end());
512 EXPECT_TRUE(switch_list
.find(switches::kFlagSwitchesBegin
) !=
514 EXPECT_TRUE(switch_list
.find(switches::kFlagSwitchesEnd
) !=
516 EXPECT_TRUE(switch_list
.find("foo") != switch_list
.end());
518 // Call ConvertFlagsToSwitches(), then RemoveFlagsSwitches() again.
519 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
520 command_line
.AppendSwitch("foo");
521 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
522 RemoveFlagsSwitches(&switch_list
);
524 // Now the about:flags-related switch should have been removed.
525 ASSERT_EQ(1u, switch_list
.size());
526 EXPECT_TRUE(switch_list
.find("foo") != switch_list
.end());
529 // Tests enabling experiments that aren't supported on the current platform.
530 TEST_F(AboutFlagsTest
, PersistAndPrune
) {
531 // Enable experiments 1 and 3.
532 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
533 SetExperimentEnabled(&flags_storage_
, kFlags3
, true);
534 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
535 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
536 EXPECT_FALSE(command_line
.HasSwitch(kSwitch3
));
538 // Convert the flags to switches. Experiment 3 shouldn't be among the switches
539 // as it is not applicable to the current platform.
540 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
541 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
542 EXPECT_FALSE(command_line
.HasSwitch(kSwitch3
));
544 // Experiment 3 should show still be persisted in preferences though.
545 const base::ListValue
* experiments_list
=
546 prefs_
.GetList(prefs::kEnabledLabsExperiments
);
547 ASSERT_TRUE(experiments_list
);
548 EXPECT_EQ(2U, experiments_list
->GetSize());
550 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
551 EXPECT_EQ(kFlags1
, s0
);
553 ASSERT_TRUE(experiments_list
->GetString(1, &s1
));
554 EXPECT_EQ(kFlags3
, s1
);
557 // Tests that switches which should have values get them in the command
559 TEST_F(AboutFlagsTest
, CheckValues
) {
560 // Enable experiments 1 and 2.
561 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
562 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
563 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
564 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
565 EXPECT_FALSE(command_line
.HasSwitch(kSwitch2
));
567 // Convert the flags to switches.
568 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
569 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
570 EXPECT_EQ(std::string(), command_line
.GetSwitchValueASCII(kSwitch1
));
571 EXPECT_TRUE(command_line
.HasSwitch(kSwitch2
));
572 EXPECT_EQ(std::string(kValueForSwitch2
),
573 command_line
.GetSwitchValueASCII(kSwitch2
));
575 // Confirm that there is no '=' in the command line for simple switches.
576 std::string switch1_with_equals
= std::string("--") +
577 std::string(kSwitch1
) +
580 EXPECT_EQ(base::string16::npos
,
581 command_line
.GetCommandLineString().find(
582 base::ASCIIToUTF16(switch1_with_equals
)));
584 EXPECT_EQ(std::string::npos
,
585 command_line
.GetCommandLineString().find(switch1_with_equals
));
588 // And confirm there is a '=' for switches with values.
589 std::string switch2_with_equals
= std::string("--") +
590 std::string(kSwitch2
) +
593 EXPECT_NE(base::string16::npos
,
594 command_line
.GetCommandLineString().find(
595 base::ASCIIToUTF16(switch2_with_equals
)));
597 EXPECT_NE(std::string::npos
,
598 command_line
.GetCommandLineString().find(switch2_with_equals
));
601 // And it should persist.
602 const base::ListValue
* experiments_list
=
603 prefs_
.GetList(prefs::kEnabledLabsExperiments
);
604 ASSERT_TRUE(experiments_list
);
605 EXPECT_EQ(2U, experiments_list
->GetSize());
607 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
608 EXPECT_EQ(kFlags1
, s0
);
610 ASSERT_TRUE(experiments_list
->GetString(1, &s1
));
611 EXPECT_EQ(kFlags2
, s1
);
614 // Tests multi-value type experiments.
615 TEST_F(AboutFlagsTest
, MultiValues
) {
616 const Experiment
& experiment
= kExperiments
[3];
617 ASSERT_EQ(kFlags4
, experiment
.internal_name
);
619 // Initially, the first "deactivated" option of the multi experiment should
622 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
623 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
624 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
625 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch2
));
628 // Enable the 2nd choice of the multi-value.
629 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(2), true);
631 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
632 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
633 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
634 EXPECT_TRUE(command_line
.HasSwitch(kMultiSwitch2
));
635 EXPECT_EQ(std::string(kValueForMultiSwitch2
),
636 command_line
.GetSwitchValueASCII(kMultiSwitch2
));
639 // Disable the multi-value experiment.
640 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(0), true);
642 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
643 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
644 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
645 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch2
));
649 // Tests that disable flags are added when an experiment is disabled.
650 TEST_F(AboutFlagsTest
, DisableFlagCommandLine
) {
653 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
654 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
655 EXPECT_FALSE(command_line
.HasSwitch(kSwitch6
));
658 // Disable the experiment 6.
659 SetExperimentEnabled(&flags_storage_
, kFlags6
, false);
661 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
662 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
663 EXPECT_TRUE(command_line
.HasSwitch(kSwitch6
));
666 // Enable experiment 6.
667 SetExperimentEnabled(&flags_storage_
, kFlags6
, true);
669 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
670 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
671 EXPECT_FALSE(command_line
.HasSwitch(kSwitch6
));
675 TEST_F(AboutFlagsTest
, EnableDisableValues
) {
676 const Experiment
& experiment
= kExperiments
[4];
677 ASSERT_EQ(kFlags5
, experiment
.internal_name
);
681 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
682 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
683 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
684 EXPECT_FALSE(command_line
.HasSwitch(kSwitch2
));
687 // "Enable" option selected.
688 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(1), true);
690 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
691 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
692 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
693 EXPECT_FALSE(command_line
.HasSwitch(kSwitch2
));
694 EXPECT_EQ(kEnableDisableValue1
, command_line
.GetSwitchValueASCII(kSwitch1
));
697 // "Disable" option selected.
698 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(2), true);
700 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
701 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
702 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
703 EXPECT_TRUE(command_line
.HasSwitch(kSwitch2
));
704 EXPECT_EQ(kEnableDisableValue2
, command_line
.GetSwitchValueASCII(kSwitch2
));
707 // "Default" option selected, same as nothing selected.
708 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(0), true);
710 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
711 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
712 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
713 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch2
));
717 // Makes sure there are no separators in any of the experiment names.
718 TEST_F(AboutFlagsTest
, NoSeparators
) {
719 testing::SetExperiments(NULL
, 0);
721 const Experiment
* experiments
= testing::GetExperiments(&count
);
722 for (size_t i
= 0; i
< count
; ++i
) {
723 std::string name
= experiments
->internal_name
;
724 EXPECT_EQ(std::string::npos
, name
.find(testing::kMultiSeparator
)) << i
;
728 class AboutFlagsHistogramTest
: public ::testing::Test
{
730 // This is a helper function to check that all IDs in enum LoginCustomFlags in
731 // histograms.xml are unique.
732 void SetSwitchToHistogramIdMapping(const std::string
& switch_name
,
733 const Sample switch_histogram_id
,
734 std::map
<std::string
, Sample
>* out_map
) {
735 const std::pair
<std::map
<std::string
, Sample
>::iterator
, bool> status
=
736 out_map
->insert(std::make_pair(switch_name
, switch_histogram_id
));
737 if (!status
.second
) {
738 EXPECT_TRUE(status
.first
->second
== switch_histogram_id
)
739 << "Duplicate switch '" << switch_name
740 << "' found in enum 'LoginCustomFlags' in histograms.xml.";
744 // This method generates a hint for the user for what string should be added
745 // to the enum LoginCustomFlags to make in consistent.
746 std::string
GetHistogramEnumEntryText(const std::string
& switch_name
,
748 return base::StringPrintf(
749 "<int value=\"%d\" label=\"%s\"/>", value
, switch_name
.c_str());
753 TEST_F(AboutFlagsHistogramTest
, CheckHistograms
) {
754 base::FilePath histograms_xml_file_path
;
756 PathService::Get(base::DIR_SOURCE_ROOT
, &histograms_xml_file_path
));
757 histograms_xml_file_path
= histograms_xml_file_path
.AppendASCII("tools")
758 .AppendASCII("metrics")
759 .AppendASCII("histograms")
760 .AppendASCII("histograms.xml");
762 XmlReader histograms_xml
;
763 ASSERT_TRUE(histograms_xml
.LoadFile(
764 FilePathStringTypeToString(histograms_xml_file_path
.value())));
765 std::map
<Sample
, std::string
> login_custom_flags
=
766 ReadEnumFromHistogramsXml("LoginCustomFlags", &histograms_xml
);
767 ASSERT_TRUE(login_custom_flags
.size())
768 << "Error reading enum 'LoginCustomFlags' from histograms.xml.";
770 // Build reverse map {switch_name => id} from login_custom_flags.
771 SwitchToIdMap histograms_xml_switches_ids
;
773 EXPECT_TRUE(login_custom_flags
.count(testing::kBadSwitchFormatHistogramId
))
774 << "Entry for UMA ID of incorrect command-line flag is not found in "
775 "histograms.xml enum LoginCustomFlags. "
776 "Consider adding entry:\n"
777 << " " << GetHistogramEnumEntryText("BAD_FLAG_FORMAT", 0);
778 // Check that all LoginCustomFlags entries have correct values.
779 for (const auto& entry
: login_custom_flags
) {
780 if (entry
.first
== testing::kBadSwitchFormatHistogramId
) {
781 // Add error value with empty name.
782 SetSwitchToHistogramIdMapping(std::string(), entry
.first
,
783 &histograms_xml_switches_ids
);
786 const Sample uma_id
= GetSwitchUMAId(entry
.second
);
787 EXPECT_EQ(uma_id
, entry
.first
)
788 << "histograms.xml enum LoginCustomFlags "
789 "entry '" << entry
.second
<< "' has incorrect value=" << entry
.first
790 << ", but " << uma_id
<< " is expected. Consider changing entry to:\n"
791 << " " << GetHistogramEnumEntryText(entry
.second
, uma_id
);
792 SetSwitchToHistogramIdMapping(entry
.second
, entry
.first
,
793 &histograms_xml_switches_ids
);
796 // Check that all flags in about_flags.cc have entries in login_custom_flags.
797 std::set
<std::string
> all_switches
= GetAllSwitchesForTesting();
798 for (const std::string
& flag
: all_switches
) {
799 // Skip empty placeholders.
802 const Sample uma_id
= GetSwitchUMAId(flag
);
803 EXPECT_NE(testing::kBadSwitchFormatHistogramId
, uma_id
)
804 << "Command-line switch '" << flag
805 << "' from about_flags.cc has UMA ID equal to reserved value "
806 "kBadSwitchFormatHistogramId="
807 << testing::kBadSwitchFormatHistogramId
808 << ". Please modify switch name.";
809 SwitchToIdMap::iterator enum_entry
=
810 histograms_xml_switches_ids
.lower_bound(flag
);
812 // Ignore case here when switch ID is incorrect - it has already been
813 // reported in the previous loop.
814 EXPECT_TRUE(enum_entry
!= histograms_xml_switches_ids
.end() &&
815 enum_entry
->first
== flag
)
816 << "histograms.xml enum LoginCustomFlags doesn't contain switch '"
817 << flag
<< "' (value=" << uma_id
818 << " expected). Consider adding entry:\n"
819 << " " << GetHistogramEnumEntryText(flag
, uma_id
);
823 } // namespace about_flags