• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.platform.test.ravenwood;
18 
19 import static com.android.ravenwood.common.RavenwoodCommonUtils.log;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.Instrumentation;
24 import android.content.Context;
25 import android.platform.test.annotations.DisabledOnRavenwood;
26 
27 import androidx.test.platform.app.InstrumentationRegistry;
28 
29 import com.android.ravenwood.common.RavenwoodCommonUtils;
30 
31 import org.junit.rules.TestRule;
32 import org.junit.runner.Description;
33 import org.junit.runners.model.Statement;
34 
35 import java.util.Objects;
36 import java.util.regex.Pattern;
37 
38 /**
39  * Reach out to g/ravenwood if you need any features in it.
40  */
41 public final class RavenwoodRule implements TestRule {
42     private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
43 
44     static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
45 
46     /**
47      * When this flag is enabled, all tests will be unconditionally run on Ravenwood to detect
48      * cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}.
49      *
50      * This is typically helpful for internal maintainers discovering tests that had previously
51      * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
52      */
53     private static final boolean RUN_DISABLED_TESTS = "1".equals(
54             System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
55 
56     /**
57      * When using ENABLE_PROBE_IGNORED, you may still want to skip certain tests,
58      * for example because the test would crash the JVM.
59      *
60      * This regex defines the tests that should still be disabled even if ENABLE_PROBE_IGNORED
61      * is set.
62      *
63      * Before running each test class and method, we check if this pattern can be found in
64      * the full test name (either [class full name], or [class full name] + "#" + [method name]),
65      * and if so, we skip it.
66      *
67      * For example, if you want to skip an entire test class, use:
68      * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest$'
69      *
70      * For example, if you want to skip an entire test class, use:
71      * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest#testSimple$'
72      *
73      * To ignore multiple classes, use (...|...), for example:
74      * RAVENWOOD_REALLY_DISABLE='\.(ClassA|ClassB)$'
75      *
76      * Because we use a regex-find, setting "." would disable all tests.
77      */
78     private static final Pattern REALLY_DISABLED_PATTERN = Pattern.compile(
79             Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLED"), ""));
80 
81     private static final boolean HAS_REALLY_DISABLE_PATTERN =
82             !REALLY_DISABLED_PATTERN.pattern().isEmpty();
83 
84     static {
85         if (RUN_DISABLED_TESTS) {
log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests")86             log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
87             if (HAS_REALLY_DISABLE_PATTERN) {
log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern())88                 log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern());
89             }
90         }
91     }
92 
93     final RavenwoodTestProperties mProperties = new RavenwoodTestProperties();
94 
95     public static class Builder {
96 
97         private final RavenwoodRule mRule = new RavenwoodRule();
98 
Builder()99         public Builder() {
100         }
101 
102         /**
103          * Configure the given system property as immutable for the duration of the test.
104          * Read access to the key is allowed, and write access will fail. When {@code value} is
105          * {@code null}, the value is left as undefined.
106          *
107          * All properties in the {@code debug.*} namespace are automatically mutable, with no
108          * developer action required.
109          *
110          * Has no effect on non-Ravenwood environments.
111          */
setSystemPropertyImmutable(@onNull String key, @Nullable Object value)112         public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
113             mRule.mProperties.setValue(key, value);
114             mRule.mProperties.setAccessReadOnly(key);
115             return this;
116         }
117 
118         /**
119          * Configure the given system property as mutable for the duration of the test.
120          * Both read and write access to the key is allowed, and its value will be reset between
121          * each test. When {@code value} is {@code null}, the value is left as undefined.
122          *
123          * All properties in the {@code debug.*} namespace are automatically mutable, with no
124          * developer action required.
125          *
126          * Has no effect on non-Ravenwood environments.
127          */
setSystemPropertyMutable(@onNull String key, @Nullable Object value)128         public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
129             mRule.mProperties.setValue(key, value);
130             mRule.mProperties.setAccessReadWrite(key);
131             return this;
132         }
133 
build()134         public RavenwoodRule build() {
135             return mRule;
136         }
137     }
138 
139     /**
140      * Return if the current process is running on a Ravenwood test environment.
141      */
isOnRavenwood()142     public static boolean isOnRavenwood() {
143         return IS_ON_RAVENWOOD;
144     }
145 
ensureOnRavenwood(String featureName)146     private static void ensureOnRavenwood(String featureName) {
147         if (!IS_ON_RAVENWOOD) {
148             throw new RuntimeException(featureName + " is only supported on Ravenwood.");
149         }
150     }
151 
152     @Override
apply(Statement base, Description description)153     public Statement apply(Statement base, Description description) {
154         if (!IS_ON_RAVENWOOD) {
155             return base;
156         }
157         return new Statement() {
158             @Override
159             public void evaluate() throws Throwable {
160                 RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this);
161                 try {
162                     base.evaluate();
163                 } finally {
164                     RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this);
165                 }
166             }
167         };
168     }
169 
170     /**
171      * Returns the "real" result from {@link System#currentTimeMillis()}.
172      *
173      * Currently, it's the same thing as calling {@link System#currentTimeMillis()},
174      * but this one is guaranteeed to return the real value, even when Ravenwood supports
175      * injecting a time to{@link System#currentTimeMillis()}.
176      */
177     public long realCurrentTimeMillis() {
178         return System.currentTimeMillis();
179     }
180 
181     /**
182      * Equivalent to setting the ANDROID_LOG_TAGS environmental variable.
183      *
184      * See https://developer.android.com/tools/logcat#filteringOutput for the string format.
185      *
186      * NOTE: this works only on Ravenwood.
187      */
188     public static void setAndroidLogTags(@Nullable String androidLogTags) {
189         ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()");
190         try {
191             Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
192             var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class);
193             setter.invoke(null, androidLogTags);
194         } catch (ReflectiveOperationException e) {
195             throw new RuntimeException(e);
196         }
197     }
198 
199     /**
200      * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level.
201      *
202      * NOTE: this works only on Ravenwood.
203      */
204     public static void setLogLevel(@Nullable String tag, int level) {
205         ensureOnRavenwood("RavenwoodRule.setLogLevel()");
206         try {
207             Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
208             var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class);
209             setter.invoke(null, tag, level);
210         } catch (ReflectiveOperationException e) {
211             throw new RuntimeException(e);
212         }
213     }
214 
215     // Below are internal to ravenwood. Don't use them from normal tests...
216 
217     public static class RavenwoodPrivate {
218         private RavenwoodPrivate() {
219         }
220 
221         private volatile Boolean mRunDisabledTestsOverride = null;
222 
223         private volatile Pattern mReallyDisabledPattern = null;
224 
225         public boolean isRunningDisabledTests() {
226             if (mRunDisabledTestsOverride != null) {
227                 return mRunDisabledTestsOverride;
228             }
229             return RUN_DISABLED_TESTS;
230         }
231 
232         public Pattern getReallyDisabledPattern() {
233             if (mReallyDisabledPattern != null) {
234                 return mReallyDisabledPattern;
235             }
236             return REALLY_DISABLED_PATTERN;
237         }
238 
239         public void overrideRunDisabledTest(boolean runDisabledTests,
240                 @Nullable String reallyDisabledPattern) {
241             mRunDisabledTestsOverride = runDisabledTests;
242             mReallyDisabledPattern =
243                     reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern);
244         }
245 
246         public void resetRunDisabledTest() {
247             mRunDisabledTestsOverride = null;
248             mReallyDisabledPattern = null;
249         }
250     }
251 
252     private static final RavenwoodPrivate sRavenwoodPrivate = new  RavenwoodPrivate();
253 
254     public static RavenwoodPrivate private$ravenwood() {
255         return sRavenwoodPrivate;
256     }
257 }
258