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