• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 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 package org.chromium.base.test.util;
6 
7 import android.text.TextUtils;
8 
9 import org.junit.Assert;
10 import org.junit.Rule;
11 
12 import org.chromium.base.CommandLine;
13 import org.chromium.base.CommandLineInitUtil;
14 import org.chromium.base.test.BaseTestResult.PreTestHook;
15 
16 import java.lang.annotation.ElementType;
17 import java.lang.annotation.Inherited;
18 import java.lang.annotation.Retention;
19 import java.lang.annotation.RetentionPolicy;
20 import java.lang.annotation.Target;
21 import java.lang.reflect.AnnotatedElement;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.Method;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 
30 /**
31  * Provides annotations related to command-line flag handling.
32  *
33  * Uses of these annotations on a derived class will take precedence over uses on its base classes,
34  * so a derived class can add a command-line flag that a base class has removed (or vice versa).
35  * Similarly, uses of these annotations on a test method will take precedence over uses on the
36  * containing class.
37  * <p>
38  * These annonations may also be used on Junit4 Rule classes and on their base classes. Note,
39  * however that the annotation processor only looks at the declared type of the Rule, not its actual
40  * type, so in, for example:
41  *
42  * <pre>
43  *     &#64Rule
44  *     TestRule mRule = new ChromeActivityTestRule();
45  * </pre>
46  *
47  * will only look for CommandLineFlags annotations on TestRule, not for CommandLineFlags annotations
48  * on ChromeActivityTestRule.
49  * <p>
50  * In addition a rule may not remove flags added by an independently invoked rule, although it may
51  * remove flags added by its base classes.
52  * <p>
53  * Uses of these annotations on the test class or methods take precedence over uses on Rule classes.
54  * <p>
55  * Note that this class should never be instantiated.
56  */
57 public final class CommandLineFlags {
58     private static final String DISABLE_FEATURES = "disable-features";
59     private static final String ENABLE_FEATURES = "enable-features";
60 
61     /**
62      * Adds command-line flags to the {@link org.chromium.base.CommandLine} for this test.
63      */
64     @Inherited
65     @Retention(RetentionPolicy.RUNTIME)
66     @Target({ElementType.METHOD, ElementType.TYPE})
67     public @interface Add {
value()68         String[] value();
69     }
70 
71     /**
72      * Removes command-line flags from the {@link org.chromium.base.CommandLine} from this test.
73      *
74      * Note that this can only remove flags added via {@link Add} above.
75      */
76     @Inherited
77     @Retention(RetentionPolicy.RUNTIME)
78     @Target({ElementType.METHOD, ElementType.TYPE})
79     public @interface Remove {
value()80         String[] value();
81     }
82 
83     /**
84      * Sets up the CommandLine with the appropriate flags.
85      *
86      * This will add the difference of the sets of flags specified by {@link CommandLineFlags.Add}
87      * and {@link CommandLineFlags.Remove} to the {@link org.chromium.base.CommandLine}. Note that
88      * trying to remove a flag set externally, i.e. by the command-line flags file, will not work.
89      */
setUp(AnnotatedElement element)90     public static void setUp(AnnotatedElement element) {
91         CommandLine.reset();
92         CommandLineInitUtil.initCommandLine(getTestCmdLineFile());
93         Set<String> enableFeatures = new HashSet<String>();
94         Set<String> disableFeatures = new HashSet<String>();
95         Set<String> flags = getFlags(element);
96         for (String flag : flags) {
97             String[] parsedFlags = flag.split("=", 2);
98             if (parsedFlags.length == 1) {
99                 CommandLine.getInstance().appendSwitch(flag);
100             } else if (ENABLE_FEATURES.equals(parsedFlags[0])) {
101                 // We collect enable/disable features flags separately and aggregate them because
102                 // they may be specified multiple times, in which case the values will trample each
103                 // other.
104                 Collections.addAll(enableFeatures, parsedFlags[1].split(","));
105             } else if (DISABLE_FEATURES.equals(parsedFlags[0])) {
106                 Collections.addAll(disableFeatures, parsedFlags[1].split(","));
107             } else {
108                 CommandLine.getInstance().appendSwitchWithValue(parsedFlags[0], parsedFlags[1]);
109             }
110         }
111 
112         if (enableFeatures.size() > 0) {
113             CommandLine.getInstance().appendSwitchWithValue(
114                     ENABLE_FEATURES, TextUtils.join(",", enableFeatures));
115         }
116         if (disableFeatures.size() > 0) {
117             CommandLine.getInstance().appendSwitchWithValue(
118                     DISABLE_FEATURES, TextUtils.join(",", disableFeatures));
119         }
120     }
121 
getFlags(AnnotatedElement type)122     private static Set<String> getFlags(AnnotatedElement type) {
123         Set<String> rule_flags = new HashSet<>();
124         updateFlagsForElement(type, rule_flags);
125         return rule_flags;
126     }
127 
updateFlagsForElement(AnnotatedElement element, Set<String> flags)128     private static void updateFlagsForElement(AnnotatedElement element, Set<String> flags) {
129         if (element instanceof Class<?>) {
130             // Get flags from rules within the class.
131             for (Field field : ((Class<?>) element).getFields()) {
132                 if (field.isAnnotationPresent(Rule.class)) {
133                     // The order in which fields are returned is undefined, so, for consistency,
134                     // a rule must not remove a flag added by a different rule. Ensure this by
135                     // initially getting the flags into a new set.
136                     Set<String> rule_flags = getFlags(field.getType());
137                     flags.addAll(rule_flags);
138                 }
139             }
140             for (Method method : ((Class<?>) element).getMethods()) {
141                 if (method.isAnnotationPresent(Rule.class)) {
142                     // The order in which methods are returned is undefined, so, for consistency,
143                     // a rule must not remove a flag added by a different rule. Ensure this by
144                     // initially getting the flags into a new set.
145                     Set<String> rule_flags = getFlags(method.getReturnType());
146                     flags.addAll(rule_flags);
147                 }
148             }
149         }
150 
151         // Add the flags from the parent. Override any flags defined by the rules.
152         AnnotatedElement parent = (element instanceof Method)
153                 ? ((Method) element).getDeclaringClass()
154                 : ((Class<?>) element).getSuperclass();
155         if (parent != null) updateFlagsForElement(parent, flags);
156 
157         // Flags on the element itself override all other flag sources.
158         if (element.isAnnotationPresent(CommandLineFlags.Add.class)) {
159             flags.addAll(
160                     Arrays.asList(element.getAnnotation(CommandLineFlags.Add.class).value()));
161         }
162 
163         if (element.isAnnotationPresent(CommandLineFlags.Remove.class)) {
164             List<String> flagsToRemove =
165                     Arrays.asList(element.getAnnotation(CommandLineFlags.Remove.class).value());
166             for (String flagToRemove : flagsToRemove) {
167                 // If your test fails here, you have tried to remove a command-line flag via
168                 // CommandLineFlags.Remove that was loaded into CommandLine via something other
169                 // than CommandLineFlags.Add (probably the command-line flag file).
170                 Assert.assertFalse("Unable to remove command-line flag \"" + flagToRemove + "\".",
171                         CommandLine.getInstance().hasSwitch(flagToRemove));
172             }
173             flags.removeAll(flagsToRemove);
174         }
175     }
176 
CommandLineFlags()177     private CommandLineFlags() {
178         throw new AssertionError("CommandLineFlags is a non-instantiable class");
179     }
180 
getRegistrationHook()181     public static PreTestHook getRegistrationHook() {
182         return (targetContext, testMethod) -> CommandLineFlags.setUp(testMethod);
183     }
184 
getTestCmdLineFile()185     public static String getTestCmdLineFile() {
186         return "test-cmdline-file";
187     }
188 }
189