593 lines
23 KiB
C++
593 lines
23 KiB
C++
// Copyright 2016 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/test/scoped_feature_list.h"
|
|
|
|
#include <atomic>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/containers/contains.h"
|
|
#include "base/containers/cxx20_erase_vector.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/metrics/field_trial_param_associator.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/strings/string_split.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/test/mock_entropy_provider.h"
|
|
#include "base/test/task_environment.h"
|
|
|
|
namespace base {
|
|
namespace test {
|
|
namespace {
|
|
|
|
// A monotonically increasing id, passed to `FeatureList`s as they are created
|
|
// to invalidate the cache member of `base::Feature` objects that were queried
|
|
// with a different `FeatureList` installed.
|
|
uint16_t g_current_caching_context = 1;
|
|
|
|
} // namespace
|
|
|
|
// A struct describes ParsedEnableFeatures()' result.
|
|
struct ScopedFeatureList::FeatureWithStudyGroup {
|
|
FeatureWithStudyGroup(const std::string& feature_name,
|
|
const std::string& study_name,
|
|
const std::string& group_name,
|
|
const std::string& params)
|
|
: feature_name(feature_name),
|
|
study_name(study_name),
|
|
group_name(group_name),
|
|
params(params) {
|
|
DCHECK(IsValidFeatureName(feature_name));
|
|
DCHECK(IsValidFeatureOrFieldTrialName(study_name));
|
|
DCHECK(IsValidFeatureOrFieldTrialName(group_name));
|
|
}
|
|
|
|
explicit FeatureWithStudyGroup(const std::string& feature_name)
|
|
: feature_name(feature_name) {
|
|
DCHECK(IsValidFeatureName(feature_name));
|
|
}
|
|
|
|
~FeatureWithStudyGroup() = default;
|
|
FeatureWithStudyGroup(const FeatureWithStudyGroup& other) = default;
|
|
|
|
bool operator==(const FeatureWithStudyGroup& other) const {
|
|
return feature_name == other.feature_name &&
|
|
StudyNameOrDefault() == other.StudyNameOrDefault() &&
|
|
GroupNameOrDefault() == other.GroupNameOrDefault();
|
|
}
|
|
|
|
std::string FeatureName() const {
|
|
return StartsWith(feature_name, "*") ? feature_name.substr(1)
|
|
: feature_name;
|
|
}
|
|
|
|
// If |study_name| is empty, returns a default study name for |feature_name|.
|
|
// Otherwise, just return |study_name|.
|
|
std::string StudyNameOrDefault() const {
|
|
return study_name.empty() ? "Study" + FeatureName() : study_name;
|
|
}
|
|
|
|
// If |group_name| is empty, returns a default group name for |feature_name|.
|
|
// Otherwise, just return |group_name|.
|
|
std::string GroupNameOrDefault() const {
|
|
return group_name.empty() ? "Group" + FeatureName() : group_name;
|
|
}
|
|
|
|
bool has_params() const { return !params.empty(); }
|
|
|
|
std::string ParamsForFeatureList() const {
|
|
if (params.empty())
|
|
return "";
|
|
return ":" + params;
|
|
}
|
|
|
|
static bool IsValidFeatureOrFieldTrialName(const StringPiece& name) {
|
|
return IsStringASCII(name) &&
|
|
name.find_first_of(",<*") == std::string::npos;
|
|
}
|
|
|
|
static bool IsValidFeatureName(const StringPiece& feature_name) {
|
|
return IsValidFeatureOrFieldTrialName(
|
|
StartsWith(feature_name, "*") ? feature_name.substr(1) : feature_name);
|
|
}
|
|
|
|
// When ParseEnableFeatures() gets
|
|
// "FeatureName<StudyName.GroupName:Param1/Value1/Param2/Value2",
|
|
// a new FeatureWithStudyGroup with:
|
|
// - feature_name = "FeatureName"
|
|
// - study_name = "StudyName"
|
|
// - group_name = "GroupName"
|
|
// - params = "Param1/Value1/Param2/Value2"
|
|
// will be created and be returned.
|
|
const std::string feature_name;
|
|
const std::string study_name;
|
|
const std::string group_name;
|
|
const std::string params;
|
|
};
|
|
|
|
struct ScopedFeatureList::Features {
|
|
std::vector<FeatureWithStudyGroup> enabled_feature_list;
|
|
std::vector<FeatureWithStudyGroup> disabled_feature_list;
|
|
};
|
|
|
|
namespace {
|
|
|
|
constexpr char kTrialGroup[] = "scoped_feature_list_trial_group";
|
|
|
|
// Checks and parses the |enable_features| flag and appends each parsed
|
|
// feature, an instance of FeatureWithStudyGroup, to |parsed_enable_features|.
|
|
// Returns true if |enable_features| is parsable, otherwise false.
|
|
// The difference between this function and ParseEnabledFeatures() defined in
|
|
// feature_list.cc is:
|
|
// if "Feature1<Study1.Group1:Param1/Value1/Param2/Value2," +
|
|
// "Feature2<Study2.Group2" is given,
|
|
// feature_list.cc's one returns strings:
|
|
// parsed_enable_features = "Feature1<Study1,Feature2<Study2"
|
|
// force_field_trials = "Study1/Group1"
|
|
// force_fieldtrial_params = "Study1<Group1:Param1/Value1/Param2/Value2"
|
|
// this function returns a vector:
|
|
// [0] FeatureWithStudyGroup("Feature1", "Study1", "Group1",
|
|
// "Param1/Value1/Param2/Value2")
|
|
// [1] FeatureWithStudyGroup("Feature2", "Study2", "Group2", "")
|
|
bool ParseEnableFeatures(const std::string& enable_features,
|
|
std::vector<ScopedFeatureList::FeatureWithStudyGroup>&
|
|
parsed_enable_features) {
|
|
for (const auto& enable_feature :
|
|
FeatureList::SplitFeatureListString(enable_features)) {
|
|
std::string feature_name;
|
|
std::string study;
|
|
std::string group;
|
|
std::string feature_params;
|
|
if (!FeatureList::ParseEnableFeatureString(
|
|
enable_feature, &feature_name, &study, &group, &feature_params)) {
|
|
return false;
|
|
}
|
|
|
|
parsed_enable_features.emplace_back(feature_name, study, group,
|
|
feature_params);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Escapes separators used by enable-features command line.
|
|
// E.g. Feature '<' Study '.' Group ':' param1 '/' value1 ','
|
|
// ('*' is not a separator. No need to escape it.)
|
|
std::string EscapeValue(const std::string& value) {
|
|
std::string escaped_str;
|
|
for (const auto ch : value) {
|
|
if (ch == ',' || ch == '/' || ch == ':' || ch == '<' || ch == '.') {
|
|
escaped_str.append(base::StringPrintf("%%%02X", ch));
|
|
} else {
|
|
escaped_str.append(1, ch);
|
|
}
|
|
}
|
|
return escaped_str;
|
|
}
|
|
|
|
// Extracts a feature name from a feature state string. For example, given
|
|
// the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature".
|
|
StringPiece GetFeatureName(StringPiece feature) {
|
|
StringPiece feature_name = feature;
|
|
|
|
// Remove default info.
|
|
if (StartsWith(feature_name, "*"))
|
|
feature_name = feature_name.substr(1);
|
|
|
|
// Remove field_trial info.
|
|
std::size_t index = feature_name.find("<");
|
|
if (index != std::string::npos)
|
|
feature_name = feature_name.substr(0, index);
|
|
|
|
return feature_name;
|
|
}
|
|
|
|
// Features in |feature_vector| came from |merged_features| in
|
|
// OverrideFeatures() and contains linkage with field trial is case when they
|
|
// have parameters (with '<' simbol). In |feature_name| name is already cleared
|
|
// with GetFeatureName() and also could be without parameters.
|
|
bool ContainsFeature(
|
|
const std::vector<ScopedFeatureList::FeatureWithStudyGroup>& feature_vector,
|
|
StringPiece feature_name) {
|
|
return Contains(feature_vector, feature_name,
|
|
[](const ScopedFeatureList::FeatureWithStudyGroup& a) {
|
|
return a.feature_name;
|
|
});
|
|
}
|
|
|
|
// Merges previously-specified feature overrides with those passed into one of
|
|
// the Init() methods. |features| should be a list of features previously
|
|
// overridden to be in the |override_state|. |merged_features| should contain
|
|
// the enabled and disabled features passed into the Init() method, plus any
|
|
// overrides merged as a result of previous calls to this function.
|
|
void OverrideFeatures(
|
|
const std::vector<ScopedFeatureList::FeatureWithStudyGroup>& features_list,
|
|
FeatureList::OverrideState override_state,
|
|
ScopedFeatureList::Features* merged_features) {
|
|
for (const auto& feature : features_list) {
|
|
StringPiece feature_name = GetFeatureName(feature.feature_name);
|
|
|
|
if (ContainsFeature(merged_features->enabled_feature_list, feature_name) ||
|
|
ContainsFeature(merged_features->disabled_feature_list, feature_name)) {
|
|
continue;
|
|
}
|
|
|
|
if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
|
|
merged_features->enabled_feature_list.push_back(feature);
|
|
} else {
|
|
DCHECK_EQ(override_state,
|
|
FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE);
|
|
merged_features->disabled_feature_list.push_back(feature);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merges previously-specified feature overrides with those passed into one of
|
|
// the Init() methods. |feature_list| should be a string whose format is the
|
|
// same as --enable-features or --disable-features command line flag, and
|
|
// specifies features overridden to be in the |override_state|.
|
|
// |merged_features| should contain the enabled and disabled features passed in
|
|
// to the Init() method, plus any overrides merged as a result of previous
|
|
// calls to this function.
|
|
void OverrideFeatures(const std::string& features_list,
|
|
FeatureList::OverrideState override_state,
|
|
ScopedFeatureList::Features* merged_features) {
|
|
std::vector<ScopedFeatureList::FeatureWithStudyGroup> parsed_features;
|
|
bool parse_enable_features_result =
|
|
ParseEnableFeatures(features_list, parsed_features);
|
|
DCHECK(parse_enable_features_result);
|
|
OverrideFeatures(parsed_features, override_state, merged_features);
|
|
}
|
|
|
|
// Hex encode params so that special characters do not break formatting.
|
|
std::string HexEncodeString(const std::string& input) {
|
|
return HexEncode(input.data(), input.size());
|
|
}
|
|
|
|
// Inverse of HexEncodeString().
|
|
std::string HexDecodeString(const std::string& input) {
|
|
if (input.empty())
|
|
return std::string();
|
|
std::string bytes;
|
|
bool result = HexStringToString(input, &bytes);
|
|
DCHECK(result);
|
|
return bytes;
|
|
}
|
|
|
|
// Returns a command line string suitable to pass to
|
|
// FeatureList::InitializeFromCommandLine(). For example,
|
|
// {{"Feature1", "Study1", "Group1", "Param1/Value1/"}, {"Feature2"}} returns:
|
|
// - |enabled_feature|=true -> "Feature1<Study1.Group1:Param1/Value1/,Feature2"
|
|
// - |enabled_feature|=false -> "Feature1<Study1.Group1,Feature2"
|
|
std::string CreateCommandLineArgumentFromFeatureList(
|
|
const std::vector<ScopedFeatureList::FeatureWithStudyGroup>& feature_list,
|
|
bool enable_features) {
|
|
std::vector<std::string> features;
|
|
for (const auto& feature : feature_list) {
|
|
std::string feature_with_study_group = feature.feature_name;
|
|
if (feature.has_params() || !feature.study_name.empty()) {
|
|
feature_with_study_group += "<";
|
|
feature_with_study_group += feature.StudyNameOrDefault();
|
|
if (feature.has_params() || !feature.group_name.empty()) {
|
|
feature_with_study_group += ".";
|
|
feature_with_study_group += feature.GroupNameOrDefault();
|
|
}
|
|
if (feature.has_params() && enable_features) {
|
|
feature_with_study_group += feature.ParamsForFeatureList();
|
|
}
|
|
}
|
|
features.push_back(feature_with_study_group);
|
|
}
|
|
return JoinString(features, ",");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FeatureRefAndParams::FeatureRefAndParams(const Feature& feature,
|
|
const FieldTrialParams& params)
|
|
: feature(feature), params(params) {}
|
|
|
|
FeatureRefAndParams::FeatureRefAndParams(const FeatureRefAndParams& other) =
|
|
default;
|
|
|
|
FeatureRefAndParams::~FeatureRefAndParams() = default;
|
|
|
|
ScopedFeatureList::ScopedFeatureList() = default;
|
|
|
|
ScopedFeatureList::ScopedFeatureList(const Feature& enable_feature) {
|
|
InitAndEnableFeature(enable_feature);
|
|
}
|
|
|
|
ScopedFeatureList::~ScopedFeatureList() {
|
|
Reset();
|
|
}
|
|
|
|
void ScopedFeatureList::Reset() {
|
|
// If one of the Init() functions was never called, don't reset anything.
|
|
if (!init_called_)
|
|
return;
|
|
|
|
init_called_ = false;
|
|
|
|
// ThreadPool tasks racily probing FeatureList while it's initialized/reset
|
|
// are problematic and while callers should ideally set up ScopedFeatureList
|
|
// before TaskEnvironment, that's not always possible. Fencing execution here
|
|
// avoids an entire class of bugs by making sure no ThreadPool task queries
|
|
// FeatureList while it's being modified. This local action is preferred to
|
|
// requiring all such callers to manually flush all tasks before each
|
|
// ScopedFeatureList Init/Reset: crbug.com/1275502#c45
|
|
//
|
|
// All FeatureList modifications in this file should have this as well.
|
|
TaskEnvironment::ParallelExecutionFence fence(
|
|
"ScopedFeatureList must be Reset from the test main thread");
|
|
|
|
FeatureList::ClearInstanceForTesting();
|
|
|
|
if (field_trial_list_) {
|
|
field_trial_list_.reset();
|
|
}
|
|
|
|
// Restore params to how they were before.
|
|
FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
|
|
if (!original_params_.empty()) {
|
|
// Before restoring params, we need to make all field trials in-active,
|
|
// because FieldTrialParamAssociator checks whether the given field trial
|
|
// is active or not, and associates no parameters if the trial is active.
|
|
// So temporarily restore field trial list to be nullptr.
|
|
FieldTrialList::RestoreInstanceForTesting(nullptr);
|
|
AssociateFieldTrialParamsFromString(original_params_, &HexDecodeString);
|
|
}
|
|
|
|
if (original_field_trial_list_) {
|
|
FieldTrialList::RestoreInstanceForTesting(original_field_trial_list_);
|
|
original_field_trial_list_ = nullptr;
|
|
}
|
|
|
|
if (original_feature_list_)
|
|
FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
|
|
}
|
|
|
|
void ScopedFeatureList::Init() {
|
|
InitWithFeaturesImpl({}, {}, {}, /*keep_existing_states=*/true);
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithEmptyFeatureAndFieldTrialLists() {
|
|
InitWithFeaturesImpl({}, {}, {}, /*keep_existing_states=*/false);
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithNullFeatureAndFieldTrialLists() {
|
|
DCHECK(!init_called_);
|
|
|
|
// Back up the current field trial parameters to be restored in Reset().
|
|
original_params_ = FieldTrialList::AllParamsToString(&HexEncodeString);
|
|
|
|
// Back up the current field trial list, to be restored in Reset().
|
|
original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting();
|
|
|
|
auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
|
|
field_trial_param_associator->ClearAllParamsForTesting();
|
|
field_trial_list_ = nullptr;
|
|
|
|
DCHECK(!original_feature_list_);
|
|
|
|
// Execution fence required while modifying FeatureList, as in Reset.
|
|
TaskEnvironment::ParallelExecutionFence fence(
|
|
"ScopedFeatureList must be Init from the test main thread");
|
|
|
|
// Back up the current feature list, to be restored in Reset().
|
|
original_feature_list_ = FeatureList::ClearInstanceForTesting();
|
|
init_called_ = true;
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithFeatureList(
|
|
std::unique_ptr<FeatureList> feature_list) {
|
|
DCHECK(!original_feature_list_);
|
|
|
|
// Execution fence required while modifying FeatureList, as in Reset.
|
|
TaskEnvironment::ParallelExecutionFence fence(
|
|
"ScopedFeatureList must be Init from the test main thread");
|
|
|
|
original_feature_list_ = FeatureList::ClearInstanceForTesting();
|
|
feature_list->SetCachingContextForTesting(++g_current_caching_context);
|
|
FeatureList::SetInstance(std::move(feature_list));
|
|
init_called_ = true;
|
|
}
|
|
|
|
void ScopedFeatureList::InitFromCommandLine(
|
|
const std::string& enable_features,
|
|
const std::string& disable_features) {
|
|
Features merged_features;
|
|
bool parse_enable_features_result =
|
|
ParseEnableFeatures(enable_features,
|
|
merged_features.enabled_feature_list) &&
|
|
ParseEnableFeatures(disable_features,
|
|
merged_features.disabled_feature_list);
|
|
DCHECK(parse_enable_features_result);
|
|
return InitWithMergedFeatures(std::move(merged_features),
|
|
/*create_associated_field_trials=*/false,
|
|
/*keep_existing_states=*/true);
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithFeatures(
|
|
const std::vector<FeatureRef>& enabled_features,
|
|
const std::vector<FeatureRef>& disabled_features) {
|
|
InitWithFeaturesImpl(enabled_features, {}, disabled_features);
|
|
}
|
|
|
|
void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
|
|
InitWithFeaturesImpl({feature}, {}, {});
|
|
}
|
|
|
|
void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
|
|
InitWithFeaturesImpl({}, {}, {feature});
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
|
|
bool enabled) {
|
|
if (enabled) {
|
|
InitAndEnableFeature(feature);
|
|
} else {
|
|
InitAndDisableFeature(feature);
|
|
}
|
|
}
|
|
|
|
void ScopedFeatureList::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) {
|
|
DCHECK(!init_called_);
|
|
DCHECK(enabled_features.empty() || enabled_features_and_params.empty());
|
|
|
|
Features merged_features;
|
|
bool create_associated_field_trials = false;
|
|
if (!enabled_features_and_params.empty()) {
|
|
for (const auto& feature : enabled_features_and_params) {
|
|
std::string trial_name = "scoped_feature_list_trial_for_";
|
|
trial_name += feature.feature->name;
|
|
|
|
// If features.params has 2 params whose values are value1 and value2,
|
|
// |params| will be "param1/value1/param2/value2/".
|
|
std::string params;
|
|
for (const auto& param : feature.params) {
|
|
// Add separator from previous param information if it exists.
|
|
if (!params.empty())
|
|
params.append(1, '/');
|
|
params.append(EscapeValue(param.first));
|
|
params.append(1, '/');
|
|
params.append(EscapeValue(param.second));
|
|
}
|
|
|
|
merged_features.enabled_feature_list.emplace_back(
|
|
feature.feature->name, trial_name, kTrialGroup, params);
|
|
}
|
|
create_associated_field_trials = true;
|
|
} else {
|
|
for (const auto& feature : enabled_features)
|
|
merged_features.enabled_feature_list.emplace_back(feature->name);
|
|
}
|
|
for (const auto& feature : disabled_features)
|
|
merged_features.disabled_feature_list.emplace_back(feature->name);
|
|
|
|
InitWithMergedFeatures(std::move(merged_features),
|
|
create_associated_field_trials, keep_existing_states);
|
|
}
|
|
|
|
void ScopedFeatureList::InitAndEnableFeatureWithParameters(
|
|
const Feature& feature,
|
|
const FieldTrialParams& feature_parameters) {
|
|
InitWithFeaturesAndParameters({{feature, feature_parameters}}, {});
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithFeaturesAndParameters(
|
|
const std::vector<FeatureRefAndParams>& enabled_features,
|
|
const std::vector<FeatureRef>& disabled_features) {
|
|
InitWithFeaturesImpl({}, enabled_features, disabled_features);
|
|
}
|
|
|
|
void ScopedFeatureList::InitWithMergedFeatures(
|
|
Features&& merged_features,
|
|
bool create_associated_field_trials,
|
|
bool keep_existing_states) {
|
|
DCHECK(!init_called_);
|
|
|
|
std::string current_enabled_features;
|
|
std::string current_disabled_features;
|
|
const FeatureList* feature_list = FeatureList::GetInstance();
|
|
if (feature_list && keep_existing_states) {
|
|
feature_list->GetFeatureOverrides(¤t_enabled_features,
|
|
¤t_disabled_features);
|
|
}
|
|
|
|
std::vector<FieldTrial::State> all_states =
|
|
FieldTrialList::GetAllFieldTrialStates(PassKey());
|
|
original_params_ = FieldTrialList::AllParamsToString(&HexEncodeString);
|
|
|
|
std::vector<ScopedFeatureList::FeatureWithStudyGroup>
|
|
parsed_current_enabled_features;
|
|
// Check relationship between current enabled features and field trials.
|
|
bool parse_enable_features_result = ParseEnableFeatures(
|
|
current_enabled_features, parsed_current_enabled_features);
|
|
DCHECK(parse_enable_features_result);
|
|
|
|
// Back up the current field trial list, to be restored in Reset().
|
|
original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting();
|
|
|
|
// Create a field trial list, to which we'll add trials corresponding to the
|
|
// features that have params, before restoring the field trial state from the
|
|
// previous instance, further down in this function.
|
|
field_trial_list_ = std::make_unique<FieldTrialList>();
|
|
|
|
auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
|
|
for (const auto& feature : merged_features.enabled_feature_list) {
|
|
// If we don't need to create any field trials for the |feature| (i.e.
|
|
// unless |create_associated_field_trials|=true or |feature| has any
|
|
// params), we can skip the code: EraseIf()...ClearParamsForTesting().
|
|
if (!(create_associated_field_trials || feature.has_params()))
|
|
continue;
|
|
|
|
// |all_states| contains the existing field trials, and is used to
|
|
// restore the field trials into a newly created field trial list with
|
|
// FieldTrialList::CreateTrialsFromFieldTrialStates().
|
|
// However |all_states| may have a field trial that's being explicitly
|
|
// set through |merged_features.enabled_feature_list|. In this case,
|
|
// FieldTrialParamAssociator::AssociateFieldTrialParams() will fail.
|
|
// So remove such field trials from |all_states| here.
|
|
EraseIf(all_states, [feature](const auto& state) {
|
|
return state.trial_name == feature.StudyNameOrDefault();
|
|
});
|
|
|
|
// If |create_associated_field_trials| is true, we want to match the
|
|
// behavior of VariationsFieldTrialCreator to always associate a field
|
|
// trial, even when there no params. Since
|
|
// FeatureList::InitializeFromCommandLine() doesn't associate a field trial
|
|
// when there are no params, we do it here.
|
|
if (!feature.has_params()) {
|
|
scoped_refptr<FieldTrial> field_trial_without_params =
|
|
FieldTrialList::CreateFieldTrial(feature.StudyNameOrDefault(),
|
|
feature.GroupNameOrDefault());
|
|
DCHECK(field_trial_without_params);
|
|
}
|
|
|
|
// Re-assigning field trial parameters is not allowed. Clear
|
|
// all field trial parameters.
|
|
field_trial_param_associator->ClearParamsForTesting(
|
|
feature.StudyNameOrDefault(), feature.GroupNameOrDefault());
|
|
}
|
|
|
|
if (keep_existing_states) {
|
|
// Restore other field trials. Note: We don't need to do anything for params
|
|
// here because the param associator already has the right state for these
|
|
// restored trials, which has been backed up via |original_params_| to be
|
|
// restored later.
|
|
FieldTrialList::CreateTrialsFromFieldTrialStates(PassKey(), all_states);
|
|
} else {
|
|
// No need to keep existing field trials. Instead, clear all parameters.
|
|
field_trial_param_associator->ClearAllParamsForTesting();
|
|
}
|
|
|
|
// Create enable-features and disable-features arguments.
|
|
OverrideFeatures(parsed_current_enabled_features,
|
|
FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
|
|
&merged_features);
|
|
OverrideFeatures(current_disabled_features,
|
|
FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
|
|
&merged_features);
|
|
|
|
std::string enabled = CreateCommandLineArgumentFromFeatureList(
|
|
merged_features.enabled_feature_list, /*enable_features=*/true);
|
|
std::string disabled = CreateCommandLineArgumentFromFeatureList(
|
|
merged_features.disabled_feature_list, /*enable_features=*/false);
|
|
|
|
std::unique_ptr<FeatureList> new_feature_list(new FeatureList);
|
|
new_feature_list->InitializeFromCommandLine(enabled, disabled);
|
|
InitWithFeatureList(std::move(new_feature_list));
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace base
|