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 study.mutable_filter()->add_channel(Study_Channel_DEV);
112 study.mutable_filter()->add_channel(Study_Channel_CANARY);
113 study.mutable_filter()->add_platform(Study_Platform_PLATFORM_ANDROID);
114
115 EXPECT_TRUE(CreateTrialFromStudy(&study));
116 EXPECT_EQ(kFlagGroup1Name,
117 base::FieldTrialList::FindFullName(kFlagStudyName));
118
119 VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName,
120 kFlagGroup1Name);
121 EXPECT_EQ(kExperimentId, id);
122 }
123
TEST_F(VariationsSeedProcessorTest,AllowVariationIdWithForcingFlag)124 TEST_F(VariationsSeedProcessorTest, AllowVariationIdWithForcingFlag) {
125 VariationsSeedProcessor seed_processor;
126 Study study = CreateStudyWithFlagGroups(100, 0, 0);
127 EXPECT_FALSE(seed_processor.AllowVariationIdWithForcingFlag(study));
128
129 study.mutable_filter()->add_channel(Study_Channel_DEV);
130 EXPECT_FALSE(seed_processor.AllowVariationIdWithForcingFlag(study));
131
132 study.mutable_filter()->add_platform(Study_Platform_PLATFORM_ANDROID);
133 EXPECT_TRUE(seed_processor.AllowVariationIdWithForcingFlag(study));
134
135 study.mutable_filter()->add_channel(Study_Channel_CANARY);
136 study.mutable_filter()->add_platform(Study_Platform_PLATFORM_IOS);
137 EXPECT_TRUE(seed_processor.AllowVariationIdWithForcingFlag(study));
138
139 study.mutable_filter()->add_platform(Study_Platform_PLATFORM_WINDOWS);
140 EXPECT_FALSE(seed_processor.AllowVariationIdWithForcingFlag(study));
141 }
142
TEST_F(VariationsSeedProcessorTest,CheckStudyChannel)143 TEST_F(VariationsSeedProcessorTest, CheckStudyChannel) {
144 VariationsSeedProcessor seed_processor;
145
146 const Study_Channel channels[] = {
147 Study_Channel_CANARY,
148 Study_Channel_DEV,
149 Study_Channel_BETA,
150 Study_Channel_STABLE,
151 };
152 bool channel_added[arraysize(channels)] = { 0 };
153
154 Study_Filter filter;
155
156 // Check in the forwarded order. The loop cond is <= arraysize(channels)
157 // instead of < so that the result of adding the last channel gets checked.
158 for (size_t i = 0; i <= arraysize(channels); ++i) {
159 for (size_t j = 0; j < arraysize(channels); ++j) {
160 const bool expected = channel_added[j] || filter.channel_size() == 0;
161 const bool result = seed_processor.CheckStudyChannel(filter, channels[j]);
162 EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
163 }
164
165 if (i < arraysize(channels)) {
166 filter.add_channel(channels[i]);
167 channel_added[i] = true;
168 }
169 }
170
171 // Do the same check in the reverse order.
172 filter.clear_channel();
173 memset(&channel_added, 0, sizeof(channel_added));
174 for (size_t i = 0; i <= arraysize(channels); ++i) {
175 for (size_t j = 0; j < arraysize(channels); ++j) {
176 const bool expected = channel_added[j] || filter.channel_size() == 0;
177 const bool result = seed_processor.CheckStudyChannel(filter, channels[j]);
178 EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
179 }
180
181 if (i < arraysize(channels)) {
182 const int index = arraysize(channels) - i - 1;
183 filter.add_channel(channels[index]);
184 channel_added[index] = true;
185 }
186 }
187 }
188
TEST_F(VariationsSeedProcessorTest,CheckStudyFormFactor)189 TEST_F(VariationsSeedProcessorTest, CheckStudyFormFactor) {
190 VariationsSeedProcessor seed_processor;
191
192 const Study_FormFactor form_factors[] = {
193 Study_FormFactor_DESKTOP,
194 Study_FormFactor_PHONE,
195 Study_FormFactor_TABLET,
196 };
197
198 ASSERT_EQ(Study_FormFactor_FormFactor_ARRAYSIZE,
199 static_cast<int>(arraysize(form_factors)));
200
201 bool form_factor_added[arraysize(form_factors)] = { 0 };
202 Study_Filter filter;
203
204 for (size_t i = 0; i <= arraysize(form_factors); ++i) {
205 for (size_t j = 0; j < arraysize(form_factors); ++j) {
206 const bool expected = form_factor_added[j] ||
207 filter.form_factor_size() == 0;
208 const bool result = seed_processor.CheckStudyFormFactor(filter,
209 form_factors[j]);
210 EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
211 }
212
213 if (i < arraysize(form_factors)) {
214 filter.add_form_factor(form_factors[i]);
215 form_factor_added[i] = true;
216 }
217 }
218
219 // Do the same check in the reverse order.
220 filter.clear_form_factor();
221 memset(&form_factor_added, 0, sizeof(form_factor_added));
222 for (size_t i = 0; i <= arraysize(form_factors); ++i) {
223 for (size_t j = 0; j < arraysize(form_factors); ++j) {
224 const bool expected = form_factor_added[j] ||
225 filter.form_factor_size() == 0;
226 const bool result = seed_processor.CheckStudyFormFactor(filter,
227 form_factors[j]);
228 EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
229 }
230
231 if (i < arraysize(form_factors)) {
232 const int index = arraysize(form_factors) - i - 1;;
233 filter.add_form_factor(form_factors[index]);
234 form_factor_added[index] = true;
235 }
236 }
237 }
238
TEST_F(VariationsSeedProcessorTest,CheckStudyLocale)239 TEST_F(VariationsSeedProcessorTest, CheckStudyLocale) {
240 VariationsSeedProcessor seed_processor;
241
242 struct {
243 const char* filter_locales;
244 bool en_us_result;
245 bool en_ca_result;
246 bool fr_result;
247 } test_cases[] = {
248 {"en-US", true, false, false},
249 {"en-US,en-CA,fr", true, true, true},
250 {"en-US,en-CA,en-GB", true, true, false},
251 {"en-GB,en-CA,en-US", true, true, false},
252 {"ja,kr,vi", false, false, false},
253 {"fr-CA", false, false, false},
254 {"", true, true, true},
255 };
256
257 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
258 std::vector<std::string> filter_locales;
259 Study_Filter filter;
260 base::SplitString(test_cases[i].filter_locales, ',', &filter_locales);
261 for (size_t j = 0; j < filter_locales.size(); ++j)
262 filter.add_locale(filter_locales[j]);
263 EXPECT_EQ(test_cases[i].en_us_result,
264 seed_processor.CheckStudyLocale(filter, "en-US"));
265 EXPECT_EQ(test_cases[i].en_ca_result,
266 seed_processor.CheckStudyLocale(filter, "en-CA"));
267 EXPECT_EQ(test_cases[i].fr_result,
268 seed_processor.CheckStudyLocale(filter, "fr"));
269 }
270 }
271
TEST_F(VariationsSeedProcessorTest,CheckStudyPlatform)272 TEST_F(VariationsSeedProcessorTest, CheckStudyPlatform) {
273 VariationsSeedProcessor seed_processor;
274
275 const Study_Platform platforms[] = {
276 Study_Platform_PLATFORM_WINDOWS,
277 Study_Platform_PLATFORM_MAC,
278 Study_Platform_PLATFORM_LINUX,
279 Study_Platform_PLATFORM_CHROMEOS,
280 Study_Platform_PLATFORM_ANDROID,
281 Study_Platform_PLATFORM_IOS,
282 };
283 ASSERT_EQ(Study_Platform_Platform_ARRAYSIZE,
284 static_cast<int>(arraysize(platforms)));
285 bool platform_added[arraysize(platforms)] = { 0 };
286
287 Study_Filter filter;
288
289 // Check in the forwarded order. The loop cond is <= arraysize(platforms)
290 // instead of < so that the result of adding the last channel gets checked.
291 for (size_t i = 0; i <= arraysize(platforms); ++i) {
292 for (size_t j = 0; j < arraysize(platforms); ++j) {
293 const bool expected = platform_added[j] || filter.platform_size() == 0;
294 const bool result = seed_processor.CheckStudyPlatform(filter,
295 platforms[j]);
296 EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
297 }
298
299 if (i < arraysize(platforms)) {
300 filter.add_platform(platforms[i]);
301 platform_added[i] = true;
302 }
303 }
304
305 // Do the same check in the reverse order.
306 filter.clear_platform();
307 memset(&platform_added, 0, sizeof(platform_added));
308 for (size_t i = 0; i <= arraysize(platforms); ++i) {
309 for (size_t j = 0; j < arraysize(platforms); ++j) {
310 const bool expected = platform_added[j] || filter.platform_size() == 0;
311 const bool result = seed_processor.CheckStudyPlatform(filter,
312 platforms[j]);
313 EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
314 }
315
316 if (i < arraysize(platforms)) {
317 const int index = arraysize(platforms) - i - 1;
318 filter.add_platform(platforms[index]);
319 platform_added[index] = true;
320 }
321 }
322 }
323
TEST_F(VariationsSeedProcessorTest,CheckStudyStartDate)324 TEST_F(VariationsSeedProcessorTest, CheckStudyStartDate) {
325 VariationsSeedProcessor seed_processor;
326
327 const base::Time now = base::Time::Now();
328 const base::TimeDelta delta = base::TimeDelta::FromHours(1);
329 const struct {
330 const base::Time start_date;
331 bool expected_result;
332 } start_test_cases[] = {
333 { now - delta, true },
334 { now, true },
335 { now + delta, false },
336 };
337
338 Study_Filter filter;
339
340 // Start date not set should result in true.
341 EXPECT_TRUE(seed_processor.CheckStudyStartDate(filter, now));
342
343 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(start_test_cases); ++i) {
344 filter.set_start_date(TimeToProtoTime(start_test_cases[i].start_date));
345 const bool result = seed_processor.CheckStudyStartDate(filter, now);
346 EXPECT_EQ(start_test_cases[i].expected_result, result)
347 << "Case " << i << " failed!";
348 }
349 }
350
TEST_F(VariationsSeedProcessorTest,CheckStudyVersion)351 TEST_F(VariationsSeedProcessorTest, CheckStudyVersion) {
352 VariationsSeedProcessor seed_processor;
353
354 const struct {
355 const char* min_version;
356 const char* version;
357 bool expected_result;
358 } min_test_cases[] = {
359 { "1.2.2", "1.2.3", true },
360 { "1.2.3", "1.2.3", true },
361 { "1.2.4", "1.2.3", false },
362 { "1.3.2", "1.2.3", false },
363 { "2.1.2", "1.2.3", false },
364 { "0.3.4", "1.2.3", true },
365 // Wildcards.
366 { "1.*", "1.2.3", true },
367 { "1.2.*", "1.2.3", true },
368 { "1.2.3.*", "1.2.3", true },
369 { "1.2.4.*", "1.2.3", false },
370 { "2.*", "1.2.3", false },
371 { "0.3.*", "1.2.3", true },
372 };
373
374 const struct {
375 const char* max_version;
376 const char* version;
377 bool expected_result;
378 } max_test_cases[] = {
379 { "1.2.2", "1.2.3", false },
380 { "1.2.3", "1.2.3", true },
381 { "1.2.4", "1.2.3", true },
382 { "2.1.1", "1.2.3", true },
383 { "2.1.1", "2.3.4", false },
384 // Wildcards
385 { "2.1.*", "2.3.4", false },
386 { "2.*", "2.3.4", true },
387 { "2.3.*", "2.3.4", true },
388 { "2.3.4.*", "2.3.4", true },
389 { "2.3.4.0.*", "2.3.4", true },
390 { "2.4.*", "2.3.4", true },
391 { "1.3.*", "2.3.4", false },
392 { "1.*", "2.3.4", false },
393 };
394
395 Study_Filter filter;
396
397 // Min/max version not set should result in true.
398 EXPECT_TRUE(seed_processor.CheckStudyVersion(filter, base::Version("1.2.3")));
399
400 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(min_test_cases); ++i) {
401 filter.set_min_version(min_test_cases[i].min_version);
402 const bool result =
403 seed_processor.CheckStudyVersion(filter,
404 Version(min_test_cases[i].version));
405 EXPECT_EQ(min_test_cases[i].expected_result, result) <<
406 "Min. version case " << i << " failed!";
407 }
408 filter.clear_min_version();
409
410 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(max_test_cases); ++i) {
411 filter.set_max_version(max_test_cases[i].max_version);
412 const bool result =
413 seed_processor.CheckStudyVersion(filter,
414 Version(max_test_cases[i].version));
415 EXPECT_EQ(max_test_cases[i].expected_result, result) <<
416 "Max version case " << i << " failed!";
417 }
418
419 // Check intersection semantics.
420 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(min_test_cases); ++i) {
421 for (size_t j = 0; j < ARRAYSIZE_UNSAFE(max_test_cases); ++j) {
422 filter.set_min_version(min_test_cases[i].min_version);
423 filter.set_max_version(max_test_cases[j].max_version);
424
425 if (!min_test_cases[i].expected_result) {
426 const bool result =
427 seed_processor.CheckStudyVersion(
428 filter, Version(min_test_cases[i].version));
429 EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
430 }
431
432 if (!max_test_cases[j].expected_result) {
433 const bool result =
434 seed_processor.CheckStudyVersion(
435 filter, Version(max_test_cases[j].version));
436 EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
437 }
438 }
439 }
440 }
441
TEST_F(VariationsSeedProcessorTest,FilterAndValidateStudies)442 TEST_F(VariationsSeedProcessorTest, FilterAndValidateStudies) {
443 const std::string kTrial1Name = "A";
444 const std::string kGroup1Name = "Group1";
445 const std::string kTrial3Name = "B";
446
447 VariationsSeed seed;
448 Study* study1 = seed.add_study();
449 study1->set_name(kTrial1Name);
450 study1->set_default_experiment_name("Default");
451 AddExperiment(kGroup1Name, 100, study1);
452 AddExperiment("Default", 0, study1);
453
454 Study* study2 = seed.add_study();
455 *study2 = *study1;
456 study2->mutable_experiment(0)->set_name("Bam");
457 ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
458
459 Study* study3 = seed.add_study();
460 study3->set_name(kTrial3Name);
461 study3->set_default_experiment_name("Default");
462 AddExperiment("A", 10, study3);
463 AddExperiment("Default", 25, study3);
464
465 std::vector<ProcessedStudy> processed_studies;
466 VariationsSeedProcessor().FilterAndValidateStudies(
467 seed, "en-CA", base::Time::Now(), base::Version("20.0.0.0"),
468 Study_Channel_STABLE, Study_FormFactor_DESKTOP, &processed_studies);
469
470 // Check that only the first kTrial1Name study was kept.
471 ASSERT_EQ(2U, processed_studies.size());
472 EXPECT_EQ(kTrial1Name, processed_studies[0].study()->name());
473 EXPECT_EQ(kGroup1Name, processed_studies[0].study()->experiment(0).name());
474 EXPECT_EQ(kTrial3Name, processed_studies[1].study()->name());
475 }
476
TEST_F(VariationsSeedProcessorTest,ForbidForceGroupWithVariationId)477 TEST_F(VariationsSeedProcessorTest, ForbidForceGroupWithVariationId) {
478 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
479
480 base::FieldTrialList field_trial_list(NULL);
481
482 Study study = CreateStudyWithFlagGroups(100, 0, 0);
483 study.mutable_experiment(1)->set_google_web_experiment_id(kExperimentId);
484 // Adding windows platform makes forcing_flag and variation Id incompatible.
485 study.mutable_filter()->add_platform(Study_Platform_PLATFORM_WINDOWS);
486
487 EXPECT_TRUE(CreateTrialFromStudy(&study));
488 EXPECT_EQ(kFlagGroup1Name,
489 base::FieldTrialList::FindFullName(kFlagStudyName));
490 VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName,
491 kFlagGroup1Name);
492 EXPECT_EQ(EMPTY_ID, id);
493 }
494
495 // Test that the group for kForcingFlag1 is forced.
TEST_F(VariationsSeedProcessorTest,ForceGroupWithFlag1)496 TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag1) {
497 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
498
499 base::FieldTrialList field_trial_list(NULL);
500
501 Study study = CreateStudyWithFlagGroups(100, 0, 0);
502 EXPECT_TRUE(CreateTrialFromStudy(&study));
503 EXPECT_EQ(kFlagGroup1Name,
504 base::FieldTrialList::FindFullName(kFlagStudyName));
505 }
506
507 // Test that the group for kForcingFlag2 is forced.
TEST_F(VariationsSeedProcessorTest,ForceGroupWithFlag2)508 TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag2) {
509 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);
510
511 base::FieldTrialList field_trial_list(NULL);
512
513 Study study = CreateStudyWithFlagGroups(100, 0, 0);
514 EXPECT_TRUE(CreateTrialFromStudy(&study));
515 EXPECT_EQ(kFlagGroup2Name,
516 base::FieldTrialList::FindFullName(kFlagStudyName));
517 }
518
TEST_F(VariationsSeedProcessorTest,ForceGroup_ChooseFirstGroupWithFlag)519 TEST_F(VariationsSeedProcessorTest, ForceGroup_ChooseFirstGroupWithFlag) {
520 // Add the flag to the command line arguments so the flag group is forced.
521 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
522 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);
523
524 base::FieldTrialList field_trial_list(NULL);
525
526 Study study = CreateStudyWithFlagGroups(100, 0, 0);
527 EXPECT_TRUE(CreateTrialFromStudy(&study));
528 EXPECT_EQ(kFlagGroup1Name,
529 base::FieldTrialList::FindFullName(kFlagStudyName));
530 }
531
TEST_F(VariationsSeedProcessorTest,ForceGroup_DontChooseGroupWithFlag)532 TEST_F(VariationsSeedProcessorTest, ForceGroup_DontChooseGroupWithFlag) {
533 base::FieldTrialList field_trial_list(NULL);
534
535 // The two flag groups are given high probability, which would normally make
536 // them very likely to be chosen. They won't be chosen since flag groups are
537 // never chosen when their flag isn't present.
538 Study study = CreateStudyWithFlagGroups(1, 999, 999);
539 EXPECT_TRUE(CreateTrialFromStudy(&study));
540 EXPECT_EQ(kNonFlagGroupName,
541 base::FieldTrialList::FindFullName(kFlagStudyName));
542 }
543
TEST_F(VariationsSeedProcessorTest,IsStudyExpired)544 TEST_F(VariationsSeedProcessorTest, IsStudyExpired) {
545 VariationsSeedProcessor seed_processor;
546
547 const base::Time now = base::Time::Now();
548 const base::TimeDelta delta = base::TimeDelta::FromHours(1);
549 const struct {
550 const base::Time expiry_date;
551 bool expected_result;
552 } expiry_test_cases[] = {
553 { now - delta, true },
554 { now, true },
555 { now + delta, false },
556 };
557
558 Study study;
559
560 // Expiry date not set should result in false.
561 EXPECT_FALSE(seed_processor.IsStudyExpired(study, now));
562
563 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(expiry_test_cases); ++i) {
564 study.set_expiry_date(TimeToProtoTime(expiry_test_cases[i].expiry_date));
565 const bool result = seed_processor.IsStudyExpired(study, now);
566 EXPECT_EQ(expiry_test_cases[i].expected_result, result)
567 << "Case " << i << " failed!";
568 }
569 }
570
TEST_F(VariationsSeedProcessorTest,NonExpiredStudyPrioritizedOverExpiredStudy)571 TEST_F(VariationsSeedProcessorTest,
572 NonExpiredStudyPrioritizedOverExpiredStudy) {
573 VariationsSeedProcessor seed_processor;
574
575 const std::string kTrialName = "A";
576 const std::string kGroup1Name = "Group1";
577
578 VariationsSeed seed;
579 Study* study1 = seed.add_study();
580 study1->set_name(kTrialName);
581 study1->set_default_experiment_name("Default");
582 AddExperiment(kGroup1Name, 100, study1);
583 AddExperiment("Default", 0, study1);
584 Study* study2 = seed.add_study();
585 *study2 = *study1;
586 ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
587
588 const base::Time year_ago =
589 base::Time::Now() - base::TimeDelta::FromDays(365);
590
591 const base::Version version("20.0.0.0");
592
593 // Check that adding [expired, non-expired] activates the non-expired one.
594 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName));
595 {
596 base::FieldTrialList field_trial_list(NULL);
597 study1->set_expiry_date(TimeToProtoTime(year_ago));
598 seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(),
599 version, Study_Channel_STABLE,
600 Study_FormFactor_DESKTOP);
601 EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName));
602 }
603
604 // Check that adding [non-expired, expired] activates the non-expired one.
605 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName));
606 {
607 base::FieldTrialList field_trial_list(NULL);
608 study1->clear_expiry_date();
609 study2->set_expiry_date(TimeToProtoTime(year_ago));
610 seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(),
611 version, Study_Channel_STABLE,
612 Study_FormFactor_DESKTOP);
613 EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName));
614 }
615 }
616
TEST_F(VariationsSeedProcessorTest,ValidateStudy)617 TEST_F(VariationsSeedProcessorTest, ValidateStudy) {
618 Study study;
619 study.set_default_experiment_name("def");
620 AddExperiment("abc", 100, &study);
621 Study_Experiment* default_group = AddExperiment("def", 200, &study);
622
623 ProcessedStudy processed_study;
624 EXPECT_TRUE(processed_study.Init(&study, false));
625 EXPECT_EQ(300, processed_study.total_probability());
626
627 // Min version checks.
628 study.mutable_filter()->set_min_version("1.2.3.*");
629 EXPECT_TRUE(processed_study.Init(&study, false));
630 study.mutable_filter()->set_min_version("1.*.3");
631 EXPECT_FALSE(processed_study.Init(&study, false));
632 study.mutable_filter()->set_min_version("1.2.3");
633 EXPECT_TRUE(processed_study.Init(&study, false));
634
635 // Max version checks.
636 study.mutable_filter()->set_max_version("2.3.4.*");
637 EXPECT_TRUE(processed_study.Init(&study, false));
638 study.mutable_filter()->set_max_version("*.3");
639 EXPECT_FALSE(processed_study.Init(&study, false));
640 study.mutable_filter()->set_max_version("2.3.4");
641 EXPECT_TRUE(processed_study.Init(&study, false));
642
643 study.clear_default_experiment_name();
644 EXPECT_FALSE(processed_study.Init(&study, false));
645
646 study.set_default_experiment_name("xyz");
647 EXPECT_FALSE(processed_study.Init(&study, false));
648
649 study.set_default_experiment_name("def");
650 default_group->clear_name();
651 EXPECT_FALSE(processed_study.Init(&study, false));
652
653 default_group->set_name("def");
654 EXPECT_TRUE(processed_study.Init(&study, false));
655 Study_Experiment* repeated_group = study.add_experiment();
656 repeated_group->set_name("abc");
657 repeated_group->set_probability_weight(1);
658 EXPECT_FALSE(processed_study.Init(&study, false));
659 }
660
TEST_F(VariationsSeedProcessorTest,VariationParams)661 TEST_F(VariationsSeedProcessorTest, VariationParams) {
662 base::FieldTrialList field_trial_list(NULL);
663
664 Study study;
665 study.set_name("Study1");
666 study.set_default_experiment_name("B");
667
668 Study_Experiment* experiment1 = AddExperiment("A", 1, &study);
669 Study_Experiment_Param* param = experiment1->add_param();
670 param->set_name("x");
671 param->set_value("y");
672
673 Study_Experiment* experiment2 = AddExperiment("B", 0, &study);
674
675 EXPECT_TRUE(CreateTrialFromStudy(&study));
676 EXPECT_EQ("y", GetVariationParamValue("Study1", "x"));
677
678 study.set_name("Study2");
679 experiment1->set_probability_weight(0);
680 experiment2->set_probability_weight(1);
681 EXPECT_TRUE(CreateTrialFromStudy(&study));
682 EXPECT_EQ(std::string(), GetVariationParamValue("Study2", "x"));
683 }
684
TEST_F(VariationsSeedProcessorTest,VariationParamsWithForcingFlag)685 TEST_F(VariationsSeedProcessorTest, VariationParamsWithForcingFlag) {
686 Study study = CreateStudyWithFlagGroups(100, 0, 0);
687 ASSERT_EQ(kForcingFlag1, study.experiment(1).forcing_flag());
688 Study_Experiment_Param* param = study.mutable_experiment(1)->add_param();
689 param->set_name("x");
690 param->set_value("y");
691
692 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
693 base::FieldTrialList field_trial_list(NULL);
694 EXPECT_TRUE(CreateTrialFromStudy(&study));
695 EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study.name()));
696 EXPECT_EQ("y", GetVariationParamValue(study.name(), "x"));
697 }
698
TEST_F(VariationsSeedProcessorTest,StartsActive)699 TEST_F(VariationsSeedProcessorTest, StartsActive) {
700 base::FieldTrialList field_trial_list(NULL);
701
702 VariationsSeed seed;
703 Study* study1 = seed.add_study();
704 study1->set_name("A");
705 study1->set_default_experiment_name("Default");
706 AddExperiment("AA", 100, study1);
707 AddExperiment("Default", 0, study1);
708
709 Study* study2 = seed.add_study();
710 study2->set_name("B");
711 study2->set_default_experiment_name("Default");
712 AddExperiment("BB", 100, study2);
713 AddExperiment("Default", 0, study2);
714 study2->set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
715
716 Study* study3 = seed.add_study();
717 study3->set_name("C");
718 study3->set_default_experiment_name("Default");
719 AddExperiment("CC", 100, study3);
720 AddExperiment("Default", 0, study3);
721 study3->set_activation_type(Study_ActivationType_ACTIVATION_EXPLICIT);
722
723 VariationsSeedProcessor seed_processor;
724 seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(),
725 base::Version("20.0.0.0"),
726 Study_Channel_STABLE,
727 Study_FormFactor_DESKTOP);
728
729 // Non-specified and ACTIVATION_EXPLICIT should not start active, but
730 // ACTIVATION_AUTO should.
731 EXPECT_FALSE(IsFieldTrialActive("A"));
732 EXPECT_TRUE(IsFieldTrialActive("B"));
733 EXPECT_FALSE(IsFieldTrialActive("C"));
734
735 EXPECT_EQ("AA", base::FieldTrialList::FindFullName("A"));
736 EXPECT_EQ("BB", base::FieldTrialList::FindFullName("B"));
737 EXPECT_EQ("CC", base::FieldTrialList::FindFullName("C"));
738
739 // Now, all studies should be active.
740 EXPECT_TRUE(IsFieldTrialActive("A"));
741 EXPECT_TRUE(IsFieldTrialActive("B"));
742 EXPECT_TRUE(IsFieldTrialActive("C"));
743 }
744
745 } // namespace chrome_variations
746