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/path_service.h"
12 #include "base/prefs/pref_registry_simple.h"
13 #include "base/prefs/testing_pref_service.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "chrome/browser/pref_service_flags_storage.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/pref_names.h"
21 #include "chrome/grit/chromium_strings.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "third_party/libxml/chromium/libxml_utils.h"
25 namespace about_flags
{
29 const char kFlags1
[] = "flag1";
30 const char kFlags2
[] = "flag2";
31 const char kFlags3
[] = "flag3";
32 const char kFlags4
[] = "flag4";
33 const char kFlags5
[] = "flag5";
34 const char kFlags6
[] = "flag6";
36 const char kSwitch1
[] = "switch";
37 const char kSwitch2
[] = "switch2";
38 const char kSwitch3
[] = "switch3";
39 const char kSwitch6
[] = "switch6";
40 const char kValueForSwitch2
[] = "value_for_switch2";
42 const char kMultiSwitch1
[] = "multi_switch1";
43 const char kMultiSwitch2
[] = "multi_switch2";
44 const char kValueForMultiSwitch2
[] = "value_for_multi_switch2";
46 const char kEnableDisableValue1
[] = "value1";
47 const char kEnableDisableValue2
[] = "value2";
49 typedef base::HistogramBase::Sample Sample
;
50 typedef std::map
<std::string
, Sample
> SwitchToIdMap
;
52 // This is a helper function to the ReadEnumFromHistogramsXml().
53 // Extracts single enum (with integer values) from histograms.xml.
54 // Expects |reader| to point at given enum.
55 // Returns map { value => label }.
56 // Returns empty map on error.
57 std::map
<Sample
, std::string
> ParseEnumFromHistogramsXml(
58 const std::string
& enum_name
,
60 int entries_index
= -1;
62 std::map
<Sample
, std::string
> result
;
66 const std::string node_name
= reader
->NodeName();
67 if (node_name
== "enum" && reader
->IsClosingElement())
70 if (node_name
== "int") {
72 std::string value_str
;
74 const bool has_value
= reader
->NodeAttribute("value", &value_str
);
75 const bool has_label
= reader
->NodeAttribute("label", &label
);
77 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
78 << entries_index
<< ", label='" << label
79 << "'): No 'value' attribute.";
83 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
84 << entries_index
<< ", value_str='" << value_str
85 << "'): No 'label' attribute.";
90 if (has_value
&& !base::StringToInt(value_str
, &value
)) {
91 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
92 << entries_index
<< ", label='" << label
93 << "', value_str='" << value_str
94 << "'): 'value' attribute is not integer.";
97 if (result
.count(value
)) {
98 ADD_FAILURE() << "Bad " << enum_name
<< " enum entry (at index "
99 << entries_index
<< ", label='" << label
100 << "', value_str='" << value_str
101 << "'): duplicate value '" << value_str
102 << "' found in enum. The previous one has label='"
103 << result
[value
] << "'.";
107 result
[value
] = label
;
110 // All enum entries are on the same level, so it is enough to iterate
114 return (success
? result
: std::map
<Sample
, std::string
>());
117 // Find and read given enum (with integer values) from histograms.xml.
118 // |enum_name| - enum name.
119 // |histograms_xml| - must be loaded histograms.xml file.
121 // Returns map { value => label } so that:
122 // <int value="9" label="enable-pinch-virtual-viewport"/>
124 // { 9 => "enable-pinch-virtual-viewport" }
125 // Returns empty map on error.
126 std::map
<Sample
, std::string
> ReadEnumFromHistogramsXml(
127 const std::string
& enum_name
,
128 XmlReader
* histograms_xml
) {
129 std::map
<Sample
, std::string
> login_custom_flags
;
131 // Implement simple depth first search.
133 const std::string node_name
= histograms_xml
->NodeName();
134 if (node_name
== "enum") {
136 if (histograms_xml
->NodeAttribute("name", &name
) && name
== enum_name
) {
137 if (!login_custom_flags
.empty()) {
138 EXPECT_TRUE(login_custom_flags
.empty())
139 << "Duplicate enum '" << enum_name
<< "' found in histograms.xml";
140 return std::map
<Sample
, std::string
>();
143 const bool got_into_enum
= histograms_xml
->Read();
146 ParseEnumFromHistogramsXml(enum_name
, histograms_xml
);
147 EXPECT_FALSE(login_custom_flags
.empty())
148 << "Bad enum '" << enum_name
149 << "' found in histograms.xml (format error).";
151 EXPECT_TRUE(got_into_enum
)
152 << "Bad enum '" << enum_name
153 << "' (looks empty) found in histograms.xml.";
155 if (login_custom_flags
.empty())
156 return std::map
<Sample
, std::string
>();
159 // Go deeper if possible (stops at the closing tag of the deepest node).
160 if (histograms_xml
->Read())
163 // Try next node on the same level (skips closing tag).
164 if (histograms_xml
->Next())
167 // Go up until next node on the same level exists.
168 while (histograms_xml
->Depth() && !histograms_xml
->SkipToElement()) {
171 // Reached top. histograms.xml consists of the single top level node
172 // 'histogram-configuration', so this is the end.
173 if (!histograms_xml
->Depth())
176 EXPECT_FALSE(login_custom_flags
.empty())
177 << "Enum '" << enum_name
<< "' is not found in histograms.xml.";
178 return login_custom_flags
;
181 std::string
FilePathStringTypeToString(const base::FilePath::StringType
& path
) {
183 return base::UTF16ToUTF8(path
);
189 std::set
<std::string
> GetAllSwitchesForTesting() {
190 std::set
<std::string
> result
;
192 size_t num_experiments
= 0;
193 const Experiment
* experiments
=
194 testing::GetExperiments(&num_experiments
);
196 for (size_t i
= 0; i
< num_experiments
; ++i
) {
197 const Experiment
& experiment
= experiments
[i
];
198 if (experiment
.type
== Experiment::SINGLE_VALUE
||
199 experiment
.type
== Experiment::SINGLE_DISABLE_VALUE
) {
200 result
.insert(experiment
.command_line_switch
);
201 } else if (experiment
.type
== Experiment::MULTI_VALUE
) {
202 for (int j
= 0; j
< experiment
.num_choices
; ++j
) {
203 result
.insert(experiment
.choices
[j
].command_line_switch
);
206 DCHECK_EQ(experiment
.type
, Experiment::ENABLE_DISABLE_VALUE
);
207 result
.insert(experiment
.command_line_switch
);
208 result
.insert(experiment
.disable_command_line_switch
);
214 } // anonymous namespace
216 const Experiment::Choice kMultiChoices
[] = {
217 { IDS_PRODUCT_NAME
, "", "" },
218 { IDS_PRODUCT_NAME
, kMultiSwitch1
, "" },
219 { IDS_PRODUCT_NAME
, kMultiSwitch2
, kValueForMultiSwitch2
},
222 // The experiments that are set for these tests. The 3rd experiment is not
223 // supported on the current platform, all others are.
224 static Experiment kExperiments
[] = {
229 0, // Ends up being mapped to the current platform.
230 Experiment::SINGLE_VALUE
,
242 0, // Ends up being mapped to the current platform.
243 Experiment::SINGLE_VALUE
,
255 0, // This ends up enabling for an OS other than the current.
256 Experiment::SINGLE_VALUE
,
268 0, // Ends up being mapped to the current platform.
269 Experiment::MULTI_VALUE
,
275 arraysize(kMultiChoices
)
281 0, // Ends up being mapped to the current platform.
282 Experiment::ENABLE_DISABLE_VALUE
,
284 kEnableDisableValue1
,
286 kEnableDisableValue2
,
295 Experiment::SINGLE_DISABLE_VALUE
,
305 class AboutFlagsTest
: public ::testing::Test
{
307 AboutFlagsTest() : flags_storage_(&prefs_
) {
308 prefs_
.registry()->RegisterListPref(prefs::kEnabledLabsExperiments
);
309 testing::ClearState();
312 void SetUp() override
{
313 for (size_t i
= 0; i
< arraysize(kExperiments
); ++i
)
314 kExperiments
[i
].supported_platforms
= GetCurrentPlatform();
316 int os_other_than_current
= 1;
317 while (os_other_than_current
== GetCurrentPlatform())
318 os_other_than_current
<<= 1;
319 kExperiments
[2].supported_platforms
= os_other_than_current
;
321 testing::SetExperiments(kExperiments
, arraysize(kExperiments
));
324 void TearDown() override
{ testing::SetExperiments(NULL
, 0); }
326 TestingPrefServiceSimple prefs_
;
327 PrefServiceFlagsStorage flags_storage_
;
331 TEST_F(AboutFlagsTest
, NoChangeNoRestart
) {
332 EXPECT_FALSE(IsRestartNeededToCommitChanges());
333 SetExperimentEnabled(&flags_storage_
, kFlags1
, false);
334 EXPECT_FALSE(IsRestartNeededToCommitChanges());
336 // kFlags6 is enabled by default, so enabling should not require a restart.
337 SetExperimentEnabled(&flags_storage_
, kFlags6
, true);
338 EXPECT_FALSE(IsRestartNeededToCommitChanges());
341 TEST_F(AboutFlagsTest
, ChangeNeedsRestart
) {
342 EXPECT_FALSE(IsRestartNeededToCommitChanges());
343 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
344 EXPECT_TRUE(IsRestartNeededToCommitChanges());
347 // Tests that disabling a default enabled experiment requires a restart.
348 TEST_F(AboutFlagsTest
, DisableChangeNeedsRestart
) {
349 EXPECT_FALSE(IsRestartNeededToCommitChanges());
350 SetExperimentEnabled(&flags_storage_
, kFlags6
, false);
351 EXPECT_TRUE(IsRestartNeededToCommitChanges());
354 TEST_F(AboutFlagsTest
, MultiFlagChangeNeedsRestart
) {
355 const Experiment
& experiment
= kExperiments
[3];
356 ASSERT_EQ(kFlags4
, experiment
.internal_name
);
357 EXPECT_FALSE(IsRestartNeededToCommitChanges());
358 // Enable the 2nd choice of the multi-value.
359 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(2), true);
360 EXPECT_TRUE(IsRestartNeededToCommitChanges());
361 testing::ClearState();
362 EXPECT_FALSE(IsRestartNeededToCommitChanges());
363 // Enable the default choice now.
364 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(0), true);
365 EXPECT_TRUE(IsRestartNeededToCommitChanges());
368 TEST_F(AboutFlagsTest
, AddTwoFlagsRemoveOne
) {
369 // Add two experiments, check they're there.
370 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
371 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
373 const base::ListValue
* experiments_list
= prefs_
.GetList(
374 prefs::kEnabledLabsExperiments
);
375 ASSERT_TRUE(experiments_list
!= NULL
);
377 ASSERT_EQ(2u, experiments_list
->GetSize());
380 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
382 ASSERT_TRUE(experiments_list
->GetString(1, &s1
));
384 EXPECT_TRUE(s0
== kFlags1
|| s1
== kFlags1
);
385 EXPECT_TRUE(s0
== kFlags2
|| s1
== kFlags2
);
387 // Remove one experiment, check the other's still around.
388 SetExperimentEnabled(&flags_storage_
, kFlags2
, false);
390 experiments_list
= prefs_
.GetList(prefs::kEnabledLabsExperiments
);
391 ASSERT_TRUE(experiments_list
!= NULL
);
392 ASSERT_EQ(1u, experiments_list
->GetSize());
393 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
394 EXPECT_TRUE(s0
== kFlags1
);
397 TEST_F(AboutFlagsTest
, AddTwoFlagsRemoveBoth
) {
398 // Add two experiments, check the pref exists.
399 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
400 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
401 const base::ListValue
* experiments_list
= prefs_
.GetList(
402 prefs::kEnabledLabsExperiments
);
403 ASSERT_TRUE(experiments_list
!= NULL
);
405 // Remove both, the pref should have been removed completely.
406 SetExperimentEnabled(&flags_storage_
, kFlags1
, false);
407 SetExperimentEnabled(&flags_storage_
, kFlags2
, false);
408 experiments_list
= prefs_
.GetList(prefs::kEnabledLabsExperiments
);
409 EXPECT_TRUE(experiments_list
== NULL
|| experiments_list
->GetSize() == 0);
412 TEST_F(AboutFlagsTest
, ConvertFlagsToSwitches
) {
413 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
415 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
416 command_line
.AppendSwitch("foo");
418 EXPECT_TRUE(command_line
.HasSwitch("foo"));
419 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
421 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
423 EXPECT_TRUE(command_line
.HasSwitch("foo"));
424 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
425 EXPECT_TRUE(command_line
.HasSwitch(switches::kFlagSwitchesBegin
));
426 EXPECT_TRUE(command_line
.HasSwitch(switches::kFlagSwitchesEnd
));
428 base::CommandLine
command_line2(base::CommandLine::NO_PROGRAM
);
430 ConvertFlagsToSwitches(&flags_storage_
, &command_line2
, kNoSentinels
);
432 EXPECT_TRUE(command_line2
.HasSwitch(kSwitch1
));
433 EXPECT_FALSE(command_line2
.HasSwitch(switches::kFlagSwitchesBegin
));
434 EXPECT_FALSE(command_line2
.HasSwitch(switches::kFlagSwitchesEnd
));
437 base::CommandLine::StringType
CreateSwitch(const std::string
& value
) {
439 return base::ASCIIToUTF16(value
);
445 TEST_F(AboutFlagsTest
, CompareSwitchesToCurrentCommandLine
) {
446 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
448 const std::string
kDoubleDash("--");
450 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
451 command_line
.AppendSwitch("foo");
453 base::CommandLine
new_command_line(base::CommandLine::NO_PROGRAM
);
454 ConvertFlagsToSwitches(&flags_storage_
, &new_command_line
, kAddSentinels
);
456 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
457 new_command_line
, command_line
, NULL
));
459 std::set
<base::CommandLine::StringType
> difference
;
460 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
461 new_command_line
, command_line
, &difference
));
462 EXPECT_EQ(1U, difference
.size());
463 EXPECT_EQ(1U, difference
.count(CreateSwitch(kDoubleDash
+ kSwitch1
)));
466 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
468 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(
469 new_command_line
, command_line
, NULL
));
471 std::set
<base::CommandLine::StringType
> difference
;
472 EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(
473 new_command_line
, command_line
, &difference
));
474 EXPECT_TRUE(difference
.empty());
477 // Now both have flags but different.
478 SetExperimentEnabled(&flags_storage_
, kFlags1
, false);
479 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
481 base::CommandLine
another_command_line(base::CommandLine::NO_PROGRAM
);
482 ConvertFlagsToSwitches(&flags_storage_
, &another_command_line
, kAddSentinels
);
484 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
485 new_command_line
, another_command_line
, NULL
));
487 std::set
<base::CommandLine::StringType
> difference
;
488 EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(
489 new_command_line
, another_command_line
, &difference
));
490 EXPECT_EQ(2U, difference
.size());
491 EXPECT_EQ(1U, difference
.count(CreateSwitch(kDoubleDash
+ kSwitch1
)));
493 difference
.count(CreateSwitch(kDoubleDash
+ kSwitch2
+ "=" +
498 TEST_F(AboutFlagsTest
, RemoveFlagSwitches
) {
499 std::map
<std::string
, base::CommandLine::StringType
> switch_list
;
500 switch_list
[kSwitch1
] = base::CommandLine::StringType();
501 switch_list
[switches::kFlagSwitchesBegin
] = base::CommandLine::StringType();
502 switch_list
[switches::kFlagSwitchesEnd
] = base::CommandLine::StringType();
503 switch_list
["foo"] = base::CommandLine::StringType();
505 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
507 // This shouldn't do anything before ConvertFlagsToSwitches() wasn't called.
508 RemoveFlagsSwitches(&switch_list
);
509 ASSERT_EQ(4u, switch_list
.size());
510 EXPECT_TRUE(switch_list
.find(kSwitch1
) != switch_list
.end());
511 EXPECT_TRUE(switch_list
.find(switches::kFlagSwitchesBegin
) !=
513 EXPECT_TRUE(switch_list
.find(switches::kFlagSwitchesEnd
) !=
515 EXPECT_TRUE(switch_list
.find("foo") != switch_list
.end());
517 // Call ConvertFlagsToSwitches(), then RemoveFlagsSwitches() again.
518 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
519 command_line
.AppendSwitch("foo");
520 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
521 RemoveFlagsSwitches(&switch_list
);
523 // Now the about:flags-related switch should have been removed.
524 ASSERT_EQ(1u, switch_list
.size());
525 EXPECT_TRUE(switch_list
.find("foo") != switch_list
.end());
528 // Tests enabling experiments that aren't supported on the current platform.
529 TEST_F(AboutFlagsTest
, PersistAndPrune
) {
530 // Enable experiments 1 and 3.
531 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
532 SetExperimentEnabled(&flags_storage_
, kFlags3
, true);
533 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
534 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
535 EXPECT_FALSE(command_line
.HasSwitch(kSwitch3
));
537 // Convert the flags to switches. Experiment 3 shouldn't be among the switches
538 // as it is not applicable to the current platform.
539 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
540 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
541 EXPECT_FALSE(command_line
.HasSwitch(kSwitch3
));
543 // Experiment 3 should show still be persisted in preferences though.
544 const base::ListValue
* experiments_list
=
545 prefs_
.GetList(prefs::kEnabledLabsExperiments
);
546 ASSERT_TRUE(experiments_list
);
547 EXPECT_EQ(2U, experiments_list
->GetSize());
549 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
550 EXPECT_EQ(kFlags1
, s0
);
552 ASSERT_TRUE(experiments_list
->GetString(1, &s1
));
553 EXPECT_EQ(kFlags3
, s1
);
556 // Tests that switches which should have values get them in the command
558 TEST_F(AboutFlagsTest
, CheckValues
) {
559 // Enable experiments 1 and 2.
560 SetExperimentEnabled(&flags_storage_
, kFlags1
, true);
561 SetExperimentEnabled(&flags_storage_
, kFlags2
, true);
562 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
563 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
564 EXPECT_FALSE(command_line
.HasSwitch(kSwitch2
));
566 // Convert the flags to switches.
567 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
568 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
569 EXPECT_EQ(std::string(), command_line
.GetSwitchValueASCII(kSwitch1
));
570 EXPECT_TRUE(command_line
.HasSwitch(kSwitch2
));
571 EXPECT_EQ(std::string(kValueForSwitch2
),
572 command_line
.GetSwitchValueASCII(kSwitch2
));
574 // Confirm that there is no '=' in the command line for simple switches.
575 std::string switch1_with_equals
= std::string("--") +
576 std::string(kSwitch1
) +
579 EXPECT_EQ(base::string16::npos
,
580 command_line
.GetCommandLineString().find(
581 base::ASCIIToUTF16(switch1_with_equals
)));
583 EXPECT_EQ(std::string::npos
,
584 command_line
.GetCommandLineString().find(switch1_with_equals
));
587 // And confirm there is a '=' for switches with values.
588 std::string switch2_with_equals
= std::string("--") +
589 std::string(kSwitch2
) +
592 EXPECT_NE(base::string16::npos
,
593 command_line
.GetCommandLineString().find(
594 base::ASCIIToUTF16(switch2_with_equals
)));
596 EXPECT_NE(std::string::npos
,
597 command_line
.GetCommandLineString().find(switch2_with_equals
));
600 // And it should persist.
601 const base::ListValue
* experiments_list
=
602 prefs_
.GetList(prefs::kEnabledLabsExperiments
);
603 ASSERT_TRUE(experiments_list
);
604 EXPECT_EQ(2U, experiments_list
->GetSize());
606 ASSERT_TRUE(experiments_list
->GetString(0, &s0
));
607 EXPECT_EQ(kFlags1
, s0
);
609 ASSERT_TRUE(experiments_list
->GetString(1, &s1
));
610 EXPECT_EQ(kFlags2
, s1
);
613 // Tests multi-value type experiments.
614 TEST_F(AboutFlagsTest
, MultiValues
) {
615 const Experiment
& experiment
= kExperiments
[3];
616 ASSERT_EQ(kFlags4
, experiment
.internal_name
);
618 // Initially, the first "deactivated" option of the multi experiment should
621 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
622 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
623 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
624 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch2
));
627 // Enable the 2nd choice of the multi-value.
628 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(2), true);
630 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
631 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
632 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
633 EXPECT_TRUE(command_line
.HasSwitch(kMultiSwitch2
));
634 EXPECT_EQ(std::string(kValueForMultiSwitch2
),
635 command_line
.GetSwitchValueASCII(kMultiSwitch2
));
638 // Disable the multi-value experiment.
639 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(0), true);
641 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
642 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
643 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
644 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch2
));
648 // Tests that disable flags are added when an experiment is disabled.
649 TEST_F(AboutFlagsTest
, DisableFlagCommandLine
) {
652 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
653 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
654 EXPECT_FALSE(command_line
.HasSwitch(kSwitch6
));
657 // Disable the experiment 6.
658 SetExperimentEnabled(&flags_storage_
, kFlags6
, false);
660 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
661 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
662 EXPECT_TRUE(command_line
.HasSwitch(kSwitch6
));
665 // Enable experiment 6.
666 SetExperimentEnabled(&flags_storage_
, kFlags6
, true);
668 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
669 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
670 EXPECT_FALSE(command_line
.HasSwitch(kSwitch6
));
674 TEST_F(AboutFlagsTest
, EnableDisableValues
) {
675 const Experiment
& experiment
= kExperiments
[4];
676 ASSERT_EQ(kFlags5
, experiment
.internal_name
);
680 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
681 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
682 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
683 EXPECT_FALSE(command_line
.HasSwitch(kSwitch2
));
686 // "Enable" option selected.
687 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(1), true);
689 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
690 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
691 EXPECT_TRUE(command_line
.HasSwitch(kSwitch1
));
692 EXPECT_FALSE(command_line
.HasSwitch(kSwitch2
));
693 EXPECT_EQ(kEnableDisableValue1
, command_line
.GetSwitchValueASCII(kSwitch1
));
696 // "Disable" option selected.
697 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(2), true);
699 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
700 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
701 EXPECT_FALSE(command_line
.HasSwitch(kSwitch1
));
702 EXPECT_TRUE(command_line
.HasSwitch(kSwitch2
));
703 EXPECT_EQ(kEnableDisableValue2
, command_line
.GetSwitchValueASCII(kSwitch2
));
706 // "Default" option selected, same as nothing selected.
707 SetExperimentEnabled(&flags_storage_
, experiment
.NameForChoice(0), true);
709 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
710 ConvertFlagsToSwitches(&flags_storage_
, &command_line
, kAddSentinels
);
711 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch1
));
712 EXPECT_FALSE(command_line
.HasSwitch(kMultiSwitch2
));
716 // Makes sure there are no separators in any of the experiment names.
717 TEST_F(AboutFlagsTest
, NoSeparators
) {
718 testing::SetExperiments(NULL
, 0);
720 const Experiment
* experiments
= testing::GetExperiments(&count
);
721 for (size_t i
= 0; i
< count
; ++i
) {
722 std::string name
= experiments
->internal_name
;
723 EXPECT_EQ(std::string::npos
, name
.find(testing::kMultiSeparator
)) << i
;
727 class AboutFlagsHistogramTest
: public ::testing::Test
{
729 // This is a helper function to check that all IDs in enum LoginCustomFlags in
730 // histograms.xml are unique.
731 void SetSwitchToHistogramIdMapping(const std::string
& switch_name
,
732 const Sample switch_histogram_id
,
733 std::map
<std::string
, Sample
>* out_map
) {
734 const std::pair
<std::map
<std::string
, Sample
>::iterator
, bool> status
=
735 out_map
->insert(std::make_pair(switch_name
, switch_histogram_id
));
736 if (!status
.second
) {
737 EXPECT_TRUE(status
.first
->second
== switch_histogram_id
)
738 << "Duplicate switch '" << switch_name
739 << "' found in enum 'LoginCustomFlags' in histograms.xml.";
743 // This method generates a hint for the user for what string should be added
744 // to the enum LoginCustomFlags to make in consistent.
745 std::string
GetHistogramEnumEntryText(const std::string
& switch_name
,
747 return base::StringPrintf(
748 "<int value=\"%d\" label=\"%s\"/>", value
, switch_name
.c_str());
752 TEST_F(AboutFlagsHistogramTest
, CheckHistograms
) {
753 base::FilePath histograms_xml_file_path
;
755 PathService::Get(base::DIR_SOURCE_ROOT
, &histograms_xml_file_path
));
756 histograms_xml_file_path
= histograms_xml_file_path
.AppendASCII("tools")
757 .AppendASCII("metrics")
758 .AppendASCII("histograms")
759 .AppendASCII("histograms.xml");
761 XmlReader histograms_xml
;
762 ASSERT_TRUE(histograms_xml
.LoadFile(
763 FilePathStringTypeToString(histograms_xml_file_path
.value())));
764 std::map
<Sample
, std::string
> login_custom_flags
=
765 ReadEnumFromHistogramsXml("LoginCustomFlags", &histograms_xml
);
766 ASSERT_TRUE(login_custom_flags
.size())
767 << "Error reading enum 'LoginCustomFlags' from histograms.xml.";
769 // Build reverse map {switch_name => id} from login_custom_flags.
770 SwitchToIdMap histograms_xml_switches_ids
;
772 EXPECT_TRUE(login_custom_flags
.count(testing::kBadSwitchFormatHistogramId
))
773 << "Entry for UMA ID of incorrect command-line flag is not found in "
774 "histograms.xml enum LoginCustomFlags. "
775 "Consider adding entry:\n"
776 << " " << GetHistogramEnumEntryText("BAD_FLAG_FORMAT", 0);
777 // Check that all LoginCustomFlags entries have correct values.
778 for (const auto& entry
: login_custom_flags
) {
779 if (entry
.first
== testing::kBadSwitchFormatHistogramId
) {
780 // Add error value with empty name.
781 SetSwitchToHistogramIdMapping(std::string(), entry
.first
,
782 &histograms_xml_switches_ids
);
785 const Sample uma_id
= GetSwitchUMAId(entry
.second
);
786 EXPECT_EQ(uma_id
, entry
.first
)
787 << "histograms.xml enum LoginCustomFlags "
788 "entry '" << entry
.second
<< "' has incorrect value=" << entry
.first
789 << ", but " << uma_id
<< " is expected. Consider changing entry to:\n"
790 << " " << GetHistogramEnumEntryText(entry
.second
, uma_id
);
791 SetSwitchToHistogramIdMapping(entry
.second
, entry
.first
,
792 &histograms_xml_switches_ids
);
795 // Check that all flags in about_flags.cc have entries in login_custom_flags.
796 std::set
<std::string
> all_switches
= GetAllSwitchesForTesting();
797 for (const std::string
& flag
: all_switches
) {
798 // Skip empty placeholders.
801 const Sample uma_id
= GetSwitchUMAId(flag
);
802 EXPECT_NE(testing::kBadSwitchFormatHistogramId
, uma_id
)
803 << "Command-line switch '" << flag
804 << "' from about_flags.cc has UMA ID equal to reserved value "
805 "kBadSwitchFormatHistogramId="
806 << testing::kBadSwitchFormatHistogramId
807 << ". Please modify switch name.";
808 SwitchToIdMap::iterator enum_entry
=
809 histograms_xml_switches_ids
.lower_bound(flag
);
811 // Ignore case here when switch ID is incorrect - it has already been
812 // reported in the previous loop.
813 EXPECT_TRUE(enum_entry
!= histograms_xml_switches_ids
.end() &&
814 enum_entry
->first
== flag
)
815 << "histograms.xml enum LoginCustomFlags doesn't contain switch '"
816 << flag
<< "' (value=" << uma_id
817 << " expected). Consider adding entry:\n"
818 << " " << GetHistogramEnumEntryText(flag
, uma_id
);
822 } // namespace about_flags