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