diff options
author | 2024-10-29 18:17:14 +0000 | |
---|---|---|
committer | 2024-10-29 18:17:14 +0000 | |
commit | 62caa8ac6108914504e968cdf44f41fa2ce44f60 (patch) | |
tree | ccb2caf934122b38a60373aa3bdfa444755142d6 /ravenwood | |
parent | cf4fd8c4b5d5eb14eeccfe5ffbbcabcd61dc203d (diff) | |
parent | 768fc687a818cde61fde6f34c0c750fd13d2db77 (diff) |
Merge "[Ravenwood] Cleanup RATR implementation and project structure" into main
Diffstat (limited to 'ravenwood')
26 files changed, 999 insertions, 1109 deletions
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 9a5e623b6d45..65ea9fe3a496 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -170,7 +170,7 @@ java_library { "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 72f62c5c29ee..7fa0ef114c82 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 000000000000..30a653d2da76 --- /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 e0f9ec94a819..000000000000 --- 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 000000000000..ffb642d60dcc --- /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 000000000000..69030350bf82 --- /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 03513ab0a2af..ead4a849dcff 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 com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMe 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.Rule; 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 @@ import java.util.WeakHashMap; 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 @@ public final class RavenwoodRunnerState { 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 final class RavenwoodRunnerState { } public void exitTestClass() { + Log.i(TAG, "exitTestClass: description=" + mClassDescription); if (mCurrentConfig != null) { try { RavenwoodRuntimeEnvironmentController.reset(); @@ -98,7 +94,7 @@ public final class RavenwoodRunnerState { 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 c2806daf99a1..de4357c4e7c5 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 @@ public class RavenwoodRuntimeEnvironmentController { /** * 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 000000000000..3bba27ab8f1d --- /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-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java index 83cbc5265d8f..dde53a5ea03c 100644 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java +++ b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java @@ -13,10 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.platform.test.ravenwood; +package android.platform.test.annotations.internal; -/** Stub class. The actual implementaetion is in junit-impl-src. */ -public class RavenwoodRunnerState { - public RavenwoodRunnerState(RavenwoodAwareTestRunner runner) { - } +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 5ba972df1193..000000000000 --- 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 000000000000..7c72f6bd8619 --- /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 93a6806ed1f4..773dba1b4620 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 @@ public final class RavenwoodRule implements TestRule { 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 000000000000..b4b751788c6e --- /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 aa8c29936082..000000000000 --- 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 43a28ba72ec9..7d3d8b9f7974 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/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp index d7f4b3e2955d..ce0033d02910 100644 --- a/ravenwood/tests/bivalenttest/Android.bp +++ b/ravenwood/tests/bivalenttest/Android.bp @@ -31,9 +31,8 @@ cc_library_shared { ], } -android_ravenwood_test { - name: "RavenwoodBivalentTest", - +java_defaults { + name: "ravenwood-bivalent-defaults", static_libs: [ "androidx.annotation_annotation", "androidx.test.ext.junit", @@ -51,15 +50,11 @@ android_ravenwood_test { 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 @@ android_test { 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 index 9e5dd11b60cb..8f1a92c0ae17 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 d7c2c6cd73a8..637f06910d55 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 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; 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 9d878f444e5e..77a807d5e1e5 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 c77841b1b55a..e6e617b401b6 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 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; 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 ea1a29d57482..ef18c82b6d79 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 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; 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 412744eb9d34..9dd7cc683719 100644 --- a/ravenwood/tests/coretest/Android.bp +++ b/ravenwood/tests/coretest/Android.bp @@ -16,11 +16,14 @@ android_ravenwood_test { "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 9a6934bf17c5..f7a2198a9bc4 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 static com.google.common.truth.Truth.assertWithMessage; 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 @@ public abstract class RavenwoodRunnerTestBase { // 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 37a797528e13..6092fcc9402d 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 testAnotType = TypeHolder(org.junit.Test::class.java) 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 cf6d6f6bcae3..81fe3da8d954 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.log 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 @@ import org.objectweb.asm.tree.ClassNode * 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 @@ class RunnerRewritingAdapter private constructor( /** * 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 @@ class RunnerRewritingAdapter private constructor( + " 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 @@ class RunnerRewritingAdapter private constructor( 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 @@ class RunnerRewritingAdapter private constructor( 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 @@ class RunnerRewritingAdapter private constructor( 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 @@ class RunnerRewritingAdapter private constructor( 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 @@ class RunnerRewritingAdapter private constructor( } 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 @@ class RunnerRewritingAdapter private constructor( } } } -}
\ No newline at end of file +} |