Home | History | Annotate | Download | only in variations
      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