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.UserHandle.SYSTEM; 21 import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME; 22 23 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK; 24 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; 25 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; 26 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP; 27 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; 28 import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath; 29 import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt; 30 import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault; 31 32 import static org.junit.Assert.assertNotNull; 33 import static org.junit.Assert.assertThrows; 34 import static org.junit.Assert.assertTrue; 35 import static org.mockito.ArgumentMatchers.any; 36 import static org.mockito.Mockito.doAnswer; 37 import static org.mockito.Mockito.mock; 38 39 import android.annotation.Nullable; 40 import android.app.ActivityManager; 41 import android.app.AppCompatCallbacks; 42 import android.app.Instrumentation; 43 import android.app.ResourcesManager; 44 import android.app.UiAutomation; 45 import android.content.Context; 46 import android.content.pm.ApplicationInfo; 47 import android.content.res.Resources; 48 import android.graphics.Typeface; 49 import android.icu.util.ULocale; 50 import android.os.Binder; 51 import android.os.Build; 52 import android.os.Build.VERSION_CODES; 53 import android.os.Bundle; 54 import android.os.HandlerThread; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.os.Process_ravenwood; 58 import android.os.ServiceManager; 59 import android.os.ServiceManager.ServiceNotFoundException; 60 import android.os.SystemProperties; 61 import android.provider.DeviceConfig_host; 62 import android.system.ErrnoException; 63 import android.system.Os; 64 import android.util.Log; 65 import android.util.Log_ravenwood; 66 import android.view.DisplayAdjustments; 67 68 import androidx.test.platform.app.InstrumentationRegistry; 69 70 import com.android.hoststubgen.hosthelper.HostTestUtils; 71 import com.android.internal.annotations.GuardedBy; 72 import com.android.internal.os.RuntimeInit; 73 import com.android.ravenwood.RavenwoodRuntimeNative; 74 import com.android.ravenwood.RavenwoodRuntimeState; 75 import com.android.ravenwood.common.RavenwoodCommonUtils; 76 import com.android.ravenwood.common.SneakyThrow; 77 import com.android.server.LocalServices; 78 import com.android.server.compat.PlatformCompat; 79 80 import org.junit.AssumptionViolatedException; 81 import org.junit.internal.management.ManagementFactory; 82 import org.junit.runner.Description; 83 84 import java.io.File; 85 import java.io.IOException; 86 import java.io.PrintStream; 87 import java.util.Collections; 88 import java.util.Comparator; 89 import java.util.HashMap; 90 import java.util.Locale; 91 import java.util.Map; 92 import java.util.Objects; 93 import java.util.Random; 94 import java.util.Set; 95 import java.util.concurrent.Executors; 96 import java.util.concurrent.ScheduledExecutorService; 97 import java.util.concurrent.ScheduledFuture; 98 import java.util.concurrent.TimeUnit; 99 import java.util.concurrent.atomic.AtomicReference; 100 import java.util.function.Supplier; 101 import java.util.stream.Collectors; 102 103 /** 104 * Responsible for initializing and the environment. 105 */ 106 public class RavenwoodRuntimeEnvironmentController { 107 private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG; 108 RavenwoodRuntimeEnvironmentController()109 private RavenwoodRuntimeEnvironmentController() { 110 } 111 112 private static final PrintStream sStdOut = System.out; 113 @SuppressWarnings("UnusedVariable") 114 private static final PrintStream sStdErr = System.err; 115 116 private static final String MAIN_THREAD_NAME = "Ravenwood:Main"; 117 private static final String TESTS_THREAD_NAME = "Ravenwood:Test"; 118 119 private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer"; 120 private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime"; 121 122 private static final String ANDROID_LOG_TAGS = "ANDROID_LOG_TAGS"; 123 private static final String RAVENWOOD_ANDROID_LOG_TAGS = "RAVENWOOD_" + ANDROID_LOG_TAGS; 124 125 static volatile Thread sTestThread; 126 static volatile Thread sMainThread; 127 128 /** 129 * When enabled, attempt to dump all thread stacks just before we hit the 130 * overall Tradefed timeout, to aid in debugging deadlocks. 131 * 132 * Note, this timeout will _not_ stop the test, as there isn't really a clean way to do it. 133 * It'll merely print stacktraces. 134 */ 135 private static final boolean ENABLE_TIMEOUT_STACKS = 136 !"0".equals(System.getenv("RAVENWOOD_ENABLE_TIMEOUT_STACKS")); 137 138 private static final boolean TOLERATE_LOOPER_ASSERTS = 139 !"0".equals(System.getenv("RAVENWOOD_TOLERATE_LOOPER_ASSERTS")); 140 141 static final int DEFAULT_TIMEOUT_SECONDS = 10; 142 private static final int TIMEOUT_MILLIS = getTimeoutSeconds() * 1000; 143 getTimeoutSeconds()144 static int getTimeoutSeconds() { 145 var e = System.getenv("RAVENWOOD_TIMEOUT_SECONDS"); 146 if (e == null || e.isEmpty()) { 147 return DEFAULT_TIMEOUT_SECONDS; 148 } 149 return Integer.parseInt(e); 150 } 151 152 153 private static final ScheduledExecutorService sTimeoutExecutor = 154 Executors.newScheduledThreadPool(1, (Runnable r) -> { 155 Thread t = Executors.defaultThreadFactory().newThread(r); 156 t.setName("Ravenwood:TimeoutMonitor"); 157 t.setDaemon(true); 158 return t; 159 }); 160 161 private static volatile ScheduledFuture<?> sPendingTimeout; 162 163 /** 164 * When enabled, attempt to detect uncaught exceptions from background threads. 165 */ 166 private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION = 167 !"0".equals(System.getenv("RAVENWOOD_ENABLE_UNCAUGHT_EXCEPTION_DETECTION")); 168 169 private static final boolean DIE_ON_UNCAUGHT_EXCEPTION = true; 170 171 /** 172 * When set, an unhandled exception was discovered (typically on a background thread), and we 173 * capture it here to ensure it's reported as a test failure. 174 */ 175 private static final AtomicReference<Throwable> sPendingUncaughtException = 176 new AtomicReference<>(); 177 178 // TODO: expose packCallingIdentity function in libbinder and use it directly 179 // See: packCallingIdentity in frameworks/native/libs/binder/IPCThreadState.cpp packBinderIdentityToken( boolean hasExplicitIdentity, int callingUid, int callingPid)180 private static long packBinderIdentityToken( 181 boolean hasExplicitIdentity, int callingUid, int callingPid) { 182 long res = ((long) callingUid << 32) | callingPid; 183 if (hasExplicitIdentity) { 184 res |= (0x1 << 30); 185 } else { 186 res &= ~(0x1 << 30); 187 } 188 return res; 189 } 190 191 /** Map from path -> resources. */ 192 private static final HashMap<File, Resources> sCachedResources = new HashMap<>(); 193 private static Set<String> sAdoptedPermissions = Collections.emptySet(); 194 195 private static final Object sInitializationLock = new Object(); 196 197 @GuardedBy("sInitializationLock") 198 private static boolean sInitialized = false; 199 200 @GuardedBy("sInitializationLock") 201 private static Throwable sExceptionFromGlobalInit; 202 203 private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT; 204 private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname"; 205 206 private static final int sMyPid = new Random().nextInt(100, 32768); 207 private static int sTargetSdkLevel; 208 private static String sTestPackageName; 209 private static String sTargetPackageName; 210 private static Instrumentation sInstrumentation; 211 private static final long sCallingIdentity = 212 packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid); 213 214 /** 215 * Initialize the global environment. 216 */ globalInitOnce()217 public static void globalInitOnce() { 218 sTestThread = Thread.currentThread(); 219 Thread.currentThread().setName(TESTS_THREAD_NAME); 220 synchronized (sInitializationLock) { 221 if (!sInitialized) { 222 // globalInitOnce() is called from class initializer, which cause 223 // this method to be called recursively, 224 sInitialized = true; 225 226 // This is the first call. 227 final long start = System.currentTimeMillis(); 228 try { 229 globalInitInner(); 230 } catch (Throwable th) { 231 Log.e(TAG, "globalInit() failed", th); 232 233 sExceptionFromGlobalInit = th; 234 SneakyThrow.sneakyThrow(th); 235 } 236 final long end = System.currentTimeMillis(); 237 // TODO Show user/system time too 238 Log.e(TAG, "globalInit() took " + (end - start) + "ms"); 239 } else { 240 // Subsequent calls. If the first call threw, just throw the same error, to prevent 241 // the test from running. 242 if (sExceptionFromGlobalInit != null) { 243 Log.e(TAG, "globalInit() failed re-throwing the same exception", 244 sExceptionFromGlobalInit); 245 246 SneakyThrow.sneakyThrow(sExceptionFromGlobalInit); 247 } 248 } 249 } 250 } 251 globalInitInner()252 private static void globalInitInner() throws IOException { 253 // We haven't initialized liblog yet, so directly write to System.out here. 254 RavenwoodCommonUtils.log(TAG, "globalInitInner()"); 255 256 if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { 257 Thread.setDefaultUncaughtExceptionHandler( 258 RavenwoodRuntimeEnvironmentController::reportUncaughtExceptions); 259 } 260 261 // Some process-wide initialization: 262 // - maybe redirect stdout/stderr 263 // - override native system property functions 264 var lib = RavenwoodCommonUtils.getJniLibraryPath(LIBRAVENWOOD_INITIALIZER_NAME); 265 System.load(lib); 266 RavenwoodRuntimeNative.reloadNativeLibrary(lib); 267 268 // Redirect stdout/stdin to the Log API. 269 RuntimeInit.redirectLogStreams(); 270 271 dumpCommandLineArgs(); 272 dumpEnvironment(); 273 dumpJavaProperties(); 274 dumpOtherInfo(); 275 276 System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); 277 var runtimePath = getRavenwoodRuntimePath(); 278 System.setProperty(RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP, runtimePath); 279 280 Log.i(TAG, "PWD=" + System.getProperty("user.dir")); 281 Log.i(TAG, "RuntimePath=" + runtimePath); 282 283 // Make sure libravenwood_runtime is loaded. 284 System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME)); 285 286 Log_ravenwood.setLogLevels(getLogTags()); 287 Log_ravenwood.onRavenwoodRuntimeNativeReady(); 288 289 // Do the basic set up for the android sysprops. 290 RavenwoodSystemProperties.initialize(); 291 292 // Set ICU data file 293 String icuData = RavenwoodCommonUtils.getRavenwoodRuntimePath() 294 + "ravenwood-data/" 295 + RavenwoodRuntimeNative.getIcuDataName() 296 + ".dat"; 297 RavenwoodRuntimeNative.setSystemProperty("ro.icu.data.path", icuData); 298 299 // Enable all log levels for native logging, until we'll have a way to change the native 300 // side log level at runtime. 301 // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()), 302 // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS). 303 // This would also prevent libbase from crashing the process (b/381112373) because 304 // the string format it accepts is very limited. 305 try { 306 Os.setenv("ANDROID_LOG_TAGS", "*:v", true); 307 } catch (ErrnoException e) { 308 throw new RuntimeException(e); 309 } 310 311 // Make sure libandroid_runtime is loaded. 312 RavenwoodNativeLoader.loadFrameworkNativeCode(); 313 314 // Start method logging. 315 RavenwoodMethodCallLogger.enable(sStdOut); 316 317 // Touch some references early to ensure they're <clinit>'ed 318 Objects.requireNonNull(Build.TYPE); 319 Objects.requireNonNull(Build.VERSION.SDK); 320 321 // Fonts can only be initialized once 322 Typeface.init(); 323 Typeface.loadPreinstalledSystemFontMap(); 324 Typeface.loadNativeSystemFonts(); 325 326 // This will let AndroidJUnit4 use the original runner. 327 System.setProperty("android.junit.runner", 328 "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); 329 330 loadRavenwoodProperties(); 331 332 assertMockitoVersion(); 333 334 Log.i(TAG, "TargetPackageName=" + sTargetPackageName); 335 Log.i(TAG, "TestPackageName=" + sTestPackageName); 336 Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel); 337 338 RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID; 339 RavenwoodRuntimeState.sPid = sMyPid; 340 RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel; 341 342 ServiceManager.init$ravenwood(); 343 LocalServices.removeAllServicesForTest(); 344 345 ActivityManager.init$ravenwood(SYSTEM.getIdentifier()); 346 347 final var main = new HandlerThread(MAIN_THREAD_NAME); 348 sMainThread = main; 349 main.start(); 350 Looper.setMainLooperForTest(main.getLooper()); 351 352 final boolean isSelfInstrumenting = 353 Objects.equals(sTestPackageName, sTargetPackageName); 354 355 // This will load the resources from the apk set to `resource_apk` in the build file. 356 // This is supposed to be the "target app"'s resources. 357 final Supplier<Resources> targetResourcesLoader = () -> { 358 var file = new File(RAVENWOOD_RESOURCE_APK); 359 return loadResources(file.exists() ? file : null); 360 }; 361 362 // Set up test context's (== instrumentation context's) resources. 363 // If the target package name == test package name, then we use the main resources. 364 final Supplier<Resources> instResourcesLoader; 365 if (isSelfInstrumenting) { 366 instResourcesLoader = targetResourcesLoader; 367 } else { 368 instResourcesLoader = () -> { 369 var file = new File(RAVENWOOD_INST_RESOURCE_APK); 370 return loadResources(file.exists() ? file : null); 371 }; 372 } 373 374 var instContext = new RavenwoodContext( 375 sTestPackageName, main, instResourcesLoader); 376 var targetContext = new RavenwoodContext( 377 sTargetPackageName, main, targetResourcesLoader); 378 379 // Set up app context. 380 var appContext = new RavenwoodContext(sTargetPackageName, main, targetResourcesLoader); 381 appContext.setApplicationContext(appContext); 382 if (isSelfInstrumenting) { 383 instContext.setApplicationContext(appContext); 384 targetContext.setApplicationContext(appContext); 385 } else { 386 // When instrumenting into another APK, the test context doesn't have an app context. 387 targetContext.setApplicationContext(appContext); 388 } 389 390 final Supplier<Resources> systemResourcesLoader = () -> loadResources(null); 391 392 var systemServerContext = 393 new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader); 394 395 var instArgs = Bundle.EMPTY; 396 RavenwoodUtils.runOnMainThreadSync(() -> { 397 try { 398 // TODO We should get the instrumentation class name from the build file or 399 // somewhere. 400 var InstClass = Class.forName("android.app.Instrumentation"); 401 sInstrumentation = (Instrumentation) InstClass.getConstructor().newInstance(); 402 sInstrumentation.basicInit(instContext, targetContext, null); 403 sInstrumentation.onCreate(instArgs); 404 } catch (Exception e) { 405 SneakyThrow.sneakyThrow(e); 406 } 407 }); 408 InstrumentationRegistry.registerInstance(sInstrumentation, instArgs); 409 410 RavenwoodSystemServer.init(systemServerContext); 411 412 initializeCompatIds(); 413 } 414 415 /** 416 * Get log tags from environmental variable. 417 */ 418 @Nullable getLogTags()419 private static String getLogTags() { 420 var logTags = System.getenv(RAVENWOOD_ANDROID_LOG_TAGS); 421 if (logTags == null) { 422 logTags = System.getenv(ANDROID_LOG_TAGS); 423 } 424 return logTags; 425 } 426 loadRavenwoodProperties()427 private static void loadRavenwoodProperties() { 428 var props = RavenwoodSystemProperties.readProperties("ravenwood.properties"); 429 430 sTargetSdkLevel = withDefault( 431 parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL); 432 sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME); 433 sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName); 434 435 // TODO(b/377765941) Read them from the manifest too? 436 } 437 438 /** 439 * Partially reset and initialize before each test class invocation 440 */ initForRunner()441 public static void initForRunner() { 442 var targetContext = sInstrumentation.getTargetContext(); 443 var instContext = sInstrumentation.getContext(); 444 // We need to recreate the mock UiAutomation for each test class, because sometimes tests 445 // will call Mockito.framework().clearInlineMocks() after execution. 446 sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation()); 447 448 // Reset some global state 449 Process_ravenwood.reset(); 450 DeviceConfig_host.reset(); 451 Binder.restoreCallingIdentity(sCallingIdentity); 452 453 SystemProperties.clearChangeCallbacksForTest(); 454 455 maybeThrowPendingUncaughtException(); 456 } 457 458 /** 459 * Called when a test method is about to be started. 460 */ enterTestMethod(Description description)461 public static void enterTestMethod(Description description) { 462 // TODO(b/375272444): this is a hacky workaround to ensure binder identity 463 Binder.restoreCallingIdentity(sCallingIdentity); 464 465 scheduleTimeout(); 466 } 467 468 /** 469 * Called when a test method finished. 470 */ exitTestMethod(Description description)471 public static void exitTestMethod(Description description) { 472 cancelTimeout(); 473 maybeThrowPendingUncaughtException(); 474 } 475 scheduleTimeout()476 private static void scheduleTimeout() { 477 if (!ENABLE_TIMEOUT_STACKS) { 478 return; 479 } 480 cancelTimeout(); 481 482 sPendingTimeout = sTimeoutExecutor.schedule( 483 RavenwoodRuntimeEnvironmentController::onTestTimedOut, 484 TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 485 } 486 cancelTimeout()487 private static void cancelTimeout() { 488 if (!ENABLE_TIMEOUT_STACKS) { 489 return; 490 } 491 var pt = sPendingTimeout; 492 if (pt != null) { 493 pt.cancel(false); 494 } 495 } 496 initializeCompatIds()497 private static void initializeCompatIds() { 498 // Set up compat-IDs for the app side. 499 // TODO: Inside the system server, all the compat-IDs should be enabled, 500 // Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in 501 // SystemServer. 502 503 // Compat framework only uses the package name and the target SDK level. 504 ApplicationInfo appInfo = new ApplicationInfo(); 505 appInfo.packageName = sTargetPackageName; 506 appInfo.targetSdkVersion = sTargetSdkLevel; 507 508 PlatformCompat platformCompat = null; 509 try { 510 platformCompat = (PlatformCompat) ServiceManager.getServiceOrThrow( 511 Context.PLATFORM_COMPAT_SERVICE); 512 } catch (ServiceNotFoundException e) { 513 throw new RuntimeException(e); 514 } 515 516 var disabledChanges = platformCompat.getDisabledChanges(appInfo); 517 var loggableChanges = platformCompat.getLoggableChanges(appInfo); 518 519 AppCompatCallbacks.install(disabledChanges, loggableChanges); 520 } 521 522 /** 523 * Load {@link Resources} from an APK, with cache. 524 */ loadResources(@ullable File apkPath)525 private static Resources loadResources(@Nullable File apkPath) { 526 var cached = sCachedResources.get(apkPath); 527 if (cached != null) { 528 return cached; 529 } 530 531 var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK); 532 533 assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile()); 534 535 final String path = fileToLoad.getAbsolutePath(); 536 final var emptyPaths = new String[0]; 537 538 ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths); 539 540 final var ret = ResourcesManager.getInstance().getResources(null, path, 541 emptyPaths, emptyPaths, emptyPaths, 542 emptyPaths, null, null, 543 new DisplayAdjustments().getCompatibilityInfo(), 544 RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null); 545 546 assertNotNull(ret); 547 548 sCachedResources.put(apkPath, ret); 549 return ret; 550 } 551 552 /** 553 * Return if an exception is benign and okay to continue running the main looper even 554 * if we detect it. 555 */ isThrowableBenign(Throwable th)556 private static boolean isThrowableBenign(Throwable th) { 557 return th instanceof AssertionError || th instanceof AssumptionViolatedException; 558 } 559 dispatchMessage(Message msg)560 static void dispatchMessage(Message msg) { 561 try { 562 msg.getTarget().dispatchMessage(msg); 563 } catch (Throwable th) { 564 var desc = String.format("Detected %s on looper thread %s", th.getClass().getName(), 565 Thread.currentThread()); 566 sStdErr.println(desc); 567 if (TOLERATE_LOOPER_ASSERTS && isThrowableBenign(th)) { 568 sStdErr.printf("*** Continuing the test because it's %s ***\n", 569 th.getClass().getSimpleName()); 570 var e = new Exception(desc, th); 571 sPendingUncaughtException.compareAndSet(null, e); 572 return; 573 } 574 throw th; 575 } 576 } 577 578 /** 579 * A callback when a test class finishes its execution, mostly only for debugging. 580 */ exitTestClass()581 public static void exitTestClass() { 582 maybeThrowPendingUncaughtException(); 583 } 584 logTestRunner(String label, Description description)585 public static void logTestRunner(String label, Description description) { 586 // This message string carefully matches the exact format emitted by on-device tests, to 587 // aid developers in debugging raw text logs 588 Log.e("TestRunner", label + ": " + description.getMethodName() 589 + "(" + description.getTestClass().getName() + ")"); 590 } 591 maybeThrowPendingUncaughtException()592 private static void maybeThrowPendingUncaughtException() { 593 final Throwable pending = sPendingUncaughtException.getAndSet(null); 594 if (pending != null) { 595 throw new IllegalStateException("Found an uncaught exception", pending); 596 } 597 } 598 599 /** 600 * Prints the stack trace from all threads. 601 */ onTestTimedOut()602 private static void onTestTimedOut() { 603 sStdErr.println("********* SLOW TEST DETECTED ********"); 604 dumpStacks(null, null); 605 } 606 607 private static final Object sDumpStackLock = new Object(); 608 609 /** 610 * Prints the stack trace from all threads. 611 */ dumpStacks( @ullable Thread exceptionThread, @Nullable Throwable throwable)612 private static void dumpStacks( 613 @Nullable Thread exceptionThread, @Nullable Throwable throwable) { 614 cancelTimeout(); 615 synchronized (sDumpStackLock) { 616 final PrintStream out = sStdErr; 617 out.println("-----BEGIN ALL THREAD STACKS-----"); 618 619 var stacks = Thread.getAllStackTraces(); 620 var threads = stacks.keySet().stream().sorted( 621 Comparator.comparingLong(Thread::getId)).collect(Collectors.toList()); 622 623 // Put the test and the main thread at the top. 624 var testThread = sTestThread; 625 var mainThread = sMainThread; 626 if (mainThread != null) { 627 threads.remove(mainThread); 628 threads.add(0, mainThread); 629 } 630 if (testThread != null) { 631 threads.remove(testThread); 632 threads.add(0, testThread); 633 } 634 // Put the exception thread at the top. 635 // Also inject the stacktrace from the exception. 636 if (exceptionThread != null) { 637 threads.remove(exceptionThread); 638 threads.add(0, exceptionThread); 639 stacks.put(exceptionThread, throwable.getStackTrace()); 640 } 641 for (var th : threads) { 642 out.println(); 643 644 out.print("Thread"); 645 if (th == exceptionThread) { 646 out.print(" [** EXCEPTION THREAD **]"); 647 } 648 out.print(": " + th.getName() + " / " + th); 649 out.println(); 650 651 for (StackTraceElement e : stacks.get(th)) { 652 out.println("\tat " + e); 653 } 654 } 655 out.println("-----END ALL THREAD STACKS-----"); 656 } 657 } 658 659 private static final String MOCKITO_ERROR = "FATAL: Unsupported Mockito detected!" 660 + " Your test or its dependencies use one of the \"mockito-target-*\"" 661 + " modules as static library, which is unusable on host side." 662 + " Please switch over to use \"mockito-ravenwood-prebuilt\" as shared library, or" 663 + " as a last resort, set `ravenizer: { strip_mockito: true }` in your test module."; 664 665 /** 666 * Assert the Mockito version at runtime to ensure no incorrect Mockito classes are loaded. 667 */ assertMockitoVersion()668 private static void assertMockitoVersion() { 669 // DexMaker should not exist 670 assertThrows( 671 MOCKITO_ERROR, 672 ClassNotFoundException.class, 673 () -> Class.forName("com.android.dx.DexMaker")); 674 // Mockito 2 should not exist 675 assertThrows( 676 MOCKITO_ERROR, 677 ClassNotFoundException.class, 678 () -> Class.forName("org.mockito.Matchers")); 679 } 680 makeDefaultThrowMock(Class<T> clazz)681 static <T> T makeDefaultThrowMock(Class<T> clazz) { 682 return mock(clazz, inv -> { 683 HostTestUtils.onThrowMethodCalled(); 684 return null; 685 }); 686 } 687 688 // TODO: use the real UiAutomation class instead of a mock createMockUiAutomation()689 private static UiAutomation createMockUiAutomation() { 690 sAdoptedPermissions = Collections.emptySet(); 691 var mock = makeDefaultThrowMock(UiAutomation.class); 692 doAnswer(inv -> { 693 sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS; 694 return null; 695 }).when(mock).adoptShellPermissionIdentity(); 696 doAnswer(inv -> { 697 if (inv.getArgument(0) == null) { 698 sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS; 699 } else { 700 sAdoptedPermissions = (Set) Set.of(inv.getArguments()); 701 } 702 return null; 703 }).when(mock).adoptShellPermissionIdentity(any()); 704 doAnswer(inv -> { 705 sAdoptedPermissions = Collections.emptySet(); 706 return null; 707 }).when(mock).dropShellPermissionIdentity(); 708 doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions(); 709 return mock; 710 } 711 dumpCommandLineArgs()712 private static void dumpCommandLineArgs() { 713 Log.v(TAG, "JVM arguments:"); 714 715 // Note, we use the wrapper in JUnit4, not the actual class ( 716 // java.lang.management.ManagementFactory), because we can't see the later at the build 717 // because this source file is compiled for the device target, where ManagementFactory 718 // doesn't exist. 719 var args = ManagementFactory.getRuntimeMXBean().getInputArguments(); 720 721 for (var arg : args) { 722 Log.v(TAG, " " + arg); 723 } 724 } 725 reportUncaughtExceptions(Thread th, Throwable e)726 private static void reportUncaughtExceptions(Thread th, Throwable e) { 727 sStdErr.printf("Uncaught exception detected: %s: %s\n", 728 th, RavenwoodCommonUtils.getStackTraceString(e)); 729 730 doBugreport(th, e, DIE_ON_UNCAUGHT_EXCEPTION); 731 } 732 doBugreport( @ullable Thread exceptionThread, @Nullable Throwable throwable, boolean killSelf)733 private static void doBugreport( 734 @Nullable Thread exceptionThread, @Nullable Throwable throwable, 735 boolean killSelf) { 736 // TODO: Print more information 737 dumpStacks(exceptionThread, throwable); 738 if (killSelf) { 739 System.exit(13); 740 } 741 } 742 dumpJavaProperties()743 private static void dumpJavaProperties() { 744 Log.v(TAG, "JVM properties:"); 745 dumpMap(System.getProperties()); 746 } 747 dumpEnvironment()748 private static void dumpEnvironment() { 749 Log.v(TAG, "Environment:"); 750 dumpMap(System.getenv()); 751 } 752 dumpMap(Map<?, ?> map)753 private static void dumpMap(Map<?, ?> map) { 754 for (var key : map.keySet().stream().sorted().toList()) { 755 Log.v(TAG, " " + key + "=" + map.get(key)); 756 } 757 } dumpOtherInfo()758 private static void dumpOtherInfo() { 759 Log.v(TAG, "Other key information:"); 760 var jloc = Locale.getDefault(); 761 Log.v(TAG, " java.util.Locale=" + jloc + " / " + jloc.toLanguageTag()); 762 var uloc = ULocale.getDefault(); 763 Log.v(TAG, " android.icu.util.ULocale=" + uloc + " / " + uloc.toLanguageTag()); 764 765 var jtz = java.util.TimeZone.getDefault(); 766 Log.v(TAG, " java.util.TimeZone=" + jtz.getDisplayName() + " / " + jtz); 767 768 var itz = android.icu.util.TimeZone.getDefault(); 769 Log.v(TAG, " android.icu.util.TimeZone=" + itz.getDisplayName() + " / " + itz); 770 } 771 } 772