• 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 org.junit.Assert.assertFalse;
20 
21 import android.app.ActivityManager;
22 import android.app.Instrumentation;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.os.HandlerThread;
26 import android.os.Looper;
27 import android.os.ServiceManager;
28 import android.util.Log;
29 
30 import androidx.test.platform.app.InstrumentationRegistry;
31 
32 import com.android.internal.os.RuntimeInit;
33 import com.android.server.LocalServices;
34 
35 import org.junit.After;
36 import org.junit.Assert;
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.Description;
40 import org.junit.runner.RunWith;
41 import org.junit.runners.model.Statement;
42 
43 import java.io.PrintStream;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Modifier;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Objects;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.ScheduledExecutorService;
52 import java.util.concurrent.ScheduledFuture;
53 import java.util.concurrent.TimeUnit;
54 import java.util.concurrent.atomic.AtomicReference;
55 
56 public class RavenwoodRuleImpl {
57     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
58 
59     /**
60      * When enabled, attempt to dump all thread stacks just before we hit the
61      * overall Tradefed timeout, to aid in debugging deadlocks.
62      */
63     private static final boolean ENABLE_TIMEOUT_STACKS = false;
64     private static final int TIMEOUT_MILLIS = 9_000;
65 
66     private static final ScheduledExecutorService sTimeoutExecutor =
67             Executors.newScheduledThreadPool(1);
68 
69     private static ScheduledFuture<?> sPendingTimeout;
70 
71     /**
72      * When enabled, attempt to detect uncaught exceptions from background threads.
73      */
74     private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION = false;
75 
76     /**
77      * When set, an unhandled exception was discovered (typically on a background thread), and we
78      * capture it here to ensure it's reported as a test failure.
79      */
80     private static final AtomicReference<Throwable> sPendingUncaughtException =
81             new AtomicReference<>();
82 
83     private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
84             (thread, throwable) -> {
85                 // Remember the first exception we discover
86                 sPendingUncaughtException.compareAndSet(null, throwable);
87             };
88 
init(RavenwoodRule rule)89     public static void init(RavenwoodRule rule) {
90         if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
91             maybeThrowPendingUncaughtException(false);
92             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
93         }
94 
95         RuntimeInit.redirectLogStreams();
96 
97         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
98         android.os.Binder.init$ravenwood();
99 //        android.os.SystemProperties.init$ravenwood(
100 //                rule.mSystemProperties.getValues(),
101 //                rule.mSystemProperties.getKeyReadablePredicate(),
102 //                rule.mSystemProperties.getKeyWritablePredicate());
103         setSystemProperties(rule.mSystemProperties);
104 
105         ServiceManager.init$ravenwood();
106         LocalServices.removeAllServicesForTest();
107 
108         ActivityManager.init$ravenwood(rule.mCurrentUser);
109 
110         final HandlerThread main;
111         if (rule.mProvideMainThread) {
112             main = new HandlerThread(MAIN_THREAD_NAME);
113             main.start();
114             Looper.setMainLooperForTest(main.getLooper());
115         } else {
116             main = null;
117         }
118 
119         rule.mContext = new RavenwoodContext(rule.mPackageName, main);
120         rule.mInstrumentation = new Instrumentation();
121         rule.mInstrumentation.basicInit(rule.mContext);
122         InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
123 
124         RavenwoodSystemServer.init(rule);
125 
126         if (ENABLE_TIMEOUT_STACKS) {
127             sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
128                     TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
129         }
130 
131         // Touch some references early to ensure they're <clinit>'ed
132         Objects.requireNonNull(Build.TYPE);
133         Objects.requireNonNull(Build.VERSION.SDK);
134     }
135 
reset(RavenwoodRule rule)136     public static void reset(RavenwoodRule rule) {
137         if (ENABLE_TIMEOUT_STACKS) {
138             sPendingTimeout.cancel(false);
139         }
140 
141         RavenwoodSystemServer.reset(rule);
142 
143         InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
144         rule.mInstrumentation = null;
145         rule.mContext = null;
146 
147         if (rule.mProvideMainThread) {
148             Looper.getMainLooper().quit();
149             Looper.clearMainLooperForTest();
150         }
151 
152         ActivityManager.reset$ravenwood();
153 
154         LocalServices.removeAllServicesForTest();
155         ServiceManager.reset$ravenwood();
156 
157         setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);
158         android.os.Binder.reset$ravenwood();
159         android.os.Process.reset$ravenwood();
160 
161         if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
162             maybeThrowPendingUncaughtException(true);
163         }
164     }
165 
logTestRunner(String label, Description description)166     public static void logTestRunner(String label, Description description) {
167         // This message string carefully matches the exact format emitted by on-device tests, to
168         // aid developers in debugging raw text logs
169         Log.e("TestRunner", label + ": " + description.getMethodName()
170                 + "(" + description.getTestClass().getName() + ")");
171     }
172 
dumpStacks()173     private static void dumpStacks() {
174         final PrintStream out = System.err;
175         out.println("-----BEGIN ALL THREAD STACKS-----");
176         final Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
177         for (Map.Entry<Thread, StackTraceElement[]> stack : stacks.entrySet()) {
178             out.println();
179             Thread t = stack.getKey();
180             out.println(t.toString() + " ID=" + t.getId());
181             for (StackTraceElement e : stack.getValue()) {
182                 out.println("\tat " + e);
183             }
184         }
185         out.println("-----END ALL THREAD STACKS-----");
186     }
187 
188     /**
189      * If there's a pending uncaught exception, consume and throw it now. Typically used to
190      * report an exception on a background thread as a failure for the currently running test.
191      */
maybeThrowPendingUncaughtException(boolean duringReset)192     private static void maybeThrowPendingUncaughtException(boolean duringReset) {
193         final Throwable pending = sPendingUncaughtException.getAndSet(null);
194         if (pending != null) {
195             if (duringReset) {
196                 throw new IllegalStateException(
197                         "Found an uncaught exception during this test", pending);
198             } else {
199                 throw new IllegalStateException(
200                         "Found an uncaught exception before this test started", pending);
201             }
202         }
203     }
204 
validate(Statement base, Description description, boolean enableOptionalValidation)205     public static void validate(Statement base, Description description,
206             boolean enableOptionalValidation) {
207         validateTestRunner(base, description, enableOptionalValidation);
208         validateTestAnnotations(base, description, enableOptionalValidation);
209     }
210 
validateTestRunner(Statement base, Description description, boolean shouldFail)211     private static void validateTestRunner(Statement base, Description description,
212             boolean shouldFail) {
213         final var testClass = description.getTestClass();
214         final var runWith = testClass.getAnnotation(RunWith.class);
215         if (runWith == null) {
216             return;
217         }
218 
219         // Due to build dependencies, we can't directly refer to androidx classes here,
220         // so just check the class name instead.
221         if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
222             var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
223                     + " test runner androidx.test.runner.AndroidJUnit4."
224                     + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
225             if (shouldFail) {
226                 Assert.fail(message);
227             } else {
228                 System.err.println("Warning: " + message);
229             }
230         }
231     }
232 
validateTestAnnotations(Statement base, Description description, boolean enableOptionalValidation)233     private static void validateTestAnnotations(Statement base, Description description,
234             boolean enableOptionalValidation) {
235         final var testClass = description.getTestClass();
236 
237         final var message = new StringBuilder();
238 
239         boolean hasErrors = false;
240         for (Method m : collectMethods(testClass)) {
241             if (Modifier.isPublic(m.getModifiers()) && m.getName().startsWith("test")) {
242                 if (m.getAnnotation(Test.class) == null) {
243                     message.append("\nMethod " + m.getName() + "() doesn't have @Test");
244                     hasErrors = true;
245                 }
246             }
247             if ("setUp".equals(m.getName())) {
248                 if (m.getAnnotation(Before.class) == null) {
249                     message.append("\nMethod " + m.getName() + "() doesn't have @Before");
250                     hasErrors = true;
251                 }
252                 if (!Modifier.isPublic(m.getModifiers())) {
253                     message.append("\nMethod " + m.getName() + "() must be public");
254                     hasErrors = true;
255                 }
256             }
257             if ("tearDown".equals(m.getName())) {
258                 if (m.getAnnotation(After.class) == null) {
259                     message.append("\nMethod " + m.getName() + "() doesn't have @After");
260                     hasErrors = true;
261                 }
262                 if (!Modifier.isPublic(m.getModifiers())) {
263                     message.append("\nMethod " + m.getName() + "() must be public");
264                     hasErrors = true;
265                 }
266             }
267         }
268         assertFalse("Problem(s) detected in class " + testClass.getCanonicalName() + ":"
269                 + message, hasErrors);
270     }
271 
272     /**
273      * Collect all (public or private or any) methods in a class, including inherited methods.
274      */
collectMethods(Class<?> clazz)275     private static List<Method> collectMethods(Class<?> clazz) {
276         var ret = new ArrayList<Method>();
277         collectMethods(clazz, ret);
278         return ret;
279     }
280 
collectMethods(Class<?> clazz, List<Method> result)281     private static void collectMethods(Class<?> clazz, List<Method> result) {
282         // Class.getMethods() only return public methods, so we need to use getDeclaredMethods()
283         // instead, and recurse.
284         for (var m : clazz.getDeclaredMethods()) {
285             result.add(m);
286         }
287         if (clazz.getSuperclass() != null) {
288             collectMethods(clazz.getSuperclass(), result);
289         }
290     }
291 
292     /**
293      * Set the current configuration to the actual SystemProperties.
294      */
setSystemProperties(RavenwoodSystemProperties ravenwoodSystemProperties)295     public static void setSystemProperties(RavenwoodSystemProperties ravenwoodSystemProperties) {
296         var clone = new RavenwoodSystemProperties(ravenwoodSystemProperties, true);
297 
298         android.os.SystemProperties.init$ravenwood(
299                 clone.getValues(),
300                 clone.getKeyReadablePredicate(),
301                 clone.getKeyWritablePredicate());
302     }
303 }
304