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