1 // Copyright 2016 The Chromium Authors. All rights reserved. 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 <algorithm> 8 #include <utility> 9 #include <vector> 10 11 #include "base/memory/ptr_util.h" 12 #include "base/metrics/field_trial_param_associator.h" 13 #include "base/stl_util.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/string_split.h" 16 #include "base/strings/string_util.h" 17 18 namespace base { 19 namespace test { 20 21 namespace { 22 23 std::vector<StringPiece> GetFeatureVector( 24 const std::vector<Feature>& features) { 25 std::vector<StringPiece> output; 26 for (const Feature& feature : features) { 27 output.push_back(feature.name); 28 } 29 30 return output; 31 } 32 33 // Extracts a feature name from a feature state string. For example, given 34 // the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature". 35 StringPiece GetFeatureName(StringPiece feature) { 36 StringPiece feature_name = feature; 37 38 // Remove default info. 39 if (feature_name.starts_with("*")) 40 feature_name = feature_name.substr(1); 41 42 // Remove field_trial info. 43 std::size_t index = feature_name.find("<"); 44 if (index != std::string::npos) 45 feature_name = feature_name.substr(0, index); 46 47 return feature_name; 48 } 49 50 struct Features { 51 std::vector<StringPiece> enabled_feature_list; 52 std::vector<StringPiece> disabled_feature_list; 53 }; 54 55 // Merges previously-specified feature overrides with those passed into one of 56 // the Init() methods. |features| should be a list of features previously 57 // overridden to be in the |override_state|. |merged_features| should contain 58 // the enabled and disabled features passed into the Init() method, plus any 59 // overrides merged as a result of previous calls to this function. 60 void OverrideFeatures(const std::string& features, 61 FeatureList::OverrideState override_state, 62 Features* merged_features) { 63 std::vector<StringPiece> features_list = 64 SplitStringPiece(features, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); 65 66 for (StringPiece feature : features_list) { 67 StringPiece feature_name = GetFeatureName(feature); 68 69 if (ContainsValue(merged_features->enabled_feature_list, feature_name) || 70 ContainsValue(merged_features->disabled_feature_list, feature_name)) 71 continue; 72 73 if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) { 74 merged_features->enabled_feature_list.push_back(feature); 75 } else { 76 DCHECK_EQ(override_state, 77 FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE); 78 merged_features->disabled_feature_list.push_back(feature); 79 } 80 } 81 } 82 83 } // namespace 84 85 ScopedFeatureList::ScopedFeatureList() = default; 86 87 ScopedFeatureList::~ScopedFeatureList() { 88 // If one of the Init() functions was never called, don't reset anything. 89 if (!init_called_) 90 return; 91 92 if (field_trial_override_) { 93 base::FieldTrialParamAssociator::GetInstance()->ClearParamsForTesting( 94 field_trial_override_->trial_name(), 95 field_trial_override_->group_name()); 96 } 97 98 FeatureList::ClearInstanceForTesting(); 99 if (original_feature_list_) 100 FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_)); 101 } 102 103 void ScopedFeatureList::Init() { 104 std::unique_ptr<FeatureList> feature_list(new FeatureList); 105 feature_list->InitializeFromCommandLine(std::string(), std::string()); 106 InitWithFeatureList(std::move(feature_list)); 107 } 108 109 void ScopedFeatureList::InitWithFeatureList( 110 std::unique_ptr<FeatureList> feature_list) { 111 DCHECK(!original_feature_list_); 112 original_feature_list_ = FeatureList::ClearInstanceForTesting(); 113 FeatureList::SetInstance(std::move(feature_list)); 114 init_called_ = true; 115 } 116 117 void ScopedFeatureList::InitFromCommandLine( 118 const std::string& enable_features, 119 const std::string& disable_features) { 120 std::unique_ptr<FeatureList> feature_list(new FeatureList); 121 feature_list->InitializeFromCommandLine(enable_features, disable_features); 122 InitWithFeatureList(std::move(feature_list)); 123 } 124 125 void ScopedFeatureList::InitWithFeatures( 126 const std::vector<Feature>& enabled_features, 127 const std::vector<Feature>& disabled_features) { 128 InitWithFeaturesAndFieldTrials(enabled_features, {}, disabled_features); 129 } 130 131 void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) { 132 InitWithFeaturesAndFieldTrials({feature}, {}, {}); 133 } 134 135 void ScopedFeatureList::InitAndEnableFeatureWithFieldTrialOverride( 136 const Feature& feature, 137 FieldTrial* trial) { 138 InitWithFeaturesAndFieldTrials({feature}, {trial}, {}); 139 } 140 141 void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) { 142 InitWithFeaturesAndFieldTrials({}, {}, {feature}); 143 } 144 145 void ScopedFeatureList::InitWithFeatureState(const Feature& feature, 146 bool enabled) { 147 if (enabled) { 148 InitAndEnableFeature(feature); 149 } else { 150 InitAndDisableFeature(feature); 151 } 152 } 153 154 void ScopedFeatureList::InitWithFeaturesAndFieldTrials( 155 const std::vector<Feature>& enabled_features, 156 const std::vector<FieldTrial*>& trials_for_enabled_features, 157 const std::vector<Feature>& disabled_features) { 158 DCHECK_LE(trials_for_enabled_features.size(), enabled_features.size()); 159 160 Features merged_features; 161 merged_features.enabled_feature_list = GetFeatureVector(enabled_features); 162 merged_features.disabled_feature_list = GetFeatureVector(disabled_features); 163 164 FeatureList* feature_list = FeatureList::GetInstance(); 165 166 // |current_enabled_features| and |current_disabled_features| must declare out 167 // of if scope to avoid them out of scope before JoinString calls because 168 // |merged_features| may contains StringPiece which holding pointer points to 169 // |current_enabled_features| and |current_disabled_features|. 170 std::string current_enabled_features; 171 std::string current_disabled_features; 172 if (feature_list) { 173 FeatureList::GetInstance()->GetFeatureOverrides(¤t_enabled_features, 174 ¤t_disabled_features); 175 OverrideFeatures(current_enabled_features, 176 FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE, 177 &merged_features); 178 OverrideFeatures(current_disabled_features, 179 FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE, 180 &merged_features); 181 } 182 183 // Add the field trial overrides. This assumes that |enabled_features| are at 184 // the begining of |merged_features.enabled_feature_list|, in the same order. 185 std::vector<FieldTrial*>::const_iterator trial_it = 186 trials_for_enabled_features.begin(); 187 auto feature_it = merged_features.enabled_feature_list.begin(); 188 std::vector<std::unique_ptr<std::string>> features_with_trial; 189 features_with_trial.reserve(trials_for_enabled_features.size()); 190 while (trial_it != trials_for_enabled_features.end()) { 191 features_with_trial.push_back(std::make_unique<std::string>( 192 feature_it->as_string() + "<" + (*trial_it)->trial_name())); 193 // |features_with_trial| owns the string, and feature_it points to it. 194 *feature_it = *(features_with_trial.back()); 195 ++trial_it; 196 ++feature_it; 197 } 198 199 std::string enabled = JoinString(merged_features.enabled_feature_list, ","); 200 std::string disabled = JoinString(merged_features.disabled_feature_list, ","); 201 InitFromCommandLine(enabled, disabled); 202 } 203 204 void ScopedFeatureList::InitAndEnableFeatureWithParameters( 205 const Feature& feature, 206 const std::map<std::string, std::string>& feature_parameters) { 207 if (!FieldTrialList::IsGlobalSetForTesting()) { 208 field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr); 209 } 210 211 // TODO(crbug.com/794021) Remove this unique field trial name hack when there 212 // is a cleaner solution. 213 // Ensure that each call to this method uses a distinct field trial name. 214 // Otherwise, nested calls might fail due to the shared FieldTrialList 215 // already having the field trial registered. 216 static int num_calls = 0; 217 ++num_calls; 218 std::string kTrialName = 219 "scoped_feature_list_trial_name" + base::NumberToString(num_calls); 220 std::string kTrialGroup = "scoped_feature_list_trial_group"; 221 222 field_trial_override_ = 223 base::FieldTrialList::CreateFieldTrial(kTrialName, kTrialGroup); 224 DCHECK(field_trial_override_); 225 FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams( 226 kTrialName, kTrialGroup, feature_parameters); 227 InitAndEnableFeatureWithFieldTrialOverride(feature, 228 field_trial_override_.get()); 229 } 230 231 } // namespace test 232 } // namespace base 233