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(¤t_enabled_features,
174 ¤t_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