[Ravenwood] Cleanup RATR implementation and project structure
- Create a dedicated RATR implementation specifically for device-side
tests in junit-stub-src so we won't need to worry about device-side
compatibility when updating RATR.
- Enable Ravenizer on RavenwoodBivalentTest to ensure the "stub"
RATR is working as intended
- Because the real RATR is now in junit-impl-src, a lot of classes no
longer need to be in junit-stub-src, as junit-src is now a lot cleaner
- Remove all Ravenwood checks in RATR since the real one will always
only run on Ravenwood tests
- Remove RATRHook and move the hook callback methods directly in RATR
Flag: EXEMPT host test change only
Bug: 356918135
Test: atest RavenwoodBivalentTest_device_ravenizer
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh
Change-Id: I18577373833d8f6390bc685c23b857be65b904dc
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 9a5e623b..65ea9fe 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -170,7 +170,7 @@
"hoststubgen-helper-runtime.ravenwood",
"mockito-ravenwood-prebuilt",
],
- visibility: ["//frameworks/base"],
+ visibility: [":__subpackages__"],
jarjar_rules: ":ravenwood-services-jarjar-rules",
}
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 72f62c5..7fa0ef1 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,8 +5,8 @@
{ "name": "hoststubgen-test-tiny-test" },
{ "name": "hoststubgen-invoke-test" },
{ "name": "RavenwoodMockitoTest_device" },
- // TODO(b/371215487): Re-enable when the test is fixed.
- // { "name": "RavenwoodBivalentTest_device" },
+ { "name": "RavenwoodBivalentTest_device" },
+ { "name": "RavenwoodBivalentTest_device_ravenizer" },
{ "name": "RavenwoodBivalentInstTest_nonself_inst" },
{ "name": "RavenwoodBivalentInstTest_self_inst_device" },
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 0000000..30a653d
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,453 @@
+/*
+ * 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_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
+
+import static org.junit.Assert.fail;
+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.platform.test.ravenwood.RavenwoodTestStats.Result;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.util.function.BiConsumer;
+
+/**
+ * A test runner used for Ravenwood.
+ *
+ * It will delegate to another runner specified with {@link InnerRunner}
+ * (default = {@link BlockJUnit4ClassRunner}) with the following features.
+ * - Add a called before the inner runner gets a chance to run. This can be used to initialize
+ * stuff used by the inner runner.
+ * - Add hook points with help from the four test rules such as {@link #sImplicitClassOuterRule},
+ * which are also injected by the ravenizer tool.
+ *
+ * We use this runner to:
+ * - Initialize the Ravenwood environment.
+ * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
+ */
+public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
+ public static final String TAG = "Ravenwood";
+
+ /** Scope of a hook. */
+ public enum Scope {
+ Class,
+ Instance,
+ }
+
+ /** Order of a hook. */
+ public enum Order {
+ Outer,
+ Inner,
+ }
+
+ private record HookRule(Scope scope, Order order) implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return getCurrentRunner().wrapWithHooks(base, description, scope, order);
+ }
+ }
+
+ // The following four rule instances will be injected to tests by the Ravenizer tool.
+ public static final TestRule sImplicitClassOuterRule = new HookRule(Scope.Class, Order.Outer);
+ public static final TestRule sImplicitClassInnerRule = new HookRule(Scope.Class, Order.Inner);
+ public static final TestRule sImplicitInstOuterRule = new HookRule(Scope.Instance, Order.Outer);
+ public static final TestRule sImplicitInstInnerRule = new HookRule(Scope.Instance, Order.Inner);
+
+ /** Keeps track of the runner on the current thread. */
+ private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
+
+ static RavenwoodAwareTestRunner getCurrentRunner() {
+ var runner = sCurrentRunner.get();
+ if (runner == null) {
+ throw new RuntimeException("Current test runner not set!");
+ }
+ return runner;
+ }
+
+ private final Class<?> mTestJavaClass;
+ private TestClass mTestClass = null;
+ private Runner mRealRunner = null;
+ private Description mDescription = null;
+ private Throwable mExceptionInConstructor = null;
+
+ /**
+ * Stores internal states / methods associated with this runner that's only needed in
+ * junit-impl.
+ */
+ final RavenwoodRunnerState mState = new RavenwoodRunnerState(this);
+
+ public TestClass getTestClass() {
+ return mTestClass;
+ }
+
+ /**
+ * Constructor.
+ */
+ public RavenwoodAwareTestRunner(Class<?> testClass) {
+ RavenwoodRuntimeEnvironmentController.globalInitOnce();
+ mTestJavaClass = testClass;
+ try {
+ /*
+ * If the class has @DisabledOnRavenwood, then we'll delegate to
+ * ClassSkippingTestRunner, which simply skips it.
+ *
+ * We need to do it before instantiating TestClass for b/367694651.
+ */
+ if (!RavenwoodEnablementChecker.shouldRunClassOnRavenwood(testClass, true)) {
+ mRealRunner = new ClassSkippingTestRunner(testClass);
+ mDescription = mRealRunner.getDescription();
+ return;
+ }
+
+ mTestClass = new TestClass(testClass);
+
+ Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
+
+ onRunnerInitializing();
+
+ mRealRunner = instantiateRealRunner(mTestClass);
+ mDescription = mRealRunner.getDescription();
+ } catch (Throwable th) {
+ // If we throw in the constructor, Tradefed may not report it and just ignore the class,
+ // so record it and throw it when the test actually started.
+ Log.e(TAG, "Fatal: Exception detected in constructor", th);
+ mExceptionInConstructor = new RuntimeException("Exception detected in constructor",
+ th);
+ mDescription = Description.createTestDescription(testClass, "Constructor");
+
+ // This is for testing if tradefed is fixed.
+ if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) {
+ throw th;
+ }
+ }
+ }
+
+ @Override
+ Runner getRealRunner() {
+ return mRealRunner;
+ }
+
+ /**
+ * Run the bare minimum setup to initialize the wrapped runner.
+ */
+ // This method is called by the ctor, so never make it virtual.
+ private void onRunnerInitializing() {
+ // This is needed to make AndroidJUnit4ClassRunner happy.
+ InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+
+ // Hook point to allow more customization.
+ runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
+ }
+
+ private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
+ Object instance) {
+ Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+
+ for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
+ ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
+
+ var methodDesc = method.getDeclaringClass().getName() + "."
+ + method.getMethod().toString();
+ try {
+ method.getMethod().invoke(instance);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw logAndFail("Caught exception while running method " + methodDesc, e);
+ }
+ }
+ }
+
+ @Override
+ public Description getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public void run(RunNotifier realNotifier) {
+ final var notifier = new RavenwoodRunNotifier(realNotifier);
+ final var description = getDescription();
+
+ if (mRealRunner instanceof ClassSkippingTestRunner) {
+ mRealRunner.run(notifier);
+ Log.i(TAG, "onClassSkipped: description=" + description);
+ RavenwoodTestStats.getInstance().onClassSkipped(description);
+ return;
+ }
+
+ Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName());
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ dumpDescription(description);
+ }
+
+ if (maybeReportExceptionFromConstructor(notifier)) {
+ return;
+ }
+
+ // TODO(b/365976974): handle nested classes better
+ final boolean skipRunnerHook =
+ mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite;
+
+ sCurrentRunner.set(this);
+ try {
+ if (!skipRunnerHook) {
+ try {
+ mState.enterTestClass(description);
+ } catch (Throwable th) {
+ notifier.reportBeforeTestFailure(description, th);
+ return;
+ }
+ }
+
+ // Delegate to the inner runner.
+ mRealRunner.run(notifier);
+ } finally {
+ sCurrentRunner.remove();
+
+ if (!skipRunnerHook) {
+ try {
+ RavenwoodTestStats.getInstance().onClassFinished(description);
+ mState.exitTestClass();
+ } catch (Throwable th) {
+ notifier.reportAfterTestFailure(th);
+ }
+ }
+ }
+ }
+
+ /** Throw the exception detected in the constructor, if any. */
+ private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) {
+ if (mExceptionInConstructor == null) {
+ return false;
+ }
+ notifier.fireTestStarted(mDescription);
+ notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor));
+ notifier.fireTestFinished(mDescription);
+
+ return true;
+ }
+
+ private Statement wrapWithHooks(Statement base, Description description, Scope scope,
+ Order order) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ runWithHooks(description, scope, order, base);
+ }
+ };
+ }
+
+ private void runWithHooks(Description description, Scope scope, Order order, Statement s)
+ throws Throwable {
+ assumeTrue(onBefore(description, scope, order));
+ try {
+ s.evaluate();
+ onAfter(description, scope, order, null);
+ } catch (Throwable t) {
+ if (onAfter(description, scope, order, t)) {
+ throw t;
+ }
+ }
+ }
+
+ /**
+ * A runner that simply skips a class. It still has to support {@link Filterable}
+ * because otherwise the result still says "SKIPPED" even when it's not included in the
+ * filter.
+ */
+ private static class ClassSkippingTestRunner extends Runner implements Filterable {
+ private final Description mDescription;
+ private boolean mFilteredOut;
+
+ ClassSkippingTestRunner(Class<?> testClass) {
+ mDescription = Description.createTestDescription(testClass, testClass.getSimpleName());
+ mFilteredOut = false;
+ }
+
+ @Override
+ public Description getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (mFilteredOut) {
+ return;
+ }
+ notifier.fireTestSuiteStarted(mDescription);
+ notifier.fireTestIgnored(mDescription);
+ notifier.fireTestSuiteFinished(mDescription);
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (filter.shouldRun(mDescription)) {
+ mFilteredOut = false;
+ } else {
+ throw new NoTestsRemainException();
+ }
+ }
+ }
+
+ /**
+ * Called before a test / class.
+ *
+ * Return false if it should be skipped.
+ */
+ 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 = mState.getClassDescription();
+
+ // Class-level annotations are checked by the runner already, so we only check
+ // method-level annotations here.
+ if (scope == Scope.Instance && order == Order.Outer) {
+ if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
+ RavenwoodTestStats.getInstance().onTestFinished(
+ classDescription, description, Result.Skipped);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Called after a test / class.
+ *
+ * Return false if the exception should be ignored.
+ */
+ private boolean onAfter(Description description, Scope scope, Order order, Throwable th) {
+ Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+
+ final var classDescription = mState.getClassDescription();
+
+ if (scope == Scope.Instance && order == Order.Outer) {
+ // End of a test method.
+ mState.exitTestMethod();
+
+ final Result result;
+ if (th == null) {
+ result = Result.Passed;
+ } else if (th instanceof AssumptionViolatedException) {
+ result = Result.Skipped;
+ } else {
+ result = Result.Failed;
+ }
+
+ RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
+ }
+
+ // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
+ if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
+ && scope == Scope.Instance && order == Order.Outer) {
+
+ boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
+ description, false);
+ if (th == null) {
+ // Test passed. Is the test method supposed to be enabled?
+ if (isTestEnabled) {
+ // Enabled and didn't throw, okay.
+ return true;
+ } else {
+ // Disabled and didn't throw. We should report it.
+ fail("Test wasn't included under Ravenwood, but it actually "
+ + "passed under Ravenwood; consider updating annotations");
+ return true; // unreachable.
+ }
+ } else {
+ // Test failed.
+ if (isTestEnabled) {
+ // Enabled but failed. We should throw the exception.
+ return true;
+ } else {
+ // Disabled and failed. Expected. Don't throw.
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Called by RavenwoodRule.
+ */
+ static void onRavenwoodRuleEnter(Description description, RavenwoodRule rule) {
+ Log.v(TAG, "onRavenwoodRuleEnter: description=" + description);
+ getCurrentRunner().mState.enterRavenwoodRule(rule);
+ }
+
+ /**
+ * Called by RavenwoodRule.
+ */
+ static void onRavenwoodRuleExit(Description description, RavenwoodRule rule) {
+ Log.v(TAG, "onRavenwoodRuleExit: description=" + description);
+ getCurrentRunner().mState.exitRavenwoodRule(rule);
+ }
+
+ private void dumpDescription(Description desc) {
+ dumpDescription(desc, "[TestDescription]=", " ");
+ }
+
+ private void dumpDescription(Description desc, String header, String indent) {
+ Log.v(TAG, indent + header + desc);
+
+ var children = desc.getChildren();
+ var childrenIndent = " " + indent;
+ for (int i = 0; i < children.size(); i++) {
+ dumpDescription(children.get(i), "#" + i + ": ", childrenIndent);
+ }
+ }
+
+ static volatile BiConsumer<String, Throwable> sCriticalErrorHandler = null;
+
+ static void onCriticalError(@NonNull String message, @Nullable Throwable th) {
+ Log.e(TAG, "Critical error! " + message, th);
+ var handler = sCriticalErrorHandler;
+ if (handler == null) {
+ Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th);
+ System.exit(1);
+ }
+ handler.accept(message, th);
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
deleted file mode 100644
index e0f9ec9..0000000
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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 org.junit.Assert.fail;
-
-import android.os.Bundle;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.runner.Description;
-import org.junit.runners.model.TestClass;
-
-/**
- * Provide hook points created by {@link RavenwoodAwareTestRunner}.
- *
- * States are associated with each {@link RavenwoodAwareTestRunner} are stored in
- * {@link RavenwoodRunnerState}, rather than as members of {@link RavenwoodAwareTestRunner}.
- * See its javadoc for the reasons.
- *
- * All methods in this class must be called from the test main thread.
- */
-public class RavenwoodAwareTestRunnerHook {
- private static final String TAG = RavenwoodAwareTestRunner.TAG;
-
- private RavenwoodAwareTestRunnerHook() {
- }
-
- /**
- * Called before any code starts. Internally it will only initialize the environment once.
- */
- public static void performGlobalInitialization() {
- RavenwoodRuntimeEnvironmentController.globalInitOnce();
- }
-
- /**
- * Called when a runner starts, before the inner runner gets a chance to run.
- */
- public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
- Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass()
- + " runner=" + runner);
-
- // This is needed to make AndroidJUnit4ClassRunner happy.
- InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
- }
-
- /**
- * Called when a whole test class is skipped.
- */
- public static void onClassSkipped(Description description) {
- Log.i(TAG, "onClassSkipped: description=" + description);
- RavenwoodTestStats.getInstance().onClassSkipped(description);
- }
-
- /**
- * Called before the inner runner starts.
- */
- public static void onBeforeInnerRunnerStart(
- RavenwoodAwareTestRunner runner, Description description) throws Throwable {
- Log.v(TAG, "onBeforeInnerRunnerStart: description=" + description);
-
- // Prepare the environment before the inner runner starts.
- runner.mState.enterTestClass(description);
- }
-
- /**
- * Called after the inner runner finished.
- */
- public static void onAfterInnerRunnerFinished(
- RavenwoodAwareTestRunner runner, Description description) throws Throwable {
- Log.v(TAG, "onAfterInnerRunnerFinished: description=" + description);
-
- RavenwoodTestStats.getInstance().onClassFinished(description);
- runner.mState.exitTestClass();
- }
-
- /**
- * Called before a test / class.
- *
- * Return false if it should be skipped.
- */
- public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
- Scope scope, Order order) throws Throwable {
- Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
-
- if (scope == Scope.Instance && order == Order.Outer) {
- // Start of a test method.
- runner.mState.enterTestMethod(description);
- }
-
- final var classDescription = runner.mState.getClassDescription();
-
- // Class-level annotations are checked by the runner already, so we only check
- // method-level annotations here.
- if (scope == Scope.Instance && order == Order.Outer) {
- if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(
- description, true)) {
- RavenwoodTestStats.getInstance().onTestFinished(
- classDescription, description, Result.Skipped);
- return false;
- }
- }
- return true;
- }
-
- /**
- * Called after a test / class.
- *
- * Return false if the exception should be ignored.
- */
- public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
- Scope scope, Order order, Throwable th) {
- Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
-
- final var classDescription = runner.mState.getClassDescription();
-
- if (scope == Scope.Instance && order == Order.Outer) {
- // End of a test method.
- runner.mState.exitTestMethod();
-
- final Result result;
- if (th == null) {
- result = Result.Passed;
- } else if (th instanceof AssumptionViolatedException) {
- result = Result.Skipped;
- } else {
- result = Result.Failed;
- }
-
- RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
- }
-
- // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
- if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
- && scope == Scope.Instance && order == Order.Outer) {
-
- boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
- description, false);
- if (th == null) {
- // Test passed. Is the test method supposed to be enabled?
- if (isTestEnabled) {
- // Enabled and didn't throw, okay.
- return true;
- } else {
- // Disabled and didn't throw. We should report it.
- fail("Test wasn't included under Ravenwood, but it actually "
- + "passed under Ravenwood; consider updating annotations");
- return true; // unreachable.
- }
- } else {
- // Test failed.
- if (isTestEnabled) {
- // Enabled but failed. We should throw the exception.
- return true;
- } else {
- // Disabled and failed. Expected. Don't throw.
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Called by {@link RavenwoodAwareTestRunner} to see if it should run a test class or not.
- */
- public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
- return RavenwoodEnablementChecker.shouldRunClassOnRavenwood(clazz, true);
- }
-
- /**
- * Called by RavenwoodRule.
- */
- public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner,
- Description description, RavenwoodRule rule) throws Throwable {
- Log.v(TAG, "onRavenwoodRuleEnter: description=" + description);
-
- runner.mState.enterRavenwoodRule(rule);
- }
-
-
- /**
- * Called by RavenwoodRule.
- */
- public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner,
- Description description, RavenwoodRule rule) throws Throwable {
- Log.v(TAG, "onRavenwoodRuleExit: description=" + description);
-
- runner.mState.exitRavenwoodRule(rule);
- }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java
new file mode 100644
index 0000000..ffb642d
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java
@@ -0,0 +1,36 @@
+/*
+ * 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 android.annotation.Nullable;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Contains Ravenwood private APIs.
+ */
+public class RavenwoodConfigPrivate {
+ private RavenwoodConfigPrivate() {
+ }
+
+ /**
+ * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call
+ * System.exit().
+ */
+ public static void setCriticalErrorHandler(@Nullable BiConsumer<String, Throwable> handler) {
+ RavenwoodAwareTestRunner.sCriticalErrorHandler = handler;
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java
new file mode 100644
index 0000000..6903035
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java
@@ -0,0 +1,227 @@
+/*
+ * 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 android.util.Log;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runner.notification.StoppedByUserException;
+import org.junit.runners.model.MultipleFailureException;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * A run notifier that wraps another notifier and provides the following features:
+ * - Handle a failure that happened before testStarted and testEnded (typically that means
+ * it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if
+ * individual tests in the class reported it. This is for b/364395552.
+ *
+ * - Logging.
+ */
+class RavenwoodRunNotifier extends RunNotifier {
+ private final RunNotifier mRealNotifier;
+
+ private final Stack<Description> mSuiteStack = new Stack<>();
+ private Description mCurrentSuite = null;
+ private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>();
+
+ private boolean mBeforeTest = true;
+ private boolean mAfterTest = false;
+
+ RavenwoodRunNotifier(RunNotifier realNotifier) {
+ mRealNotifier = realNotifier;
+ }
+
+ private boolean isInTest() {
+ return !mBeforeTest && !mAfterTest;
+ }
+
+ @Override
+ public void addListener(RunListener listener) {
+ mRealNotifier.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(RunListener listener) {
+ mRealNotifier.removeListener(listener);
+ }
+
+ @Override
+ public void addFirstListener(RunListener listener) {
+ mRealNotifier.addFirstListener(listener);
+ }
+
+ @Override
+ public void fireTestRunStarted(Description description) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testRunStarted: " + description);
+ mRealNotifier.fireTestRunStarted(description);
+ }
+
+ @Override
+ public void fireTestRunFinished(Result result) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testRunFinished: "
+ + result.getRunCount() + ","
+ + result.getFailureCount() + ","
+ + result.getAssumptionFailureCount() + ","
+ + result.getIgnoreCount());
+ mRealNotifier.fireTestRunFinished(result);
+ }
+
+ @Override
+ public void fireTestSuiteStarted(Description description) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteStarted: " + description);
+ mRealNotifier.fireTestSuiteStarted(description);
+
+ mBeforeTest = true;
+ mAfterTest = false;
+
+ // Keep track of the current suite, needed if the outer test is a Suite,
+ // in which case its children are test classes. (not test methods)
+ mCurrentSuite = description;
+ mSuiteStack.push(description);
+
+ mOutOfTestFailures.clear();
+ }
+
+ @Override
+ public void fireTestSuiteFinished(Description description) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteFinished: " + description);
+ mRealNotifier.fireTestSuiteFinished(description);
+
+ maybeHandleOutOfTestFailures();
+
+ mBeforeTest = true;
+ mAfterTest = false;
+
+ // Restore the upper suite.
+ mSuiteStack.pop();
+ mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek();
+ }
+
+ @Override
+ public void fireTestStarted(Description description) throws StoppedByUserException {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testStarted: " + description);
+ mRealNotifier.fireTestStarted(description);
+
+ mAfterTest = false;
+ mBeforeTest = false;
+ }
+
+ @Override
+ public void fireTestFailure(Failure failure) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testFailure: " + failure);
+
+ if (isInTest()) {
+ mRealNotifier.fireTestFailure(failure);
+ } else {
+ mOutOfTestFailures.add(failure.getException());
+ }
+ }
+
+ @Override
+ public void fireTestAssumptionFailed(Failure failure) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testAssumptionFailed: " + failure);
+
+ if (isInTest()) {
+ mRealNotifier.fireTestAssumptionFailed(failure);
+ } else {
+ mOutOfTestFailures.add(failure.getException());
+ }
+ }
+
+ @Override
+ public void fireTestIgnored(Description description) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testIgnored: " + description);
+ mRealNotifier.fireTestIgnored(description);
+ }
+
+ @Override
+ public void fireTestFinished(Description description) {
+ Log.i(RavenwoodAwareTestRunner.TAG, "testFinished: " + description);
+ mRealNotifier.fireTestFinished(description);
+
+ mAfterTest = true;
+ }
+
+ @Override
+ public void pleaseStop() {
+ Log.w(RavenwoodAwareTestRunner.TAG, "pleaseStop:");
+ mRealNotifier.pleaseStop();
+ }
+
+ /**
+ * At the end of each Suite, we handle failures happened out of test methods.
+ * (typically in @BeforeClass or @AfterClasses)
+ *
+ * This is to work around b/364395552.
+ */
+ private boolean maybeHandleOutOfTestFailures() {
+ if (mOutOfTestFailures.size() == 0) {
+ return false;
+ }
+ Throwable th;
+ if (mOutOfTestFailures.size() == 1) {
+ th = mOutOfTestFailures.get(0);
+ } else {
+ th = new MultipleFailureException(mOutOfTestFailures);
+ }
+ if (mBeforeTest) {
+ reportBeforeTestFailure(mCurrentSuite, th);
+ return true;
+ }
+ if (mAfterTest) {
+ reportAfterTestFailure(th);
+ return true;
+ }
+ return false;
+ }
+
+ public void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
+ // If a failure happens befere running any tests, we'll need to pretend
+ // as if each test in the suite reported the failure, to work around b/364395552.
+ for (var child : suiteDesc.getChildren()) {
+ if (child.isSuite()) {
+ // If the chiil is still a "parent" -- a test class or a test suite
+ // -- propagate to its children.
+ mRealNotifier.fireTestSuiteStarted(child);
+ reportBeforeTestFailure(child, th);
+ mRealNotifier.fireTestSuiteFinished(child);
+ } else {
+ mRealNotifier.fireTestStarted(child);
+ Failure f = new Failure(child, th);
+ if (th instanceof AssumptionViolatedException) {
+ mRealNotifier.fireTestAssumptionFailed(f);
+ } else {
+ mRealNotifier.fireTestFailure(f);
+ }
+ mRealNotifier.fireTestFinished(child);
+ }
+ }
+ }
+
+ public void reportAfterTestFailure(Throwable th) {
+ // Unfortunately, there's no good way to report it, so kill the own process.
+ RavenwoodAwareTestRunner.onCriticalError(
+ "Failures detected in @AfterClass, which would be swallowed by tradefed",
+ th);
+ }
+}
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 03513ab..ead4a84 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.fail;
import android.annotation.Nullable;
+import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
import com.android.ravenwood.common.RavenwoodRuntimeException;
import org.junit.ClassRule;
@@ -29,9 +29,7 @@
import org.junit.rules.TestRule;
import org.junit.runner.Description;
-import java.io.IOException;
import java.lang.reflect.Field;
-import java.util.WeakHashMap;
/**
* Used to store various states associated with the current test runner that's inly needed
@@ -45,10 +43,6 @@
public final class RavenwoodRunnerState {
private static final String TAG = "RavenwoodRunnerState";
- @GuardedBy("sStates")
- private static final WeakHashMap<RavenwoodAwareTestRunner, RavenwoodRunnerState> sStates =
- new WeakHashMap<>();
-
private final RavenwoodAwareTestRunner mRunner;
/**
@@ -69,7 +63,8 @@
return mClassDescription;
}
- public void enterTestClass(Description classDescription) throws IOException {
+ public void enterTestClass(Description classDescription) {
+ Log.i(TAG, "enterTestClass: description=" + classDescription);
mClassDescription = classDescription;
mHasRavenwoodRule = hasRavenwoodRule(mRunner.getTestClass().getJavaClass());
@@ -81,6 +76,7 @@
}
public void exitTestClass() {
+ Log.i(TAG, "exitTestClass: description=" + mClassDescription);
if (mCurrentConfig != null) {
try {
RavenwoodRuntimeEnvironmentController.reset();
@@ -98,7 +94,7 @@
mMethodDescription = null;
}
- public void enterRavenwoodRule(RavenwoodRule rule) throws IOException {
+ public void enterRavenwoodRule(RavenwoodRule rule) {
if (!mHasRavenwoodRule) {
fail("If you have a RavenwoodRule in your test, make sure the field type is"
+ " RavenwoodRule so Ravenwood can detect it.");
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 c2806da..de4357c 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -191,7 +191,7 @@
/**
* Initialize the environment.
*/
- public static void init(RavenwoodConfig config) throws IOException {
+ public static void init(RavenwoodConfig config) {
if (RAVENWOOD_VERBOSE_LOGGING) {
Log.i(TAG, "init() called here", new RuntimeException("STACKTRACE"));
}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java b/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java
new file mode 100644
index 0000000..3bba27a
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java
@@ -0,0 +1,32 @@
+/*
+ * 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.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation similar to JUnit's BeforeClass, but this gets executed before
+ * the inner runner is instantiated, and only on Ravenwood.
+ * It can be used to initialize what's needed by the inner runner.
+ */
+@Target({METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RavenwoodTestRunnerInitializing {
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java
new file mode 100644
index 0000000..dde53a5
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java
@@ -0,0 +1,32 @@
+/*
+ * 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.annotations.internal;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import org.junit.runner.Runner;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Target({TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InnerRunner {
+ Class<? extends Runner> value();
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
deleted file mode 100644
index 5ba972df..0000000
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ /dev/null
@@ -1,733 +0,0 @@
-/*
- * 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_VERBOSE_LOGGING;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Log;
-
-import org.junit.Assume;
-import org.junit.AssumptionViolatedException;
-import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.Result;
-import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.InvalidOrderingException;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.manipulation.Orderable;
-import org.junit.runner.manipulation.Orderer;
-import org.junit.runner.manipulation.Sortable;
-import org.junit.runner.manipulation.Sorter;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runner.notification.StoppedByUserException;
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.Suite;
-import org.junit.runners.model.MultipleFailureException;
-import org.junit.runners.model.RunnerBuilder;
-import org.junit.runners.model.Statement;
-import org.junit.runners.model.TestClass;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Stack;
-import java.util.function.BiConsumer;
-
-/**
- * A test runner used for Ravenwood.
- *
- * It will delegate to another runner specified with {@link InnerRunner}
- * (default = {@link BlockJUnit4ClassRunner}) with the following features.
- * - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before
- * the inner runner gets a chance to run. This can be used to initialize stuff used by the
- * inner runner.
- * - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from
- * the four test rules such as {@link #sImplicitClassOuterRule}, which are also injected by
- * the ravenizer tool.
- *
- * We use this runner to:
- * - Initialize the bare minimum environmnet just to be enough to make the actual test runners
- * happy.
- * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
- *
- * This class is built such that it can also be used on a real device, but in that case
- * it will basically just delegate to the inner wrapper, and won't do anything special.
- * (no hooks, etc.)
- */
-public final class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
- public static final String TAG = "Ravenwood";
-
- @Inherited
- @Target({TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface InnerRunner {
- Class<? extends Runner> value();
- }
-
- /**
- * An annotation similar to JUnit's BeforeClass, but this gets executed before
- * the inner runner is instantiated, and only on Ravenwood.
- * It can be used to initialize what's needed by the inner runner.
- */
- @Target({METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface RavenwoodTestRunnerInitializing {
- }
-
- /** Scope of a hook. */
- public enum Scope {
- Class,
- Instance,
- }
-
- /** Order of a hook. */
- public enum Order {
- Outer,
- Inner,
- }
-
- // The following four rule instances will be injected to tests by the Ravenizer tool.
- private static class RavenwoodClassOuterRule implements TestRule {
- @Override
- public Statement apply(Statement base, Description description) {
- return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Outer);
- }
- }
-
- private static class RavenwoodClassInnerRule implements TestRule {
- @Override
- public Statement apply(Statement base, Description description) {
- return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Inner);
- }
- }
-
- private static class RavenwoodInstanceOuterRule implements TestRule {
- @Override
- public Statement apply(Statement base, Description description) {
- return getCurrentRunner().wrapWithHooks(
- base, description, Scope.Instance, Order.Outer);
- }
- }
-
- private static class RavenwoodInstanceInnerRule implements TestRule {
- @Override
- public Statement apply(Statement base, Description description) {
- return getCurrentRunner().wrapWithHooks(
- base, description, Scope.Instance, Order.Inner);
- }
- }
-
- public static final TestRule sImplicitClassOuterRule = new RavenwoodClassOuterRule();
- public static final TestRule sImplicitClassInnerRule = new RavenwoodClassInnerRule();
- public static final TestRule sImplicitInstOuterRule = new RavenwoodInstanceOuterRule();
- public static final TestRule sImplicitInstInnerRule = new RavenwoodInstanceOuterRule();
-
- public static final String IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule";
- public static final String IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule";
- public static final String IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule";
- public static final String IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule";
-
- /** Keeps track of the runner on the current thread. */
- private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
-
- static RavenwoodAwareTestRunner getCurrentRunner() {
- var runner = sCurrentRunner.get();
- if (runner == null) {
- throw new RuntimeException("Current test runner not set!");
- }
- return runner;
- }
-
- private final Class<?> mTestJavaClass;
- private TestClass mTestClass = null;
- private Runner mRealRunner = null;
- private Description mDescription = null;
- private Throwable mExceptionInConstructor = null;
- private boolean mRealRunnerTakesRunnerBuilder = false;
-
- /**
- * Stores internal states / methods associated with this runner that's only needed in
- * junit-impl.
- */
- final RavenwoodRunnerState mState = new RavenwoodRunnerState(this);
-
- private Error logAndFail(String message, Throwable exception) {
- Log.e(TAG, message, exception);
- throw new AssertionError(message, exception);
- }
-
- public TestClass getTestClass() {
- return mTestClass;
- }
-
- /**
- * Constructor.
- */
- public RavenwoodAwareTestRunner(Class<?> testClass) {
- mTestJavaClass = testClass;
- try {
- performGlobalInitialization();
-
- /*
- * If the class has @DisabledOnRavenwood, then we'll delegate to
- * ClassSkippingTestRunner, which simply skips it.
- *
- * We need to do it before instantiating TestClass for b/367694651.
- */
- if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
- testClass)) {
- mRealRunner = new ClassSkippingTestRunner(testClass);
- mDescription = mRealRunner.getDescription();
- return;
- }
-
- mTestClass = new TestClass(testClass);
-
- Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
-
- onRunnerInitializing();
-
- // Find the real runner.
- final Class<? extends Runner> realRunnerClass;
- final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class);
- if (innerRunnerAnnotation != null) {
- realRunnerClass = innerRunnerAnnotation.value();
- } else {
- // Default runner.
- realRunnerClass = BlockJUnit4ClassRunner.class;
- }
-
- try {
- Log.i(TAG, "Initializing the inner runner: " + realRunnerClass);
-
- mRealRunner = instantiateRealRunner(realRunnerClass, testClass);
- mDescription = mRealRunner.getDescription();
-
- } catch (InstantiationException | IllegalAccessException
- | InvocationTargetException | NoSuchMethodException e) {
- throw logAndFail("Failed to instantiate " + realRunnerClass, e);
- }
- } catch (Throwable th) {
- // If we throw in the constructor, Tradefed may not report it and just ignore the class,
- // so record it and throw it when the test actually started.
- Log.e(TAG, "Fatal: Exception detected in constructor", th);
- mExceptionInConstructor = new RuntimeException("Exception detected in constructor",
- th);
- mDescription = Description.createTestDescription(testClass, "Constructor");
-
- // This is for testing if tradefed is fixed.
- if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) {
- throw th;
- }
- }
- }
-
- private Runner instantiateRealRunner(
- Class<? extends Runner> realRunnerClass,
- Class<?> testClass)
- throws NoSuchMethodException, InvocationTargetException, InstantiationException,
- IllegalAccessException {
- try {
- return realRunnerClass.getConstructor(Class.class).newInstance(testClass);
- } catch (NoSuchMethodException e) {
- var constructor = realRunnerClass.getConstructor(Class.class, RunnerBuilder.class);
- mRealRunnerTakesRunnerBuilder = true;
- return constructor.newInstance(testClass, new AllDefaultPossibilitiesBuilder());
- }
- }
-
- private void performGlobalInitialization() {
- if (!isOnRavenwood()) {
- return;
- }
- RavenwoodAwareTestRunnerHook.performGlobalInitialization();
- }
-
- /**
- * Run the bare minimum setup to initialize the wrapped runner.
- */
- // This method is called by the ctor, so never make it virtual.
- private void onRunnerInitializing() {
- if (!isOnRavenwood()) {
- return;
- }
-
- RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass);
-
- // Hook point to allow more customization.
- runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
- }
-
- private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
- Object instance) {
- if (!isOnRavenwood()) {
- return;
- }
- Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
-
- for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
- ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
-
- var methodDesc = method.getDeclaringClass().getName() + "."
- + method.getMethod().toString();
- try {
- method.getMethod().invoke(instance);
- } catch (IllegalAccessException | InvocationTargetException e) {
- throw logAndFail("Caught exception while running method " + methodDesc, e);
- }
- }
- }
-
- @Override
- public Description getDescription() {
- return mDescription;
- }
-
- @Override
- public void run(RunNotifier realNotifier) {
- final RavenwoodRunNotifier notifier = new RavenwoodRunNotifier(realNotifier);
-
- if (mRealRunner instanceof ClassSkippingTestRunner) {
- mRealRunner.run(notifier);
- RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription());
- return;
- }
-
- Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName());
- if (RAVENWOOD_VERBOSE_LOGGING) {
- dumpDescription(getDescription());
- }
-
- if (maybeReportExceptionFromConstructor(notifier)) {
- return;
- }
-
- // TODO(b/365976974): handle nested classes better
- final boolean skipRunnerHook =
- mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite;
-
- sCurrentRunner.set(this);
- try {
- if (!skipRunnerHook) {
- try {
- RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart(
- this, getDescription());
- } catch (Throwable th) {
- notifier.reportBeforeTestFailure(getDescription(), th);
- return;
- }
- }
-
- // Delegate to the inner runner.
- mRealRunner.run(notifier);
- } finally {
- sCurrentRunner.remove();
-
- if (!skipRunnerHook) {
- try {
- RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished(
- this, getDescription());
- } catch (Throwable th) {
- notifier.reportAfterTestFailure(th);
- }
- }
- }
- }
-
- /** Throw the exception detected in the constructor, if any. */
- private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) {
- if (mExceptionInConstructor == null) {
- return false;
- }
- notifier.fireTestStarted(mDescription);
- notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor));
- notifier.fireTestFinished(mDescription);
-
- return true;
- }
-
- @Override
- public void filter(Filter filter) throws NoTestsRemainException {
- if (mRealRunner instanceof Filterable r) {
- r.filter(filter);
- }
- }
-
- @Override
- public void order(Orderer orderer) throws InvalidOrderingException {
- if (mRealRunner instanceof Orderable r) {
- r.order(orderer);
- }
- }
-
- @Override
- public void sort(Sorter sorter) {
- if (mRealRunner instanceof Sortable r) {
- r.sort(sorter);
- }
- }
-
- private Statement wrapWithHooks(Statement base, Description description, Scope scope,
- Order order) {
- if (!isOnRavenwood()) {
- return base;
- }
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- runWithHooks(description, scope, order, base);
- }
- };
- }
-
- private void runWithHooks(Description description, Scope scope, Order order, Runnable r)
- throws Throwable {
- runWithHooks(description, scope, order, new Statement() {
- @Override
- public void evaluate() throws Throwable {
- r.run();
- }
- });
- }
-
- private void runWithHooks(Description description, Scope scope, Order order, Statement s)
- throws Throwable {
- if (isOnRavenwood()) {
- Assume.assumeTrue(
- RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
- }
- try {
- s.evaluate();
- if (isOnRavenwood()) {
- RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, null);
- }
- } catch (Throwable t) {
- boolean shouldThrow = true;
- if (isOnRavenwood()) {
- shouldThrow = RavenwoodAwareTestRunnerHook.onAfter(
- this, description, scope, order, t);
- }
- if (shouldThrow) {
- throw t;
- }
- }
- }
-
- /**
- * A runner that simply skips a class. It still has to support {@link Filterable}
- * because otherwise the result still says "SKIPPED" even when it's not included in the
- * filter.
- */
- private static class ClassSkippingTestRunner extends Runner implements Filterable {
- private final Description mDescription;
- private boolean mFilteredOut;
-
- ClassSkippingTestRunner(Class<?> testClass) {
- mDescription = Description.createTestDescription(testClass, testClass.getSimpleName());
- mFilteredOut = false;
- }
-
- @Override
- public Description getDescription() {
- return mDescription;
- }
-
- @Override
- public void run(RunNotifier notifier) {
- if (mFilteredOut) {
- return;
- }
- notifier.fireTestSuiteStarted(mDescription);
- notifier.fireTestIgnored(mDescription);
- notifier.fireTestSuiteFinished(mDescription);
- }
-
- @Override
- public void filter(Filter filter) throws NoTestsRemainException {
- if (filter.shouldRun(mDescription)) {
- mFilteredOut = false;
- } else {
- throw new NoTestsRemainException();
- }
- }
- }
-
- private void dumpDescription(Description desc) {
- dumpDescription(desc, "[TestDescription]=", " ");
- }
-
- private void dumpDescription(Description desc, String header, String indent) {
- Log.v(TAG, indent + header + desc);
-
- var children = desc.getChildren();
- var childrenIndent = " " + indent;
- for (int i = 0; i < children.size(); i++) {
- dumpDescription(children.get(i), "#" + i + ": ", childrenIndent);
- }
- }
-
- /**
- * A run notifier that wraps another notifier and provides the following features:
- * - Handle a failure that happened before testStarted and testEnded (typically that means
- * it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if
- * individual tests in the class reported it. This is for b/364395552.
- *
- * - Logging.
- */
- private class RavenwoodRunNotifier extends RunNotifier {
- private final RunNotifier mRealNotifier;
-
- private final Stack<Description> mSuiteStack = new Stack<>();
- private Description mCurrentSuite = null;
- private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>();
-
- private boolean mBeforeTest = true;
- private boolean mAfterTest = false;
-
- private RavenwoodRunNotifier(RunNotifier realNotifier) {
- mRealNotifier = realNotifier;
- }
-
- private boolean isInTest() {
- return !mBeforeTest && !mAfterTest;
- }
-
- @Override
- public void addListener(RunListener listener) {
- mRealNotifier.addListener(listener);
- }
-
- @Override
- public void removeListener(RunListener listener) {
- mRealNotifier.removeListener(listener);
- }
-
- @Override
- public void addFirstListener(RunListener listener) {
- mRealNotifier.addFirstListener(listener);
- }
-
- @Override
- public void fireTestRunStarted(Description description) {
- Log.i(TAG, "testRunStarted: " + description);
- mRealNotifier.fireTestRunStarted(description);
- }
-
- @Override
- public void fireTestRunFinished(Result result) {
- Log.i(TAG, "testRunFinished: "
- + result.getRunCount() + ","
- + result.getFailureCount() + ","
- + result.getAssumptionFailureCount() + ","
- + result.getIgnoreCount());
- mRealNotifier.fireTestRunFinished(result);
- }
-
- @Override
- public void fireTestSuiteStarted(Description description) {
- Log.i(TAG, "testSuiteStarted: " + description);
- mRealNotifier.fireTestSuiteStarted(description);
-
- mBeforeTest = true;
- mAfterTest = false;
-
- // Keep track of the current suite, needed if the outer test is a Suite,
- // in which case its children are test classes. (not test methods)
- mCurrentSuite = description;
- mSuiteStack.push(description);
-
- mOutOfTestFailures.clear();
- }
-
- @Override
- public void fireTestSuiteFinished(Description description) {
- Log.i(TAG, "testSuiteFinished: " + description);
- mRealNotifier.fireTestSuiteFinished(description);
-
- maybeHandleOutOfTestFailures();
-
- mBeforeTest = true;
- mAfterTest = false;
-
- // Restore the upper suite.
- mSuiteStack.pop();
- mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek();
- }
-
- @Override
- public void fireTestStarted(Description description) throws StoppedByUserException {
- Log.i(TAG, "testStarted: " + description);
- mRealNotifier.fireTestStarted(description);
-
- mAfterTest = false;
- mBeforeTest = false;
- }
-
- @Override
- public void fireTestFailure(Failure failure) {
- Log.i(TAG, "testFailure: " + failure);
-
- if (isInTest()) {
- mRealNotifier.fireTestFailure(failure);
- } else {
- mOutOfTestFailures.add(failure.getException());
- }
- }
-
- @Override
- public void fireTestAssumptionFailed(Failure failure) {
- Log.i(TAG, "testAssumptionFailed: " + failure);
-
- if (isInTest()) {
- mRealNotifier.fireTestAssumptionFailed(failure);
- } else {
- mOutOfTestFailures.add(failure.getException());
- }
- }
-
- @Override
- public void fireTestIgnored(Description description) {
- Log.i(TAG, "testIgnored: " + description);
- mRealNotifier.fireTestIgnored(description);
- }
-
- @Override
- public void fireTestFinished(Description description) {
- Log.i(TAG, "testFinished: " + description);
- mRealNotifier.fireTestFinished(description);
-
- mAfterTest = true;
- }
-
- @Override
- public void pleaseStop() {
- Log.w(TAG, "pleaseStop:");
- mRealNotifier.pleaseStop();
- }
-
- /**
- * At the end of each Suite, we handle failures happened out of test methods.
- * (typically in @BeforeClass or @AfterClasses)
- *
- * This is to work around b/364395552.
- */
- private boolean maybeHandleOutOfTestFailures() {
- if (mOutOfTestFailures.size() == 0) {
- return false;
- }
- Throwable th;
- if (mOutOfTestFailures.size() == 1) {
- th = mOutOfTestFailures.get(0);
- } else {
- th = new MultipleFailureException(mOutOfTestFailures);
- }
- if (mBeforeTest) {
- reportBeforeTestFailure(mCurrentSuite, th);
- return true;
- }
- if (mAfterTest) {
- reportAfterTestFailure(th);
- return true;
- }
- return false;
- }
-
- public void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
- // If a failure happens befere running any tests, we'll need to pretend
- // as if each test in the suite reported the failure, to work around b/364395552.
- for (var child : suiteDesc.getChildren()) {
- if (child.isSuite()) {
- // If the chiil is still a "parent" -- a test class or a test suite
- // -- propagate to its children.
- mRealNotifier.fireTestSuiteStarted(child);
- reportBeforeTestFailure(child, th);
- mRealNotifier.fireTestSuiteFinished(child);
- } else {
- mRealNotifier.fireTestStarted(child);
- Failure f = new Failure(child, th);
- if (th instanceof AssumptionViolatedException) {
- mRealNotifier.fireTestAssumptionFailed(f);
- } else {
- mRealNotifier.fireTestFailure(f);
- }
- mRealNotifier.fireTestFinished(child);
- }
- }
- }
-
- public void reportAfterTestFailure(Throwable th) {
- // Unfortunately, there's no good way to report it, so kill the own process.
- onCriticalError(
- "Failures detected in @AfterClass, which would be swallowed by tradefed",
- th);
- }
- }
-
- private static volatile BiConsumer<String, Throwable> sCriticalErrorHanler;
-
- private void onCriticalError(@NonNull String message, @Nullable Throwable th) {
- Log.e(TAG, "Critical error! " + message, th);
- var handler = sCriticalErrorHanler;
- if (handler == null) {
- handler = sDefaultCriticalErrorHandler;
- }
- handler.accept(message, th);
- }
-
- private static BiConsumer<String, Throwable> sDefaultCriticalErrorHandler = (message, th) -> {
- Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th);
- System.exit(1);
- };
-
- /**
- * Contains Ravenwood private APIs.
- */
- public static class RavenwoodPrivate {
- private RavenwoodPrivate() {
- }
-
- /**
- * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call
- * System.exit().
- */
- public void setCriticalErrorHandler(
- @Nullable BiConsumer<String, Throwable> handler) {
- sCriticalErrorHanler = handler;
- }
- }
-
- private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate();
-
- public static RavenwoodPrivate private$ravenwood() {
- return sRavenwoodPrivate;
- }
-}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
new file mode 100644
index 0000000..7c72f6b
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
@@ -0,0 +1,101 @@
+/*
+ * 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 android.platform.test.annotations.internal.InnerRunner;
+import android.util.Log;
+
+import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.InvalidOrderingException;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Orderable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.RunnerBuilder;
+import org.junit.runners.model.TestClass;
+
+abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable, Orderable {
+ private static final String TAG = "Ravenwood";
+
+ boolean mRealRunnerTakesRunnerBuilder = false;
+
+ abstract Runner getRealRunner();
+
+ final Runner instantiateRealRunner(TestClass testClass) {
+ // Find the real runner.
+ final Class<? extends Runner> runnerClass;
+ final InnerRunner innerRunnerAnnotation = testClass.getAnnotation(InnerRunner.class);
+ if (innerRunnerAnnotation != null) {
+ runnerClass = innerRunnerAnnotation.value();
+ } else {
+ // Default runner.
+ runnerClass = BlockJUnit4ClassRunner.class;
+ }
+
+ try {
+ Log.i(TAG, "Initializing the inner runner: " + runnerClass);
+ try {
+ return runnerClass.getConstructor(Class.class)
+ .newInstance(testClass.getJavaClass());
+ } catch (NoSuchMethodException e) {
+ var constructor = runnerClass.getConstructor(Class.class, RunnerBuilder.class);
+ mRealRunnerTakesRunnerBuilder = true;
+ return constructor.newInstance(
+ testClass.getJavaClass(), new AllDefaultPossibilitiesBuilder());
+ }
+ } catch (ReflectiveOperationException e) {
+ throw logAndFail("Failed to instantiate " + runnerClass, e);
+ }
+ }
+
+ final Error logAndFail(String message, Throwable exception) {
+ Log.e(TAG, message, exception);
+ return new AssertionError(message, exception);
+ }
+
+ @Override
+ public Description getDescription() {
+ return getRealRunner().getDescription();
+ }
+
+ @Override
+ public final void filter(Filter filter) throws NoTestsRemainException {
+ if (getRealRunner() instanceof Filterable r) {
+ r.filter(filter);
+ }
+ }
+
+ @Override
+ public final void order(Orderer orderer) throws InvalidOrderingException {
+ if (getRealRunner() instanceof Orderable r) {
+ r.order(orderer);
+ }
+ }
+
+ @Override
+ public final void sort(Sorter sorter) {
+ if (getRealRunner() instanceof Sortable r) {
+ r.sort(sorter);
+ }
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 93a6806..773dba1 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -242,15 +242,11 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
- RavenwoodAwareTestRunnerHook.onRavenwoodRuleEnter(
- RavenwoodAwareTestRunner.getCurrentRunner(), description,
- RavenwoodRule.this);
+ RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this);
try {
base.evaluate();
} finally {
- RavenwoodAwareTestRunnerHook.onRavenwoodRuleExit(
- RavenwoodAwareTestRunner.getCurrentRunner(), description,
- RavenwoodRule.this);
+ RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this);
}
}
};
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 0000000..b4b75178
--- /dev/null
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+/**
+ * A simple pass-through runner that just delegates to the inner runner without doing
+ * anything special (no hooks, etc.).
+ *
+ * This is only used when a real device-side test has Ravenizer enabled.
+ */
+public class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
+ private static final String TAG = "Ravenwood";
+
+ private static class NopRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return base;
+ }
+ }
+
+ public static final TestRule sImplicitClassOuterRule = new NopRule();
+ public static final TestRule sImplicitClassInnerRule = sImplicitClassOuterRule;
+ public static final TestRule sImplicitInstOuterRule = sImplicitClassOuterRule;
+ public static final TestRule sImplicitInstInnerRule = sImplicitClassOuterRule;
+
+ private final Runner mRealRunner;
+
+ public RavenwoodAwareTestRunner(Class<?> clazz) {
+ Log.v(TAG, "RavenwoodAwareTestRunner starting for " + clazz.getCanonicalName());
+ mRealRunner = instantiateRealRunner(new TestClass(clazz));
+ }
+
+ @Override
+ Runner getRealRunner() {
+ return mRealRunner;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ mRealRunner.run(notifier);
+ }
+
+ static void onRavenwoodRuleEnter(Description description, RavenwoodRule rule) {
+ }
+
+ static void onRavenwoodRuleExit(Description description, RavenwoodRule rule) {
+ }
+}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
deleted file mode 100644
index aa8c299..0000000
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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 android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
-
-import org.junit.runner.Description;
-import org.junit.runners.model.TestClass;
-
-/**
- * Provide hook points created by {@link RavenwoodAwareTestRunner}. This is a version
- * that's used on a device side test.
- *
- * All methods are no-op in real device tests.
- *
- * TODO: Use some kind of factory to provide different implementation for the device test
- * and the ravenwood test.
- */
-public class RavenwoodAwareTestRunnerHook {
- private RavenwoodAwareTestRunnerHook() {
- }
-
- /**
- * Called before any code starts. Internally it will only initialize the environment once.
- */
- public static void performGlobalInitialization() {
- }
-
- /**
- * Called when a runner starts, before the inner runner gets a chance to run.
- */
- public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
- }
-
- /**
- * Called when a whole test class is skipped.
- */
- public static void onClassSkipped(Description description) {
- }
-
- /**
- * Called before the inner runner starts.
- */
- public static void onBeforeInnerRunnerStart(
- RavenwoodAwareTestRunner runner, Description description) throws Throwable {
- }
-
- /**
- * Called after the inner runner finished.
- */
- public static void onAfterInnerRunnerFinished(
- RavenwoodAwareTestRunner runner, Description description) throws Throwable {
- }
-
- /**
- * Called before a test / class.
- *
- * Return false if it should be skipped.
- */
- public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
- Scope scope, Order order) throws Throwable {
- return true;
- }
-
- public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner,
- Description description, RavenwoodRule rule) throws Throwable {
- }
-
- public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner,
- Description description, RavenwoodRule rule) throws Throwable {
- }
-
-
- /**
- * Called after a test / class.
- *
- * Return false if the exception should be ignored.
- */
- public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
- Scope scope, Order order, Throwable th) {
- return true;
- }
-
- public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
- return true;
- }
-}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
index 43a28ba..7d3d8b9 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
@@ -15,7 +15,7 @@
*/
package android.platform.test.ravenwood;
-/** Stub class. The actual implementaetion is in junit-impl-src. */
+/** Stub class. The actual implementation is in junit-impl-src. */
public class RavenwoodConfigState {
public RavenwoodConfigState(RavenwoodConfig config) {
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
deleted file mode 100644
index 83cbc52..0000000
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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;
-
-/** Stub class. The actual implementaetion is in junit-impl-src. */
-public class RavenwoodRunnerState {
- public RavenwoodRunnerState(RavenwoodAwareTestRunner runner) {
- }
-}
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index d7f4b3e..ce0033d 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -31,9 +31,8 @@
],
}
-android_ravenwood_test {
- name: "RavenwoodBivalentTest",
-
+java_defaults {
+ name: "ravenwood-bivalent-defaults",
static_libs: [
"androidx.annotation_annotation",
"androidx.test.ext.junit",
@@ -51,15 +50,11 @@
jni_libs: [
"libravenwoodbivalenttest_jni",
],
- auto_gen_config: true,
}
-android_test {
- name: "RavenwoodBivalentTest_device",
-
- srcs: [
- "test/**/*.java",
- ],
+java_defaults {
+ name: "ravenwood-bivalent-device-defaults",
+ defaults: ["ravenwood-bivalent-defaults"],
// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
exclude_srcs: [
"test/**/ravenizer/*.java",
@@ -67,23 +62,32 @@
static_libs: [
"junit",
"truth",
-
- "androidx.annotation_annotation",
- "androidx.test.ext.junit",
- "androidx.test.rules",
-
- "junit-params",
- "platform-parametric-runner-lib",
-
"ravenwood-junit",
],
- jni_libs: [
- "libravenwoodbivalenttest_jni",
- ],
test_suites: [
"device-tests",
],
optimize: {
enabled: false,
},
+ test_config_template: "AndroidTestTemplate.xml",
+}
+
+android_ravenwood_test {
+ name: "RavenwoodBivalentTest",
+ defaults: ["ravenwood-bivalent-defaults"],
+ auto_gen_config: true,
+}
+
+android_test {
+ name: "RavenwoodBivalentTest_device",
+ defaults: ["ravenwood-bivalent-device-defaults"],
+}
+
+android_test {
+ name: "RavenwoodBivalentTest_device_ravenizer",
+ defaults: ["ravenwood-bivalent-device-defaults"],
+ ravenizer: {
+ enabled: true,
+ },
}
diff --git a/ravenwood/tests/bivalenttest/AndroidTest.xml b/ravenwood/tests/bivalenttest/AndroidTestTemplate.xml
similarity index 94%
rename from ravenwood/tests/bivalenttest/AndroidTest.xml
rename to ravenwood/tests/bivalenttest/AndroidTestTemplate.xml
index 9e5dd11..8f1a92c 100644
--- a/ravenwood/tests/bivalenttest/AndroidTest.xml
+++ b/ravenwood/tests/bivalenttest/AndroidTestTemplate.xml
@@ -19,7 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="RavenwoodBivalentTest_device.apk" />
+ <option name="test-file-name" value="{MODULE}.apk"/>
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
index d7c2c6c..637f069 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertFalse;
import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
index 9d878f4..77a807d 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
@@ -16,7 +16,7 @@
package com.android.ravenwoodtest.bivalenttest.ravenizer;
import android.platform.test.annotations.NoRavenizer;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import org.junit.Test;
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
index c77841b..e6e617b 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.fail;
import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
index ea1a29d..ef18c82 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.fail;
import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
index 412744e..9dd7cc6 100644
--- a/ravenwood/tests/coretest/Android.bp
+++ b/ravenwood/tests/coretest/Android.bp
@@ -16,11 +16,14 @@
"androidx.test.rules",
"junit-params",
"platform-parametric-runner-lib",
- "truth",
// 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",
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
index 9a6934b..f7a2198 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
@@ -20,6 +20,7 @@
import android.platform.test.annotations.NoRavenizer;
import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
+import android.platform.test.ravenwood.RavenwoodConfigPrivate;
import android.util.Log;
import junitparams.JUnitParamsRunner;
@@ -137,15 +138,14 @@
// Set a listener to critical errors. This will also prevent
// {@link RavenwoodAwareTestRunner} from calling System.exit() when there's
// a critical error.
- RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(
- listener.sCriticalErrorListener);
+ RavenwoodConfigPrivate.setCriticalErrorHandler(listener.sCriticalErrorListener);
try {
// Run the test class.
junitCore.run(testClazz);
} finally {
// Clear the critical error listener.
- RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(null);
+ RavenwoodConfigPrivate.setCriticalErrorHandler(null);
}
// Check the result.
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 37a7975..6092fcc 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,6 +15,7 @@
*/
package com.android.platform.test.ravenwood.ravenizer
+import android.platform.test.annotations.internal.InnerRunner
import android.platform.test.annotations.NoRavenizer
import android.platform.test.ravenwood.RavenwoodAwareTestRunner
import com.android.hoststubgen.asm.ClassNodes
@@ -39,7 +40,7 @@
val ruleAnotType = TypeHolder(org.junit.Rule::class.java)
val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java)
val runWithAnotType = TypeHolder(RunWith::class.java)
-val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java)
+val innerRunnerAnotType = TypeHolder(InnerRunner::class.java)
val noRavenizerAnotType = TypeHolder(NoRavenizer::class.java)
val testRuleType = TypeHolder(TestRule::class.java)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
index cf6d6f6..81fe3da 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -15,7 +15,6 @@
*/
package com.android.platform.test.ravenwood.ravenizer.adapter
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner
import com.android.hoststubgen.ClassParseException
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
@@ -28,8 +27,8 @@
import com.android.hoststubgen.visitors.OPCODE_VERSION
import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException
import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType
-import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
import com.android.platform.test.ravenwood.ravenizer.noRavenizerAnotType
import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType
import com.android.platform.test.ravenwood.ravenizer.ruleAnotType
@@ -50,7 +49,7 @@
* Class visitor to update the RunWith and inject some necessary rules.
*
* - Change the @RunWith(RavenwoodAwareTestRunner.class).
- * - If the original class has a @RunWith(...), then change it to an @OrigRunWith(...).
+ * - If the original class has a @RunWith(...), then change it to an @InnerRunner(...).
* - Add RavenwoodAwareTestRunner's member rules as junit rules.
* - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX.
*/
@@ -146,7 +145,7 @@
/**
* Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has
- * a `@RunWith`, then change it to add a `@OrigRunWith`.
+ * a `@RunWith`, then change it to add a `@InnerRunner`.
*/
private fun injectRunWithAnnotation() {
// Extract the original RunWith annotation and its value.
@@ -172,7 +171,7 @@
+ " in class ${classInternalName.toHumanReadableClassName()}")
}
- // Inject an @OrigRunWith.
+ // Inject an @InnerRunner.
visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av ->
av.visit("value", runWithClass)
av.visitEnd()
@@ -302,7 +301,7 @@
override fun visitCode() {
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_CLASS_OUTER_RULE_NAME,
+ IMPLICIT_CLASS_OUTER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTSTATIC,
@@ -313,7 +312,7 @@
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_CLASS_INNER_RULE_NAME,
+ IMPLICIT_CLASS_INNER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTSTATIC,
@@ -361,7 +360,7 @@
visitVarInsn(ALOAD, 0)
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_INST_OUTER_RULE_NAME,
+ IMPLICIT_INST_OUTER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTFIELD,
@@ -373,7 +372,7 @@
visitVarInsn(ALOAD, 0)
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_INST_INNER_RULE_NAME,
+ IMPLICIT_INST_INNER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTFIELD,
@@ -435,6 +434,11 @@
}
companion object {
+ const val IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule"
+ const val IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule"
+ const val IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule"
+ const val IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule"
+
fun shouldProcess(classes: ClassNodes, className: String): Boolean {
if (!isTestLookingClass(classes, className)) {
return false
@@ -463,4 +467,4 @@
}
}
}
-}
\ No newline at end of file
+}