xref: /aosp_15_r20/external/cronet/base/test/scoped_feature_list.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 #ifndef BASE_TEST_SCOPED_FEATURE_LIST_H_
6 #define BASE_TEST_SCOPED_FEATURE_LIST_H_
7 
8 #include <memory>
9 #include <string>
10 #include <vector>
11 
12 #include "base/containers/flat_map.h"
13 #include "base/feature_list.h"
14 #include "base/memory/raw_ptr.h"
15 #include "base/memory/raw_ref.h"
16 #include "base/metrics/field_trial.h"
17 #include "base/metrics/field_trial_params.h"
18 #include "base/types/pass_key.h"
19 #include "third_party/abseil-cpp/absl/base/attributes.h"
20 
21 namespace base::test {
22 
23 // A reference to a base::Feature and field trial params that should be force
24 // enabled and overwritten for test purposes.
25 struct FeatureRefAndParams {
26   FeatureRefAndParams(const Feature& feature ABSL_ATTRIBUTE_LIFETIME_BOUND,
27                       const FieldTrialParams& params);
28 
29   FeatureRefAndParams(const FeatureRefAndParams& other);
30 
31   ~FeatureRefAndParams();
32 
33   const raw_ref<const Feature> feature;
34   const FieldTrialParams params;
35 };
36 
37 // A lightweight wrapper for a reference to a base::Feature. Allows lists of
38 // features to be enabled/disabled to be easily passed without actually copying
39 // the underlying base::Feature. Actual C++ references do not work well for this
40 // purpose, as vectors of references are disallowed.
41 class FeatureRef {
42  public:
43   // NOLINTNEXTLINE(google-explicit-constructor)
FeatureRef(const Feature & feature ABSL_ATTRIBUTE_LIFETIME_BOUND)44   FeatureRef(const Feature& feature ABSL_ATTRIBUTE_LIFETIME_BOUND)
45       : feature_(feature) {}
46 
47   const Feature& operator*() const { return *feature_; }
48   const Feature* operator->() const { return &*feature_; }
49 
50  private:
51   friend bool operator==(const FeatureRef& lhs, const FeatureRef& rhs) {
52     return &*lhs == &*rhs;
53   }
54   friend bool operator<(const FeatureRef& lhs, const FeatureRef& rhs) {
55     return &*lhs < &*rhs;
56   }
57 
58   raw_ref<const Feature> feature_;
59 };
60 
61 // ScopedFeatureList resets the global FeatureList instance to a new instance
62 // and restores the original instance upon destruction. Whether the existing
63 // FeatureList state is kept or discarded depends on the `Init` method called.
64 // When using the non-deprecated APIs, a corresponding FieldTrialList is also
65 // created.
66 //
67 // Note: Re-using the same object is allowed. To reset the feature list and
68 // initialize it anew, call `Reset` and then one of the `Init` methods.
69 //
70 // If multiple instances of this class are used in a nested fashion, they
71 // should be destroyed in the opposite order of their Init*() methods being
72 // called.
73 //
74 // ScopedFeatureList needs to be initialized on the main thread (via one of
75 // Init*() methods) before running code that inspects the state of features,
76 // such as in the constructor of the test harness.
77 //
78 // WARNING: To be clear, in multithreaded test environments (such as browser
79 // tests) there may background threads using FeatureList before the test body is
80 // even entered. In these cases it is imperative that ScopedFeatureList be
81 // initialized BEFORE those threads are started, hence the recommendation to do
82 // initialization in the test harness's constructor.
83 class ScopedFeatureList final {
84  public:
85   struct Features;
86   struct FeatureWithStudyGroup;
87 
88   // Constructs the instance in a non-initialized state.
89   ScopedFeatureList();
90 
91   // Shorthand for immediately initializing with InitAndEnableFeature().
92   explicit ScopedFeatureList(const Feature& enable_feature);
93 
94   ScopedFeatureList(const ScopedFeatureList&) = delete;
95   ScopedFeatureList& operator=(const ScopedFeatureList&) = delete;
96 
97   ~ScopedFeatureList();
98 
99   // Resets the instance to a non-initialized state.
100   void Reset();
101 
102   // Initializes and registers a FeatureList instance without any additional
103   // enabled or disabled features. Existing state, if any, will be kept.
104   // This is equivalent to calling InitWithFeatures({}, {}).
105   void Init();
106 
107   // Initializes a FeatureList instance without any additional enabled or
108   // disabled features. Existing state, if any, will be discarded.
109   // Using this function is not generally recommended, as doing so in a test
110   // removes the ability to run the test while passing additional
111   // --enable-features flags from the command line.
112   void InitWithEmptyFeatureAndFieldTrialLists();
113 
114   // Initializes a FeatureList instance and FieldTrialLists to be null and
115   // clear all field trial parameters.
116   // WARNING: This should not be generally used except for tests that require
117   // manually instantiating objects like FieldTrialList, for example when
118   // mocking an EntropyProvider.
119   void InitWithNullFeatureAndFieldTrialLists();
120 
121   // WARNING: This method will reset any globally configured features to their
122   // default values, which can hide feature interaction bugs. Please use
123   // sparingly.  https://crbug.com/713390
124   // Initializes and registers the given FeatureList instance.
125   void InitWithFeatureList(std::unique_ptr<FeatureList> feature_list);
126 
127   // Initializes and registers a FeatureList instance based on the current
128   // FeatureList and overridden with the given enabled features and the
129   // specified field trial parameters, and the given disabled features
130   // with the given enabled and disabled features (comma-separated names).
131   // Note: This creates a scoped global field trial list if there is not
132   // currently one.
133   void InitFromCommandLine(const std::string& enable_features,
134                            const std::string& disable_features);
135 
136   // Initializes and registers a FeatureList instance based on the current
137   // FeatureList and overridden with the given enabled and disabled features.
138   // Any feature overrides already present in the global FeatureList will
139   // continue to apply, unless they conflict with the overrides passed into this
140   // method. This is important for testing potentially unexpected feature
141   // interactions.
142   void InitWithFeatures(const std::vector<FeatureRef>& enabled_features,
143                         const std::vector<FeatureRef>& disabled_features);
144 
145   // Initializes and registers a FeatureList instance based on the current
146   // FeatureList and overridden with single enabled feature.
147   void InitAndEnableFeature(const Feature& feature);
148 
149   // Initializes and registers a FeatureList instance based on the current
150   // FeatureList and overridden with single enabled feature and associated field
151   // trial parameters.
152   // Note: this creates a scoped global field trial list if there is not
153   // currently one.
154   void InitAndEnableFeatureWithParameters(
155       const Feature& feature,
156       const FieldTrialParams& feature_parameters);
157 
158   // Initializes and registers a FeatureList instance based on the current
159   // FeatureList and overridden with the given enabled features and the
160   // specified field trial parameters, and the given disabled features.
161   // Note: This creates a scoped global field trial list if there is not
162   // currently one.
163   void InitWithFeaturesAndParameters(
164       const std::vector<FeatureRefAndParams>& enabled_features,
165       const std::vector<FeatureRef>& disabled_features);
166 
167   // Initializes and registers a FeatureList instance based on the current
168   // FeatureList and overridden with single disabled feature.
169   void InitAndDisableFeature(const Feature& feature);
170 
171   // Initializes and registers a FeatureList instance based on the current
172   // FeatureList and overridden with a single feature either enabled or
173   // disabled depending on |enabled|.
174   void InitWithFeatureState(const Feature& feature, bool enabled);
175 
176   // Same as `InitWithFeatureState()`, but supports multiple features at a time.
177   // `feature_states` - a map where the keys are features and the values are
178   //                    their overridden states (`false` for force-disabled,
179   //                    `true` for force-enabled).
180   void InitWithFeatureStates(const flat_map<FeatureRef, bool>& feature_states);
181 
182  private:
183   using PassKey = base::PassKey<ScopedFeatureList>;
184 
185   // Initializes and registers a FeatureList instance based on the current
186   // FeatureList and overridden with the given enabled and disabled features.
187   // Any feature overrides already present in the global FeatureList will
188   // continue to apply, unless they conflict with the overrides passed into this
189   // method.
190   // Features to enable may be specified through either |enabled_features| or
191   // |enabled_feature_and_params|, but not both (i.e. one of these must be
192   // empty).
193   void InitWithFeaturesImpl(
194       const std::vector<FeatureRef>& enabled_features,
195       const std::vector<FeatureRefAndParams>& enabled_features_and_params,
196       const std::vector<FeatureRef>& disabled_features,
197       bool keep_existing_states = true);
198 
199   // Initializes and registers a FeatureList instance based on the current
200   // FeatureList and overridden with the given enabled and disabled features.
201   // Any feature overrides already present in the global FeatureList will
202   // continue to apply, unless they conflict with the overrides passed into this
203   // method.
204   // If |create_associated_field_trials| is true, associated field trials are
205   // always created independent of feature parameters. If false, field trials
206   // for features whose parameters are specified will be created.
207   // If |keep_existing_states| is true, keep all states and override them
208   // according to the |merged_features|. Otherwise, clear all states and
209   // newly initialize all states with |merged_features|.
210   void InitWithMergedFeatures(Features&& merged_features,
211                               bool create_associated_field_trials,
212                               bool keep_existing_states);
213 
214   bool init_called_ = false;
215   std::unique_ptr<FeatureList> original_feature_list_;
216   raw_ptr<base::FieldTrialList> original_field_trial_list_ = nullptr;
217   std::string original_params_;
218   std::unique_ptr<base::FieldTrialList> field_trial_list_;
219 };
220 
221 }  // namespace base::test
222 
223 #endif  // BASE_TEST_SCOPED_FEATURE_LIST_H_
224