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 <map> 8 #include <vector> 9 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" 17 18 namespace variations { 19 20 namespace { 21 22 // Converts |time| to Study proto format. 23 int64 TimeToProtoTime(const base::Time& time) { 24 return (time - base::Time::UnixEpoch()).InSeconds(); 25 } 26 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"; 34 35 const VariationID kExperimentId = 123; 36 37 // Adds an experiment to |study| with the specified |name| and |probability|. 38 Study_Experiment* AddExperiment(const std::string& name, int probability, 39 Study* study) { 40 Study_Experiment* experiment = study->add_experiment(); 41 experiment->set_name(name); 42 experiment->set_probability_weight(probability); 43 return experiment; 44 } 45 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); 56 Study study; 57 study.set_name(kFlagStudyName); 58 study.set_default_experiment_name(kNonFlagGroupName); 59 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); 65 66 return study; 67 } 68 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) 75 return true; 76 } 77 return false; 78 } 79 80 class TestOverrideStringCallback { 81 public: 82 typedef std::map<uint32_t, base::string16> OverrideMap; 83 84 TestOverrideStringCallback() 85 : callback_(base::Bind(&TestOverrideStringCallback::Override, 86 base::Unretained(this))) {} 87 88 virtual ~TestOverrideStringCallback() {} 89 90 const VariationsSeedProcessor::UIStringOverrideCallback& callback() const { 91 return callback_; 92 } 93 94 const OverrideMap& overrides() const { return overrides_; } 95 96 private: 97 void Override(uint32_t hash, const base::string16& string) { 98 overrides_[hash] = string; 99 } 100 101 VariationsSeedProcessor::UIStringOverrideCallback callback_; 102 OverrideMap overrides_; 103 104 DISALLOW_COPY_AND_ASSIGN(TestOverrideStringCallback); 105 }; 106 107 } // namespace 108 109 class VariationsSeedProcessorTest : public ::testing::Test { 110 public: 111 VariationsSeedProcessorTest() { 112 } 113 114 virtual ~VariationsSeedProcessorTest() { 115 // Ensure that the maps are cleared between tests, since they are stored as 116 // process singletons. 117 testing::ClearAllVariationIDs(); 118 testing::ClearAllVariationParams(); 119 } 120 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()); 126 return true; 127 } 128 return false; 129 } 130 131 protected: 132 TestOverrideStringCallback override_callback_; 133 134 private: 135 DISALLOW_COPY_AND_ASSIGN(VariationsSeedProcessorTest); 136 }; 137 138 TEST_F(VariationsSeedProcessorTest, AllowForceGroupAndVariationId) { 139 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 140 141 base::FieldTrialList field_trial_list(NULL); 142 143 Study study = CreateStudyWithFlagGroups(100, 0, 0); 144 study.mutable_experiment(1)->set_google_web_experiment_id(kExperimentId); 145 146 EXPECT_TRUE(CreateTrialFromStudy(&study)); 147 EXPECT_EQ(kFlagGroup1Name, 148 base::FieldTrialList::FindFullName(kFlagStudyName)); 149 150 VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName, 151 kFlagGroup1Name); 152 EXPECT_EQ(kExperimentId, id); 153 } 154 155 // Test that the group for kForcingFlag1 is forced. 156 TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag1) { 157 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 158 159 base::FieldTrialList field_trial_list(NULL); 160 161 Study study = CreateStudyWithFlagGroups(100, 0, 0); 162 EXPECT_TRUE(CreateTrialFromStudy(&study)); 163 EXPECT_EQ(kFlagGroup1Name, 164 base::FieldTrialList::FindFullName(kFlagStudyName)); 165 } 166 167 // Test that the group for kForcingFlag2 is forced. 168 TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag2) { 169 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2); 170 171 base::FieldTrialList field_trial_list(NULL); 172 173 Study study = CreateStudyWithFlagGroups(100, 0, 0); 174 EXPECT_TRUE(CreateTrialFromStudy(&study)); 175 EXPECT_EQ(kFlagGroup2Name, 176 base::FieldTrialList::FindFullName(kFlagStudyName)); 177 } 178 179 TEST_F(VariationsSeedProcessorTest, ForceGroup_ChooseFirstGroupWithFlag) { 180 // Add the flag to the command line arguments so the flag group is forced. 181 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 182 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2); 183 184 base::FieldTrialList field_trial_list(NULL); 185 186 Study study = CreateStudyWithFlagGroups(100, 0, 0); 187 EXPECT_TRUE(CreateTrialFromStudy(&study)); 188 EXPECT_EQ(kFlagGroup1Name, 189 base::FieldTrialList::FindFullName(kFlagStudyName)); 190 } 191 192 TEST_F(VariationsSeedProcessorTest, ForceGroup_DontChooseGroupWithFlag) { 193 base::FieldTrialList field_trial_list(NULL); 194 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)); 202 } 203 204 TEST_F(VariationsSeedProcessorTest, 205 NonExpiredStudyPrioritizedOverExpiredStudy) { 206 VariationsSeedProcessor seed_processor; 207 208 const std::string kTrialName = "A"; 209 const std::string kGroup1Name = "Group1"; 210 211 VariationsSeed seed; 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(); 218 *study2 = *study1; 219 ASSERT_EQ(seed.study(0).name(), seed.study(1).name()); 220 221 const base::Time year_ago = 222 base::Time::Now() - base::TimeDelta::FromDays(365); 223 224 const base::Version version("20.0.0.0"); 225 226 // Check that adding [expired, non-expired] activates the non-expired one. 227 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName)); 228 { 229 base::FieldTrialList field_trial_list(NULL); 230 study1->set_expiry_date(TimeToProtoTime(year_ago)); 231 seed_processor.CreateTrialsFromSeed(seed, 232 "en-CA", 233 base::Time::Now(), 234 version, 235 Study_Channel_STABLE, 236 Study_FormFactor_DESKTOP, 237 "", 238 override_callback_.callback()); 239 EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName)); 240 } 241 242 // Check that adding [non-expired, expired] activates the non-expired one. 243 ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName)); 244 { 245 base::FieldTrialList field_trial_list(NULL); 246 study1->clear_expiry_date(); 247 study2->set_expiry_date(TimeToProtoTime(year_ago)); 248 seed_processor.CreateTrialsFromSeed(seed, 249 "en-CA", 250 base::Time::Now(), 251 version, 252 Study_Channel_STABLE, 253 Study_FormFactor_DESKTOP, 254 "", 255 override_callback_.callback()); 256 EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName)); 257 } 258 } 259 260 TEST_F(VariationsSeedProcessorTest, OverrideUIStrings) { 261 base::FieldTrialList field_trial_list(NULL); 262 263 Study study; 264 study.set_name("Study1"); 265 study.set_default_experiment_name("B"); 266 study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO); 267 268 Study_Experiment* experiment1 = AddExperiment("A", 0, &study); 269 Study_Experiment_OverrideUIString* override = 270 experiment1->add_override_ui_string(); 271 272 override->set_name_hash(1234); 273 override->set_value("test"); 274 275 Study_Experiment* experiment2 = AddExperiment("B", 1, &study); 276 277 EXPECT_TRUE(CreateTrialFromStudy(&study)); 278 279 const TestOverrideStringCallback::OverrideMap& overrides = 280 override_callback_.overrides(); 281 282 EXPECT_TRUE(overrides.empty()); 283 284 study.set_name("Study2"); 285 experiment1->set_probability_weight(1); 286 experiment2->set_probability_weight(0); 287 288 EXPECT_TRUE(CreateTrialFromStudy(&study)); 289 290 EXPECT_EQ(1u, overrides.size()); 291 TestOverrideStringCallback::OverrideMap::const_iterator it = 292 overrides.find(1234); 293 EXPECT_EQ(base::ASCIIToUTF16("test"), it->second); 294 } 295 296 TEST_F(VariationsSeedProcessorTest, OverrideUIStringsWithForcingFlag) { 297 Study study = CreateStudyWithFlagGroups(100, 0, 0); 298 ASSERT_EQ(kForcingFlag1, study.experiment(1).forcing_flag()); 299 300 study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO); 301 Study_Experiment_OverrideUIString* override = 302 study.mutable_experiment(1)->add_override_ui_string(); 303 override->set_name_hash(1234); 304 override->set_value("test"); 305 306 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 307 base::FieldTrialList field_trial_list(NULL); 308 EXPECT_TRUE(CreateTrialFromStudy(&study)); 309 EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study.name())); 310 311 const TestOverrideStringCallback::OverrideMap& overrides = 312 override_callback_.overrides(); 313 EXPECT_EQ(1u, overrides.size()); 314 TestOverrideStringCallback::OverrideMap::const_iterator it = 315 overrides.find(1234); 316 EXPECT_EQ(base::ASCIIToUTF16("test"), it->second); 317 } 318 319 TEST_F(VariationsSeedProcessorTest, ValidateStudy) { 320 Study study; 321 study.set_default_experiment_name("def"); 322 AddExperiment("abc", 100, &study); 323 Study_Experiment* default_group = AddExperiment("def", 200, &study); 324 325 ProcessedStudy processed_study; 326 EXPECT_TRUE(processed_study.Init(&study, false)); 327 EXPECT_EQ(300, processed_study.total_probability()); 328 329 // Min version checks. 330 study.mutable_filter()->set_min_version("1.2.3.*"); 331 EXPECT_TRUE(processed_study.Init(&study, false)); 332 study.mutable_filter()->set_min_version("1.*.3"); 333 EXPECT_FALSE(processed_study.Init(&study, false)); 334 study.mutable_filter()->set_min_version("1.2.3"); 335 EXPECT_TRUE(processed_study.Init(&study, false)); 336 337 // Max version checks. 338 study.mutable_filter()->set_max_version("2.3.4.*"); 339 EXPECT_TRUE(processed_study.Init(&study, false)); 340 study.mutable_filter()->set_max_version("*.3"); 341 EXPECT_FALSE(processed_study.Init(&study, false)); 342 study.mutable_filter()->set_max_version("2.3.4"); 343 EXPECT_TRUE(processed_study.Init(&study, false)); 344 345 study.clear_default_experiment_name(); 346 EXPECT_FALSE(processed_study.Init(&study, false)); 347 348 study.set_default_experiment_name("xyz"); 349 EXPECT_FALSE(processed_study.Init(&study, false)); 350 351 study.set_default_experiment_name("def"); 352 default_group->clear_name(); 353 EXPECT_FALSE(processed_study.Init(&study, false)); 354 355 default_group->set_name("def"); 356 EXPECT_TRUE(processed_study.Init(&study, false)); 357 Study_Experiment* repeated_group = study.add_experiment(); 358 repeated_group->set_name("abc"); 359 repeated_group->set_probability_weight(1); 360 EXPECT_FALSE(processed_study.Init(&study, false)); 361 } 362 363 TEST_F(VariationsSeedProcessorTest, VariationParams) { 364 base::FieldTrialList field_trial_list(NULL); 365 366 Study study; 367 study.set_name("Study1"); 368 study.set_default_experiment_name("B"); 369 370 Study_Experiment* experiment1 = AddExperiment("A", 1, &study); 371 Study_Experiment_Param* param = experiment1->add_param(); 372 param->set_name("x"); 373 param->set_value("y"); 374 375 Study_Experiment* experiment2 = AddExperiment("B", 0, &study); 376 377 EXPECT_TRUE(CreateTrialFromStudy(&study)); 378 EXPECT_EQ("y", GetVariationParamValue("Study1", "x")); 379 380 study.set_name("Study2"); 381 experiment1->set_probability_weight(0); 382 experiment2->set_probability_weight(1); 383 EXPECT_TRUE(CreateTrialFromStudy(&study)); 384 EXPECT_EQ(std::string(), GetVariationParamValue("Study2", "x")); 385 } 386 387 TEST_F(VariationsSeedProcessorTest, VariationParamsWithForcingFlag) { 388 Study study = CreateStudyWithFlagGroups(100, 0, 0); 389 ASSERT_EQ(kForcingFlag1, study.experiment(1).forcing_flag()); 390 Study_Experiment_Param* param = study.mutable_experiment(1)->add_param(); 391 param->set_name("x"); 392 param->set_value("y"); 393 394 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 395 base::FieldTrialList field_trial_list(NULL); 396 EXPECT_TRUE(CreateTrialFromStudy(&study)); 397 EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study.name())); 398 EXPECT_EQ("y", GetVariationParamValue(study.name(), "x")); 399 } 400 401 TEST_F(VariationsSeedProcessorTest, StartsActive) { 402 base::FieldTrialList field_trial_list(NULL); 403 404 VariationsSeed seed; 405 Study* study1 = seed.add_study(); 406 study1->set_name("A"); 407 study1->set_default_experiment_name("Default"); 408 AddExperiment("AA", 100, study1); 409 AddExperiment("Default", 0, study1); 410 411 Study* study2 = seed.add_study(); 412 study2->set_name("B"); 413 study2->set_default_experiment_name("Default"); 414 AddExperiment("BB", 100, study2); 415 AddExperiment("Default", 0, study2); 416 study2->set_activation_type(Study_ActivationType_ACTIVATION_AUTO); 417 418 Study* study3 = seed.add_study(); 419 study3->set_name("C"); 420 study3->set_default_experiment_name("Default"); 421 AddExperiment("CC", 100, study3); 422 AddExperiment("Default", 0, study3); 423 study3->set_activation_type(Study_ActivationType_ACTIVATION_EXPLICIT); 424 425 VariationsSeedProcessor seed_processor; 426 seed_processor.CreateTrialsFromSeed(seed, 427 "en-CA", 428 base::Time::Now(), 429 base::Version("20.0.0.0"), 430 Study_Channel_STABLE, 431 Study_FormFactor_DESKTOP, 432 "", 433 override_callback_.callback()); 434 435 // Non-specified and ACTIVATION_EXPLICIT should not start active, but 436 // ACTIVATION_AUTO should. 437 EXPECT_FALSE(IsFieldTrialActive("A")); 438 EXPECT_TRUE(IsFieldTrialActive("B")); 439 EXPECT_FALSE(IsFieldTrialActive("C")); 440 441 EXPECT_EQ("AA", base::FieldTrialList::FindFullName("A")); 442 EXPECT_EQ("BB", base::FieldTrialList::FindFullName("B")); 443 EXPECT_EQ("CC", base::FieldTrialList::FindFullName("C")); 444 445 // Now, all studies should be active. 446 EXPECT_TRUE(IsFieldTrialActive("A")); 447 EXPECT_TRUE(IsFieldTrialActive("B")); 448 EXPECT_TRUE(IsFieldTrialActive("C")); 449 } 450 451 TEST_F(VariationsSeedProcessorTest, StartsActiveWithFlag) { 452 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 453 454 base::FieldTrialList field_trial_list(NULL); 455 456 Study study = CreateStudyWithFlagGroups(100, 0, 0); 457 study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO); 458 459 EXPECT_TRUE(CreateTrialFromStudy(&study)); 460 EXPECT_TRUE(IsFieldTrialActive(kFlagStudyName)); 461 462 EXPECT_EQ(kFlagGroup1Name, 463 base::FieldTrialList::FindFullName(kFlagStudyName)); 464 } 465 466 TEST_F(VariationsSeedProcessorTest, ForcingFlagAlreadyForced) { 467 Study study = CreateStudyWithFlagGroups(100, 0, 0); 468 ASSERT_EQ(kNonFlagGroupName, study.experiment(0).name()); 469 Study_Experiment_Param* param = study.mutable_experiment(0)->add_param(); 470 param->set_name("x"); 471 param->set_value("y"); 472 study.mutable_experiment(0)->set_google_web_experiment_id(kExperimentId); 473 474 base::FieldTrialList field_trial_list(NULL); 475 base::FieldTrialList::CreateFieldTrial(kFlagStudyName, kNonFlagGroupName); 476 477 CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); 478 EXPECT_TRUE(CreateTrialFromStudy(&study)); 479 // The previously forced experiment should still hold. 480 EXPECT_EQ(kNonFlagGroupName, 481 base::FieldTrialList::FindFullName(study.name())); 482 483 // Check that params and experiment ids correspond. 484 EXPECT_EQ("y", GetVariationParamValue(study.name(), "x")); 485 VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName, 486 kNonFlagGroupName); 487 EXPECT_EQ(kExperimentId, id); 488 } 489 490 } // namespace variations 491