• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The Chromium Authors
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 <atomic>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/check_op.h"
12 #include "base/containers/contains.h"
13 #include "base/containers/cxx20_erase_vector.h"
14 #include "base/containers/flat_map.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/metrics/field_trial_param_associator.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_piece.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/test/mock_entropy_provider.h"
23 #include "base/test/task_environment.h"
24 
25 namespace base {
26 namespace test {
27 namespace {
28 
29 // A monotonically increasing id, passed to `FeatureList`s as they are created
30 // to invalidate the cache member of `base::Feature` objects that were queried
31 // with a different `FeatureList` installed.
32 uint16_t g_current_caching_context = 1;
33 
34 }  // namespace
35 
36 // A struct describes ParsedEnableFeatures()' result.
37 struct ScopedFeatureList::FeatureWithStudyGroup {
FeatureWithStudyGroupbase::test::ScopedFeatureList::FeatureWithStudyGroup38   FeatureWithStudyGroup(const std::string& feature_name,
39                         const std::string& study_name,
40                         const std::string& group_name,
41                         const std::string& params)
42       : feature_name(feature_name),
43         study_name(study_name),
44         group_name(group_name),
45         params(params) {
46     DCHECK(IsValidFeatureName(feature_name));
47     DCHECK(IsValidFeatureOrFieldTrialName(study_name));
48     DCHECK(IsValidFeatureOrFieldTrialName(group_name));
49   }
50 
FeatureWithStudyGroupbase::test::ScopedFeatureList::FeatureWithStudyGroup51   explicit FeatureWithStudyGroup(const std::string& feature_name)
52       : feature_name(feature_name) {
53     DCHECK(IsValidFeatureName(feature_name));
54   }
55 
56   ~FeatureWithStudyGroup() = default;
57   FeatureWithStudyGroup(const FeatureWithStudyGroup& other) = default;
58 
operator ==base::test::ScopedFeatureList::FeatureWithStudyGroup59   bool operator==(const FeatureWithStudyGroup& other) const {
60     return feature_name == other.feature_name &&
61            StudyNameOrDefault() == other.StudyNameOrDefault() &&
62            GroupNameOrDefault() == other.GroupNameOrDefault();
63   }
64 
FeatureNamebase::test::ScopedFeatureList::FeatureWithStudyGroup65   std::string FeatureName() const {
66     return StartsWith(feature_name, "*") ? feature_name.substr(1)
67                                          : feature_name;
68   }
69 
70   // If |study_name| is empty, returns a default study name for |feature_name|.
71   // Otherwise, just return |study_name|.
StudyNameOrDefaultbase::test::ScopedFeatureList::FeatureWithStudyGroup72   std::string StudyNameOrDefault() const {
73     return study_name.empty() ? "Study" + FeatureName() : study_name;
74   }
75 
76   // If |group_name| is empty, returns a default group name for |feature_name|.
77   // Otherwise, just return |group_name|.
GroupNameOrDefaultbase::test::ScopedFeatureList::FeatureWithStudyGroup78   std::string GroupNameOrDefault() const {
79     return group_name.empty() ? "Group" + FeatureName() : group_name;
80   }
81 
has_paramsbase::test::ScopedFeatureList::FeatureWithStudyGroup82   bool has_params() const { return !params.empty(); }
83 
ParamsForFeatureListbase::test::ScopedFeatureList::FeatureWithStudyGroup84   std::string ParamsForFeatureList() const {
85     if (params.empty())
86       return "";
87     return ":" + params;
88   }
89 
IsValidFeatureOrFieldTrialNamebase::test::ScopedFeatureList::FeatureWithStudyGroup90   static bool IsValidFeatureOrFieldTrialName(const StringPiece& name) {
91     return IsStringASCII(name) &&
92            name.find_first_of(",<*") == std::string::npos;
93   }
94 
IsValidFeatureNamebase::test::ScopedFeatureList::FeatureWithStudyGroup95   static bool IsValidFeatureName(const StringPiece& feature_name) {
96     return IsValidFeatureOrFieldTrialName(
97         StartsWith(feature_name, "*") ? feature_name.substr(1) : feature_name);
98   }
99 
100   // When ParseEnableFeatures() gets
101   // "FeatureName<StudyName.GroupName:Param1/Value1/Param2/Value2",
102   // a new FeatureWithStudyGroup with:
103   // - feature_name = "FeatureName"
104   // - study_name = "StudyName"
105   // - group_name = "GroupName"
106   // - params = "Param1/Value1/Param2/Value2"
107   // will be created and be returned.
108   const std::string feature_name;
109   const std::string study_name;
110   const std::string group_name;
111   const std::string params;
112 };
113 
114 struct ScopedFeatureList::Features {
115   std::vector<FeatureWithStudyGroup> enabled_feature_list;
116   std::vector<FeatureWithStudyGroup> disabled_feature_list;
117 };
118 
119 namespace {
120 
121 constexpr char kTrialGroup[] = "scoped_feature_list_trial_group";
122 
123 // Checks and parses the |enable_features| flag and appends each parsed
124 // feature, an instance of FeatureWithStudyGroup, to |parsed_enable_features|.
125 // Returns true if |enable_features| is parsable, otherwise false.
126 // The difference between this function and ParseEnabledFeatures() defined in
127 // feature_list.cc is:
128 // if "Feature1<Study1.Group1:Param1/Value1/Param2/Value2," +
129 //    "Feature2<Study2.Group2" is given,
130 // feature_list.cc's one returns strings:
131 //   parsed_enable_features = "Feature1<Study1,Feature2<Study2"
132 //   force_field_trials = "Study1/Group1"
133 //   force_fieldtrial_params = "Study1<Group1:Param1/Value1/Param2/Value2"
134 //  this function returns a vector:
135 //   [0] FeatureWithStudyGroup("Feature1", "Study1", "Group1",
136 //         "Param1/Value1/Param2/Value2")
137 //   [1] FeatureWithStudyGroup("Feature2", "Study2", "Group2", "")
ParseEnableFeatures(const std::string & enable_features,std::vector<ScopedFeatureList::FeatureWithStudyGroup> & parsed_enable_features)138 bool ParseEnableFeatures(const std::string& enable_features,
139                          std::vector<ScopedFeatureList::FeatureWithStudyGroup>&
140                              parsed_enable_features) {
141   for (const auto& enable_feature :
142        FeatureList::SplitFeatureListString(enable_features)) {
143     std::string feature_name;
144     std::string study;
145     std::string group;
146     std::string feature_params;
147     if (!FeatureList::ParseEnableFeatureString(
148             enable_feature, &feature_name, &study, &group, &feature_params)) {
149       return false;
150     }
151 
152     parsed_enable_features.emplace_back(feature_name, study, group,
153                                         feature_params);
154   }
155   return true;
156 }
157 
158 // Escapes separators used by enable-features command line.
159 // E.g. Feature '<' Study '.' Group ':' param1 '/' value1 ','
160 // ('*' is not a separator. No need to escape it.)
EscapeValue(const std::string & value)161 std::string EscapeValue(const std::string& value) {
162   std::string escaped_str;
163   for (const auto ch : value) {
164     if (ch == ',' || ch == '/' || ch == ':' || ch == '<' || ch == '.') {
165       escaped_str.append(base::StringPrintf("%%%02X", ch));
166     } else {
167       escaped_str.append(1, ch);
168     }
169   }
170   return escaped_str;
171 }
172 
173 // Extracts a feature name from a feature state string. For example, given
174 // the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature".
GetFeatureName(StringPiece feature)175 StringPiece GetFeatureName(StringPiece feature) {
176   StringPiece feature_name = feature;
177 
178   // Remove default info.
179   if (StartsWith(feature_name, "*"))
180     feature_name = feature_name.substr(1);
181 
182   // Remove field_trial info.
183   std::size_t index = feature_name.find("<");
184   if (index != std::string::npos)
185     feature_name = feature_name.substr(0, index);
186 
187   return feature_name;
188 }
189 
190 // Features in |feature_vector| came from |merged_features| in
191 // OverrideFeatures() and contains linkage with field trial is case when they
192 // have parameters (with '<' simbol). In |feature_name| name is already cleared
193 // with GetFeatureName() and also could be without parameters.
ContainsFeature(const std::vector<ScopedFeatureList::FeatureWithStudyGroup> & feature_vector,StringPiece feature_name)194 bool ContainsFeature(
195     const std::vector<ScopedFeatureList::FeatureWithStudyGroup>& feature_vector,
196     StringPiece feature_name) {
197   return Contains(feature_vector, feature_name,
198                   [](const ScopedFeatureList::FeatureWithStudyGroup& a) {
199                     return a.feature_name;
200                   });
201 }
202 
203 // Merges previously-specified feature overrides with those passed into one of
204 // the Init() methods. |features| should be a list of features previously
205 // overridden to be in the |override_state|. |merged_features| should contain
206 // the enabled and disabled features passed into the Init() method, plus any
207 // overrides merged as a result of previous calls to this function.
OverrideFeatures(const std::vector<ScopedFeatureList::FeatureWithStudyGroup> & features_list,FeatureList::OverrideState override_state,ScopedFeatureList::Features * merged_features)208 void OverrideFeatures(
209     const std::vector<ScopedFeatureList::FeatureWithStudyGroup>& features_list,
210     FeatureList::OverrideState override_state,
211     ScopedFeatureList::Features* merged_features) {
212   for (const auto& feature : features_list) {
213     StringPiece feature_name = GetFeatureName(feature.feature_name);
214 
215     if (ContainsFeature(merged_features->enabled_feature_list, feature_name) ||
216         ContainsFeature(merged_features->disabled_feature_list, feature_name)) {
217       continue;
218     }
219 
220     if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
221       merged_features->enabled_feature_list.push_back(feature);
222     } else {
223       DCHECK_EQ(override_state,
224                 FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE);
225       merged_features->disabled_feature_list.push_back(feature);
226     }
227   }
228 }
229 
230 // Merges previously-specified feature overrides with those passed into one of
231 // the Init() methods. |feature_list| should be a string whose format is the
232 // same as --enable-features or --disable-features command line flag, and
233 // specifies features overridden to be in the |override_state|.
234 // |merged_features| should contain the enabled and disabled features passed in
235 // to the Init() method, plus any overrides merged as a result of previous
236 // calls to this function.
OverrideFeatures(const std::string & features_list,FeatureList::OverrideState override_state,ScopedFeatureList::Features * merged_features)237 void OverrideFeatures(const std::string& features_list,
238                       FeatureList::OverrideState override_state,
239                       ScopedFeatureList::Features* merged_features) {
240   std::vector<ScopedFeatureList::FeatureWithStudyGroup> parsed_features;
241   bool parse_enable_features_result =
242       ParseEnableFeatures(features_list, parsed_features);
243   DCHECK(parse_enable_features_result);
244   OverrideFeatures(parsed_features, override_state, merged_features);
245 }
246 
247 // Hex encode params so that special characters do not break formatting.
HexEncodeString(const std::string & input)248 std::string HexEncodeString(const std::string& input) {
249   return HexEncode(input.data(), input.size());
250 }
251 
252 // Inverse of HexEncodeString().
HexDecodeString(const std::string & input)253 std::string HexDecodeString(const std::string& input) {
254   if (input.empty())
255     return std::string();
256   std::string bytes;
257   bool result = HexStringToString(input, &bytes);
258   DCHECK(result);
259   return bytes;
260 }
261 
262 // Returns a command line string suitable to pass to
263 // FeatureList::InitFromCommandLine(). For example,
264 // {{"Feature1", "Study1", "Group1", "Param1/Value1/"}, {"Feature2"}} returns:
265 // - |enabled_feature|=true -> "Feature1<Study1.Group1:Param1/Value1/,Feature2"
266 // - |enabled_feature|=false -> "Feature1<Study1.Group1,Feature2"
CreateCommandLineArgumentFromFeatureList(const std::vector<ScopedFeatureList::FeatureWithStudyGroup> & feature_list,bool enable_features)267 std::string CreateCommandLineArgumentFromFeatureList(
268     const std::vector<ScopedFeatureList::FeatureWithStudyGroup>& feature_list,
269     bool enable_features) {
270   std::vector<std::string> features;
271   for (const auto& feature : feature_list) {
272     std::string feature_with_study_group = feature.feature_name;
273     if (feature.has_params() || !feature.study_name.empty()) {
274       feature_with_study_group += "<";
275       feature_with_study_group += feature.StudyNameOrDefault();
276       if (feature.has_params() || !feature.group_name.empty()) {
277         feature_with_study_group += ".";
278         feature_with_study_group += feature.GroupNameOrDefault();
279       }
280       if (feature.has_params() && enable_features) {
281         feature_with_study_group += feature.ParamsForFeatureList();
282       }
283     }
284     features.push_back(feature_with_study_group);
285   }
286   return JoinString(features, ",");
287 }
288 
289 }  // namespace
290 
FeatureRefAndParams(const Feature & feature,const FieldTrialParams & params)291 FeatureRefAndParams::FeatureRefAndParams(const Feature& feature,
292                                          const FieldTrialParams& params)
293     : feature(feature), params(params) {}
294 
295 FeatureRefAndParams::FeatureRefAndParams(const FeatureRefAndParams& other) =
296     default;
297 
298 FeatureRefAndParams::~FeatureRefAndParams() = default;
299 
300 ScopedFeatureList::ScopedFeatureList() = default;
301 
ScopedFeatureList(const Feature & enable_feature)302 ScopedFeatureList::ScopedFeatureList(const Feature& enable_feature) {
303   InitAndEnableFeature(enable_feature);
304 }
305 
~ScopedFeatureList()306 ScopedFeatureList::~ScopedFeatureList() {
307   Reset();
308 }
309 
Reset()310 void ScopedFeatureList::Reset() {
311   // If one of the Init() functions was never called, don't reset anything.
312   if (!init_called_)
313     return;
314 
315   init_called_ = false;
316 
317   // ThreadPool tasks racily probing FeatureList while it's initialized/reset
318   // are problematic and while callers should ideally set up ScopedFeatureList
319   // before TaskEnvironment, that's not always possible. Fencing execution here
320   // avoids an entire class of bugs by making sure no ThreadPool task queries
321   // FeatureList while it's being modified. This local action is preferred to
322   // requiring all such callers to manually flush all tasks before each
323   // ScopedFeatureList Init/Reset: crbug.com/1275502#c45
324   //
325   // All FeatureList modifications in this file should have this as well.
326   TaskEnvironment::ParallelExecutionFence fence(
327       "ScopedFeatureList must be Reset from the test main thread");
328 
329   FeatureList::ClearInstanceForTesting();
330 
331   if (field_trial_list_) {
332     field_trial_list_.reset();
333   }
334 
335   // Restore params to how they were before.
336   FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
337   if (!original_params_.empty()) {
338     // Before restoring params, we need to make all field trials in-active,
339     // because FieldTrialParamAssociator checks whether the given field trial
340     // is active or not, and associates no parameters if the trial is active.
341     // So temporarily restore field trial list to be nullptr.
342     FieldTrialList::RestoreInstanceForTesting(nullptr);
343     AssociateFieldTrialParamsFromString(original_params_, &HexDecodeString);
344   }
345 
346   if (original_field_trial_list_) {
347     FieldTrialList::RestoreInstanceForTesting(original_field_trial_list_);
348     original_field_trial_list_ = nullptr;
349   }
350 
351   if (original_feature_list_)
352     FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
353 }
354 
Init()355 void ScopedFeatureList::Init() {
356   InitWithFeaturesImpl({}, {}, {}, /*keep_existing_states=*/true);
357 }
358 
InitWithEmptyFeatureAndFieldTrialLists()359 void ScopedFeatureList::InitWithEmptyFeatureAndFieldTrialLists() {
360   InitWithFeaturesImpl({}, {}, {}, /*keep_existing_states=*/false);
361 }
362 
InitWithNullFeatureAndFieldTrialLists()363 void ScopedFeatureList::InitWithNullFeatureAndFieldTrialLists() {
364   DCHECK(!init_called_);
365 
366   // Back up the current field trial parameters to be restored in Reset().
367   original_params_ = FieldTrialList::AllParamsToString(&HexEncodeString);
368 
369   // Back up the current field trial list, to be restored in Reset().
370   original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting();
371 
372   auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
373   field_trial_param_associator->ClearAllParamsForTesting();
374   field_trial_list_ = nullptr;
375 
376   DCHECK(!original_feature_list_);
377 
378   // Execution fence required while modifying FeatureList, as in Reset.
379   TaskEnvironment::ParallelExecutionFence fence(
380       "ScopedFeatureList must be Init from the test main thread");
381 
382   // Back up the current feature list, to be restored in Reset().
383   original_feature_list_ = FeatureList::ClearInstanceForTesting();
384   init_called_ = true;
385 }
386 
InitWithFeatureList(std::unique_ptr<FeatureList> feature_list)387 void ScopedFeatureList::InitWithFeatureList(
388     std::unique_ptr<FeatureList> feature_list) {
389   DCHECK(!original_feature_list_);
390 
391   // Execution fence required while modifying FeatureList, as in Reset.
392   TaskEnvironment::ParallelExecutionFence fence(
393       "ScopedFeatureList must be Init from the test main thread");
394 
395   original_feature_list_ = FeatureList::ClearInstanceForTesting();
396   feature_list->SetCachingContextForTesting(++g_current_caching_context);
397   FeatureList::SetInstance(std::move(feature_list));
398   init_called_ = true;
399 }
400 
InitFromCommandLine(const std::string & enable_features,const std::string & disable_features)401 void ScopedFeatureList::InitFromCommandLine(
402     const std::string& enable_features,
403     const std::string& disable_features) {
404   Features merged_features;
405   bool parse_enable_features_result =
406       ParseEnableFeatures(enable_features,
407                           merged_features.enabled_feature_list) &&
408       ParseEnableFeatures(disable_features,
409                           merged_features.disabled_feature_list);
410   DCHECK(parse_enable_features_result);
411   return InitWithMergedFeatures(std::move(merged_features),
412                                 /*create_associated_field_trials=*/false,
413                                 /*keep_existing_states=*/true);
414 }
415 
InitWithFeatures(const std::vector<FeatureRef> & enabled_features,const std::vector<FeatureRef> & disabled_features)416 void ScopedFeatureList::InitWithFeatures(
417     const std::vector<FeatureRef>& enabled_features,
418     const std::vector<FeatureRef>& disabled_features) {
419   InitWithFeaturesImpl(enabled_features, {}, disabled_features);
420 }
421 
InitAndEnableFeature(const Feature & feature)422 void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
423   InitWithFeaturesImpl({feature}, {}, {});
424 }
425 
InitAndDisableFeature(const Feature & feature)426 void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
427   InitWithFeaturesImpl({}, {}, {feature});
428 }
429 
InitWithFeatureState(const Feature & feature,bool enabled)430 void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
431                                              bool enabled) {
432   if (enabled) {
433     InitAndEnableFeature(feature);
434   } else {
435     InitAndDisableFeature(feature);
436   }
437 }
438 
InitWithFeatureStates(const flat_map<FeatureRef,bool> & feature_states)439 void ScopedFeatureList::InitWithFeatureStates(
440     const flat_map<FeatureRef, bool>& feature_states) {
441   std::vector<FeatureRef> enabled_features, disabled_features;
442   for (const auto& [feature, enabled] : feature_states) {
443     if (enabled) {
444       enabled_features.push_back(feature);
445     } else {
446       disabled_features.push_back(feature);
447     }
448   }
449   InitWithFeaturesImpl(enabled_features, {}, disabled_features);
450 }
451 
InitWithFeaturesImpl(const std::vector<FeatureRef> & enabled_features,const std::vector<FeatureRefAndParams> & enabled_features_and_params,const std::vector<FeatureRef> & disabled_features,bool keep_existing_states)452 void ScopedFeatureList::InitWithFeaturesImpl(
453     const std::vector<FeatureRef>& enabled_features,
454     const std::vector<FeatureRefAndParams>& enabled_features_and_params,
455     const std::vector<FeatureRef>& disabled_features,
456     bool keep_existing_states) {
457   DCHECK(!init_called_);
458   DCHECK(enabled_features.empty() || enabled_features_and_params.empty());
459 
460   Features merged_features;
461   bool create_associated_field_trials = false;
462   if (!enabled_features_and_params.empty()) {
463     for (const auto& feature : enabled_features_and_params) {
464       std::string trial_name = "scoped_feature_list_trial_for_";
465       trial_name += feature.feature->name;
466 
467       // If features.params has 2 params whose values are value1 and value2,
468       // |params| will be "param1/value1/param2/value2/".
469       std::string params;
470       for (const auto& param : feature.params) {
471         // Add separator from previous param information if it exists.
472         if (!params.empty())
473           params.append(1, '/');
474         params.append(EscapeValue(param.first));
475         params.append(1, '/');
476         params.append(EscapeValue(param.second));
477       }
478 
479       merged_features.enabled_feature_list.emplace_back(
480           feature.feature->name, trial_name, kTrialGroup, params);
481     }
482     create_associated_field_trials = true;
483   } else {
484     for (const auto& feature : enabled_features)
485       merged_features.enabled_feature_list.emplace_back(feature->name);
486   }
487   for (const auto& feature : disabled_features)
488     merged_features.disabled_feature_list.emplace_back(feature->name);
489 
490   InitWithMergedFeatures(std::move(merged_features),
491                          create_associated_field_trials, keep_existing_states);
492 }
493 
InitAndEnableFeatureWithParameters(const Feature & feature,const FieldTrialParams & feature_parameters)494 void ScopedFeatureList::InitAndEnableFeatureWithParameters(
495     const Feature& feature,
496     const FieldTrialParams& feature_parameters) {
497   InitWithFeaturesAndParameters({{feature, feature_parameters}}, {});
498 }
499 
InitWithFeaturesAndParameters(const std::vector<FeatureRefAndParams> & enabled_features,const std::vector<FeatureRef> & disabled_features)500 void ScopedFeatureList::InitWithFeaturesAndParameters(
501     const std::vector<FeatureRefAndParams>& enabled_features,
502     const std::vector<FeatureRef>& disabled_features) {
503   InitWithFeaturesImpl({}, enabled_features, disabled_features);
504 }
505 
InitWithMergedFeatures(Features && merged_features,bool create_associated_field_trials,bool keep_existing_states)506 void ScopedFeatureList::InitWithMergedFeatures(
507     Features&& merged_features,
508     bool create_associated_field_trials,
509     bool keep_existing_states) {
510   DCHECK(!init_called_);
511 
512   std::string current_enabled_features;
513   std::string current_disabled_features;
514   const FeatureList* feature_list = FeatureList::GetInstance();
515   if (feature_list && keep_existing_states) {
516     feature_list->GetFeatureOverrides(&current_enabled_features,
517                                       &current_disabled_features);
518   }
519 
520   std::vector<FieldTrial::State> all_states =
521       FieldTrialList::GetAllFieldTrialStates(PassKey());
522   original_params_ = FieldTrialList::AllParamsToString(&HexEncodeString);
523 
524   std::vector<ScopedFeatureList::FeatureWithStudyGroup>
525       parsed_current_enabled_features;
526   // Check relationship between current enabled features and field trials.
527   bool parse_enable_features_result = ParseEnableFeatures(
528       current_enabled_features, parsed_current_enabled_features);
529   DCHECK(parse_enable_features_result);
530 
531   // Back up the current field trial list, to be restored in Reset().
532   original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting();
533 
534   // Create a field trial list, to which we'll add trials corresponding to the
535   // features that have params, before restoring the field trial state from the
536   // previous instance, further down in this function.
537   field_trial_list_ = std::make_unique<FieldTrialList>();
538 
539   auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
540   for (const auto& feature : merged_features.enabled_feature_list) {
541     // If we don't need to create any field trials for the |feature| (i.e.
542     // unless |create_associated_field_trials|=true or |feature| has any
543     // params), we can skip the code: EraseIf()...ClearParamsForTesting().
544     if (!(create_associated_field_trials || feature.has_params()))
545       continue;
546 
547     // |all_states| contains the existing field trials, and is used to
548     // restore the field trials into a newly created field trial list with
549     // FieldTrialList::CreateTrialsFromFieldTrialStates().
550     // However |all_states| may have a field trial that's being explicitly
551     // set through |merged_features.enabled_feature_list|. In this case,
552     // FieldTrialParamAssociator::AssociateFieldTrialParams() will fail.
553     // So remove such field trials from |all_states| here.
554     EraseIf(all_states, [feature](const auto& state) {
555       return state.trial_name == feature.StudyNameOrDefault();
556     });
557 
558     // If |create_associated_field_trials| is true, we want to match the
559     // behavior of VariationsFieldTrialCreator to always associate a field
560     // trial, even when there no params. Since
561     // FeatureList::InitFromCommandLine() doesn't associate a field trial
562     // when there are no params, we do it here.
563     if (!feature.has_params()) {
564       scoped_refptr<FieldTrial> field_trial_without_params =
565           FieldTrialList::CreateFieldTrial(feature.StudyNameOrDefault(),
566                                            feature.GroupNameOrDefault());
567       DCHECK(field_trial_without_params);
568     }
569 
570     // Re-assigning field trial parameters is not allowed. Clear
571     // all field trial parameters.
572     field_trial_param_associator->ClearParamsForTesting(
573         feature.StudyNameOrDefault(), feature.GroupNameOrDefault());
574   }
575 
576   if (keep_existing_states) {
577     // Restore other field trials. Note: We don't need to do anything for params
578     // here because the param associator already has the right state for these
579     // restored trials, which has been backed up via |original_params_| to be
580     // restored later.
581     FieldTrialList::CreateTrialsFromFieldTrialStates(PassKey(), all_states);
582   } else {
583     // No need to keep existing field trials. Instead, clear all parameters.
584     field_trial_param_associator->ClearAllParamsForTesting();
585   }
586 
587   // Create enable-features and disable-features arguments.
588   OverrideFeatures(parsed_current_enabled_features,
589                    FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
590                    &merged_features);
591   OverrideFeatures(current_disabled_features,
592                    FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
593                    &merged_features);
594 
595   std::string enabled = CreateCommandLineArgumentFromFeatureList(
596       merged_features.enabled_feature_list, /*enable_features=*/true);
597   std::string disabled = CreateCommandLineArgumentFromFeatureList(
598       merged_features.disabled_feature_list, /*enable_features=*/false);
599 
600   std::unique_ptr<FeatureList> new_feature_list(new FeatureList);
601   new_feature_list->InitFromCommandLine(enabled, disabled);
602   InitWithFeatureList(std::move(new_feature_list));
603 }
604 
605 }  // namespace test
606 }  // namespace base
607