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 "base/prefs/pref_registry_simple.h"
6#include "base/prefs/testing_pref_service.h"
7#include "base/strings/string_number_conversions.h"
8#include "base/strings/utf_string_conversions.h"
9#include "base/values.h"
10#include "chrome/browser/about_flags.h"
11#include "chrome/browser/pref_service_flags_storage.h"
12#include "chrome/common/chrome_switches.h"
13#include "chrome/common/pref_names.h"
14#include "grit/chromium_strings.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17const char kFlags1[] = "flag1";
18const char kFlags2[] = "flag2";
19const char kFlags3[] = "flag3";
20const char kFlags4[] = "flag4";
21const char kFlags5[] = "flag5";
22
23const char kSwitch1[] = "switch";
24const char kSwitch2[] = "switch2";
25const char kSwitch3[] = "switch3";
26const char kValueForSwitch2[] = "value_for_switch2";
27
28const char kMultiSwitch1[] = "multi_switch1";
29const char kMultiSwitch2[] = "multi_switch2";
30const char kValueForMultiSwitch2[] = "value_for_multi_switch2";
31
32const char kEnableDisableValue1[] = "value1";
33const char kEnableDisableValue2[] = "value2";
34
35namespace about_flags {
36
37const Experiment::Choice kMultiChoices[] = {
38  { IDS_PRODUCT_NAME, "", "" },
39  { IDS_PRODUCT_NAME, kMultiSwitch1, "" },
40  { IDS_PRODUCT_NAME, kMultiSwitch2, kValueForMultiSwitch2 },
41};
42
43// The experiments that are set for these tests. The 3rd experiment is not
44// supported on the current platform, all others are.
45static Experiment kExperiments[] = {
46  {
47    kFlags1,
48    IDS_PRODUCT_NAME,
49    IDS_PRODUCT_NAME,
50    0,  // Ends up being mapped to the current platform.
51    Experiment::SINGLE_VALUE,
52    kSwitch1,
53    "",
54    NULL,
55    NULL,
56    NULL,
57    0
58  },
59  {
60    kFlags2,
61    IDS_PRODUCT_NAME,
62    IDS_PRODUCT_NAME,
63    0,  // Ends up being mapped to the current platform.
64    Experiment::SINGLE_VALUE,
65    kSwitch2,
66    kValueForSwitch2,
67    NULL,
68    NULL,
69    NULL,
70    0
71  },
72  {
73    kFlags3,
74    IDS_PRODUCT_NAME,
75    IDS_PRODUCT_NAME,
76    0,  // This ends up enabling for an OS other than the current.
77    Experiment::SINGLE_VALUE,
78    kSwitch3,
79    "",
80    NULL,
81    NULL,
82    NULL,
83    0
84  },
85  {
86    kFlags4,
87    IDS_PRODUCT_NAME,
88    IDS_PRODUCT_NAME,
89    0,  // Ends up being mapped to the current platform.
90    Experiment::MULTI_VALUE,
91    "",
92    "",
93    "",
94    "",
95    kMultiChoices,
96    arraysize(kMultiChoices)
97  },
98  {
99    kFlags5,
100    IDS_PRODUCT_NAME,
101    IDS_PRODUCT_NAME,
102    0,  // Ends up being mapped to the current platform.
103    Experiment::ENABLE_DISABLE_VALUE,
104    kSwitch1,
105    kEnableDisableValue1,
106    kSwitch2,
107    kEnableDisableValue2,
108    NULL,
109    3
110  },
111};
112
113class AboutFlagsTest : public ::testing::Test {
114 protected:
115  AboutFlagsTest() : flags_storage_(&prefs_) {
116    prefs_.registry()->RegisterListPref(prefs::kEnabledLabsExperiments);
117    testing::ClearState();
118  }
119
120  virtual void SetUp() OVERRIDE {
121    for (size_t i = 0; i < arraysize(kExperiments); ++i)
122      kExperiments[i].supported_platforms = GetCurrentPlatform();
123
124    int os_other_than_current = 1;
125    while (os_other_than_current == GetCurrentPlatform())
126      os_other_than_current <<= 1;
127    kExperiments[2].supported_platforms = os_other_than_current;
128
129    testing::SetExperiments(kExperiments, arraysize(kExperiments));
130  }
131
132  virtual void TearDown() OVERRIDE {
133    testing::SetExperiments(NULL, 0);
134  }
135
136  TestingPrefServiceSimple prefs_;
137  PrefServiceFlagsStorage flags_storage_;
138};
139
140
141TEST_F(AboutFlagsTest, NoChangeNoRestart) {
142  EXPECT_FALSE(IsRestartNeededToCommitChanges());
143  SetExperimentEnabled(&flags_storage_, kFlags1, false);
144  EXPECT_FALSE(IsRestartNeededToCommitChanges());
145}
146
147TEST_F(AboutFlagsTest, ChangeNeedsRestart) {
148  EXPECT_FALSE(IsRestartNeededToCommitChanges());
149  SetExperimentEnabled(&flags_storage_, kFlags1, true);
150  EXPECT_TRUE(IsRestartNeededToCommitChanges());
151}
152
153TEST_F(AboutFlagsTest, MultiFlagChangeNeedsRestart) {
154  const Experiment& experiment = kExperiments[3];
155  ASSERT_EQ(kFlags4, experiment.internal_name);
156  EXPECT_FALSE(IsRestartNeededToCommitChanges());
157  // Enable the 2nd choice of the multi-value.
158  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(2), true);
159  EXPECT_TRUE(IsRestartNeededToCommitChanges());
160  testing::ClearState();
161  EXPECT_FALSE(IsRestartNeededToCommitChanges());
162  // Enable the default choice now.
163  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(0), true);
164  EXPECT_TRUE(IsRestartNeededToCommitChanges());
165}
166
167TEST_F(AboutFlagsTest, AddTwoFlagsRemoveOne) {
168  // Add two experiments, check they're there.
169  SetExperimentEnabled(&flags_storage_, kFlags1, true);
170  SetExperimentEnabled(&flags_storage_, kFlags2, true);
171
172  const ListValue* experiments_list = prefs_.GetList(
173      prefs::kEnabledLabsExperiments);
174  ASSERT_TRUE(experiments_list != NULL);
175
176  ASSERT_EQ(2u, experiments_list->GetSize());
177
178  std::string s0;
179  ASSERT_TRUE(experiments_list->GetString(0, &s0));
180  std::string s1;
181  ASSERT_TRUE(experiments_list->GetString(1, &s1));
182
183  EXPECT_TRUE(s0 == kFlags1 || s1 == kFlags1);
184  EXPECT_TRUE(s0 == kFlags2 || s1 == kFlags2);
185
186  // Remove one experiment, check the other's still around.
187  SetExperimentEnabled(&flags_storage_, kFlags2, false);
188
189  experiments_list = prefs_.GetList(prefs::kEnabledLabsExperiments);
190  ASSERT_TRUE(experiments_list != NULL);
191  ASSERT_EQ(1u, experiments_list->GetSize());
192  ASSERT_TRUE(experiments_list->GetString(0, &s0));
193  EXPECT_TRUE(s0 == kFlags1);
194}
195
196TEST_F(AboutFlagsTest, AddTwoFlagsRemoveBoth) {
197  // Add two experiments, check the pref exists.
198  SetExperimentEnabled(&flags_storage_, kFlags1, true);
199  SetExperimentEnabled(&flags_storage_, kFlags2, true);
200  const ListValue* experiments_list = prefs_.GetList(
201      prefs::kEnabledLabsExperiments);
202  ASSERT_TRUE(experiments_list != NULL);
203
204  // Remove both, the pref should have been removed completely.
205  SetExperimentEnabled(&flags_storage_, kFlags1, false);
206  SetExperimentEnabled(&flags_storage_, kFlags2, false);
207  experiments_list = prefs_.GetList(prefs::kEnabledLabsExperiments);
208  EXPECT_TRUE(experiments_list == NULL || experiments_list->GetSize() == 0);
209}
210
211TEST_F(AboutFlagsTest, ConvertFlagsToSwitches) {
212  SetExperimentEnabled(&flags_storage_, kFlags1, true);
213
214  CommandLine command_line(CommandLine::NO_PROGRAM);
215  command_line.AppendSwitch("foo");
216
217  EXPECT_TRUE(command_line.HasSwitch("foo"));
218  EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
219
220  ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
221
222  EXPECT_TRUE(command_line.HasSwitch("foo"));
223  EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
224  EXPECT_TRUE(command_line.HasSwitch(switches::kFlagSwitchesBegin));
225  EXPECT_TRUE(command_line.HasSwitch(switches::kFlagSwitchesEnd));
226
227  CommandLine command_line2(CommandLine::NO_PROGRAM);
228
229  ConvertFlagsToSwitches(&flags_storage_, &command_line2, kNoSentinels);
230
231  EXPECT_TRUE(command_line2.HasSwitch(kSwitch1));
232  EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesBegin));
233  EXPECT_FALSE(command_line2.HasSwitch(switches::kFlagSwitchesEnd));
234}
235
236TEST_F(AboutFlagsTest, CompareSwitchesToCurrentCommandLine) {
237  SetExperimentEnabled(&flags_storage_, kFlags1, true);
238
239  CommandLine command_line(CommandLine::NO_PROGRAM);
240  command_line.AppendSwitch("foo");
241
242  CommandLine new_command_line(CommandLine::NO_PROGRAM);
243  ConvertFlagsToSwitches(&flags_storage_, &new_command_line, kAddSentinels);
244
245  EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(new_command_line,
246                                                        command_line));
247
248  ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
249
250  EXPECT_TRUE(AreSwitchesIdenticalToCurrentCommandLine(new_command_line,
251                                                       command_line));
252
253  // Now both have flags but different.
254  SetExperimentEnabled(&flags_storage_, kFlags1, false);
255  SetExperimentEnabled(&flags_storage_, kFlags2, true);
256
257  CommandLine another_command_line(CommandLine::NO_PROGRAM);
258  ConvertFlagsToSwitches(&flags_storage_, &another_command_line, kAddSentinels);
259
260  EXPECT_FALSE(AreSwitchesIdenticalToCurrentCommandLine(new_command_line,
261                                                        another_command_line));
262}
263
264TEST_F(AboutFlagsTest, RemoveFlagSwitches) {
265  std::map<std::string, CommandLine::StringType> switch_list;
266  switch_list[kSwitch1] = CommandLine::StringType();
267  switch_list[switches::kFlagSwitchesBegin] = CommandLine::StringType();
268  switch_list[switches::kFlagSwitchesEnd] = CommandLine::StringType();
269  switch_list["foo"] = CommandLine::StringType();
270
271  SetExperimentEnabled(&flags_storage_, kFlags1, true);
272
273  // This shouldn't do anything before ConvertFlagsToSwitches() wasn't called.
274  RemoveFlagsSwitches(&switch_list);
275  ASSERT_EQ(4u, switch_list.size());
276  EXPECT_TRUE(switch_list.find(kSwitch1) != switch_list.end());
277  EXPECT_TRUE(switch_list.find(switches::kFlagSwitchesBegin) !=
278              switch_list.end());
279  EXPECT_TRUE(switch_list.find(switches::kFlagSwitchesEnd) !=
280              switch_list.end());
281  EXPECT_TRUE(switch_list.find("foo") != switch_list.end());
282
283  // Call ConvertFlagsToSwitches(), then RemoveFlagsSwitches() again.
284  CommandLine command_line(CommandLine::NO_PROGRAM);
285  command_line.AppendSwitch("foo");
286  ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
287  RemoveFlagsSwitches(&switch_list);
288
289  // Now the about:flags-related switch should have been removed.
290  ASSERT_EQ(1u, switch_list.size());
291  EXPECT_TRUE(switch_list.find("foo") != switch_list.end());
292}
293
294// Tests enabling experiments that aren't supported on the current platform.
295TEST_F(AboutFlagsTest, PersistAndPrune) {
296  // Enable experiments 1 and 3.
297  SetExperimentEnabled(&flags_storage_, kFlags1, true);
298  SetExperimentEnabled(&flags_storage_, kFlags3, true);
299  CommandLine command_line(CommandLine::NO_PROGRAM);
300  EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
301  EXPECT_FALSE(command_line.HasSwitch(kSwitch3));
302
303  // Convert the flags to switches. Experiment 3 shouldn't be among the switches
304  // as it is not applicable to the current platform.
305  ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
306  EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
307  EXPECT_FALSE(command_line.HasSwitch(kSwitch3));
308
309  // Experiment 3 should show still be persisted in preferences though.
310  const ListValue* experiments_list =
311      prefs_.GetList(prefs::kEnabledLabsExperiments);
312  ASSERT_TRUE(experiments_list);
313  EXPECT_EQ(2U, experiments_list->GetSize());
314  std::string s0;
315  ASSERT_TRUE(experiments_list->GetString(0, &s0));
316  EXPECT_EQ(kFlags1, s0);
317  std::string s1;
318  ASSERT_TRUE(experiments_list->GetString(1, &s1));
319  EXPECT_EQ(kFlags3, s1);
320}
321
322// Tests that switches which should have values get them in the command
323// line.
324TEST_F(AboutFlagsTest, CheckValues) {
325  // Enable experiments 1 and 2.
326  SetExperimentEnabled(&flags_storage_, kFlags1, true);
327  SetExperimentEnabled(&flags_storage_, kFlags2, true);
328  CommandLine command_line(CommandLine::NO_PROGRAM);
329  EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
330  EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
331
332  // Convert the flags to switches.
333  ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
334  EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
335  EXPECT_EQ(std::string(), command_line.GetSwitchValueASCII(kSwitch1));
336  EXPECT_TRUE(command_line.HasSwitch(kSwitch2));
337  EXPECT_EQ(std::string(kValueForSwitch2),
338            command_line.GetSwitchValueASCII(kSwitch2));
339
340  // Confirm that there is no '=' in the command line for simple switches.
341  std::string switch1_with_equals = std::string("--") +
342                                    std::string(kSwitch1) +
343                                    std::string("=");
344#if defined(OS_WIN)
345  EXPECT_EQ(std::wstring::npos,
346            command_line.GetCommandLineString().find(
347                ASCIIToWide(switch1_with_equals)));
348#else
349  EXPECT_EQ(std::string::npos,
350            command_line.GetCommandLineString().find(switch1_with_equals));
351#endif
352
353  // And confirm there is a '=' for switches with values.
354  std::string switch2_with_equals = std::string("--") +
355                                    std::string(kSwitch2) +
356                                    std::string("=");
357#if defined(OS_WIN)
358  EXPECT_NE(std::wstring::npos,
359            command_line.GetCommandLineString().find(
360                ASCIIToWide(switch2_with_equals)));
361#else
362  EXPECT_NE(std::string::npos,
363            command_line.GetCommandLineString().find(switch2_with_equals));
364#endif
365
366  // And it should persist.
367  const ListValue* experiments_list =
368      prefs_.GetList(prefs::kEnabledLabsExperiments);
369  ASSERT_TRUE(experiments_list);
370  EXPECT_EQ(2U, experiments_list->GetSize());
371  std::string s0;
372  ASSERT_TRUE(experiments_list->GetString(0, &s0));
373  EXPECT_EQ(kFlags1, s0);
374  std::string s1;
375  ASSERT_TRUE(experiments_list->GetString(1, &s1));
376  EXPECT_EQ(kFlags2, s1);
377}
378
379// Tests multi-value type experiments.
380TEST_F(AboutFlagsTest, MultiValues) {
381  const Experiment& experiment = kExperiments[3];
382  ASSERT_EQ(kFlags4, experiment.internal_name);
383
384  // Initially, the first "deactivated" option of the multi experiment should
385  // be set.
386  {
387    CommandLine command_line(CommandLine::NO_PROGRAM);
388    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
389    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
390    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
391  }
392
393  // Enable the 2nd choice of the multi-value.
394  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(2), true);
395  {
396    CommandLine command_line(CommandLine::NO_PROGRAM);
397    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
398    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
399    EXPECT_TRUE(command_line.HasSwitch(kMultiSwitch2));
400    EXPECT_EQ(std::string(kValueForMultiSwitch2),
401              command_line.GetSwitchValueASCII(kMultiSwitch2));
402  }
403
404  // Disable the multi-value experiment.
405  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(0), true);
406  {
407    CommandLine command_line(CommandLine::NO_PROGRAM);
408    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
409    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
410    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
411  }
412}
413
414TEST_F(AboutFlagsTest, EnableDisableValues) {
415  const Experiment& experiment = kExperiments[4];
416  ASSERT_EQ(kFlags5, experiment.internal_name);
417
418  // Nothing selected.
419  {
420    CommandLine command_line(CommandLine::NO_PROGRAM);
421    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
422    EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
423    EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
424  }
425
426  // "Enable" option selected.
427  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(1), true);
428  {
429    CommandLine command_line(CommandLine::NO_PROGRAM);
430    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
431    EXPECT_TRUE(command_line.HasSwitch(kSwitch1));
432    EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
433    EXPECT_EQ(kEnableDisableValue1, command_line.GetSwitchValueASCII(kSwitch1));
434  }
435
436  // "Disable" option selected.
437  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(2), true);
438  {
439    CommandLine command_line(CommandLine::NO_PROGRAM);
440    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
441    EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
442    EXPECT_TRUE(command_line.HasSwitch(kSwitch2));
443    EXPECT_EQ(kEnableDisableValue2, command_line.GetSwitchValueASCII(kSwitch2));
444  }
445
446  // "Default" option selected, same as nothing selected.
447  SetExperimentEnabled(&flags_storage_, experiment.NameForChoice(0), true);
448  {
449    CommandLine command_line(CommandLine::NO_PROGRAM);
450    ConvertFlagsToSwitches(&flags_storage_, &command_line, kAddSentinels);
451    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
452    EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
453  }
454}
455
456// Makes sure there are no separators in any of the experiment names.
457TEST_F(AboutFlagsTest, NoSeparators) {
458  testing::SetExperiments(NULL, 0);
459  size_t count;
460  const Experiment* experiments = testing::GetExperiments(&count);
461    for (size_t i = 0; i < count; ++i) {
462    std::string name = experiments->internal_name;
463    EXPECT_EQ(std::string::npos, name.find(testing::kMultiSeparator)) << i;
464  }
465}
466
467}  // namespace about_flags
468