• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 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 package org.chromium.base.test.util;
6 
7 import android.text.TextUtils;
8 
9 import androidx.annotation.Nullable;
10 
11 import org.chromium.base.CommandLine;
12 import org.chromium.base.FeatureList;
13 
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Map;
18 import java.util.Set;
19 
20 /**
21  * Base class to help with setting Feature flags during tests. Relies on registering the
22  * appropriate {@link Processor} rule on the test class.
23  *
24  * Subclasses should introduce a {@code EnableFeatures} and {@code DisableFeatures}
25  * annotation and register them in classes that extend the {@link BaseJUnitProcessor} or
26  * {@link BaseInstrumentationProcessor}
27  *
28  * See {@link org.chromium.chrome.test.util.browser.Features} for an example of this.
29  *
30  * Subclasses should offer Singleton access to enable and disable features, letting other rules
31  * affect the final configuration before the start of the test.
32  */
33 public class FeaturesBase {
34     protected static @Nullable FeaturesBase sInstance;
35     protected final Map<String, Boolean> mRegisteredState = new HashMap<>();
36 
37     /**
38      * Explicitly applies features collected so far to the command line.
39      * Note: This is only valid during instrumentation tests.
40      * TODO(dgn): remove once we have the compound test rule is available to enable a deterministic
41      * rule execution order.
42      */
ensureCommandLineIsUpToDate()43     public void ensureCommandLineIsUpToDate() {
44         sInstance.applyForInstrumentation();
45     }
46 
47     /** Collects the provided features to be registered as enabled. */
enable(String... featureNames)48     public void enable(String... featureNames) {
49         // TODO(dgn): assert that it's not being called too late and will be able to be applied.
50         for (String featureName : featureNames) mRegisteredState.put(featureName, true);
51     }
52 
53     /** Collects the provided features to be registered as disabled. */
disable(String... featureNames)54     public void disable(String... featureNames) {
55         // TODO(dgn): assert that it's not being called too late and will be able to be applied.
56         for (String featureName : featureNames) mRegisteredState.put(featureName, false);
57     }
58 
applyForJUnit()59     protected void applyForJUnit() {
60         FeatureList.setTestFeatures(mRegisteredState);
61     }
62 
applyForInstrumentation()63     protected void applyForInstrumentation() {
64         FeatureList.setTestCanUseDefaultsForTesting();
65         mergeFeatureLists("enable-features", true);
66         mergeFeatureLists("disable-features", false);
67     }
68 
69     /**
70      * Feature processor intended to be used in unit tests. The collected feature states would be
71      * applied to {@link FeatureList}'s internal test-only feature map.
72      */
73     public abstract static class BaseJUnitProcessor extends Processor {
BaseJUnitProcessor(Class enabledFeatures, Class disabledFeatures)74         public BaseJUnitProcessor(Class enabledFeatures, Class disabledFeatures) {
75             super(enabledFeatures, disabledFeatures);
76         }
77 
78         @Override
applyFeatures()79         protected void applyFeatures() {
80             sInstance.applyForJUnit();
81         }
82 
83         @Override
after()84         protected void after() {
85             super.after();
86             sInstance = null;
87         }
88     }
89 
90     /**
91      * Feature processor intended to be used in instrumentation tests with native library. The
92      * collected feature states would be applied to {@link CommandLine}.
93      */
94     public abstract static class BaseInstrumentationProcessor extends Processor {
BaseInstrumentationProcessor(Class enableFeatures, Class disableFeatures)95         public BaseInstrumentationProcessor(Class enableFeatures, Class disableFeatures) {
96             super(enableFeatures, disableFeatures);
97         }
98 
99         @Override
applyFeatures()100         protected void applyFeatures() {
101             sInstance.applyForInstrumentation();
102         }
103     }
104 
105     /** Resets Features-related state that might persist in between tests. */
reset()106     private static void reset() {
107         FeatureList.setTestFeatures(null);
108         FeatureList.resetTestCanUseDefaultsForTesting();
109     }
110 
clearRegisteredState()111     private void clearRegisteredState() {
112         mRegisteredState.clear();
113     }
114 
115     /**
116      * Add this rule to tests to activate the {@link Features} annotations and choose flags
117      * to enable, or get rid of exceptions when the production code tries to check for enabled
118      * features.
119      */
120     private abstract static class Processor extends AnnotationRule {
Processor(Class enableFeatures, Class disableFeatures)121         public Processor(Class enableFeatures, Class disableFeatures) {
122             super(enableFeatures, disableFeatures);
123         }
124 
125         @Override
before()126         protected void before() {
127             assert sInstance != null
128                     : "Classes extending BaseProcessor need to create an instance.";
129             collectFeatures();
130             applyFeatures();
131         }
132 
133         @Override
after()134         protected void after() {
135             reset();
136 
137             // sInstance may already be null if there are nested usages.
138             if (sInstance == null) return;
139 
140             sInstance.clearRegisteredState();
141         }
142 
applyFeatures()143         protected abstract void applyFeatures();
144 
collectFeatures()145         protected abstract void collectFeatures();
146     }
147 
148     /**
149      * Updates the reference list of features held by the CommandLine by merging it with the feature
150      * state registered via this utility.
151      * @param switchName Name of the command line switch that is the reference feature state.
152      * @param enabled Whether the feature list being modified is the enabled or disabled one.
153      */
mergeFeatureLists(String switchName, boolean enabled)154     private void mergeFeatureLists(String switchName, boolean enabled) {
155         CommandLine commandLine = CommandLine.getInstance();
156         String switchValue = commandLine.getSwitchValue(switchName);
157         Set<String> existingFeatures = new HashSet<>();
158         if (switchValue != null) {
159             Collections.addAll(existingFeatures, switchValue.split(","));
160         }
161         for (String additionalFeature : mRegisteredState.keySet()) {
162             if (mRegisteredState.get(additionalFeature) != enabled) continue;
163             existingFeatures.add(additionalFeature);
164         }
165 
166         // Not really append, it puts the value in a map so we can override values that way too.
167         commandLine.appendSwitchWithValue(switchName, TextUtils.join(",", existingFeatures));
168     }
169 }
170