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 android.os.Process.FIRST_APPLICATION_UID;
20 import static android.os.Process.SYSTEM_UID;
21 import static android.os.UserHandle.USER_SYSTEM;
22 
23 import static org.junit.Assert.fail;
24 
25 import android.app.Instrumentation;
26 import android.content.Context;
27 import android.platform.test.annotations.DisabledOnNonRavenwood;
28 import android.platform.test.annotations.DisabledOnRavenwood;
29 import android.platform.test.annotations.EnabledOnRavenwood;
30 import android.platform.test.annotations.IgnoreUnderRavenwood;
31 
32 import com.android.ravenwood.common.RavenwoodCommonUtils;
33 
34 import org.junit.Assume;
35 import org.junit.rules.TestRule;
36 import org.junit.runner.Description;
37 import org.junit.runners.model.Statement;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.concurrent.atomic.AtomicInteger;
43 import java.util.regex.Pattern;
44 
45 /**
46  * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when
47  * tests are run on non-Ravenwood test environments.
48  *
49  * This rule initializes and resets the Ravenwood environment between each test method to offer a
50  * hermetic testing environment.
51  *
52  * By default, all tests are executed on Ravenwood, but annotations such as
53  * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
54  * and class level to "ignore" tests that may not be ready. When needed, a
55  * {@link RavenwoodClassRule} can be used in addition to a {@link RavenwoodRule} to ignore tests
56  * before a test class is fully initialized.
57  */
58 public class RavenwoodRule implements TestRule {
59     static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
60 
61     /**
62      * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
63      * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
64      *
65      * This is typically helpful for internal maintainers discovering tests that had previously
66      * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
67      */
68     static final boolean ENABLE_PROBE_IGNORED = "1".equals(
69             System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
70 
71     /**
72      * When using ENABLE_PROBE_IGNORED, you may still want to skip certain tests,
73      * for example because the test would crash the JVM.
74      *
75      * This regex defines the tests that should still be disabled even if ENABLE_PROBE_IGNORED
76      * is set.
77      *
78      * Before running each test class and method, we check if this pattern can be found in
79      * the full test name (either [class full name], or [class full name] + "#" + [method name]),
80      * and if so, we skip it.
81      *
82      * For example, if you want to skip an entire test class, use:
83      * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest$'
84      *
85      * For example, if you want to skip an entire test class, use:
86      * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest#testSimple$'
87      *
88      * To ignore multiple classes, use (...|...), for example:
89      * RAVENWOOD_REALLY_DISABLE='\.(ClassA|ClassB)$'
90      *
91      * Because we use a regex-find, setting "." would disable all tests.
92      */
93     private static final Pattern REALLY_DISABLE_PATTERN = Pattern.compile(
94             Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLE"), ""));
95 
96     private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
97             !REALLY_DISABLE_PATTERN.pattern().isEmpty();
98 
99     /**
100      * If true, enable optional validation on running tests.
101      */
102     private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals(
103             System.getenv("RAVENWOOD_OPTIONAL_VALIDATION"));
104 
105     static {
106         if (ENABLE_PROBE_IGNORED) {
107             System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
108             if (ENABLE_REALLY_DISABLE_PATTERN) {
109                 System.out.println("$RAVENWOOD_REALLY_DISABLE=" + REALLY_DISABLE_PATTERN.pattern());
110             }
111         }
112     }
113 
114     private static final int NOBODY_UID = 9999;
115 
116     private static final AtomicInteger sNextPid = new AtomicInteger(100);
117 
118     int mCurrentUser = USER_SYSTEM;
119 
120     /**
121      * Unless the test author requests differently, run as "nobody", and give each collection of
122      * tests its own unique PID.
123      */
124     int mUid = NOBODY_UID;
125     int mPid = sNextPid.getAndIncrement();
126 
127     String mPackageName;
128 
129     boolean mProvideMainThread = false;
130 
131     final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
132 
133     final List<Class<?>> mServicesRequired = new ArrayList<>();
134 
135     volatile Context mContext;
136     volatile Instrumentation mInstrumentation;
137 
RavenwoodRule()138     public RavenwoodRule() {
139     }
140 
141     public static class Builder {
142         private RavenwoodRule mRule = new RavenwoodRule();
143 
Builder()144         public Builder() {
145         }
146 
147         /**
148          * Configure the identity of this process to be the system UID for the duration of the
149          * test. Has no effect on non-Ravenwood environments.
150          */
setProcessSystem()151         public Builder setProcessSystem() {
152             mRule.mUid = SYSTEM_UID;
153             return this;
154         }
155 
156         /**
157          * Configure the identity of this process to be an app UID for the duration of the
158          * test. Has no effect on non-Ravenwood environments.
159          */
setProcessApp()160         public Builder setProcessApp() {
161             mRule.mUid = FIRST_APPLICATION_UID;
162             return this;
163         }
164 
165         /**
166          * Configure the identity of this process to be the given package name for the duration
167          * of the test. Has no effect on non-Ravenwood environments.
168          */
setPackageName( String packageName)169         public Builder setPackageName(/* @NonNull */ String packageName) {
170             mRule.mPackageName = Objects.requireNonNull(packageName);
171             return this;
172         }
173 
174         /**
175          * Configure a "main" thread to be available for the duration of the test, as defined
176          * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
177          */
setProvideMainThread(boolean provideMainThread)178         public Builder setProvideMainThread(boolean provideMainThread) {
179             mRule.mProvideMainThread = provideMainThread;
180             return this;
181         }
182 
183         /**
184          * Configure the given system property as immutable for the duration of the test.
185          * Read access to the key is allowed, and write access will fail. When {@code value} is
186          * {@code null}, the value is left as undefined.
187          *
188          * All properties in the {@code debug.*} namespace are automatically mutable, with no
189          * developer action required.
190          *
191          * Has no effect on non-Ravenwood environments.
192          */
setSystemPropertyImmutable( String key, Object value)193         public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
194                 /* @Nullable */ Object value) {
195             mRule.mSystemProperties.setValue(key, value);
196             mRule.mSystemProperties.setAccessReadOnly(key);
197             return this;
198         }
199 
200         /**
201          * Configure the given system property as mutable for the duration of the test.
202          * Both read and write access to the key is allowed, and its value will be reset between
203          * each test. When {@code value} is {@code null}, the value is left as undefined.
204          *
205          * All properties in the {@code debug.*} namespace are automatically mutable, with no
206          * developer action required.
207          *
208          * Has no effect on non-Ravenwood environments.
209          */
setSystemPropertyMutable( String key, Object value)210         public Builder setSystemPropertyMutable(/* @NonNull */ String key,
211                 /* @Nullable */ Object value) {
212             mRule.mSystemProperties.setValue(key, value);
213             mRule.mSystemProperties.setAccessReadWrite(key);
214             return this;
215         }
216 
217         /**
218          * Configure the set of system services that are required for this test to operate.
219          *
220          * For example, passing {@code android.hardware.SerialManager.class} as an argument will
221          * ensure that the underlying service is created, initialized, and ready to use for the
222          * duration of the test. The {@code SerialManager} instance can be obtained via
223          * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
224          * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
225          */
setServicesRequired(Class<?>.... services)226         public Builder setServicesRequired(Class<?>... services) {
227             mRule.mServicesRequired.clear();
228             for (Class<?> service : services) {
229                 mRule.mServicesRequired.add(service);
230             }
231             return this;
232         }
233 
build()234         public RavenwoodRule build() {
235             return mRule;
236         }
237     }
238 
239     /**
240      * @deprecated replaced by {@link #isOnRavenwood()}
241      */
242     @Deprecated
isUnderRavenwood()243     public static boolean isUnderRavenwood() {
244         return IS_ON_RAVENWOOD;
245     }
246 
247     /**
248      * Return if the current process is running on a Ravenwood test environment.
249      */
isOnRavenwood()250     public static boolean isOnRavenwood() {
251         return IS_ON_RAVENWOOD;
252     }
253 
254     /**
255      * Return a {@code Context} available for usage during the currently running test case.
256      *
257      * Each test should obtain needed information or references via this method;
258      * references must not be stored beyond the scope of a test case.
259      */
getContext()260     public Context getContext() {
261         return Objects.requireNonNull(mContext,
262                 "Context is only available during @Test execution");
263     }
264 
265     /**
266      * Return a {@code Instrumentation} available for usage during the currently running test case.
267      *
268      * Each test should obtain needed information or references via this method;
269      * references must not be stored beyond the scope of a test case.
270      */
getInstrumentation()271     public Instrumentation getInstrumentation() {
272         return Objects.requireNonNull(mInstrumentation,
273                 "Instrumentation is only available during @Test execution");
274     }
275 
shouldEnableOnDevice(Description description)276     static boolean shouldEnableOnDevice(Description description) {
277         if (description.isTest()) {
278             if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) {
279                 return false;
280             }
281         }
282         final var clazz = description.getTestClass();
283         if (clazz != null) {
284             if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) {
285                 return false;
286             }
287         }
288         return true;
289     }
290 
291     /**
292      * Determine if the given {@link Description} should be enabled when running on the
293      * Ravenwood test environment.
294      *
295      * A more specific method-level annotation always takes precedence over any class-level
296      * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
297      * an {@link DisabledOnRavenwood} annotation.
298      */
shouldEnableOnRavenwood(Description description)299     static boolean shouldEnableOnRavenwood(Description description) {
300         // First, consult any method-level annotations
301         if (description.isTest()) {
302             // Stopgap for http://g/ravenwood/EPAD-N5ntxM
303             if (description.getMethodName().endsWith("$noRavenwood")) {
304                 return false;
305             }
306             if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
307                 return true;
308             }
309             if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
310                 return false;
311             }
312             if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
313                 return false;
314             }
315         }
316 
317         // Otherwise, consult any class-level annotations
318         final var clazz = description.getTestClass();
319         if (clazz != null) {
320             if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
321                 return true;
322             }
323             if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
324                 return false;
325             }
326             if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
327                 return false;
328             }
329         }
330 
331         // When no annotations have been requested, assume test should be included
332         return true;
333     }
334 
shouldStillIgnoreInProbeIgnoreMode(Description description)335     static boolean shouldStillIgnoreInProbeIgnoreMode(Description description) {
336         if (!ENABLE_REALLY_DISABLE_PATTERN) {
337             return false;
338         }
339 
340         final var fullname = description.getTestClass().getName()
341                 + (description.isTest() ? "#" + description.getMethodName() : "");
342 
343         if (REALLY_DISABLE_PATTERN.matcher(fullname).find()) {
344             System.out.println("Still ignoring " + fullname);
345             return true;
346         }
347         return false;
348     }
349 
350     @Override
apply(Statement base, Description description)351     public Statement apply(Statement base, Description description) {
352         // No special treatment when running outside Ravenwood; run tests as-is
353         if (!IS_ON_RAVENWOOD) {
354             Assume.assumeTrue(shouldEnableOnDevice(description));
355             return base;
356         }
357 
358         if (ENABLE_PROBE_IGNORED) {
359             return applyProbeIgnored(base, description);
360         } else {
361             return applyDefault(base, description);
362         }
363     }
364 
commonPrologue(Statement base, Description description)365     private void commonPrologue(Statement base, Description description) {
366         RavenwoodRuleImpl.logTestRunner("started", description);
367         RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION);
368         RavenwoodRuleImpl.init(RavenwoodRule.this);
369     }
370 
371     /**
372      * Run the given {@link Statement} with no special treatment.
373      */
applyDefault(Statement base, Description description)374     private Statement applyDefault(Statement base, Description description) {
375         return new Statement() {
376             @Override
377             public void evaluate() throws Throwable {
378                 Assume.assumeTrue(shouldEnableOnRavenwood(description));
379 
380                 commonPrologue(base, description);
381                 try {
382                     base.evaluate();
383                     RavenwoodRuleImpl.logTestRunner("finished", description);
384                 } catch (Throwable t) {
385                     RavenwoodRuleImpl.logTestRunner("failed", description);
386                     throw t;
387                 } finally {
388                     RavenwoodRuleImpl.reset(RavenwoodRule.this);
389                 }
390             }
391         };
392     }
393 
394     /**
395      * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
396      * run on Ravenwood to detect cases where a test is able to pass despite being marked as
397      * {@code IgnoreUnderRavenwood}.
398      */
399     private Statement applyProbeIgnored(Statement base, Description description) {
400         return new Statement() {
401             @Override
402             public void evaluate() throws Throwable {
403                 Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
404 
405                 commonPrologue(base, description);
406                 try {
407                     base.evaluate();
408                 } catch (Throwable t) {
409                     // If the test isn't included, eat the exception and report the
410                     // assumption failure that test authors expect; otherwise throw
411                     Assume.assumeTrue(shouldEnableOnRavenwood(description));
412                     throw t;
413                 } finally {
414                     RavenwoodRuleImpl.logTestRunner("finished", description);
415                     RavenwoodRuleImpl.reset(RavenwoodRule.this);
416                 }
417 
418                 if (!shouldEnableOnRavenwood(description)) {
419                     fail("Test wasn't included under Ravenwood, but it actually "
420                             + "passed under Ravenwood; consider updating annotations");
421                 }
422             }
423         };
424     }
425 
426     public static class _$RavenwoodPrivate {
427         public static boolean isOptionalValidationEnabled() {
428             return ENABLE_OPTIONAL_VALIDATION;
429         }
430     }
431 
432     /**
433      * Returns the "real" result from {@link System#currentTimeMillis()}.
434      *
435      * Currently, it's the same thing as calling {@link System#currentTimeMillis()},
436      * but this one is guaranteeed to return the real value, even when Ravenwood supports
437      * injecting a time to{@link System#currentTimeMillis()}.
438      */
439     public long realCurrentTimeMillis() {
440         return System.currentTimeMillis();
441     }
442 }
443