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