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 
GetFeatureVector(const std::vector<Feature> & features)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".
GetFeatureName(StringPiece feature)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.
OverrideFeatures(const std::string & features,FeatureList::OverrideState override_state,Features * merged_features)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 
~ScopedFeatureList()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 
Init()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 
InitWithFeatureList(std::unique_ptr<FeatureList> feature_list)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 
InitFromCommandLine(const std::string & enable_features,const std::string & disable_features)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 
InitWithFeatures(const std::vector<Feature> & enabled_features,const std::vector<Feature> & disabled_features)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 
InitAndEnableFeature(const Feature & feature)131 void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
132   InitWithFeaturesAndFieldTrials({feature}, {}, {});
133 }
134 
InitAndEnableFeatureWithFieldTrialOverride(const Feature & feature,FieldTrial * trial)135 void ScopedFeatureList::InitAndEnableFeatureWithFieldTrialOverride(
136     const Feature& feature,
137     FieldTrial* trial) {
138   InitWithFeaturesAndFieldTrials({feature}, {trial}, {});
139 }
140 
InitAndDisableFeature(const Feature & feature)141 void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
142   InitWithFeaturesAndFieldTrials({}, {}, {feature});
143 }
144 
InitWithFeatureState(const Feature & feature,bool enabled)145 void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
146                                              bool enabled) {
147   if (enabled) {
148     InitAndEnableFeature(feature);
149   } else {
150     InitAndDisableFeature(feature);
151   }
152 }
153 
InitWithFeaturesAndFieldTrials(const std::vector<Feature> & enabled_features,const std::vector<FieldTrial * > & trials_for_enabled_features,const std::vector<Feature> & disabled_features)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 
InitAndEnableFeatureWithParameters(const Feature & feature,const std::map<std::string,std::string> & feature_parameters)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