Home | History | Annotate | Download | only in test
      1 // Copyright 2016 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 "base/test/scoped_feature_list.h"
      6 
      7 #include <algorithm>
      8 #include <utility>
      9 #include <vector>
     10 
     11 #include "base/memory/ptr_util.h"
     12 #include "base/metrics/field_trial_param_associator.h"
     13 #include "base/stl_util.h"
     14 #include "base/strings/string_number_conversions.h"
     15 #include "base/strings/string_split.h"
     16 #include "base/strings/string_util.h"
     17 
     18 namespace base {
     19 namespace test {
     20 
     21 namespace {
     22 
     23 std::vector<StringPiece> GetFeatureVector(
     24     const std::vector<Feature>& features) {
     25   std::vector<StringPiece> output;
     26   for (const Feature& feature : features) {
     27     output.push_back(feature.name);
     28   }
     29 
     30   return output;
     31 }
     32 
     33 // Extracts a feature name from a feature state string. For example, given
     34 // the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature".
     35 StringPiece GetFeatureName(StringPiece feature) {
     36   StringPiece feature_name = feature;
     37 
     38   // Remove default info.
     39   if (feature_name.starts_with("*"))
     40     feature_name = feature_name.substr(1);
     41 
     42   // Remove field_trial info.
     43   std::size_t index = feature_name.find("<");
     44   if (index != std::string::npos)
     45     feature_name = feature_name.substr(0, index);
     46 
     47   return feature_name;
     48 }
     49 
     50 struct Features {
     51   std::vector<StringPiece> enabled_feature_list;
     52   std::vector<StringPiece> disabled_feature_list;
     53 };
     54 
     55 // Merges previously-specified feature overrides with those passed into one of
     56 // the Init() methods. |features| should be a list of features previously
     57 // overridden to be in the |override_state|. |merged_features| should contain
     58 // the enabled and disabled features passed into the Init() method, plus any
     59 // overrides merged as a result of previous calls to this function.
     60 void OverrideFeatures(const std::string& features,
     61                       FeatureList::OverrideState override_state,
     62                       Features* merged_features) {
     63   std::vector<StringPiece> features_list =
     64       SplitStringPiece(features, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
     65 
     66   for (StringPiece feature : features_list) {
     67     StringPiece feature_name = GetFeatureName(feature);
     68 
     69     if (ContainsValue(merged_features->enabled_feature_list, feature_name) ||
     70         ContainsValue(merged_features->disabled_feature_list, feature_name))
     71       continue;
     72 
     73     if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
     74       merged_features->enabled_feature_list.push_back(feature);
     75     } else {
     76       DCHECK_EQ(override_state,
     77                 FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE);
     78       merged_features->disabled_feature_list.push_back(feature);
     79     }
     80   }
     81 }
     82 
     83 }  // namespace
     84 
     85 ScopedFeatureList::ScopedFeatureList() = default;
     86 
     87 ScopedFeatureList::~ScopedFeatureList() {
     88   // If one of the Init() functions was never called, don't reset anything.
     89   if (!init_called_)
     90     return;
     91 
     92   if (field_trial_override_) {
     93     base::FieldTrialParamAssociator::GetInstance()->ClearParamsForTesting(
     94         field_trial_override_->trial_name(),
     95         field_trial_override_->group_name());
     96   }
     97 
     98   FeatureList::ClearInstanceForTesting();
     99   if (original_feature_list_)
    100     FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
    101 }
    102 
    103 void ScopedFeatureList::Init() {
    104   std::unique_ptr<FeatureList> feature_list(new FeatureList);
    105   feature_list->InitializeFromCommandLine(std::string(), std::string());
    106   InitWithFeatureList(std::move(feature_list));
    107 }
    108 
    109 void ScopedFeatureList::InitWithFeatureList(
    110     std::unique_ptr<FeatureList> feature_list) {
    111   DCHECK(!original_feature_list_);
    112   original_feature_list_ = FeatureList::ClearInstanceForTesting();
    113   FeatureList::SetInstance(std::move(feature_list));
    114   init_called_ = true;
    115 }
    116 
    117 void ScopedFeatureList::InitFromCommandLine(
    118     const std::string& enable_features,
    119     const std::string& disable_features) {
    120   std::unique_ptr<FeatureList> feature_list(new FeatureList);
    121   feature_list->InitializeFromCommandLine(enable_features, disable_features);
    122   InitWithFeatureList(std::move(feature_list));
    123 }
    124 
    125 void ScopedFeatureList::InitWithFeatures(
    126     const std::vector<Feature>& enabled_features,
    127     const std::vector<Feature>& disabled_features) {
    128   InitWithFeaturesAndFieldTrials(enabled_features, {}, disabled_features);
    129 }
    130 
    131 void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
    132   InitWithFeaturesAndFieldTrials({feature}, {}, {});
    133 }
    134 
    135 void ScopedFeatureList::InitAndEnableFeatureWithFieldTrialOverride(
    136     const Feature& feature,
    137     FieldTrial* trial) {
    138   InitWithFeaturesAndFieldTrials({feature}, {trial}, {});
    139 }
    140 
    141 void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
    142   InitWithFeaturesAndFieldTrials({}, {}, {feature});
    143 }
    144 
    145 void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
    146                                              bool enabled) {
    147   if (enabled) {
    148     InitAndEnableFeature(feature);
    149   } else {
    150     InitAndDisableFeature(feature);
    151   }
    152 }
    153 
    154 void ScopedFeatureList::InitWithFeaturesAndFieldTrials(
    155     const std::vector<Feature>& enabled_features,
    156     const std::vector<FieldTrial*>& trials_for_enabled_features,
    157     const std::vector<Feature>& disabled_features) {
    158   DCHECK_LE(trials_for_enabled_features.size(), enabled_features.size());
    159 
    160   Features merged_features;
    161   merged_features.enabled_feature_list = GetFeatureVector(enabled_features);
    162   merged_features.disabled_feature_list = GetFeatureVector(disabled_features);
    163 
    164   FeatureList* feature_list = FeatureList::GetInstance();
    165 
    166   // |current_enabled_features| and |current_disabled_features| must declare out
    167   // of if scope to avoid them out of scope before JoinString calls because
    168   // |merged_features| may contains StringPiece which holding pointer points to
    169   // |current_enabled_features| and |current_disabled_features|.
    170   std::string current_enabled_features;
    171   std::string current_disabled_features;
    172   if (feature_list) {
    173     FeatureList::GetInstance()->GetFeatureOverrides(&current_enabled_features,
    174                                                     &current_disabled_features);
    175     OverrideFeatures(current_enabled_features,
    176                      FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
    177                      &merged_features);
    178     OverrideFeatures(current_disabled_features,
    179                      FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
    180                      &merged_features);
    181   }
    182 
    183   // Add the field trial overrides. This assumes that |enabled_features| are at
    184   // the begining of |merged_features.enabled_feature_list|, in the same order.
    185   std::vector<FieldTrial*>::const_iterator trial_it =
    186       trials_for_enabled_features.begin();
    187   auto feature_it = merged_features.enabled_feature_list.begin();
    188   std::vector<std::unique_ptr<std::string>> features_with_trial;
    189   features_with_trial.reserve(trials_for_enabled_features.size());
    190   while (trial_it != trials_for_enabled_features.end()) {
    191     features_with_trial.push_back(std::make_unique<std::string>(
    192         feature_it->as_string() + "<" + (*trial_it)->trial_name()));
    193     // |features_with_trial| owns the string, and feature_it points to it.
    194     *feature_it = *(features_with_trial.back());
    195     ++trial_it;
    196     ++feature_it;
    197   }
    198 
    199   std::string enabled = JoinString(merged_features.enabled_feature_list, ",");
    200   std::string disabled = JoinString(merged_features.disabled_feature_list, ",");
    201   InitFromCommandLine(enabled, disabled);
    202 }
    203 
    204 void ScopedFeatureList::InitAndEnableFeatureWithParameters(
    205     const Feature& feature,
    206     const std::map<std::string, std::string>& feature_parameters) {
    207   if (!FieldTrialList::IsGlobalSetForTesting()) {
    208     field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
    209   }
    210 
    211   // TODO(crbug.com/794021) Remove this unique field trial name hack when there
    212   // is a cleaner solution.
    213   // Ensure that each call to this method uses a distinct field trial name.
    214   // Otherwise, nested calls might fail due to the shared FieldTrialList
    215   // already having the field trial registered.
    216   static int num_calls = 0;
    217   ++num_calls;
    218   std::string kTrialName =
    219       "scoped_feature_list_trial_name" + base::NumberToString(num_calls);
    220   std::string kTrialGroup = "scoped_feature_list_trial_group";
    221 
    222   field_trial_override_ =
    223       base::FieldTrialList::CreateFieldTrial(kTrialName, kTrialGroup);
    224   DCHECK(field_trial_override_);
    225   FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
    226       kTrialName, kTrialGroup, feature_parameters);
    227   InitAndEnableFeatureWithFieldTrialOverride(feature,
    228                                              field_trial_override_.get());
    229 }
    230 
    231 }  // namespace test
    232 }  // namespace base
    233