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.
4
5 #include "components/variations/variations_seed_processor.h"
6
7 #include <vector>
8
9 #include "base/command_line.h"
10 #include "base/strings/string_split.h"
11 #include "components/variations/processed_study.h"
12 #include "components/variations/variations_associated_data.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 namespace chrome_variations {
16
17 namespace {
18
19 // Converts |time| to Study proto format.
TimeToProtoTime(const base::Time & time)20 int64 TimeToProtoTime(const base::Time& time) {
21 return (time - base::Time::UnixEpoch()).InSeconds();
22 }
23
24 // Constants for testing associating command line flags with trial groups.
25 const char kFlagStudyName[] = "flag_test_trial";
26 const char kFlagGroup1Name[] = "flag_group1";
27 const char kFlagGroup2Name[] = "flag_group2";
28 const char kNonFlagGroupName[] = "non_flag_group";
29 const char kForcingFlag1[] = "flag_test1";
30 const char kForcingFlag2[] = "flag_test2";
31
32 const VariationID kExperimentId = 123;
33
34 // Adds an experiment to |study| with the specified |name| and |probability|.
AddExperiment(const std::string & name,int probability,Study * study)35 Study_Experiment* AddExperiment(const std::string& name, int probability,
36 Study* study) {
37 Study_Experiment* experiment = study->add_experiment();
38 experiment->set_name(name);
39 experiment->set_probability_weight(probability);
40 return experiment;
41 }
42
43 // Populates |study| with test data used for testing associating command line
44 // flags with trials groups. The study will contain three groups, a default
45 // group that isn't associated with a flag, and two other groups, both
46 // associated with different flags.
CreateStudyWithFlagGroups(int default_group_probability,int flag_group1_probability,int flag_group2_probability)47 Study CreateStudyWithFlagGroups(int default_group_probability,
48 int flag_group1_probability,
49 int flag_group2_probability) {
50 DCHECK_GE(default_group_probability, 0);
51 DCHECK_GE(flag_group1_probability, 0);
52 DCHECK_GE(flag_group2_probability, 0);
53 Study study;
54 study.set_name(kFlagStudyName);
55 study.set_default_experiment_name(kNonFlagGroupName);
56
57 AddExperiment(kNonFlagGroupName, default_group_probability, &study);
58 AddExperiment(kFlagGroup1Name, flag_group1_probability, &study)
59 ->set_forcing_flag(kForcingFlag1);
60 AddExperiment(kFlagGroup2Name, flag_group2_probability, &study)
61 ->set_forcing_flag(kForcingFlag2);
62
63 return study;
64 }
65
66 // Tests whether a field trial is active (i.e. group() has been called on it).
IsFieldTrialActive(const std::string & trial_name)67 bool IsFieldTrialActive(const std::string& trial_name) {
68 base::FieldTrial::ActiveGroups active_groups;
69 base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
70 for (size_t i = 0; i < active_groups.size(); ++i) {
71 if (active_groups[i].trial_name == trial_name)
72 return true;
73 }
74 return false;
75 }
76
77 } // namespace
78
79 class VariationsSeedProcessorTest : public ::testing::Test {
80 public:
VariationsSeedProcessorTest()81 VariationsSeedProcessorTest() {
82 }
83
~VariationsSeedProcessorTest()84 virtual ~VariationsSeedProcessorTest() {
85 // Ensure that the maps are cleared between tests, since they are stored as
86 // process singletons.
87 testing::ClearAllVariationIDs();
88 testing::ClearAllVariationParams();
89 }
90
CreateTrialFromStudy(const Study * study)91 bool CreateTrialFromStudy(const Study* study) {
92 ProcessedStudy processed_study;
93 if (processed_study.Init(study, false)) {
94 VariationsSeedProcessor().CreateTrialFromStudy(processed_study);
95 return true;
96 }
97 return false;
98 }
99
100 private:
101 DISALLOW_COPY_AND_ASSIGN(VariationsSeedProcessorTest);
102 };
103
TEST_F(VariationsSeedProcessorTest,AllowForceGroupAndVariationId)104 TEST_F(VariationsSeedProcessorTest, AllowForceGroupAndVariationId) {
105 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
106
107 base::FieldTrialList field_trial_list(NULL);
108
109 Study study = CreateStudyWithFlagGroups(100, 0, 0);
110 study.mutable_experiment(1)->set_google_web_experiment_id(kExperimentId);
111
112 EXPECT_TRUE(CreateTrialFromStudy(&study));
113 EXPECT_EQ(kFlagGroup1Name,
114 base::FieldTrialList::FindFullName(kFlagStudyName));
115
116 VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName,
117 kFlagGroup1Name);
118 EXPECT_EQ(kExperimentId, id);
119 }
120
121 // Test that the group for kForcingFlag1 is forced.
TEST_F(VariationsSeedProcessorTest,ForceGroupWithFlag1)122 TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag1) {
123 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
124
125 base::FieldTrialList field_trial_list(NULL);
126
127 Study study = CreateStudyWithFlagGroups(100, 0, 0);
128 EXPECT_TRUE(CreateTrialFromStudy(&study));
129 EXPECT_EQ(kFlagGroup1Name,
130 base::FieldTrialList::FindFullName(kFlagStudyName));
131 }
132
133 // Test that the group for kForcingFlag2 is forced.
TEST_F(VariationsSeedProcessorTest,ForceGroupWithFlag2)134 TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag2) {
135 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);
136
137 base::FieldTrialList field_trial_list(NULL);
138
139 Study study = CreateStudyWithFlagGroups(100, 0, 0);
140 EXPECT_TRUE(CreateTrialFromStudy(&study));
141 EXPECT_EQ(kFlagGroup2Name,
142 base::FieldTrialList::FindFullName(kFlagStudyName));
143 }
144
TEST_F(VariationsSeedProcessorTest,ForceGroup_ChooseFirstGroupWithFlag)145 TEST_F(VariationsSeedProcessorTest, ForceGroup_ChooseFirstGroupWithFlag) {
146 // Add the flag to the command line arguments so the flag group is forced.
147 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
148 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);
149
150 base::FieldTrialList field_trial_list(NULL);
151
152 Study study = CreateStudyWithFlagGroups(100, 0, 0);
153 EXPECT_TRUE(CreateTrialFromStudy(&study));
154 EXPECT_EQ(kFlagGroup1Name,
155 base::FieldTrialList::FindFullName(kFlagStudyName));
156 }
157
TEST_F(VariationsSeedProcessorTest,ForceGroup_DontChooseGroupWithFlag)158 TEST_F(VariationsSeedProcessorTest, ForceGroup_DontChooseGroupWithFlag) {
159 base::FieldTrialList field_trial_list(NULL);
160
161 // The two flag groups are given high probability, which would normally make
162 // them very likely to be chosen. They won't be chosen since flag groups are
163 // never chosen when their flag isn't present.
164 Study study = CreateStudyWithFlagGroups(1, 999, 999);
165 EXPECT_TRUE(CreateTrialFromStudy(&study));
166 EXPECT_EQ(kNonFlagGroupName,
167 base::FieldTrialList::FindFullName(kFlagStudyName));
168 }
169
TEST_F(VariationsSeedProcessorTest,NonExpiredStudyPrioritizedOverExpiredStudy)170 TEST_F(VariationsSeedProcessorTest,
171 NonExpiredStudyPrioritizedOverExpiredStudy) {
172 VariationsSeedProcessor seed_processor;
173
174 const std::string kTrialName = "A";
175 const std::string kGroup1Name = "Group1";
176
177 VariationsSeed seed;
178 Study* study1 = seed.add_study();
179 study1->set_name(kTrialName);
180 study1->set_default_experiment_name("Default");
181 AddExperiment(kGroup1Name, 100, study1);
182 AddExperiment("Default", 0, study1);
183 Study* study2 = seed.add_study();
184 *study2 = *study1;
185 ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
186
187 const base::Time year_ago =
188 base::Time::Now() - base::TimeDelta::FromDays(365);
189
190 const base::Version version("20.0.0.0");
191
192 // Check that adding [expired, non-expired] activates the non-expired one.
193 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName));
194 {
195 base::FieldTrialList field_trial_list(NULL);
196 study1->set_expiry_date(TimeToProtoTime(year_ago));
197 seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(),
198 version, Study_Channel_STABLE,
199 Study_FormFactor_DESKTOP, "");
200 EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName));
201 }
202
203 // Check that adding [non-expired, expired] activates the non-expired one.
204 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName));
205 {
206 base::FieldTrialList field_trial_list(NULL);
207 study1->clear_expiry_date();
208 study2->set_expiry_date(TimeToProtoTime(year_ago));
209 seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(),
210 version, Study_Channel_STABLE,
211 Study_FormFactor_DESKTOP, "");
212 EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName));
213 }
214 }
215
TEST_F(VariationsSeedProcessorTest,ValidateStudy)216 TEST_F(VariationsSeedProcessorTest, ValidateStudy) {
217 Study study;
218 study.set_default_experiment_name("def");
219 AddExperiment("abc", 100, &study);
220 Study_Experiment* default_group = AddExperiment("def", 200, &study);
221
222 ProcessedStudy processed_study;
223 EXPECT_TRUE(processed_study.Init(&study, false));
224 EXPECT_EQ(300, processed_study.total_probability());
225
226 // Min version checks.
227 study.mutable_filter()->set_min_version("1.2.3.*");
228 EXPECT_TRUE(processed_study.Init(&study, false));
229 study.mutable_filter()->set_min_version("1.*.3");
230 EXPECT_FALSE(processed_study.Init(&study, false));
231 study.mutable_filter()->set_min_version("1.2.3");
232 EXPECT_TRUE(processed_study.Init(&study, false));
233
234 // Max version checks.
235 study.mutable_filter()->set_max_version("2.3.4.*");
236 EXPECT_TRUE(processed_study.Init(&study, false));
237 study.mutable_filter()->set_max_version("*.3");
238 EXPECT_FALSE(processed_study.Init(&study, false));
239 study.mutable_filter()->set_max_version("2.3.4");
240 EXPECT_TRUE(processed_study.Init(&study, false));
241
242 study.clear_default_experiment_name();
243 EXPECT_FALSE(processed_study.Init(&study, false));
244
245 study.set_default_experiment_name("xyz");
246 EXPECT_FALSE(processed_study.Init(&study, false));
247
248 study.set_default_experiment_name("def");
249 default_group->clear_name();
250 EXPECT_FALSE(processed_study.Init(&study, false));
251
252 default_group->set_name("def");
253 EXPECT_TRUE(processed_study.Init(&study, false));
254 Study_Experiment* repeated_group = study.add_experiment();
255 repeated_group->set_name("abc");
256 repeated_group->set_probability_weight(1);
257 EXPECT_FALSE(processed_study.Init(&study, false));
258 }
259
TEST_F(VariationsSeedProcessorTest,VariationParams)260 TEST_F(VariationsSeedProcessorTest, VariationParams) {
261 base::FieldTrialList field_trial_list(NULL);
262
263 Study study;
264 study.set_name("Study1");
265 study.set_default_experiment_name("B");
266
267 Study_Experiment* experiment1 = AddExperiment("A", 1, &study);
268 Study_Experiment_Param* param = experiment1->add_param();
269 param->set_name("x");
270 param->set_value("y");
271
272 Study_Experiment* experiment2 = AddExperiment("B", 0, &study);
273
274 EXPECT_TRUE(CreateTrialFromStudy(&study));
275 EXPECT_EQ("y", GetVariationParamValue("Study1", "x"));
276
277 study.set_name("Study2");
278 experiment1->set_probability_weight(0);
279 experiment2->set_probability_weight(1);
280 EXPECT_TRUE(CreateTrialFromStudy(&study));
281 EXPECT_EQ(std::string(), GetVariationParamValue("Study2", "x"));
282 }
283
TEST_F(VariationsSeedProcessorTest,VariationParamsWithForcingFlag)284 TEST_F(VariationsSeedProcessorTest, VariationParamsWithForcingFlag) {
285 Study study = CreateStudyWithFlagGroups(100, 0, 0);
286 ASSERT_EQ(kForcingFlag1, study.experiment(1).forcing_flag());
287 Study_Experiment_Param* param = study.mutable_experiment(1)->add_param();
288 param->set_name("x");
289 param->set_value("y");
290
291 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
292 base::FieldTrialList field_trial_list(NULL);
293 EXPECT_TRUE(CreateTrialFromStudy(&study));
294 EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study.name()));
295 EXPECT_EQ("y", GetVariationParamValue(study.name(), "x"));
296 }
297
TEST_F(VariationsSeedProcessorTest,StartsActive)298 TEST_F(VariationsSeedProcessorTest, StartsActive) {
299 base::FieldTrialList field_trial_list(NULL);
300
301 VariationsSeed seed;
302 Study* study1 = seed.add_study();
303 study1->set_name("A");
304 study1->set_default_experiment_name("Default");
305 AddExperiment("AA", 100, study1);
306 AddExperiment("Default", 0, study1);
307
308 Study* study2 = seed.add_study();
309 study2->set_name("B");
310 study2->set_default_experiment_name("Default");
311 AddExperiment("BB", 100, study2);
312 AddExperiment("Default", 0, study2);
313 study2->set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
314
315 Study* study3 = seed.add_study();
316 study3->set_name("C");
317 study3->set_default_experiment_name("Default");
318 AddExperiment("CC", 100, study3);
319 AddExperiment("Default", 0, study3);
320 study3->set_activation_type(Study_ActivationType_ACTIVATION_EXPLICIT);
321
322 VariationsSeedProcessor seed_processor;
323 seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(),
324 base::Version("20.0.0.0"),
325 Study_Channel_STABLE,
326 Study_FormFactor_DESKTOP, "");
327
328 // Non-specified and ACTIVATION_EXPLICIT should not start active, but
329 // ACTIVATION_AUTO should.
330 EXPECT_FALSE(IsFieldTrialActive("A"));
331 EXPECT_TRUE(IsFieldTrialActive("B"));
332 EXPECT_FALSE(IsFieldTrialActive("C"));
333
334 EXPECT_EQ("AA", base::FieldTrialList::FindFullName("A"));
335 EXPECT_EQ("BB", base::FieldTrialList::FindFullName("B"));
336 EXPECT_EQ("CC", base::FieldTrialList::FindFullName("C"));
337
338 // Now, all studies should be active.
339 EXPECT_TRUE(IsFieldTrialActive("A"));
340 EXPECT_TRUE(IsFieldTrialActive("B"));
341 EXPECT_TRUE(IsFieldTrialActive("C"));
342 }
343
TEST_F(VariationsSeedProcessorTest,StartsActiveWithFlag)344 TEST_F(VariationsSeedProcessorTest, StartsActiveWithFlag) {
345 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
346
347 base::FieldTrialList field_trial_list(NULL);
348
349 Study study = CreateStudyWithFlagGroups(100, 0, 0);
350 study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
351
352 EXPECT_TRUE(CreateTrialFromStudy(&study));
353 EXPECT_TRUE(IsFieldTrialActive(kFlagStudyName));
354
355 EXPECT_EQ(kFlagGroup1Name,
356 base::FieldTrialList::FindFullName(kFlagStudyName));
357 }
358
359 } // namespace chrome_variations
360