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(&current_enabled_features,
230                                       &current_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