summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Makoto Onuki <omakoto@google.com> 2025-02-21 14:13:40 -0800
committer Makoto Onuki <omakoto@google.com> 2025-02-24 12:54:47 -0800
commitc8dac37e3e93fc73df332e1723888344655fe413 (patch)
tree06185ec61e481981f9921a4ad3710246f0929e5a
parentca8ecbebc7e95f3dfec071fc68f43f0d388f7b7c (diff)
Multiple improvements
- Sysprops allowlist now fully moved to the text file. - Always enable the test timeout w/ improved stack traces - Add a method to "run on main thread" - Always enable the uncaught exception handler. - Tolerate assertion failures on the main looper. (rather than letting it crash the thread) - Also some other minor changes needed in my main work. Flag: EXEMPT host test change only Bug: 292141694 Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh Change-Id: I47b060021ce46b6e9fbe4aca63835745311a4b06
-rw-r--r--core/java/android/app/Instrumentation.java1
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java17
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java7
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java265
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java83
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java138
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java68
-rw-r--r--ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java3
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java8
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java3
-rwxr-xr-xravenwood/scripts/add-annotations.sh2
-rw-r--r--ravenwood/tests/coretest/Android.bp31
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMainThreadTest.java82
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodReflectorTest.java63
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodSystemPropertiesTest.java65
-rw-r--r--ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java13
-rw-r--r--ravenwood/texts/ravenwood-build.prop36
-rw-r--r--ravenwood/texts/ravenwood-services-jarjar-rules.txt4
18 files changed, 765 insertions, 124 deletions
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index eb9feb95bf3d..8af5b1bd40f8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -189,6 +189,7 @@ public class Instrumentation {
* @param arguments Any additional arguments that were supplied when the
* instrumentation was started.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public void onCreate(Bundle arguments) {
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 3ebef02284d6..b6167665c985 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -23,13 +23,10 @@ import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Bundle;
import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.annotations.internal.InnerRunner;
import android.util.Log;
-import androidx.test.platform.app.InstrumentationRegistry;
-
import com.android.ravenwood.common.RavenwoodCommonUtils;
import org.junit.rules.TestRule;
@@ -285,11 +282,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
private boolean onBefore(Description description, Scope scope, Order order) {
Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
- if (scope == Scope.Instance && order == Order.Outer) {
- // Start of a test method.
- mState.enterTestMethod(description);
- }
-
final var classDescription = getDescription();
// Class-level annotations are checked by the runner already, so we only check
@@ -299,6 +291,12 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
return false;
}
}
+
+ if (scope == Scope.Instance && order == Order.Outer) {
+ // Start of a test method.
+ mState.enterTestMethod(description);
+ }
+
return true;
}
@@ -314,8 +312,7 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
if (scope == Scope.Instance && order == Order.Outer) {
// End of a test method.
- mState.exitTestMethod();
-
+ mState.exitTestMethod(description);
}
// If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 70bc52bdaa12..705186edba00 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -81,12 +81,15 @@ public final class RavenwoodRunnerState {
RavenwoodRuntimeEnvironmentController.exitTestClass();
}
+ /** Called when a test method is about to start */
public void enterTestMethod(Description description) {
mMethodDescription = description;
- RavenwoodRuntimeEnvironmentController.initForMethod();
+ RavenwoodRuntimeEnvironmentController.enterTestMethod(description);
}
- public void exitTestMethod() {
+ /** Called when a test method finishes */
+ public void exitTestMethod(Description description) {
+ RavenwoodRuntimeEnvironmentController.exitTestMethod(description);
mMethodDescription = null;
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index f205d238c693..d935626c34df 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -51,6 +51,7 @@ import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Message;
import android.os.Process_ravenwood;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -74,6 +75,7 @@ import com.android.ravenwood.common.SneakyThrow;
import com.android.server.LocalServices;
import com.android.server.compat.PlatformCompat;
+import org.junit.AssumptionViolatedException;
import org.junit.internal.management.ManagementFactory;
import org.junit.runner.Description;
@@ -81,6 +83,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -93,6 +96,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* Responsible for initializing and the environment.
@@ -107,32 +111,60 @@ public class RavenwoodRuntimeEnvironmentController {
@SuppressWarnings("UnusedVariable")
private static final PrintStream sStdErr = System.err;
- private static final String MAIN_THREAD_NAME = "RavenwoodMain";
+ private static final String MAIN_THREAD_NAME = "Ravenwood:Main";
+ private static final String TESTS_THREAD_NAME = "Ravenwood:Test";
+
private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
private static final String ANDROID_LOG_TAGS = "ANDROID_LOG_TAGS";
private static final String RAVENWOOD_ANDROID_LOG_TAGS = "RAVENWOOD_" + ANDROID_LOG_TAGS;
+ static volatile Thread sTestThread;
+ static volatile Thread sMainThread;
+
/**
* When enabled, attempt to dump all thread stacks just before we hit the
* overall Tradefed timeout, to aid in debugging deadlocks.
+ *
+ * Note, this timeout will _not_ stop the test, as there isn't really a clean way to do it.
+ * It'll merely print stacktraces.
*/
private static final boolean ENABLE_TIMEOUT_STACKS =
- "1".equals(System.getenv("RAVENWOOD_ENABLE_TIMEOUT_STACKS"));
+ !"0".equals(System.getenv("RAVENWOOD_ENABLE_TIMEOUT_STACKS"));
+
+ private static final boolean TOLERATE_LOOPER_ASSERTS =
+ !"0".equals(System.getenv("RAVENWOOD_TOLERATE_LOOPER_ASSERTS"));
+
+ static final int DEFAULT_TIMEOUT_SECONDS = 10;
+ private static final int TIMEOUT_MILLIS = getTimeoutSeconds() * 1000;
+
+ static int getTimeoutSeconds() {
+ var e = System.getenv("RAVENWOOD_TIMEOUT_SECONDS");
+ if (e == null || e.isEmpty()) {
+ return DEFAULT_TIMEOUT_SECONDS;
+ }
+ return Integer.parseInt(e);
+ }
- private static final int TIMEOUT_MILLIS = 9_000;
private static final ScheduledExecutorService sTimeoutExecutor =
- Executors.newScheduledThreadPool(1);
+ Executors.newScheduledThreadPool(1, (Runnable r) -> {
+ Thread t = Executors.defaultThreadFactory().newThread(r);
+ t.setName("Ravenwood:TimeoutMonitor");
+ t.setDaemon(true);
+ return t;
+ });
- private static ScheduledFuture<?> sPendingTimeout;
+ private static volatile ScheduledFuture<?> sPendingTimeout;
/**
* When enabled, attempt to detect uncaught exceptions from background threads.
*/
private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION =
- "1".equals(System.getenv("RAVENWOOD_ENABLE_UNCAUGHT_EXCEPTION_DETECTION"));
+ !"0".equals(System.getenv("RAVENWOOD_ENABLE_UNCAUGHT_EXCEPTION_DETECTION"));
+
+ private static final boolean DIE_ON_UNCAUGHT_EXCEPTION = true;
/**
* When set, an unhandled exception was discovered (typically on a background thread), and we
@@ -141,12 +173,6 @@ public class RavenwoodRuntimeEnvironmentController {
private static final AtomicReference<Throwable> sPendingUncaughtException =
new AtomicReference<>();
- private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
- (thread, throwable) -> {
- // Remember the first exception we discover
- sPendingUncaughtException.compareAndSet(null, throwable);
- };
-
// TODO: expose packCallingIdentity function in libbinder and use it directly
// See: packCallingIdentity in frameworks/native/libs/binder/IPCThreadState.cpp
private static long packBinderIdentityToken(
@@ -187,6 +213,8 @@ public class RavenwoodRuntimeEnvironmentController {
* Initialize the global environment.
*/
public static void globalInitOnce() {
+ sTestThread = Thread.currentThread();
+ Thread.currentThread().setName(TESTS_THREAD_NAME);
synchronized (sInitializationLock) {
if (!sInitialized) {
// globalInitOnce() is called from class initializer, which cause
@@ -194,6 +222,7 @@ public class RavenwoodRuntimeEnvironmentController {
sInitialized = true;
// This is the first call.
+ final long start = System.currentTimeMillis();
try {
globalInitInner();
} catch (Throwable th) {
@@ -202,6 +231,9 @@ public class RavenwoodRuntimeEnvironmentController {
sExceptionFromGlobalInit = th;
SneakyThrow.sneakyThrow(th);
}
+ final long end = System.currentTimeMillis();
+ // TODO Show user/system time too
+ Log.e(TAG, "globalInit() took " + (end - start) + "ms");
} else {
// Subsequent calls. If the first call threw, just throw the same error, to prevent
// the test from running.
@@ -220,7 +252,8 @@ public class RavenwoodRuntimeEnvironmentController {
RavenwoodCommonUtils.log(TAG, "globalInitInner()");
if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
- Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+ Thread.setDefaultUncaughtExceptionHandler(
+ RavenwoodRuntimeEnvironmentController::reportUncaughtExceptions);
}
// Some process-wide initialization:
@@ -304,6 +337,7 @@ public class RavenwoodRuntimeEnvironmentController {
ActivityManager.init$ravenwood(SYSTEM.getIdentifier());
final var main = new HandlerThread(MAIN_THREAD_NAME);
+ sMainThread = main;
main.start();
Looper.setMainLooperForTest(main.getLooper());
@@ -350,9 +384,20 @@ public class RavenwoodRuntimeEnvironmentController {
var systemServerContext =
new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
- sInstrumentation = new Instrumentation();
- sInstrumentation.basicInit(instContext, targetContext, null);
- InstrumentationRegistry.registerInstance(sInstrumentation, Bundle.EMPTY);
+ var instArgs = Bundle.EMPTY;
+ RavenwoodUtils.runOnMainThreadSync(() -> {
+ try {
+ // TODO We should get the instrumentation class name from the build file or
+ // somewhere.
+ var InstClass = Class.forName("android.app.Instrumentation");
+ sInstrumentation = (Instrumentation) InstClass.getConstructor().newInstance();
+ sInstrumentation.basicInit(instContext, targetContext, null);
+ sInstrumentation.onCreate(instArgs);
+ } catch (Exception e) {
+ SneakyThrow.sneakyThrow(e);
+ }
+ });
+ InstrumentationRegistry.registerInstance(sInstrumentation, instArgs);
RavenwoodSystemServer.init(systemServerContext);
@@ -399,22 +444,46 @@ public class RavenwoodRuntimeEnvironmentController {
SystemProperties.clearChangeCallbacksForTest();
- if (ENABLE_TIMEOUT_STACKS) {
- sPendingTimeout = sTimeoutExecutor.schedule(
- RavenwoodRuntimeEnvironmentController::dumpStacks,
- TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- }
- if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
- maybeThrowPendingUncaughtException(false);
- }
+ maybeThrowPendingUncaughtException();
}
/**
- * Partially reset and initialize before each test method invocation
+ * Called when a test method is about to be started.
*/
- public static void initForMethod() {
+ public static void enterTestMethod(Description description) {
// TODO(b/375272444): this is a hacky workaround to ensure binder identity
Binder.restoreCallingIdentity(sCallingIdentity);
+
+ scheduleTimeout();
+ }
+
+ /**
+ * Called when a test method finished.
+ */
+ public static void exitTestMethod(Description description) {
+ cancelTimeout();
+ maybeThrowPendingUncaughtException();
+ }
+
+ private static void scheduleTimeout() {
+ if (!ENABLE_TIMEOUT_STACKS) {
+ return;
+ }
+ cancelTimeout();
+
+ sPendingTimeout = sTimeoutExecutor.schedule(
+ RavenwoodRuntimeEnvironmentController::onTestTimedOut,
+ TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ }
+
+ private static void cancelTimeout() {
+ if (!ENABLE_TIMEOUT_STACKS) {
+ return;
+ }
+ var pt = sPendingTimeout;
+ if (pt != null) {
+ pt.cancel(false);
+ }
}
private static void initializeCompatIds() {
@@ -473,15 +542,36 @@ public class RavenwoodRuntimeEnvironmentController {
}
/**
+ * Return if an exception is benign and okay to continue running the main looper even
+ * if we detect it.
+ */
+ private static boolean isThrowableBenign(Throwable th) {
+ return th instanceof AssertionError || th instanceof AssumptionViolatedException;
+ }
+
+ static void dispatchMessage(Message msg) {
+ try {
+ msg.getTarget().dispatchMessage(msg);
+ } catch (Throwable th) {
+ var desc = String.format("Detected %s on looper thread %s", th.getClass().getName(),
+ Thread.currentThread());
+ sStdErr.println(desc);
+ if (TOLERATE_LOOPER_ASSERTS && isThrowableBenign(th)) {
+ sStdErr.printf("*** Continuing the test because it's %s ***\n",
+ th.getClass().getSimpleName());
+ var e = new Exception(desc, th);
+ sPendingUncaughtException.compareAndSet(null, e);
+ return;
+ }
+ throw th;
+ }
+ }
+
+ /**
* 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);
- }
+ maybeThrowPendingUncaughtException();
}
public static void logTestRunner(String label, Description description) {
@@ -491,35 +581,70 @@ public class RavenwoodRuntimeEnvironmentController {
+ "(" + description.getTestClass().getName() + ")");
}
- private static void dumpStacks() {
- final PrintStream out = System.err;
- out.println("-----BEGIN ALL THREAD STACKS-----");
- final Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
- for (Map.Entry<Thread, StackTraceElement[]> stack : stacks.entrySet()) {
- out.println();
- Thread t = stack.getKey();
- out.println(t.toString() + " ID=" + t.getId());
- for (StackTraceElement e : stack.getValue()) {
- out.println("\tat " + e);
- }
+ private static void maybeThrowPendingUncaughtException() {
+ final Throwable pending = sPendingUncaughtException.getAndSet(null);
+ if (pending != null) {
+ throw new IllegalStateException("Found an uncaught exception", pending);
}
- out.println("-----END ALL THREAD STACKS-----");
}
/**
- * If there's a pending uncaught exception, consume and throw it now. Typically used to
- * report an exception on a background thread as a failure for the currently running test.
+ * Prints the stack trace from all threads.
*/
- private static void maybeThrowPendingUncaughtException(boolean duringReset) {
- final Throwable pending = sPendingUncaughtException.getAndSet(null);
- if (pending != null) {
- if (duringReset) {
- throw new IllegalStateException(
- "Found an uncaught exception during this test", pending);
- } else {
- throw new IllegalStateException(
- "Found an uncaught exception before this test started", pending);
+ private static void onTestTimedOut() {
+ sStdErr.println("********* SLOW TEST DETECTED ********");
+ dumpStacks(null, null);
+ }
+
+ private static final Object sDumpStackLock = new Object();
+
+ /**
+ * Prints the stack trace from all threads.
+ */
+ private static void dumpStacks(
+ @Nullable Thread exceptionThread, @Nullable Throwable throwable) {
+ cancelTimeout();
+ synchronized (sDumpStackLock) {
+ final PrintStream out = sStdErr;
+ out.println("-----BEGIN ALL THREAD STACKS-----");
+
+ var stacks = Thread.getAllStackTraces();
+ var threads = stacks.keySet().stream().sorted(
+ Comparator.comparingLong(Thread::getId)).collect(Collectors.toList());
+
+ // Put the test and the main thread at the top.
+ var testThread = sTestThread;
+ var mainThread = sMainThread;
+ if (mainThread != null) {
+ threads.remove(mainThread);
+ threads.add(0, mainThread);
+ }
+ if (testThread != null) {
+ threads.remove(testThread);
+ threads.add(0, testThread);
+ }
+ // Put the exception thread at the top.
+ // Also inject the stacktrace from the exception.
+ if (exceptionThread != null) {
+ threads.remove(exceptionThread);
+ threads.add(0, exceptionThread);
+ stacks.put(exceptionThread, throwable.getStackTrace());
+ }
+ for (var th : threads) {
+ out.println();
+
+ out.print("Thread");
+ if (th == exceptionThread) {
+ out.print(" [** EXCEPTION THREAD **]");
+ }
+ out.print(": " + th.getName() + " / " + th);
+ out.println();
+
+ for (StackTraceElement e : stacks.get(th)) {
+ out.println("\tat " + e);
+ }
}
+ out.println("-----END ALL THREAD STACKS-----");
}
}
@@ -545,13 +670,17 @@ public class RavenwoodRuntimeEnvironmentController {
() -> Class.forName("org.mockito.Matchers"));
}
- // TODO: use the real UiAutomation class instead of a mock
- private static UiAutomation createMockUiAutomation() {
- sAdoptedPermissions = Collections.emptySet();
- var mock = mock(UiAutomation.class, inv -> {
+ static <T> T makeDefaultThrowMock(Class<T> clazz) {
+ return mock(clazz, inv -> {
HostTestUtils.onThrowMethodCalled();
return null;
});
+ }
+
+ // TODO: use the real UiAutomation class instead of a mock
+ private static UiAutomation createMockUiAutomation() {
+ sAdoptedPermissions = Collections.emptySet();
+ var mock = makeDefaultThrowMock(UiAutomation.class);
doAnswer(inv -> {
sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
return null;
@@ -586,6 +715,23 @@ public class RavenwoodRuntimeEnvironmentController {
}
}
+ private static void reportUncaughtExceptions(Thread th, Throwable e) {
+ sStdErr.printf("Uncaught exception detected: %s: %s\n",
+ th, RavenwoodCommonUtils.getStackTraceString(e));
+
+ doBugreport(th, e, DIE_ON_UNCAUGHT_EXCEPTION);
+ }
+
+ private static void doBugreport(
+ @Nullable Thread exceptionThread, @Nullable Throwable throwable,
+ boolean killSelf) {
+ // TODO: Print more information
+ dumpStacks(exceptionThread, throwable);
+ if (killSelf) {
+ System.exit(13);
+ }
+ }
+
private static void dumpJavaProperties() {
Log.v(TAG, "JVM properties:");
dumpMap(System.getProperties());
@@ -601,7 +747,6 @@ public class RavenwoodRuntimeEnvironmentController {
Log.v(TAG, " " + key + "=" + map.get(key));
}
}
-
private static void dumpOtherInfo() {
Log.v(TAG, "Other key information:");
var jloc = Locale.getDefault();
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 70c161c1f19a..819d93a9c336 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -26,6 +26,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -45,6 +46,9 @@ public class RavenwoodSystemProperties {
/** The default values. */
static final Map<String, String> sDefaultValues = new HashMap<>();
+ static final Set<String> sReadableKeys = new HashSet<>();
+ static final Set<String> sWritableKeys = new HashSet<>();
+
private static final String[] PARTITIONS = {
"bootimage",
"odm",
@@ -88,9 +92,24 @@ public class RavenwoodSystemProperties {
ravenwoodProps.forEach((key, origValue) -> {
final String value;
- // If a value starts with "$$$", then this is a reference to the device-side value.
if (origValue.startsWith("$$$")) {
+ // If a value starts with "$$$", then:
+ // - If it's "$$$r", the key is allowed to read.
+ // - If it's "$$$w", the key is allowed to write.
+ // - Otherwise, it's a reference to the device-side value.
+ // In case of $$$r and $$$w, if the key ends with a '.', then it'll be treaded
+ // as a prefix match.
var deviceKey = origValue.substring(3);
+ if ("r".equals(deviceKey)) {
+ sReadableKeys.add(key);
+ Log.v(TAG, key + " (readable)");
+ return;
+ } else if ("w".equals(deviceKey)) {
+ sWritableKeys.add(key);
+ Log.v(TAG, key + " (writable)");
+ return;
+ }
+
var deviceValue = deviceProps.get(deviceKey);
if (deviceValue == null) {
throw new RuntimeException("Failed to initialize system properties. Key '"
@@ -131,50 +150,38 @@ public class RavenwoodSystemProperties {
sDefaultValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
}
- private static boolean isKeyReadable(String key) {
- // All writable keys are also readable
- if (isKeyWritable(key)) return true;
+ private static boolean checkAllowedInner(String key, Set<String> allowed) {
+ if (allowed.contains(key)) {
+ return true;
+ }
- final String root = getKeyRoot(key);
+ // Also search for a prefix match.
+ for (var k : allowed) {
+ if (k.endsWith(".") && key.startsWith(k)) {
+ return true;
+ }
+ }
+ return false;
+ }
- // This set is carefully curated to help identify situations where a test may
- // accidentally depend on a default value of an obscure property whose owner hasn't
- // decided how Ravenwood should behave.
- if (root.startsWith("boot.")) return true;
- if (root.startsWith("build.")) return true;
- if (root.startsWith("product.")) return true;
- if (root.startsWith("soc.")) return true;
- if (root.startsWith("system.")) return true;
+ private static boolean checkAllowed(String key, Set<String> allowed) {
+ return checkAllowedInner(key, allowed) || checkAllowedInner(getKeyRoot(key), allowed);
+ }
+ private static boolean isKeyReadable(String key) {
// All core values should be readable
- if (sDefaultValues.containsKey(key)) return true;
-
- // Hardcoded allowlist
- return switch (key) {
- case "gsm.version.baseband",
- "no.such.thing",
- "qemu.sf.lcd_density",
- "ro.bootloader",
- "ro.hardware",
- "ro.hw_timeout_multiplier",
- "ro.odm.build.media_performance_class",
- "ro.sf.lcd_density",
- "ro.treble.enabled",
- "ro.vndk.version",
- "ro.icu.data.path" -> true;
- default -> false;
- };
+ if (sDefaultValues.containsKey(key)) {
+ return true;
+ }
+ if (checkAllowed(key, sReadableKeys)) {
+ return true;
+ }
+ // All writable keys are also readable
+ return isKeyWritable(key);
}
private static boolean isKeyWritable(String key) {
- final String root = getKeyRoot(key);
-
- if (root.startsWith("debug.")) return true;
-
- // For PropertyInvalidatedCache
- if (root.startsWith("cache_key.")) return true;
-
- return false;
+ return checkAllowed(key, sWritableKeys);
}
static boolean isKeyAccessible(String key, boolean write) {
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
index 19c1bffaebcd..3e2c4051b792 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
@@ -15,7 +15,20 @@
*/
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ReflectedMethod.reflectMethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+
import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.common.SneakyThrow;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
/**
* Utilities for writing (bivalent) ravenwood tests.
@@ -47,4 +60,129 @@ public class RavenwoodUtils {
public static void loadJniLibrary(String libname) {
RavenwoodCommonUtils.loadJniLibrary(libname);
}
+
+ private class MainHandlerHolder {
+ static Handler sMainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * Returns the main thread handler.
+ */
+ public static Handler getMainHandler() {
+ return MainHandlerHolder.sMainHandler;
+ }
+
+ /**
+ * Run a Callable on Handler and wait for it to complete.
+ */
+ @Nullable
+ public static <T> T runOnHandlerSync(@NonNull Handler h, @NonNull Callable<T> c) {
+ var result = new AtomicReference<T>();
+ var thrown = new AtomicReference<Throwable>();
+ var latch = new CountDownLatch(1);
+ h.post(() -> {
+ try {
+ result.set(c.call());
+ } catch (Throwable th) {
+ thrown.set(th);
+ }
+ latch.countDown();
+ });
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while waiting on the Runnable", e);
+ }
+ var th = thrown.get();
+ if (th != null) {
+ SneakyThrow.sneakyThrow(th);
+ }
+ return result.get();
+ }
+
+
+ /**
+ * Run a Runnable on Handler and wait for it to complete.
+ */
+ @Nullable
+ public static void runOnHandlerSync(@NonNull Handler h, @NonNull Runnable r) {
+ runOnHandlerSync(h, () -> {
+ r.run();
+ return null;
+ });
+ }
+
+ /**
+ * Run a Callable on main thread and wait for it to complete.
+ */
+ @Nullable
+ public static <T> T runOnMainThreadSync(@NonNull Callable<T> c) {
+ return runOnHandlerSync(getMainHandler(), c);
+ }
+
+ /**
+ * Run a Runnable on main thread and wait for it to complete.
+ */
+ @Nullable
+ public static void runOnMainThreadSync(@NonNull Runnable r) {
+ runOnHandlerSync(getMainHandler(), r);
+ }
+
+ public static class MockitoHelper {
+ private MockitoHelper() {
+ }
+
+ /**
+ * Allow verifyZeroInteractions to work on ravenwood. It was replaced with a different
+ * method on. (Maybe we should do it in Ravenizer.)
+ */
+ public static void verifyZeroInteractions(Object... mocks) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ // Mockito 4 or later
+ reflectMethod("org.mockito.Mockito", "verifyNoInteractions", Object[].class)
+ .callStatic(new Object[]{mocks});
+ } else {
+ // Mockito 2
+ reflectMethod("org.mockito.Mockito", "verifyZeroInteractions", Object[].class)
+ .callStatic(new Object[]{mocks});
+ }
+ }
+ }
+
+
+ /**
+ * Wrap the given {@link Supplier} to become memoized.
+ *
+ * The underlying {@link Supplier} will only be invoked once, and that result will be cached
+ * and returned for any future requests.
+ */
+ static <T> Supplier<T> memoize(ThrowingSupplier<T> supplier) {
+ return new Supplier<>() {
+ private T mInstance;
+
+ @Override
+ public T get() {
+ synchronized (this) {
+ if (mInstance == null) {
+ mInstance = create();
+ }
+ return mInstance;
+ }
+ }
+
+ private T create() {
+ try {
+ return supplier.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ }
+
+ /** Used by {@link #memoize(ThrowingSupplier)} */
+ public interface ThrowingSupplier<T> {
+ /** */
+ T get() throws Exception;
+ }
}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index a967a3fff0d7..893b354d4645 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -26,10 +26,12 @@ import java.io.FileInputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
+import java.util.Objects;
import java.util.function.Supplier;
public class RavenwoodCommonUtils {
@@ -329,4 +331,70 @@ public class RavenwoodCommonUtils {
public static <T> T withDefault(@Nullable T value, @Nullable T def) {
return value != null ? value : def;
}
+
+ /**
+ * Utility for calling a method with reflections. Used to call a method by name.
+ * Note, this intentionally does _not_ support non-public methods, as we generally
+ * shouldn't violate java visibility in ravenwood.
+ *
+ * @param <TTHIS> class owning the method.
+ */
+ public static class ReflectedMethod<TTHIS> {
+ private final Class<TTHIS> mThisClass;
+ private final Method mMethod;
+
+ private ReflectedMethod(Class<TTHIS> thisClass, Method method) {
+ mThisClass = thisClass;
+ mMethod = method;
+ }
+
+ /** Factory method. */
+ @SuppressWarnings("unchecked")
+ public static <TTHIS> ReflectedMethod<TTHIS> reflectMethod(
+ @NonNull Class<TTHIS> clazz, @NonNull String methodName,
+ @NonNull Class<?>... argTypes) {
+ try {
+ return new ReflectedMethod(clazz, clazz.getMethod(methodName, argTypes));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Factory method. */
+ @SuppressWarnings("unchecked")
+ public static <TTHIS> ReflectedMethod<TTHIS> reflectMethod(
+ @NonNull String className, @NonNull String methodName,
+ @NonNull Class<?>... argTypes) {
+ try {
+ return reflectMethod((Class<TTHIS>) Class.forName(className), methodName, argTypes);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Call the instance method */
+ @SuppressWarnings("unchecked")
+ public <RET> RET call(@NonNull TTHIS thisObject, @NonNull Object... args) {
+ try {
+ return (RET) mMethod.invoke(Objects.requireNonNull(thisObject), args);
+ } catch (InvocationTargetException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Call the static method */
+ @SuppressWarnings("unchecked")
+ public <RET> RET callStatic(@NonNull Object... args) {
+ try {
+ return (RET) mMethod.invoke(null, args);
+ } catch (InvocationTargetException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /** Handy method to create an array */
+ public static <T> T[] arr(@NonNull T... objects) {
+ return objects;
+ }
}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
index 7ab9cda378b7..855a4ff21671 100644
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
@@ -21,7 +21,6 @@ import android.util.Log.Level;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
@@ -164,7 +163,7 @@ public class Log_ravenwood {
* Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
* that we don't end up in a recursive loop.
*/
- private static PrintStream getRealOut() {
+ public static PrintStream getRealOut() {
if (RuntimeInit.sOut$ravenwood != null) {
return RuntimeInit.sOut$ravenwood;
} else {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index eaadac6a8b92..50cfd3bbe863 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -57,4 +57,12 @@ public class VMRuntime {
public int getTargetSdkVersion() {
return RavenwoodRuntimeState.sTargetSdkLevel;
}
+
+ /** Ignored on ravenwood. */
+ public void registerNativeAllocation(long bytes) {
+ }
+
+ /** Ignored on ravenwood. */
+ public void registerNativeFree(long bytes) {
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index cf1a5138cbc6..985e00e8641d 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -97,6 +97,9 @@ public class NativeAllocationRegistry {
if (referent == null) {
throw new IllegalArgumentException("referent is null");
}
+ if (mFreeFunction == 0) {
+ return () -> {}; // do nothing
+ }
if (nativePtr == 0) {
throw new IllegalArgumentException("nativePtr is null");
}
diff --git a/ravenwood/scripts/add-annotations.sh b/ravenwood/scripts/add-annotations.sh
index 3e86037d7c7b..8c394f51d8c4 100755
--- a/ravenwood/scripts/add-annotations.sh
+++ b/ravenwood/scripts/add-annotations.sh
@@ -35,7 +35,7 @@ set -e
# We add this line to each methods found.
# Note, if we used a single @, that'd be handled as an at file. Use
# the double-at instead.
-annotation="@@android.platform.test.annotations.DisabledOnRavenwood"
+annotation="@@android.platform.test.annotations.DisabledOnRavenwood(reason = \"bulk-disabled by script\")"
while getopts "t:" opt; do
case "$opt" in
t)
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
index 9dd7cc683719..182a7cf3d3de 100644
--- a/ravenwood/tests/coretest/Android.bp
+++ b/ravenwood/tests/coretest/Android.bp
@@ -33,3 +33,34 @@ android_ravenwood_test {
},
auto_gen_config: true,
}
+
+// Same as RavenwoodCoreTest, but it excludes tests using platform-parametric-runner-lib,
+// because that modules has too many dependencies and slow to build incrementally.
+android_ravenwood_test {
+ name: "RavenwoodCoreTest-light",
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+
+ // This library should be removed by Ravenizer
+ "mockito-target-minus-junit4",
+ ],
+ libs: [
+ // We access internal private classes
+ "ravenwood-junit-impl",
+ ],
+ srcs: [
+ "test/**/*.java",
+ "test/**/*.kt",
+ ],
+
+ exclude_srcs: [
+ "test/com/android/ravenwoodtest/runnercallbacktests/*",
+ ],
+ ravenizer: {
+ strip_mockito: true,
+ },
+ auto_gen_config: true,
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMainThreadTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMainThreadTest.java
new file mode 100644
index 000000000000..68387d76b675
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodMainThreadTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2025 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 com.android.ravenwoodtest.coretest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.ravenwood.RavenwoodUtils;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+public class RavenwoodMainThreadTest {
+ private static final boolean RUN_UNSAFE_TESTS =
+ "1".equals(System.getenv("RAVENWOOD_RUN_UNSAFE_TESTS"));
+
+ @Test
+ public void testRunOnMainThread() {
+ AtomicReference<Thread> thr = new AtomicReference<>();
+ RavenwoodUtils.runOnMainThreadSync(() -> {
+ thr.set(Thread.currentThread());
+ });
+ var th = thr.get();
+ assertThat(th).isNotNull();
+ assertThat(th).isNotEqualTo(Thread.currentThread());
+ }
+
+ /**
+ * Sleep a long time on the main thread. This test would then "pass", but Ravenwood
+ * should show the stack traces.
+ *
+ * This is "unsafe" because this test is slow.
+ */
+ @Test
+ public void testUnsafeMainThreadHang() {
+ assumeTrue(RUN_UNSAFE_TESTS);
+
+ // The test should time out.
+ RavenwoodUtils.runOnMainThreadSync(() -> {
+ try {
+ Thread.sleep(30_000);
+ } catch (InterruptedException e) {
+ fail("Interrupted");
+ }
+ });
+ }
+
+ /**
+ * AssertionError on the main thread would be swallowed and reported "normally".
+ * (Other kinds of exceptions would be caught by the unhandled exception handler, and kills
+ * the process)
+ *
+ * This is "unsafe" only because this feature can be disabled via the env var.
+ */
+ @Test
+ public void testUnsafeAssertFailureOnMainThread() {
+ assumeTrue(RUN_UNSAFE_TESTS);
+
+ assertThrows(AssertionError.class, () -> {
+ RavenwoodUtils.runOnMainThreadSync(() -> {
+ fail();
+ });
+ });
+ }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodReflectorTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodReflectorTest.java
new file mode 100644
index 000000000000..421fb50e0c9a
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodReflectorTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 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 com.android.ravenwoodtest.coretest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils.ReflectedMethod;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link ReflectedMethod}.
+ */
+public class RavenwoodReflectorTest {
+ /** test target */
+ public class Target {
+ private final int mVar;
+
+ /** test target */
+ public Target(int var) {
+ mVar = var;
+ }
+
+ /** test target */
+ public int foo(int x) {
+ return x + mVar;
+ }
+
+ /** test target */
+ public static int bar(int x) {
+ return x + 1;
+ }
+ }
+
+ /** Test for a non-static method call */
+ @Test
+ public void testNonStatic() {
+ var obj = new Target(5);
+
+ var m = ReflectedMethod.reflectMethod(Target.class, "foo", int.class);
+ assertThat((int) m.call(obj, 2)).isEqualTo(7);
+ }
+
+ /** Test for a static method call */
+ @Test
+ public void testStatic() {
+ var m = ReflectedMethod.reflectMethod(Target.class, "bar", int.class);
+ assertThat((int) m.callStatic(1)).isEqualTo(2);
+ }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodSystemPropertiesTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodSystemPropertiesTest.java
new file mode 100644
index 000000000000..454f5a9576d9
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodSystemPropertiesTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 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 com.android.ravenwoodtest.coretest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.os.SystemProperties;
+
+import org.junit.Test;
+
+public class RavenwoodSystemPropertiesTest {
+ @Test
+ public void testRead() {
+ assertThat(SystemProperties.get("ro.board.first_api_level")).isEqualTo("1");
+ }
+
+ @Test
+ public void testWrite() {
+ SystemProperties.set("debug.xxx", "5");
+ assertThat(SystemProperties.get("debug.xxx")).isEqualTo("5");
+ }
+
+ private static void assertException(String expectedMessage, Runnable r) {
+ try {
+ r.run();
+ fail("Excepted exception with message '" + expectedMessage + "' but wasn't thrown");
+ } catch (RuntimeException e) {
+ if (e.getMessage().contains(expectedMessage)) {
+ return;
+ }
+ fail("Excepted exception with message '" + expectedMessage + "' but was '"
+ + e.getMessage() + "'");
+ }
+ }
+
+
+ @Test
+ public void testReadDisallowed() {
+ assertException("Read access to system property 'nonexisitent' denied", () -> {
+ SystemProperties.get("nonexisitent");
+ });
+ }
+
+ @Test
+ public void testWriteDisallowed() {
+ assertException("failed to set system property \"ro.board.first_api_level\" ", () -> {
+ SystemProperties.set("ro.board.first_api_level", "2");
+ });
+ }
+}
diff --git a/ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java b/ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java
index 30abaa2e7d38..b1a40f082656 100644
--- a/ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java
+++ b/ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java
@@ -16,28 +16,27 @@
package com.android.ravenwoodtest;
import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
-import org.junit.Rule;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class RavenwoodMinimumTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
- .setProcessApp()
- .build();
-
@Test
public void testSimple() {
Assert.assertTrue(android.os.Process.isApplicationUid(android.os.Process.myUid()));
}
@Test
+ public void testAssumeNot() {
+ Assume.assumeFalse(android.os.Process.isApplicationUid(android.os.Process.myUid()));
+ }
+
+ @Test
@IgnoreUnderRavenwood
public void testIgnored() {
throw new RuntimeException("Shouldn't be executed under ravenwood");
diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop
index 37c50f11f73f..512b459113da 100644
--- a/ravenwood/texts/ravenwood-build.prop
+++ b/ravenwood/texts/ravenwood-build.prop
@@ -8,9 +8,41 @@ ro.soc.manufacturer=Android
ro.soc.model=Ravenwood
ro.debuggable=1
-# For the graphics stack
-ro.hwui.max_texture_allocation_size=104857600
persist.sys.locale=en-US
+ro.product.locale=en-US
+
+ro.hwui.max_texture_allocation_size=104857600
+
+# Allowlist control:
+# This set is carefully curated to help identify situations where a test may
+# accidentally depend on a default value of an obscure property whose owner hasn't
+# decided how Ravenwood should behave.
+
+boot.=$$$r
+build.=$$$r
+product.=$$$r
+soc.=$$$r
+system.=$$$r
+wm.debug.=$$$r
+wm.extensions.=$$$r
+
+gsm.version.baseband=$$$r
+no.such.thing=$$$r
+qemu.sf.lcd_density=$$$r
+ro.bootloader=$$$r
+ro.hardware=$$$r
+ro.hw_timeout_multiplier=$$$r
+ro.odm.build.media_performance_class=$$$r
+ro.sf.lcd_density=$$$r
+ro.treble.enabled=$$$r
+ro.vndk.version=$$$r
+ro.icu.data.path=$$$r
+
+# Writable keys
+debug.=$$$w
+
+# For PropertyInvalidatedCache
+cache_key.=$$$w
# The ones starting with "ro.product" or "ro.build" will be copied to all "partitions" too.
# See RavenwoodSystemProperties.
diff --git a/ravenwood/texts/ravenwood-services-jarjar-rules.txt b/ravenwood/texts/ravenwood-services-jarjar-rules.txt
index 8fdd3408f74d..64a0e2548e2e 100644
--- a/ravenwood/texts/ravenwood-services-jarjar-rules.txt
+++ b/ravenwood/texts/ravenwood-services-jarjar-rules.txt
@@ -5,7 +5,7 @@ rule com.android.server.pm.pkg.AndroidPackageSplit @0
# Rename all other service internals so that tests can continue to statically
# link services code when owners aren't ready to support on Ravenwood
-rule com.android.server.** repackaged.@0
+rule com.android.server.** repackaged.services.@0
# TODO: support AIDL generated Parcelables via hoststubgen
-rule android.hardware.power.stats.** repackaged.@0
+rule android.hardware.power.stats.** repackaged.services.@0