Loading ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +0 −3 Original line number Diff line number Diff line Loading @@ -133,9 +133,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); // This is needed to make AndroidJUnit4ClassRunner happy. InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); // Hook point to allow more customization. runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null); Loading ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.javadeleted 100644 → 0 +0 −85 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.platform.test.ravenwood; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.annotation.Nullable; import android.app.ResourcesManager; import android.content.res.Resources; import android.view.DisplayAdjustments; import java.io.File; import java.util.HashMap; /** * Used to store various states associated with {@link RavenwoodConfig} that's inly needed * in junit-impl. * * We don't want to put it in junit-src to avoid having to recompile all the downstream * dependencies after changing this class. * * All members must be called from the runner's main thread. */ public class RavenwoodConfigState { private static final String TAG = "RavenwoodConfigState"; private final RavenwoodConfig mConfig; // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry? RavenwoodContext mSystemServerContext; public RavenwoodConfigState(RavenwoodConfig config) { mConfig = config; } /** Map from path -> resources. */ private final HashMap<File, Resources> mCachedResources = new HashMap<>(); /** * Load {@link Resources} from an APK, with cache. */ public Resources loadResources(@Nullable File apkPath) { var cached = mCachedResources.get(apkPath); if (cached != null) { return cached; } var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK); assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile()); final String path = fileToLoad.getAbsolutePath(); final var emptyPaths = new String[0]; ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths); final var ret = ResourcesManager.getInstance().getResources(null, path, emptyPaths, emptyPaths, emptyPaths, emptyPaths, null, null, new DisplayAdjustments().getCompatibilityInfo(), RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null); assertNotNull(ret); mCachedResources.put(apkPath, ret); return ret; } } ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java +4 −28 Original line number Diff line number Diff line Loading @@ -52,11 +52,6 @@ public final class RavenwoodRunnerState { mRunner = runner; } /** * The RavenwoodConfig used to configure the current Ravenwood environment. * This can either come from mConfig or mRule. */ private RavenwoodConfig mCurrentConfig; /** * The RavenwoodConfig declared in the test class */ Loading @@ -68,10 +63,6 @@ public final class RavenwoodRunnerState { private boolean mHasRavenwoodRule; private Description mMethodDescription; public RavenwoodConfig getConfig() { return mCurrentConfig; } public void enterTestRunner() { Log.i(TAG, "enterTestRunner: " + mRunner); Loading @@ -83,31 +74,19 @@ public final class RavenwoodRunnerState { fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class." + " Suggest migrating to RavenwoodConfig."); } mCurrentConfig = mConfig; } else if (!mHasRavenwoodRule) { // If no RavenwoodConfig and no RavenwoodRule, use a default config mCurrentConfig = new RavenwoodConfig.Builder().build(); } if (mCurrentConfig != null) { RavenwoodRuntimeEnvironmentController.init(mRunner); } RavenwoodRuntimeEnvironmentController.initForRunner(); } public void enterTestClass() { Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName()); if (mCurrentConfig != null) { RavenwoodRuntimeEnvironmentController.init(mRunner); } } public void exitTestClass() { Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName()); try { if (mCurrentConfig != null) { RavenwoodRuntimeEnvironmentController.reset(); } RavenwoodRuntimeEnvironmentController.exitTestClass(); } finally { mConfig = null; mRule = null; Loading @@ -116,11 +95,11 @@ public final class RavenwoodRunnerState { public void enterTestMethod(Description description) { mMethodDescription = description; RavenwoodRuntimeEnvironmentController.initForMethod(); } public void exitTestMethod() { mMethodDescription = null; RavenwoodRuntimeEnvironmentController.reinit(); } public void enterRavenwoodRule(RavenwoodRule rule) { Loading @@ -133,10 +112,7 @@ public final class RavenwoodRunnerState { + " which is not supported."); } mRule = rule; if (mCurrentConfig == null) { mCurrentConfig = rule.getConfiguration(); } RavenwoodRuntimeEnvironmentController.init(mRunner); RavenwoodRuntimeEnvironmentController.setSystemProperties(rule.mSystemProperties); } public void exitRavenwoodRule(RavenwoodRule rule) { Loading ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +107 −144 Original line number Diff line number Diff line Loading @@ -16,8 +16,11 @@ package android.platform.test.ravenwood; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.UserHandle.SYSTEM; import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; Loading @@ -25,7 +28,9 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSIO import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt; import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; Loading Loading @@ -53,6 +58,7 @@ import android.provider.DeviceConfig_host; import android.system.ErrnoException; import android.system.Os; import android.util.Log; import android.view.DisplayAdjustments; import androidx.test.platform.app.InstrumentationRegistry; Loading @@ -62,7 +68,6 @@ import com.android.internal.os.RuntimeInit; import com.android.ravenwood.RavenwoodRuntimeNative; import com.android.ravenwood.RavenwoodRuntimeState; import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.RavenwoodRuntimeException; import com.android.ravenwood.common.SneakyThrow; import com.android.server.LocalServices; import com.android.server.compat.PlatformCompat; Loading @@ -74,8 +79,10 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; Loading @@ -85,8 +92,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; /** * Responsible for initializing and de-initializing the environment, according to a * {@link RavenwoodConfig}. * Responsible for initializing and the environment. */ public class RavenwoodRuntimeEnvironmentController { private static final String TAG = "RavenwoodRuntimeEnvironmentController"; Loading @@ -113,8 +119,6 @@ public class RavenwoodRuntimeEnvironmentController { private static ScheduledFuture<?> sPendingTimeout; private static long sOriginalIdentityToken = -1; /** * When enabled, attempt to detect uncaught exceptions from background threads. */ Loading Loading @@ -147,6 +151,10 @@ public class RavenwoodRuntimeEnvironmentController { return res; } /** Map from path -> resources. */ private static final HashMap<File, Resources> sCachedResources = new HashMap<>(); private static Set<String> sAdoptedPermissions = Collections.emptySet(); private static final Object sInitializationLock = new Object(); @GuardedBy("sInitializationLock") Loading @@ -155,15 +163,18 @@ public class RavenwoodRuntimeEnvironmentController { @GuardedBy("sInitializationLock") private static Throwable sExceptionFromGlobalInit; private static RavenwoodAwareTestRunner sRunner; private static RavenwoodSystemProperties sProps; private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT; private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname"; private static final int sMyPid = new Random().nextInt(100, 32768); private static int sTargetSdkLevel; private static String sTestPackageName; private static String sTargetPackageName; private static Instrumentation sInstrumentation; private static final long sCallingIdentity = packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid); /** * Initialize the global environment. Loading @@ -182,7 +193,7 @@ public class RavenwoodRuntimeEnvironmentController { Log.e(TAG, "globalInit() failed", th); sExceptionFromGlobalInit = th; throw th; SneakyThrow.sneakyThrow(th); } } else { // Subsequent calls. If the first call threw, just throw the same error, to prevent Loading @@ -197,10 +208,13 @@ public class RavenwoodRuntimeEnvironmentController { } } private static void globalInitInner() { private static void globalInitInner() throws IOException { if (RAVENWOOD_VERBOSE_LOGGING) { Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH")); } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } // Some process-wide initialization. (maybe redirect stdout/stderr) RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME); Loading Loading @@ -251,82 +265,32 @@ public class RavenwoodRuntimeEnvironmentController { loadRavenwoodProperties(); assertMockitoVersion(); } private static void loadRavenwoodProperties() { var props = RavenwoodSystemProperties.readProperties("ravenwood.properties"); sTargetSdkLevel = withDefault( parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL); sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME); sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName); // TODO(b/377765941) Read them from the manifest too? } /** * Initialize the environment. */ public static void init(RavenwoodAwareTestRunner runner) { if (RAVENWOOD_VERBOSE_LOGGING) { Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE")); } if (sRunner == runner) { return; } if (sRunner != null) { reset(); } sRunner = runner; try { initInner(runner.mState.getConfig()); } catch (Exception th) { Log.e(TAG, "init() failed", th); RavenwoodCommonUtils.runIgnoringException(()-> reset()); SneakyThrow.sneakyThrow(th); } } private static void initInner(RavenwoodConfig config) throws IOException { if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(false); Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } config.mTargetPackageName = sTargetPackageName; config.mTestPackageName = sTestPackageName; config.mTargetSdkLevel = sTargetSdkLevel; Log.i(TAG, "TargetPackageName=" + sTargetPackageName); Log.i(TAG, "TestPackageName=" + sTestPackageName); Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel); RavenwoodRuntimeState.sUid = config.mUid; RavenwoodRuntimeState.sPid = config.mPid; RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel; sOriginalIdentityToken = Binder.clearCallingIdentity(); reinit(); setSystemProperties(config.mSystemProperties); RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID; RavenwoodRuntimeState.sPid = sMyPid; RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel; ServiceManager.init$ravenwood(); LocalServices.removeAllServicesForTest(); ActivityManager.init$ravenwood(config.mCurrentUser); ActivityManager.init$ravenwood(SYSTEM.getIdentifier()); final var main = new HandlerThread(MAIN_THREAD_NAME); main.start(); Looper.setMainLooperForTest(main.getLooper()); final boolean isSelfInstrumenting = Objects.equals(config.mTestPackageName, config.mTargetPackageName); Objects.equals(sTestPackageName, sTargetPackageName); // This will load the resources from the apk set to `resource_apk` in the build file. // This is supposed to be the "target app"'s resources. final Supplier<Resources> targetResourcesLoader = () -> { var file = new File(RAVENWOOD_RESOURCE_APK); return config.mState.loadResources(file.exists() ? file : null); return loadResources(file.exists() ? file : null); }; // Set up test context's (== instrumentation context's) resources. Loading @@ -337,18 +301,17 @@ public class RavenwoodRuntimeEnvironmentController { } else { instResourcesLoader = () -> { var file = new File(RAVENWOOD_INST_RESOURCE_APK); return config.mState.loadResources(file.exists() ? file : null); return loadResources(file.exists() ? file : null); }; } var instContext = new RavenwoodContext( config.mTestPackageName, main, instResourcesLoader); sTestPackageName, main, instResourcesLoader); var targetContext = new RavenwoodContext( config.mTargetPackageName, main, targetResourcesLoader); sTargetPackageName, main, targetResourcesLoader); // Set up app context. var appContext = new RavenwoodContext( config.mTargetPackageName, main, targetResourcesLoader); var appContext = new RavenwoodContext(sTargetPackageName, main, targetResourcesLoader); appContext.setApplicationContext(appContext); if (isSelfInstrumenting) { instContext.setApplicationContext(appContext); Loading @@ -357,42 +320,65 @@ public class RavenwoodRuntimeEnvironmentController { // When instrumenting into another APK, the test context doesn't have an app context. targetContext.setApplicationContext(appContext); } config.mInstContext = instContext; config.mTargetContext = targetContext; final Supplier<Resources> systemResourcesLoader = () -> config.mState.loadResources(null); final Supplier<Resources> systemResourcesLoader = () -> loadResources(null); config.mState.mSystemServerContext = var systemServerContext = new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader); // Prepare other fields. config.mInstrumentation = new Instrumentation(); config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation()); InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY); sInstrumentation = new Instrumentation(); sInstrumentation.basicInit(instContext, targetContext, null); InstrumentationRegistry.registerInstance(sInstrumentation, Bundle.EMPTY); RavenwoodSystemServer.init(config); RavenwoodSystemServer.init(systemServerContext); initializeCompatIds(config); initializeCompatIds(); } private static void loadRavenwoodProperties() { var props = RavenwoodSystemProperties.readProperties("ravenwood.properties"); sTargetSdkLevel = withDefault( parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL); sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME); sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName); // TODO(b/377765941) Read them from the manifest too? } /** * Partially reset and initialize before each test class invocation */ public static void initForRunner() { var targetContext = sInstrumentation.getTargetContext(); var instContext = sInstrumentation.getContext(); // We need to recreate the mock UiAutomation for each test class, because sometimes tests // will call Mockito.framework().clearInlineMocks() after execution. sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation()); Process_ravenwood.reset(); DeviceConfig_host.reset(); Binder.restoreCallingIdentity(sCallingIdentity); if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout = sTimeoutExecutor.schedule( RavenwoodRuntimeEnvironmentController::dumpStacks, TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(false); } } /** * Partially re-initialize after each test method invocation * Partially reset and initialize before each test method invocation */ public static void reinit() { // sRunner could be null, if there was a failure in the initialization. if (sRunner != null) { var config = sRunner.mState.getConfig(); Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid)); } public static void initForMethod() { // TODO(b/375272444): this is a hacky workaround to ensure binder identity Binder.restoreCallingIdentity(sCallingIdentity); } private static void initializeCompatIds(RavenwoodConfig config) { private static void initializeCompatIds() { // Set up compat-IDs for the app side. // TODO: Inside the system server, all the compat-IDs should be enabled, // Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in Loading @@ -400,8 +386,8 @@ public class RavenwoodRuntimeEnvironmentController { // Compat framework only uses the package name and the target SDK level. ApplicationInfo appInfo = new ApplicationInfo(); appInfo.packageName = config.mTargetPackageName; appInfo.targetSdkVersion = config.mTargetSdkLevel; appInfo.packageName = sTargetPackageName; appInfo.targetSdkVersion = sTargetSdkLevel; PlatformCompat platformCompat = null; try { Loading @@ -418,65 +404,42 @@ public class RavenwoodRuntimeEnvironmentController { } /** * De-initialize. * * Note, we call this method when init() fails too, so this method should deal with * any partially-initialized states. * Load {@link Resources} from an APK, with cache. */ public static void reset() { if (RAVENWOOD_VERBOSE_LOGGING) { Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE")); } if (sRunner == null) { throw new RavenwoodRuntimeException("Internal error: reset() already called"); private static Resources loadResources(@Nullable File apkPath) { var cached = sCachedResources.get(apkPath); if (cached != null) { return cached; } var config = sRunner.mState.getConfig(); sRunner = null; if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout.cancel(false); } var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK); RavenwoodSystemServer.reset(config); assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile()); InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); config.mInstrumentation = null; if (config.mInstContext != null) { ((RavenwoodContext) config.mInstContext).cleanUp(); config.mInstContext = null; } if (config.mTargetContext != null) { ((RavenwoodContext) config.mTargetContext).cleanUp(); config.mTargetContext = null; } if (config.mState.mSystemServerContext != null) { config.mState.mSystemServerContext.cleanUp(); } final String path = fileToLoad.getAbsolutePath(); final var emptyPaths = new String[0]; if (Looper.getMainLooper() != null) { Looper.getMainLooper().quit(); } Looper.clearMainLooperForTest(); ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths); ActivityManager.reset$ravenwood(); final var ret = ResourcesManager.getInstance().getResources(null, path, emptyPaths, emptyPaths, emptyPaths, emptyPaths, null, null, new DisplayAdjustments().getCompatibilityInfo(), RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null); LocalServices.removeAllServicesForTest(); ServiceManager.reset$ravenwood(); assertNotNull(ret); setSystemProperties(null); if (sOriginalIdentityToken != -1) { Binder.restoreCallingIdentity(sOriginalIdentityToken); sCachedResources.put(apkPath, ret); return ret; } RavenwoodRuntimeState.reset(); Process_ravenwood.reset(); DeviceConfig_host.reset(); try { ResourcesManager.setInstance(null); // Better structure needed. } catch (Exception e) { // AOSP-CHANGE: AOSP doesn't support resources yet. /** * A callback when a test class finishes its execution, mostly only for debugging. */ public static void exitTestClass() { if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout.cancel(false); } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(true); } Loading Loading @@ -524,7 +487,7 @@ public class RavenwoodRuntimeEnvironmentController { /** * Set the current configuration to the actual SystemProperties. */ private static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) { public static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) { SystemProperties.clearChangeCallbacksForTest(); RavenwoodRuntimeNative.clearSystemProperties(); if (systemProperties == null) systemProperties = new RavenwoodSystemProperties(); Loading Loading @@ -558,28 +521,28 @@ public class RavenwoodRuntimeEnvironmentController { // TODO: use the real UiAutomation class instead of a mock private static UiAutomation createMockUiAutomation() { final Set[] adoptedPermission = { Collections.emptySet() }; sAdoptedPermissions = Collections.emptySet(); var mock = mock(UiAutomation.class, inv -> { HostTestUtils.onThrowMethodCalled(); return null; }); doAnswer(inv -> { adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS; sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS; return null; }).when(mock).adoptShellPermissionIdentity(); doAnswer(inv -> { if (inv.getArgument(0) == null) { adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS; sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS; } else { adoptedPermission[0] = Set.of(inv.getArguments()); sAdoptedPermissions = (Set) Set.of(inv.getArguments()); } return null; }).when(mock).adoptShellPermissionIdentity(any()); doAnswer(inv -> { adoptedPermission[0] = Collections.emptySet(); sAdoptedPermissions = Collections.emptySet(); return null; }).when(mock).dropShellPermissionIdentity(); doAnswer(inv -> adoptedPermission[0]).when(mock).getAdoptedShellPermissions(); doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions(); return mock; } Loading ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +7 −10 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +0 −3 Original line number Diff line number Diff line Loading @@ -133,9 +133,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); // This is needed to make AndroidJUnit4ClassRunner happy. InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); // Hook point to allow more customization. runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null); Loading
ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.javadeleted 100644 → 0 +0 −85 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.platform.test.ravenwood; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.annotation.Nullable; import android.app.ResourcesManager; import android.content.res.Resources; import android.view.DisplayAdjustments; import java.io.File; import java.util.HashMap; /** * Used to store various states associated with {@link RavenwoodConfig} that's inly needed * in junit-impl. * * We don't want to put it in junit-src to avoid having to recompile all the downstream * dependencies after changing this class. * * All members must be called from the runner's main thread. */ public class RavenwoodConfigState { private static final String TAG = "RavenwoodConfigState"; private final RavenwoodConfig mConfig; // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry? RavenwoodContext mSystemServerContext; public RavenwoodConfigState(RavenwoodConfig config) { mConfig = config; } /** Map from path -> resources. */ private final HashMap<File, Resources> mCachedResources = new HashMap<>(); /** * Load {@link Resources} from an APK, with cache. */ public Resources loadResources(@Nullable File apkPath) { var cached = mCachedResources.get(apkPath); if (cached != null) { return cached; } var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK); assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile()); final String path = fileToLoad.getAbsolutePath(); final var emptyPaths = new String[0]; ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths); final var ret = ResourcesManager.getInstance().getResources(null, path, emptyPaths, emptyPaths, emptyPaths, emptyPaths, null, null, new DisplayAdjustments().getCompatibilityInfo(), RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null); assertNotNull(ret); mCachedResources.put(apkPath, ret); return ret; } }
ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java +4 −28 Original line number Diff line number Diff line Loading @@ -52,11 +52,6 @@ public final class RavenwoodRunnerState { mRunner = runner; } /** * The RavenwoodConfig used to configure the current Ravenwood environment. * This can either come from mConfig or mRule. */ private RavenwoodConfig mCurrentConfig; /** * The RavenwoodConfig declared in the test class */ Loading @@ -68,10 +63,6 @@ public final class RavenwoodRunnerState { private boolean mHasRavenwoodRule; private Description mMethodDescription; public RavenwoodConfig getConfig() { return mCurrentConfig; } public void enterTestRunner() { Log.i(TAG, "enterTestRunner: " + mRunner); Loading @@ -83,31 +74,19 @@ public final class RavenwoodRunnerState { fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class." + " Suggest migrating to RavenwoodConfig."); } mCurrentConfig = mConfig; } else if (!mHasRavenwoodRule) { // If no RavenwoodConfig and no RavenwoodRule, use a default config mCurrentConfig = new RavenwoodConfig.Builder().build(); } if (mCurrentConfig != null) { RavenwoodRuntimeEnvironmentController.init(mRunner); } RavenwoodRuntimeEnvironmentController.initForRunner(); } public void enterTestClass() { Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName()); if (mCurrentConfig != null) { RavenwoodRuntimeEnvironmentController.init(mRunner); } } public void exitTestClass() { Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName()); try { if (mCurrentConfig != null) { RavenwoodRuntimeEnvironmentController.reset(); } RavenwoodRuntimeEnvironmentController.exitTestClass(); } finally { mConfig = null; mRule = null; Loading @@ -116,11 +95,11 @@ public final class RavenwoodRunnerState { public void enterTestMethod(Description description) { mMethodDescription = description; RavenwoodRuntimeEnvironmentController.initForMethod(); } public void exitTestMethod() { mMethodDescription = null; RavenwoodRuntimeEnvironmentController.reinit(); } public void enterRavenwoodRule(RavenwoodRule rule) { Loading @@ -133,10 +112,7 @@ public final class RavenwoodRunnerState { + " which is not supported."); } mRule = rule; if (mCurrentConfig == null) { mCurrentConfig = rule.getConfiguration(); } RavenwoodRuntimeEnvironmentController.init(mRunner); RavenwoodRuntimeEnvironmentController.setSystemProperties(rule.mSystemProperties); } public void exitRavenwoodRule(RavenwoodRule rule) { Loading
ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +107 −144 Original line number Diff line number Diff line Loading @@ -16,8 +16,11 @@ package android.platform.test.ravenwood; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.UserHandle.SYSTEM; import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; Loading @@ -25,7 +28,9 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSIO import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt; import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; Loading Loading @@ -53,6 +58,7 @@ import android.provider.DeviceConfig_host; import android.system.ErrnoException; import android.system.Os; import android.util.Log; import android.view.DisplayAdjustments; import androidx.test.platform.app.InstrumentationRegistry; Loading @@ -62,7 +68,6 @@ import com.android.internal.os.RuntimeInit; import com.android.ravenwood.RavenwoodRuntimeNative; import com.android.ravenwood.RavenwoodRuntimeState; import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.RavenwoodRuntimeException; import com.android.ravenwood.common.SneakyThrow; import com.android.server.LocalServices; import com.android.server.compat.PlatformCompat; Loading @@ -74,8 +79,10 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; Loading @@ -85,8 +92,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; /** * Responsible for initializing and de-initializing the environment, according to a * {@link RavenwoodConfig}. * Responsible for initializing and the environment. */ public class RavenwoodRuntimeEnvironmentController { private static final String TAG = "RavenwoodRuntimeEnvironmentController"; Loading @@ -113,8 +119,6 @@ public class RavenwoodRuntimeEnvironmentController { private static ScheduledFuture<?> sPendingTimeout; private static long sOriginalIdentityToken = -1; /** * When enabled, attempt to detect uncaught exceptions from background threads. */ Loading Loading @@ -147,6 +151,10 @@ public class RavenwoodRuntimeEnvironmentController { return res; } /** Map from path -> resources. */ private static final HashMap<File, Resources> sCachedResources = new HashMap<>(); private static Set<String> sAdoptedPermissions = Collections.emptySet(); private static final Object sInitializationLock = new Object(); @GuardedBy("sInitializationLock") Loading @@ -155,15 +163,18 @@ public class RavenwoodRuntimeEnvironmentController { @GuardedBy("sInitializationLock") private static Throwable sExceptionFromGlobalInit; private static RavenwoodAwareTestRunner sRunner; private static RavenwoodSystemProperties sProps; private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT; private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname"; private static final int sMyPid = new Random().nextInt(100, 32768); private static int sTargetSdkLevel; private static String sTestPackageName; private static String sTargetPackageName; private static Instrumentation sInstrumentation; private static final long sCallingIdentity = packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid); /** * Initialize the global environment. Loading @@ -182,7 +193,7 @@ public class RavenwoodRuntimeEnvironmentController { Log.e(TAG, "globalInit() failed", th); sExceptionFromGlobalInit = th; throw th; SneakyThrow.sneakyThrow(th); } } else { // Subsequent calls. If the first call threw, just throw the same error, to prevent Loading @@ -197,10 +208,13 @@ public class RavenwoodRuntimeEnvironmentController { } } private static void globalInitInner() { private static void globalInitInner() throws IOException { if (RAVENWOOD_VERBOSE_LOGGING) { Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH")); } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } // Some process-wide initialization. (maybe redirect stdout/stderr) RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME); Loading Loading @@ -251,82 +265,32 @@ public class RavenwoodRuntimeEnvironmentController { loadRavenwoodProperties(); assertMockitoVersion(); } private static void loadRavenwoodProperties() { var props = RavenwoodSystemProperties.readProperties("ravenwood.properties"); sTargetSdkLevel = withDefault( parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL); sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME); sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName); // TODO(b/377765941) Read them from the manifest too? } /** * Initialize the environment. */ public static void init(RavenwoodAwareTestRunner runner) { if (RAVENWOOD_VERBOSE_LOGGING) { Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE")); } if (sRunner == runner) { return; } if (sRunner != null) { reset(); } sRunner = runner; try { initInner(runner.mState.getConfig()); } catch (Exception th) { Log.e(TAG, "init() failed", th); RavenwoodCommonUtils.runIgnoringException(()-> reset()); SneakyThrow.sneakyThrow(th); } } private static void initInner(RavenwoodConfig config) throws IOException { if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(false); Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } config.mTargetPackageName = sTargetPackageName; config.mTestPackageName = sTestPackageName; config.mTargetSdkLevel = sTargetSdkLevel; Log.i(TAG, "TargetPackageName=" + sTargetPackageName); Log.i(TAG, "TestPackageName=" + sTestPackageName); Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel); RavenwoodRuntimeState.sUid = config.mUid; RavenwoodRuntimeState.sPid = config.mPid; RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel; sOriginalIdentityToken = Binder.clearCallingIdentity(); reinit(); setSystemProperties(config.mSystemProperties); RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID; RavenwoodRuntimeState.sPid = sMyPid; RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel; ServiceManager.init$ravenwood(); LocalServices.removeAllServicesForTest(); ActivityManager.init$ravenwood(config.mCurrentUser); ActivityManager.init$ravenwood(SYSTEM.getIdentifier()); final var main = new HandlerThread(MAIN_THREAD_NAME); main.start(); Looper.setMainLooperForTest(main.getLooper()); final boolean isSelfInstrumenting = Objects.equals(config.mTestPackageName, config.mTargetPackageName); Objects.equals(sTestPackageName, sTargetPackageName); // This will load the resources from the apk set to `resource_apk` in the build file. // This is supposed to be the "target app"'s resources. final Supplier<Resources> targetResourcesLoader = () -> { var file = new File(RAVENWOOD_RESOURCE_APK); return config.mState.loadResources(file.exists() ? file : null); return loadResources(file.exists() ? file : null); }; // Set up test context's (== instrumentation context's) resources. Loading @@ -337,18 +301,17 @@ public class RavenwoodRuntimeEnvironmentController { } else { instResourcesLoader = () -> { var file = new File(RAVENWOOD_INST_RESOURCE_APK); return config.mState.loadResources(file.exists() ? file : null); return loadResources(file.exists() ? file : null); }; } var instContext = new RavenwoodContext( config.mTestPackageName, main, instResourcesLoader); sTestPackageName, main, instResourcesLoader); var targetContext = new RavenwoodContext( config.mTargetPackageName, main, targetResourcesLoader); sTargetPackageName, main, targetResourcesLoader); // Set up app context. var appContext = new RavenwoodContext( config.mTargetPackageName, main, targetResourcesLoader); var appContext = new RavenwoodContext(sTargetPackageName, main, targetResourcesLoader); appContext.setApplicationContext(appContext); if (isSelfInstrumenting) { instContext.setApplicationContext(appContext); Loading @@ -357,42 +320,65 @@ public class RavenwoodRuntimeEnvironmentController { // When instrumenting into another APK, the test context doesn't have an app context. targetContext.setApplicationContext(appContext); } config.mInstContext = instContext; config.mTargetContext = targetContext; final Supplier<Resources> systemResourcesLoader = () -> config.mState.loadResources(null); final Supplier<Resources> systemResourcesLoader = () -> loadResources(null); config.mState.mSystemServerContext = var systemServerContext = new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader); // Prepare other fields. config.mInstrumentation = new Instrumentation(); config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation()); InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY); sInstrumentation = new Instrumentation(); sInstrumentation.basicInit(instContext, targetContext, null); InstrumentationRegistry.registerInstance(sInstrumentation, Bundle.EMPTY); RavenwoodSystemServer.init(config); RavenwoodSystemServer.init(systemServerContext); initializeCompatIds(config); initializeCompatIds(); } private static void loadRavenwoodProperties() { var props = RavenwoodSystemProperties.readProperties("ravenwood.properties"); sTargetSdkLevel = withDefault( parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL); sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME); sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName); // TODO(b/377765941) Read them from the manifest too? } /** * Partially reset and initialize before each test class invocation */ public static void initForRunner() { var targetContext = sInstrumentation.getTargetContext(); var instContext = sInstrumentation.getContext(); // We need to recreate the mock UiAutomation for each test class, because sometimes tests // will call Mockito.framework().clearInlineMocks() after execution. sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation()); Process_ravenwood.reset(); DeviceConfig_host.reset(); Binder.restoreCallingIdentity(sCallingIdentity); if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout = sTimeoutExecutor.schedule( RavenwoodRuntimeEnvironmentController::dumpStacks, TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(false); } } /** * Partially re-initialize after each test method invocation * Partially reset and initialize before each test method invocation */ public static void reinit() { // sRunner could be null, if there was a failure in the initialization. if (sRunner != null) { var config = sRunner.mState.getConfig(); Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid)); } public static void initForMethod() { // TODO(b/375272444): this is a hacky workaround to ensure binder identity Binder.restoreCallingIdentity(sCallingIdentity); } private static void initializeCompatIds(RavenwoodConfig config) { private static void initializeCompatIds() { // Set up compat-IDs for the app side. // TODO: Inside the system server, all the compat-IDs should be enabled, // Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in Loading @@ -400,8 +386,8 @@ public class RavenwoodRuntimeEnvironmentController { // Compat framework only uses the package name and the target SDK level. ApplicationInfo appInfo = new ApplicationInfo(); appInfo.packageName = config.mTargetPackageName; appInfo.targetSdkVersion = config.mTargetSdkLevel; appInfo.packageName = sTargetPackageName; appInfo.targetSdkVersion = sTargetSdkLevel; PlatformCompat platformCompat = null; try { Loading @@ -418,65 +404,42 @@ public class RavenwoodRuntimeEnvironmentController { } /** * De-initialize. * * Note, we call this method when init() fails too, so this method should deal with * any partially-initialized states. * Load {@link Resources} from an APK, with cache. */ public static void reset() { if (RAVENWOOD_VERBOSE_LOGGING) { Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE")); } if (sRunner == null) { throw new RavenwoodRuntimeException("Internal error: reset() already called"); private static Resources loadResources(@Nullable File apkPath) { var cached = sCachedResources.get(apkPath); if (cached != null) { return cached; } var config = sRunner.mState.getConfig(); sRunner = null; if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout.cancel(false); } var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK); RavenwoodSystemServer.reset(config); assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile()); InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); config.mInstrumentation = null; if (config.mInstContext != null) { ((RavenwoodContext) config.mInstContext).cleanUp(); config.mInstContext = null; } if (config.mTargetContext != null) { ((RavenwoodContext) config.mTargetContext).cleanUp(); config.mTargetContext = null; } if (config.mState.mSystemServerContext != null) { config.mState.mSystemServerContext.cleanUp(); } final String path = fileToLoad.getAbsolutePath(); final var emptyPaths = new String[0]; if (Looper.getMainLooper() != null) { Looper.getMainLooper().quit(); } Looper.clearMainLooperForTest(); ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths); ActivityManager.reset$ravenwood(); final var ret = ResourcesManager.getInstance().getResources(null, path, emptyPaths, emptyPaths, emptyPaths, emptyPaths, null, null, new DisplayAdjustments().getCompatibilityInfo(), RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null); LocalServices.removeAllServicesForTest(); ServiceManager.reset$ravenwood(); assertNotNull(ret); setSystemProperties(null); if (sOriginalIdentityToken != -1) { Binder.restoreCallingIdentity(sOriginalIdentityToken); sCachedResources.put(apkPath, ret); return ret; } RavenwoodRuntimeState.reset(); Process_ravenwood.reset(); DeviceConfig_host.reset(); try { ResourcesManager.setInstance(null); // Better structure needed. } catch (Exception e) { // AOSP-CHANGE: AOSP doesn't support resources yet. /** * A callback when a test class finishes its execution, mostly only for debugging. */ public static void exitTestClass() { if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout.cancel(false); } if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) { maybeThrowPendingUncaughtException(true); } Loading Loading @@ -524,7 +487,7 @@ public class RavenwoodRuntimeEnvironmentController { /** * Set the current configuration to the actual SystemProperties. */ private static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) { public static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) { SystemProperties.clearChangeCallbacksForTest(); RavenwoodRuntimeNative.clearSystemProperties(); if (systemProperties == null) systemProperties = new RavenwoodSystemProperties(); Loading Loading @@ -558,28 +521,28 @@ public class RavenwoodRuntimeEnvironmentController { // TODO: use the real UiAutomation class instead of a mock private static UiAutomation createMockUiAutomation() { final Set[] adoptedPermission = { Collections.emptySet() }; sAdoptedPermissions = Collections.emptySet(); var mock = mock(UiAutomation.class, inv -> { HostTestUtils.onThrowMethodCalled(); return null; }); doAnswer(inv -> { adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS; sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS; return null; }).when(mock).adoptShellPermissionIdentity(); doAnswer(inv -> { if (inv.getArgument(0) == null) { adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS; sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS; } else { adoptedPermission[0] = Set.of(inv.getArguments()); sAdoptedPermissions = (Set) Set.of(inv.getArguments()); } return null; }).when(mock).adoptShellPermissionIdentity(any()); doAnswer(inv -> { adoptedPermission[0] = Collections.emptySet(); sAdoptedPermissions = Collections.emptySet(); return null; }).when(mock).dropShellPermissionIdentity(); doAnswer(inv -> adoptedPermission[0]).when(mock).getAdoptedShellPermissions(); doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions(); return mock; } Loading
ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +7 −10 File changed.Preview size limit exceeded, changes collapsed. Show changes