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 <utility>
8 #include <vector>
9
10 #include "base/memory/ptr_util.h"
11 #include "base/metrics/field_trial_param_associator.h"
12 #include "base/ranges/algorithm.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/test/mock_entropy_provider.h"
17
18 namespace base {
19 namespace test {
20
21 namespace {
22
23 constexpr char kTrialGroup[] = "scoped_feature_list_trial_group";
24
GetFeatureVector(const std::vector<Feature> & features)25 std::vector<StringPiece> GetFeatureVector(
26 const std::vector<Feature>& features) {
27 std::vector<StringPiece> output;
28 for (const Feature& feature : features) {
29 output.push_back(feature.name);
30 }
31
32 return output;
33 }
34
GetFeatureVectorFromFeaturesAndParams(const std::vector<ScopedFeatureList::FeatureAndParams> & features_and_params)35 std::vector<StringPiece> GetFeatureVectorFromFeaturesAndParams(
36 const std::vector<ScopedFeatureList::FeatureAndParams>&
37 features_and_params) {
38 std::vector<StringPiece> output;
39 for (const auto& entry : features_and_params) {
40 output.push_back(entry.feature.name);
41 }
42
43 return output;
44 }
45
46 // Extracts a feature name from a feature state string. For example, given
47 // the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature".
GetFeatureName(StringPiece feature)48 StringPiece GetFeatureName(StringPiece feature) {
49 StringPiece feature_name = feature;
50
51 // Remove default info.
52 if (StartsWith(feature_name, "*"))
53 feature_name = feature_name.substr(1);
54
55 // Remove field_trial info.
56 std::size_t index = feature_name.find("<");
57 if (index != std::string::npos)
58 feature_name = feature_name.substr(0, index);
59
60 return feature_name;
61 }
62
63 struct Features {
64 std::vector<StringPiece> enabled_feature_list;
65 std::vector<StringPiece> disabled_feature_list;
66 };
67
68 // Features in |feature_vector| came from |merged_features| in
69 // OverrideFeatures() and contains linkage with field trial is case when they
70 // have parameters (with '<' simbol). In |feature_name| name is already cleared
71 // with GetFeatureName() and also could be without parameters.
ContainsFeature(const std::vector<StringPiece> & feature_vector,StringPiece feature_name)72 bool ContainsFeature(const std::vector<StringPiece>& feature_vector,
73 StringPiece feature_name) {
74 auto iter =
75 ranges::find_if(feature_vector, [&feature_name](const StringPiece& a) {
76 return GetFeatureName(a) == feature_name;
77 });
78 return iter != feature_vector.end();
79 }
80
81 // Merges previously-specified feature overrides with those passed into one of
82 // the Init() methods. |features| should be a list of features previously
83 // overridden to be in the |override_state|. |merged_features| should contain
84 // the enabled and disabled features passed into the Init() method, plus any
85 // overrides merged as a result of previous calls to this function.
OverrideFeatures(const std::string & features,FeatureList::OverrideState override_state,Features * merged_features)86 void OverrideFeatures(const std::string& features,
87 FeatureList::OverrideState override_state,
88 Features* merged_features) {
89 std::vector<StringPiece> features_list =
90 SplitStringPiece(features, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
91
92 for (StringPiece feature : features_list) {
93 StringPiece feature_name = GetFeatureName(feature);
94
95 if (ContainsFeature(merged_features->enabled_feature_list, feature_name) ||
96 ContainsFeature(merged_features->disabled_feature_list, feature_name)) {
97 continue;
98 }
99
100 if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
101 merged_features->enabled_feature_list.push_back(feature);
102 } else {
103 DCHECK_EQ(override_state,
104 FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE);
105 merged_features->disabled_feature_list.push_back(feature);
106 }
107 }
108 }
109
110 // Hex encode params so that special characters do not break formatting.
HexEncodeString(const std::string & input)111 std::string HexEncodeString(const std::string& input) {
112 return HexEncode(input.data(), input.size());
113 }
114
115 // Inverse of HexEncodeString().
HexDecodeString(const std::string & input)116 std::string HexDecodeString(const std::string& input) {
117 if (input.empty())
118 return std::string();
119 std::string bytes;
120 bool result = HexStringToString(input, &bytes);
121 DCHECK(result);
122 return bytes;
123 }
124
125 } // namespace
126
FeatureAndParams(const Feature & feature,const FieldTrialParams & params)127 ScopedFeatureList::FeatureAndParams::FeatureAndParams(
128 const Feature& feature,
129 const FieldTrialParams& params)
130 : feature(feature), params(params) {}
131
132 ScopedFeatureList::FeatureAndParams::~FeatureAndParams() = default;
133
134 ScopedFeatureList::FeatureAndParams::FeatureAndParams(
135 const FeatureAndParams& other) = default;
136
137 ScopedFeatureList::ScopedFeatureList() = default;
138
~ScopedFeatureList()139 ScopedFeatureList::~ScopedFeatureList() {
140 Reset();
141 }
142
Reset()143 void ScopedFeatureList::Reset() {
144 // If one of the Init() functions was never called, don't reset anything.
145 if (!init_called_)
146 return;
147
148 init_called_ = false;
149
150 FeatureList::ClearInstanceForTesting();
151
152 if (field_trial_list_) {
153 field_trial_list_.reset();
154
155 // Restore params to how they were before.
156 FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
157 AssociateFieldTrialParamsFromString(original_params_, &HexDecodeString);
158
159 FieldTrialList::RestoreInstanceForTesting(original_field_trial_list_);
160 original_field_trial_list_ = nullptr;
161 }
162 if (original_feature_list_)
163 FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
164 }
165
Init()166 void ScopedFeatureList::Init() {
167 InitWithFeaturesImpl({}, {}, {});
168 }
169
InitWithFeatureList(std::unique_ptr<FeatureList> feature_list)170 void ScopedFeatureList::InitWithFeatureList(
171 std::unique_ptr<FeatureList> feature_list) {
172 DCHECK(!original_feature_list_);
173 original_feature_list_ = FeatureList::ClearInstanceForTesting();
174 FeatureList::SetInstance(std::move(feature_list));
175 init_called_ = true;
176 }
177
InitFromCommandLine(const std::string & enable_features,const std::string & disable_features)178 void ScopedFeatureList::InitFromCommandLine(
179 const std::string& enable_features,
180 const std::string& disable_features) {
181 std::unique_ptr<FeatureList> feature_list(new FeatureList);
182 feature_list->InitializeFromCommandLine(enable_features, disable_features);
183 InitWithFeatureList(std::move(feature_list));
184 }
185
InitWithFeatures(const std::vector<Feature> & enabled_features,const std::vector<Feature> & disabled_features)186 void ScopedFeatureList::InitWithFeatures(
187 const std::vector<Feature>& enabled_features,
188 const std::vector<Feature>& disabled_features) {
189 InitWithFeaturesImpl(enabled_features, {}, disabled_features);
190 }
191
InitAndEnableFeature(const Feature & feature)192 void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
193 InitWithFeaturesImpl({feature}, {}, {});
194 }
195
InitAndDisableFeature(const Feature & feature)196 void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
197 InitWithFeaturesImpl({}, {}, {feature});
198 }
199
InitWithFeatureState(const Feature & feature,bool enabled)200 void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
201 bool enabled) {
202 if (enabled) {
203 InitAndEnableFeature(feature);
204 } else {
205 InitAndDisableFeature(feature);
206 }
207 }
208
InitWithFeaturesImpl(const std::vector<Feature> & enabled_features,const std::vector<FeatureAndParams> & enabled_features_and_params,const std::vector<Feature> & disabled_features)209 void ScopedFeatureList::InitWithFeaturesImpl(
210 const std::vector<Feature>& enabled_features,
211 const std::vector<FeatureAndParams>& enabled_features_and_params,
212 const std::vector<Feature>& disabled_features) {
213 DCHECK(!init_called_);
214 DCHECK(enabled_features.empty() || enabled_features_and_params.empty());
215
216 Features merged_features;
217 if (!enabled_features_and_params.empty()) {
218 merged_features.enabled_feature_list =
219 GetFeatureVectorFromFeaturesAndParams(enabled_features_and_params);
220 } else {
221 merged_features.enabled_feature_list = GetFeatureVector(enabled_features);
222 }
223 merged_features.disabled_feature_list = GetFeatureVector(disabled_features);
224
225 std::string current_enabled_features;
226 std::string current_disabled_features;
227 FeatureList* feature_list = FeatureList::GetInstance();
228 if (feature_list) {
229 feature_list->GetFeatureOverrides(¤t_enabled_features,
230 ¤t_disabled_features);
231 }
232
233 // Save off the existing field trials and params.
234 std::string existing_trial_state;
235 FieldTrialList::AllStatesToString(&existing_trial_state, true);
236 original_params_ = FieldTrialList::AllParamsToString(true, &HexEncodeString);
237
238 // Back up the current field trial list, to be restored in Reset().
239 original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting();
240
241 // Create a field trial list, to which we'll add trials corresponding to the
242 // features that have params, before restoring the field trial state from the
243 // previous instance, further down in this function.
244 field_trial_list_ =
245 std::make_unique<FieldTrialList>(std::make_unique<MockEntropyProvider>());
246
247 // Associate override params. This needs to be done before trial state gets
248 // restored, as that will activate trials, locking down param association.
249 auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
250 std::vector<std::string> features_with_trial;
251 auto feature_it = merged_features.enabled_feature_list.begin();
252 for (const auto& enabled_feature : enabled_features_and_params) {
253 const std::string feature_name = enabled_feature.feature.name;
254 const std::string trial_name =
255 "scoped_feature_list_trial_for_" + feature_name;
256
257 scoped_refptr<FieldTrial> field_trial_override =
258 FieldTrialList::CreateFieldTrial(trial_name, kTrialGroup);
259 DCHECK(field_trial_override);
260
261 field_trial_param_associator->ClearParamsForTesting(trial_name,
262 kTrialGroup);
263 bool success = field_trial_param_associator->AssociateFieldTrialParams(
264 trial_name, kTrialGroup, enabled_feature.params);
265 DCHECK(success);
266
267 features_with_trial.push_back(feature_name + "<" + trial_name);
268 *feature_it = features_with_trial.back();
269 ++feature_it;
270 }
271 // Restore other field trials. Note: We don't need to do anything for params
272 // here because the param associator already has the right state, which has
273 // been backed up via |original_params_| to be restored later.
274 FieldTrialList::CreateTrialsFromString(existing_trial_state);
275
276 OverrideFeatures(current_enabled_features,
277 FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
278 &merged_features);
279 OverrideFeatures(current_disabled_features,
280 FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
281 &merged_features);
282
283 std::string enabled = JoinString(merged_features.enabled_feature_list, ",");
284 std::string disabled = JoinString(merged_features.disabled_feature_list, ",");
285 InitFromCommandLine(enabled, disabled);
286 }
287
InitAndEnableFeatureWithParameters(const Feature & feature,const FieldTrialParams & feature_parameters)288 void ScopedFeatureList::InitAndEnableFeatureWithParameters(
289 const Feature& feature,
290 const FieldTrialParams& feature_parameters) {
291 InitWithFeaturesAndParameters({{feature, feature_parameters}}, {});
292 }
293
InitWithFeaturesAndParameters(const std::vector<FeatureAndParams> & enabled_features,const std::vector<Feature> & disabled_features)294 void ScopedFeatureList::InitWithFeaturesAndParameters(
295 const std::vector<FeatureAndParams>& enabled_features,
296 const std::vector<Feature>& disabled_features) {
297 InitWithFeaturesImpl({}, enabled_features, disabled_features);
298 }
299
300 } // namespace test
301 } // namespace base
302