1 // Copyright 2013 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 "components/variations/variations_seed_processor.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "components/variations/processed_study.h"
15 #include "components/variations/variations_associated_data.h"
16 #include "testing/gtest/include/gtest/gtest.h"
18 namespace variations
{
22 // Converts |time| to Study proto format.
23 int64
TimeToProtoTime(const base::Time
& time
) {
24 return (time
- base::Time::UnixEpoch()).InSeconds();
27 // Constants for testing associating command line flags with trial groups.
28 const char kFlagStudyName
[] = "flag_test_trial";
29 const char kFlagGroup1Name
[] = "flag_group1";
30 const char kFlagGroup2Name
[] = "flag_group2";
31 const char kNonFlagGroupName
[] = "non_flag_group";
32 const char kForcingFlag1
[] = "flag_test1";
33 const char kForcingFlag2
[] = "flag_test2";
35 const VariationID kExperimentId
= 123;
37 // Adds an experiment to |study| with the specified |name| and |probability|.
38 Study_Experiment
* AddExperiment(const std::string
& name
, int probability
,
40 Study_Experiment
* experiment
= study
->add_experiment();
41 experiment
->set_name(name
);
42 experiment
->set_probability_weight(probability
);
46 // Populates |study| with test data used for testing associating command line
47 // flags with trials groups. The study will contain three groups, a default
48 // group that isn't associated with a flag, and two other groups, both
49 // associated with different flags.
50 Study
CreateStudyWithFlagGroups(int default_group_probability
,
51 int flag_group1_probability
,
52 int flag_group2_probability
) {
53 DCHECK_GE(default_group_probability
, 0);
54 DCHECK_GE(flag_group1_probability
, 0);
55 DCHECK_GE(flag_group2_probability
, 0);
57 study
.set_name(kFlagStudyName
);
58 study
.set_default_experiment_name(kNonFlagGroupName
);
60 AddExperiment(kNonFlagGroupName
, default_group_probability
, &study
);
61 AddExperiment(kFlagGroup1Name
, flag_group1_probability
, &study
)
62 ->set_forcing_flag(kForcingFlag1
);
63 AddExperiment(kFlagGroup2Name
, flag_group2_probability
, &study
)
64 ->set_forcing_flag(kForcingFlag2
);
69 // Tests whether a field trial is active (i.e. group() has been called on it).
70 bool IsFieldTrialActive(const std::string
& trial_name
) {
71 base::FieldTrial::ActiveGroups active_groups
;
72 base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups
);
73 for (size_t i
= 0; i
< active_groups
.size(); ++i
) {
74 if (active_groups
[i
].trial_name
== trial_name
)
80 class TestOverrideStringCallback
{
82 typedef std::map
<uint32_t, base::string16
> OverrideMap
;
84 TestOverrideStringCallback()
85 : callback_(base::Bind(&TestOverrideStringCallback::Override
,
86 base::Unretained(this))) {}
88 virtual ~TestOverrideStringCallback() {}
90 const VariationsSeedProcessor::UIStringOverrideCallback
& callback() const {
94 const OverrideMap
& overrides() const { return overrides_
; }
97 void Override(uint32_t hash
, const base::string16
& string
) {
98 overrides_
[hash
] = string
;
101 VariationsSeedProcessor::UIStringOverrideCallback callback_
;
102 OverrideMap overrides_
;
104 DISALLOW_COPY_AND_ASSIGN(TestOverrideStringCallback
);
109 class VariationsSeedProcessorTest
: public ::testing::Test
{
111 VariationsSeedProcessorTest() {
114 ~VariationsSeedProcessorTest() override
{
115 // Ensure that the maps are cleared between tests, since they are stored as
116 // process singletons.
117 testing::ClearAllVariationIDs();
118 testing::ClearAllVariationParams();
121 bool CreateTrialFromStudy(const Study
* study
) {
122 ProcessedStudy processed_study
;
123 if (processed_study
.Init(study
, false)) {
124 VariationsSeedProcessor().CreateTrialFromStudy(
125 processed_study
, override_callback_
.callback());
132 TestOverrideStringCallback override_callback_
;
135 DISALLOW_COPY_AND_ASSIGN(VariationsSeedProcessorTest
);
138 TEST_F(VariationsSeedProcessorTest
, AllowForceGroupAndVariationId
) {
139 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
141 base::FieldTrialList
field_trial_list(NULL
);
143 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
144 study
.mutable_experiment(1)->set_google_web_experiment_id(kExperimentId
);
146 EXPECT_TRUE(CreateTrialFromStudy(&study
));
147 EXPECT_EQ(kFlagGroup1Name
,
148 base::FieldTrialList::FindFullName(kFlagStudyName
));
150 VariationID id
= GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, kFlagStudyName
,
152 EXPECT_EQ(kExperimentId
, id
);
155 // Test that the group for kForcingFlag1 is forced.
156 TEST_F(VariationsSeedProcessorTest
, ForceGroupWithFlag1
) {
157 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
159 base::FieldTrialList
field_trial_list(NULL
);
161 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
162 EXPECT_TRUE(CreateTrialFromStudy(&study
));
163 EXPECT_EQ(kFlagGroup1Name
,
164 base::FieldTrialList::FindFullName(kFlagStudyName
));
167 // Test that the group for kForcingFlag2 is forced.
168 TEST_F(VariationsSeedProcessorTest
, ForceGroupWithFlag2
) {
169 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2
);
171 base::FieldTrialList
field_trial_list(NULL
);
173 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
174 EXPECT_TRUE(CreateTrialFromStudy(&study
));
175 EXPECT_EQ(kFlagGroup2Name
,
176 base::FieldTrialList::FindFullName(kFlagStudyName
));
179 TEST_F(VariationsSeedProcessorTest
, ForceGroup_ChooseFirstGroupWithFlag
) {
180 // Add the flag to the command line arguments so the flag group is forced.
181 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
182 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2
);
184 base::FieldTrialList
field_trial_list(NULL
);
186 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
187 EXPECT_TRUE(CreateTrialFromStudy(&study
));
188 EXPECT_EQ(kFlagGroup1Name
,
189 base::FieldTrialList::FindFullName(kFlagStudyName
));
192 TEST_F(VariationsSeedProcessorTest
, ForceGroup_DontChooseGroupWithFlag
) {
193 base::FieldTrialList
field_trial_list(NULL
);
195 // The two flag groups are given high probability, which would normally make
196 // them very likely to be chosen. They won't be chosen since flag groups are
197 // never chosen when their flag isn't present.
198 Study study
= CreateStudyWithFlagGroups(1, 999, 999);
199 EXPECT_TRUE(CreateTrialFromStudy(&study
));
200 EXPECT_EQ(kNonFlagGroupName
,
201 base::FieldTrialList::FindFullName(kFlagStudyName
));
204 TEST_F(VariationsSeedProcessorTest
,
205 NonExpiredStudyPrioritizedOverExpiredStudy
) {
206 VariationsSeedProcessor seed_processor
;
208 const std::string kTrialName
= "A";
209 const std::string kGroup1Name
= "Group1";
212 Study
* study1
= seed
.add_study();
213 study1
->set_name(kTrialName
);
214 study1
->set_default_experiment_name("Default");
215 AddExperiment(kGroup1Name
, 100, study1
);
216 AddExperiment("Default", 0, study1
);
217 Study
* study2
= seed
.add_study();
219 ASSERT_EQ(seed
.study(0).name(), seed
.study(1).name());
221 const base::Time year_ago
=
222 base::Time::Now() - base::TimeDelta::FromDays(365);
224 const base::Version
version("20.0.0.0");
226 // Check that adding [expired, non-expired] activates the non-expired one.
227 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName
));
229 base::FieldTrialList
field_trial_list(NULL
);
230 study1
->set_expiry_date(TimeToProtoTime(year_ago
));
231 seed_processor
.CreateTrialsFromSeed(
232 seed
, "en-CA", base::Time::Now(), version
, Study_Channel_STABLE
,
233 Study_FormFactor_DESKTOP
, "", "", "", override_callback_
.callback());
234 EXPECT_EQ(kGroup1Name
, base::FieldTrialList::FindFullName(kTrialName
));
237 // Check that adding [non-expired, expired] activates the non-expired one.
238 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName
));
240 base::FieldTrialList
field_trial_list(NULL
);
241 study1
->clear_expiry_date();
242 study2
->set_expiry_date(TimeToProtoTime(year_ago
));
243 seed_processor
.CreateTrialsFromSeed(
244 seed
, "en-CA", base::Time::Now(), version
, Study_Channel_STABLE
,
245 Study_FormFactor_DESKTOP
, "", "", "", override_callback_
.callback());
246 EXPECT_EQ(kGroup1Name
, base::FieldTrialList::FindFullName(kTrialName
));
250 TEST_F(VariationsSeedProcessorTest
, OverrideUIStrings
) {
251 base::FieldTrialList
field_trial_list(NULL
);
254 study
.set_name("Study1");
255 study
.set_default_experiment_name("B");
256 study
.set_activation_type(Study_ActivationType_ACTIVATION_AUTO
);
258 Study_Experiment
* experiment1
= AddExperiment("A", 0, &study
);
259 Study_Experiment_OverrideUIString
* override
=
260 experiment1
->add_override_ui_string();
262 override
->set_name_hash(1234);
263 override
->set_value("test");
265 Study_Experiment
* experiment2
= AddExperiment("B", 1, &study
);
267 EXPECT_TRUE(CreateTrialFromStudy(&study
));
269 const TestOverrideStringCallback::OverrideMap
& overrides
=
270 override_callback_
.overrides();
272 EXPECT_TRUE(overrides
.empty());
274 study
.set_name("Study2");
275 experiment1
->set_probability_weight(1);
276 experiment2
->set_probability_weight(0);
278 EXPECT_TRUE(CreateTrialFromStudy(&study
));
280 EXPECT_EQ(1u, overrides
.size());
281 TestOverrideStringCallback::OverrideMap::const_iterator it
=
282 overrides
.find(1234);
283 EXPECT_EQ(base::ASCIIToUTF16("test"), it
->second
);
286 TEST_F(VariationsSeedProcessorTest
, OverrideUIStringsWithForcingFlag
) {
287 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
288 ASSERT_EQ(kForcingFlag1
, study
.experiment(1).forcing_flag());
290 study
.set_activation_type(Study_ActivationType_ACTIVATION_AUTO
);
291 Study_Experiment_OverrideUIString
* override
=
292 study
.mutable_experiment(1)->add_override_ui_string();
293 override
->set_name_hash(1234);
294 override
->set_value("test");
296 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
297 base::FieldTrialList
field_trial_list(NULL
);
298 EXPECT_TRUE(CreateTrialFromStudy(&study
));
299 EXPECT_EQ(kFlagGroup1Name
, base::FieldTrialList::FindFullName(study
.name()));
301 const TestOverrideStringCallback::OverrideMap
& overrides
=
302 override_callback_
.overrides();
303 EXPECT_EQ(1u, overrides
.size());
304 TestOverrideStringCallback::OverrideMap::const_iterator it
=
305 overrides
.find(1234);
306 EXPECT_EQ(base::ASCIIToUTF16("test"), it
->second
);
309 TEST_F(VariationsSeedProcessorTest
, ValidateStudy
) {
311 study
.set_default_experiment_name("def");
312 AddExperiment("abc", 100, &study
);
313 Study_Experiment
* default_group
= AddExperiment("def", 200, &study
);
315 ProcessedStudy processed_study
;
316 EXPECT_TRUE(processed_study
.Init(&study
, false));
317 EXPECT_EQ(300, processed_study
.total_probability());
318 EXPECT_FALSE(processed_study
.all_assignments_to_one_group());
320 // Min version checks.
321 study
.mutable_filter()->set_min_version("1.2.3.*");
322 EXPECT_TRUE(processed_study
.Init(&study
, false));
323 study
.mutable_filter()->set_min_version("1.*.3");
324 EXPECT_FALSE(processed_study
.Init(&study
, false));
325 study
.mutable_filter()->set_min_version("1.2.3");
326 EXPECT_TRUE(processed_study
.Init(&study
, false));
328 // Max version checks.
329 study
.mutable_filter()->set_max_version("2.3.4.*");
330 EXPECT_TRUE(processed_study
.Init(&study
, false));
331 study
.mutable_filter()->set_max_version("*.3");
332 EXPECT_FALSE(processed_study
.Init(&study
, false));
333 study
.mutable_filter()->set_max_version("2.3.4");
334 EXPECT_TRUE(processed_study
.Init(&study
, false));
336 study
.clear_default_experiment_name();
337 EXPECT_FALSE(processed_study
.Init(&study
, false));
339 study
.set_default_experiment_name("xyz");
340 EXPECT_FALSE(processed_study
.Init(&study
, false));
342 study
.set_default_experiment_name("def");
343 default_group
->clear_name();
344 EXPECT_FALSE(processed_study
.Init(&study
, false));
346 default_group
->set_name("def");
347 EXPECT_TRUE(processed_study
.Init(&study
, false));
348 Study_Experiment
* repeated_group
= study
.add_experiment();
349 repeated_group
->set_name("abc");
350 repeated_group
->set_probability_weight(1);
351 EXPECT_FALSE(processed_study
.Init(&study
, false));
354 TEST_F(VariationsSeedProcessorTest
, ProcessedStudyAllAssignmentsToOneGroup
) {
356 study
.set_default_experiment_name("def");
357 AddExperiment("def", 100, &study
);
359 ProcessedStudy processed_study
;
360 EXPECT_TRUE(processed_study
.Init(&study
, false));
361 EXPECT_TRUE(processed_study
.all_assignments_to_one_group());
363 AddExperiment("abc", 0, &study
);
364 AddExperiment("flag", 0, &study
)->set_forcing_flag(kForcingFlag1
);
365 EXPECT_TRUE(processed_study
.Init(&study
, false));
366 EXPECT_TRUE(processed_study
.all_assignments_to_one_group());
368 AddExperiment("xyz", 1, &study
);
369 EXPECT_TRUE(processed_study
.Init(&study
, false));
370 EXPECT_FALSE(processed_study
.all_assignments_to_one_group());
372 // Try with default group and first group being at 0.
374 study2
.set_default_experiment_name("def");
375 AddExperiment("def", 0, &study2
);
376 AddExperiment("xyz", 34, &study2
);
377 EXPECT_TRUE(processed_study
.Init(&study2
, false));
378 EXPECT_TRUE(processed_study
.all_assignments_to_one_group());
379 AddExperiment("abc", 12, &study2
);
380 EXPECT_TRUE(processed_study
.Init(&study2
, false));
381 EXPECT_FALSE(processed_study
.all_assignments_to_one_group());
384 TEST_F(VariationsSeedProcessorTest
, VariationParams
) {
385 base::FieldTrialList
field_trial_list(NULL
);
388 study
.set_name("Study1");
389 study
.set_default_experiment_name("B");
391 Study_Experiment
* experiment1
= AddExperiment("A", 1, &study
);
392 Study_Experiment_Param
* param
= experiment1
->add_param();
393 param
->set_name("x");
394 param
->set_value("y");
396 Study_Experiment
* experiment2
= AddExperiment("B", 0, &study
);
398 EXPECT_TRUE(CreateTrialFromStudy(&study
));
399 EXPECT_EQ("y", GetVariationParamValue("Study1", "x"));
401 study
.set_name("Study2");
402 experiment1
->set_probability_weight(0);
403 experiment2
->set_probability_weight(1);
404 EXPECT_TRUE(CreateTrialFromStudy(&study
));
405 EXPECT_EQ(std::string(), GetVariationParamValue("Study2", "x"));
408 TEST_F(VariationsSeedProcessorTest
, VariationParamsWithForcingFlag
) {
409 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
410 ASSERT_EQ(kForcingFlag1
, study
.experiment(1).forcing_flag());
411 Study_Experiment_Param
* param
= study
.mutable_experiment(1)->add_param();
412 param
->set_name("x");
413 param
->set_value("y");
415 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
416 base::FieldTrialList
field_trial_list(NULL
);
417 EXPECT_TRUE(CreateTrialFromStudy(&study
));
418 EXPECT_EQ(kFlagGroup1Name
, base::FieldTrialList::FindFullName(study
.name()));
419 EXPECT_EQ("y", GetVariationParamValue(study
.name(), "x"));
422 TEST_F(VariationsSeedProcessorTest
, StartsActive
) {
423 base::FieldTrialList
field_trial_list(NULL
);
426 Study
* study1
= seed
.add_study();
427 study1
->set_name("A");
428 study1
->set_default_experiment_name("Default");
429 AddExperiment("AA", 100, study1
);
430 AddExperiment("Default", 0, study1
);
432 Study
* study2
= seed
.add_study();
433 study2
->set_name("B");
434 study2
->set_default_experiment_name("Default");
435 AddExperiment("BB", 100, study2
);
436 AddExperiment("Default", 0, study2
);
437 study2
->set_activation_type(Study_ActivationType_ACTIVATION_AUTO
);
439 Study
* study3
= seed
.add_study();
440 study3
->set_name("C");
441 study3
->set_default_experiment_name("Default");
442 AddExperiment("CC", 100, study3
);
443 AddExperiment("Default", 0, study3
);
444 study3
->set_activation_type(Study_ActivationType_ACTIVATION_EXPLICIT
);
446 VariationsSeedProcessor seed_processor
;
447 seed_processor
.CreateTrialsFromSeed(
448 seed
, "en-CA", base::Time::Now(), base::Version("20.0.0.0"),
449 Study_Channel_STABLE
, Study_FormFactor_DESKTOP
, "", "", "",
450 override_callback_
.callback());
452 // Non-specified and ACTIVATION_EXPLICIT should not start active, but
453 // ACTIVATION_AUTO should.
454 EXPECT_FALSE(IsFieldTrialActive("A"));
455 EXPECT_TRUE(IsFieldTrialActive("B"));
456 EXPECT_FALSE(IsFieldTrialActive("C"));
458 EXPECT_EQ("AA", base::FieldTrialList::FindFullName("A"));
459 EXPECT_EQ("BB", base::FieldTrialList::FindFullName("B"));
460 EXPECT_EQ("CC", base::FieldTrialList::FindFullName("C"));
462 // Now, all studies should be active.
463 EXPECT_TRUE(IsFieldTrialActive("A"));
464 EXPECT_TRUE(IsFieldTrialActive("B"));
465 EXPECT_TRUE(IsFieldTrialActive("C"));
468 TEST_F(VariationsSeedProcessorTest
, StartsActiveWithFlag
) {
469 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
471 base::FieldTrialList
field_trial_list(NULL
);
473 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
474 study
.set_activation_type(Study_ActivationType_ACTIVATION_AUTO
);
476 EXPECT_TRUE(CreateTrialFromStudy(&study
));
477 EXPECT_TRUE(IsFieldTrialActive(kFlagStudyName
));
479 EXPECT_EQ(kFlagGroup1Name
,
480 base::FieldTrialList::FindFullName(kFlagStudyName
));
483 TEST_F(VariationsSeedProcessorTest
, ForcingFlagAlreadyForced
) {
484 Study study
= CreateStudyWithFlagGroups(100, 0, 0);
485 ASSERT_EQ(kNonFlagGroupName
, study
.experiment(0).name());
486 Study_Experiment_Param
* param
= study
.mutable_experiment(0)->add_param();
487 param
->set_name("x");
488 param
->set_value("y");
489 study
.mutable_experiment(0)->set_google_web_experiment_id(kExperimentId
);
491 base::FieldTrialList
field_trial_list(NULL
);
492 base::FieldTrialList::CreateFieldTrial(kFlagStudyName
, kNonFlagGroupName
);
494 base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1
);
495 EXPECT_TRUE(CreateTrialFromStudy(&study
));
496 // The previously forced experiment should still hold.
497 EXPECT_EQ(kNonFlagGroupName
,
498 base::FieldTrialList::FindFullName(study
.name()));
500 // Check that params and experiment ids correspond.
501 EXPECT_EQ("y", GetVariationParamValue(study
.name(), "x"));
502 VariationID id
= GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, kFlagStudyName
,
504 EXPECT_EQ(kExperimentId
, id
);
507 } // namespace variations