xref: /aosp_15_r20/frameworks/base/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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          * @deprecated no longer used. We always use an app UID.
104          */
105         @Deprecated
setProcessSystem()106         public Builder setProcessSystem() {
107             return this;
108         }
109 
110         /**
111          * @deprecated no longer used. We always use an app UID.
112          */
113         @Deprecated
setProcessApp()114         public Builder setProcessApp() {
115             return this;
116         }
117 
118         /**
119          * @deprecated no longer used.
120          */
121         @Deprecated
setPackageName(@onNull String packageName)122         public Builder setPackageName(@NonNull String packageName) {
123             return this;
124         }
125 
126         /**
127          * @deprecated no longer used. Main thread is always available.
128          */
129         @Deprecated
setProvideMainThread(boolean provideMainThread)130         public Builder setProvideMainThread(boolean provideMainThread) {
131             return this;
132         }
133 
134         /**
135          * Configure the given system property as immutable for the duration of the test.
136          * Read access to the key is allowed, and write access will fail. When {@code value} is
137          * {@code null}, the value is left as undefined.
138          *
139          * All properties in the {@code debug.*} namespace are automatically mutable, with no
140          * developer action required.
141          *
142          * Has no effect on non-Ravenwood environments.
143          */
setSystemPropertyImmutable(@onNull String key, @Nullable Object value)144         public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
145             mRule.mProperties.setValue(key, value);
146             mRule.mProperties.setAccessReadOnly(key);
147             return this;
148         }
149 
150         /**
151          * Configure the given system property as mutable for the duration of the test.
152          * Both read and write access to the key is allowed, and its value will be reset between
153          * each test. When {@code value} is {@code null}, the value is left as undefined.
154          *
155          * All properties in the {@code debug.*} namespace are automatically mutable, with no
156          * developer action required.
157          *
158          * Has no effect on non-Ravenwood environments.
159          */
setSystemPropertyMutable(@onNull String key, @Nullable Object value)160         public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
161             mRule.mProperties.setValue(key, value);
162             mRule.mProperties.setAccessReadWrite(key);
163             return this;
164         }
165 
166         /**
167          * @deprecated no longer used. All supported services are available.
168          */
169         @Deprecated
setServicesRequired(@onNull Class<?>.... services)170         public Builder setServicesRequired(@NonNull Class<?>... services) {
171             return this;
172         }
173 
build()174         public RavenwoodRule build() {
175             return mRule;
176         }
177     }
178 
179     /**
180      * @deprecated replaced by {@link #isOnRavenwood()}
181      */
182     @Deprecated
isUnderRavenwood()183     public static boolean isUnderRavenwood() {
184         return IS_ON_RAVENWOOD;
185     }
186 
187     /**
188      * Return if the current process is running on a Ravenwood test environment.
189      */
isOnRavenwood()190     public static boolean isOnRavenwood() {
191         return IS_ON_RAVENWOOD;
192     }
193 
ensureOnRavenwood(String featureName)194     private static void ensureOnRavenwood(String featureName) {
195         if (!IS_ON_RAVENWOOD) {
196             throw new RuntimeException(featureName + " is only supported on Ravenwood.");
197         }
198     }
199 
200     /**
201      * @deprecated Use
202      * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()}
203      * instead.
204      */
205     @Deprecated
getContext()206     public Context getContext() {
207         return InstrumentationRegistry.getInstrumentation().getContext();
208     }
209 
210     /**
211      * @deprecated Use
212      * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()}
213      * instead.
214      */
215     @Deprecated
getInstrumentation()216     public Instrumentation getInstrumentation() {
217         return InstrumentationRegistry.getInstrumentation();
218     }
219 
220     @Override
apply(Statement base, Description description)221     public Statement apply(Statement base, Description description) {
222         if (!IS_ON_RAVENWOOD) {
223             return base;
224         }
225         return new Statement() {
226             @Override
227             public void evaluate() throws Throwable {
228                 RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this);
229                 try {
230                     base.evaluate();
231                 } finally {
232                     RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this);
233                 }
234             }
235         };
236     }
237 
238     /**
239      * Returns the "real" result from {@link System#currentTimeMillis()}.
240      *
241      * Currently, it's the same thing as calling {@link System#currentTimeMillis()},
242      * but this one is guaranteeed to return the real value, even when Ravenwood supports
243      * injecting a time to{@link System#currentTimeMillis()}.
244      */
245     public long realCurrentTimeMillis() {
246         return System.currentTimeMillis();
247     }
248 
249     /**
250      * Equivalent to setting the ANDROID_LOG_TAGS environmental variable.
251      *
252      * See https://developer.android.com/tools/logcat#filteringOutput for the string format.
253      *
254      * NOTE: this works only on Ravenwood.
255      */
256     public static void setAndroidLogTags(@Nullable String androidLogTags) {
257         ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()");
258         try {
259             Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
260             var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class);
261             setter.invoke(null, androidLogTags);
262         } catch (ReflectiveOperationException e) {
263             throw new RuntimeException(e);
264         }
265     }
266 
267     /**
268      * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level.
269      *
270      * NOTE: this works only on Ravenwood.
271      */
272     public static void setLogLevel(@Nullable String tag, int level) {
273         ensureOnRavenwood("RavenwoodRule.setLogLevel()");
274         try {
275             Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
276             var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class);
277             setter.invoke(null, tag, level);
278         } catch (ReflectiveOperationException e) {
279             throw new RuntimeException(e);
280         }
281     }
282 
283     // Below are internal to ravenwood. Don't use them from normal tests...
284 
285     public static class RavenwoodPrivate {
286         private RavenwoodPrivate() {
287         }
288 
289         private volatile Boolean mRunDisabledTestsOverride = null;
290 
291         private volatile Pattern mReallyDisabledPattern = null;
292 
293         public boolean isRunningDisabledTests() {
294             if (mRunDisabledTestsOverride != null) {
295                 return mRunDisabledTestsOverride;
296             }
297             return RUN_DISABLED_TESTS;
298         }
299 
300         public Pattern getReallyDisabledPattern() {
301             if (mReallyDisabledPattern != null) {
302                 return mReallyDisabledPattern;
303             }
304             return REALLY_DISABLED_PATTERN;
305         }
306 
307         public void overrideRunDisabledTest(boolean runDisabledTests,
308                 @Nullable String reallyDisabledPattern) {
309             mRunDisabledTestsOverride = runDisabledTests;
310             mReallyDisabledPattern =
311                     reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern);
312         }
313 
314         public void resetRunDisabledTest() {
315             mRunDisabledTestsOverride = null;
316             mReallyDisabledPattern = null;
317         }
318     }
319 
320     private static final RavenwoodPrivate sRavenwoodPrivate = new  RavenwoodPrivate();
321 
322     public static RavenwoodPrivate private$ravenwood() {
323         return sRavenwoodPrivate;
324     }
325 }
326