From 983461633b96db0bc58205a657edeffad3ce4080 Mon Sep 17 00:00:00 2001 From: John Wu Date: Thu, 26 Sep 2024 22:59:40 +0000 Subject: Cherry-pick Ravenwood "core" code - Copied f/b/r and f/b/t/h - Ported files under f/b/core, only what needed to for run-ravenwood-tests.sh to pass - Local changes because of missing resoucres support - Added @DisabledOnRavenwood(reason="AOSP is missing resources support") to tests under f/b/r that depends on resources bivalentinst and servicestest - Added try-catch around ResourcesManager.setInstance() Flag: EXEMPT host test change only Bug: 292141694 Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh Merged-in: I8a9b8374be3ae052ba4f152eb43af20d0871597f Change-Id: Iefd574dbded8c4ab2e244c4918c26641364a3432 --- .../test/annotations/DisabledOnNonRavenwood.java | 51 -- .../platform/test/annotations/NoRavenizer.java | 36 + .../test/ravenwood/RavenwoodAwareTestRunner.java | 733 +++++++++++++++++++++ .../test/ravenwood/RavenwoodClassRule.java | 30 +- .../platform/test/ravenwood/RavenwoodConfig.java | 217 ++++++ .../platform/test/ravenwood/RavenwoodRule.java | 341 +++------- .../test/ravenwood/RavenwoodSystemProperties.java | 2 + 7 files changed, 1098 insertions(+), 312 deletions(-) delete mode 100644 ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java create mode 100644 ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java create mode 100644 ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java create mode 100644 ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java (limited to 'ravenwood/junit-src') diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java deleted file mode 100644 index 2fb8074c850a..000000000000 --- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java +++ /dev/null @@ -1,51 +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.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Tests marked with this annotation are only executed when running on Ravenwood, but not - * on a device. - * - * This is basically equivalent to the opposite of {@link DisabledOnRavenwood}, but in order to - * avoid complex structure, and there's no equivalent to the opposite {@link EnabledOnRavenwood}, - * which means if a test class has this annotation, you can't negate it in subclasses or - * on a per-method basis. - * - * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT. - * See {@link com.android.ravenwoodtest.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest} - * for the reason. - * - * The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be - * propagated to the device. (We may support it in the future, possibly using a debug. sysprop.) - * - * @hide - */ -@Inherited -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface DisabledOnNonRavenwood { - /** - * General free-form description of why this test is being ignored. - */ - String reason() default ""; -} diff --git a/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java new file mode 100644 index 000000000000..a84f16f619d3 --- /dev/null +++ b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.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.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Disable the ravenizer preprocessor for a class. This should be only used for testing + * ravenizer itself, or to workaround issues with the preprocessor. A test class probably won't run + * properly if it's not preprocessed. + * + * @hide + */ +@Inherited +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoRavenizer { +} diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java new file mode 100644 index 000000000000..5ba972df1193 --- /dev/null +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -0,0 +1,733 @@ +/* + * 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 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 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 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 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 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 mSuiteStack = new Stack<>(); + private Description mCurrentSuite = null; + private final ArrayList 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 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 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 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/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java index f4b7ec360dbf..85297fe96d6a 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java @@ -16,42 +16,20 @@ package android.platform.test.ravenwood; -import static android.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED; -import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD; -import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnDevice; -import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood; -import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInProbeIgnoreMode; - -import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.annotations.EnabledOnRavenwood; - -import org.junit.Assert; -import org.junit.Assume; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** - * {@code @ClassRule} that respects Ravenwood-specific class annotations. This rule has no effect - * when tests are run on non-Ravenwood test environments. + * No longer needed. * - * By default, all tests are executed on Ravenwood, but annotations such as - * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method - * and class level to "ignore" tests that may not be ready. + * @deprecated this class used to be used to handle the class level annotation, which + * is now done by the test runner, so this class is not needed. */ +@Deprecated public class RavenwoodClassRule implements TestRule { @Override public Statement apply(Statement base, Description description) { - if (!IS_ON_RAVENWOOD) { - // This should be "Assume", not Assert, but if we use assume here, the device side - // test runner would complain. - // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest. - Assert.assertTrue(shouldEnableOnDevice(description)); - } else if (ENABLE_PROBE_IGNORED) { - Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description)); - } else { - Assume.assumeTrue(shouldEnableOnRavenwood(description)); - } return base; } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java new file mode 100644 index 000000000000..446f819ad41b --- /dev/null +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -0,0 +1,217 @@ +/* + * 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 android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.SYSTEM_UID; +import static android.os.UserHandle.SYSTEM; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Instrumentation; +import android.content.Context; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Represents how to configure the ravenwood environment for a test class. + * + * If a ravenwood test class has a public static field with the {@link Config} annotation, + * Ravenwood will extract the config from it and initializes the environment. The type of the + * field must be of {@link RavenwoodConfig}. + */ +public final class RavenwoodConfig { + /** + * Use this to mark a field as the configuration. + * @hide + */ + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Config { + } + + private static final int NOBODY_UID = 9999; + + private static final AtomicInteger sNextPid = new AtomicInteger(100); + + int mCurrentUser = SYSTEM.getIdentifier(); + + /** + * Unless the test author requests differently, run as "nobody", and give each collection of + * tests its own unique PID. + */ + int mUid = NOBODY_UID; + int mPid = sNextPid.getAndIncrement(); + + String mTestPackageName; + String mTargetPackageName; + + int mMinSdkLevel; + + boolean mProvideMainThread = false; + + final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); + + final List> mServicesRequired = new ArrayList<>(); + + volatile Context mInstContext; + volatile Context mTargetContext; + volatile Instrumentation mInstrumentation; + + /** + * Stores internal states / methods associated with this config that's only needed in + * junit-impl. + */ + final RavenwoodConfigState mState = new RavenwoodConfigState(this); + private RavenwoodConfig() { + } + + /** + * Return if the current process is running on a Ravenwood test environment. + */ + public static boolean isOnRavenwood() { + return RavenwoodRule.isOnRavenwood(); + } + + private void setDefaults() { + if (mTargetPackageName == null) { + mTargetPackageName = mTestPackageName; + } + } + + public static class Builder { + private final RavenwoodConfig mConfig = new RavenwoodConfig(); + + public Builder() { + } + + /** + * Configure the identity of this process to be the system UID for the duration of the + * test. Has no effect on non-Ravenwood environments. + */ + public Builder setProcessSystem() { + mConfig.mUid = SYSTEM_UID; + return this; + } + + /** + * Configure the identity of this process to be an app UID for the duration of the + * test. Has no effect on non-Ravenwood environments. + */ + public Builder setProcessApp() { + mConfig.mUid = FIRST_APPLICATION_UID; + return this; + } + + /** + * Configure the package name of the test, which corresponds to + * {@link Instrumentation#getContext()}. + */ + public Builder setPackageName(@NonNull String packageName) { + mConfig.mTestPackageName = Objects.requireNonNull(packageName); + return this; + } + + /** + * Configure the package name of the target app, which corresponds to + * {@link Instrumentation#getTargetContext()}. Defaults to {@link #setPackageName}. + */ + public Builder setTargetPackageName(@NonNull String packageName) { + mConfig.mTargetPackageName = Objects.requireNonNull(packageName); + return this; + } + + /** + * Configure the min SDK level of the test. + */ + public Builder setMinSdkLevel(int sdkLevel) { + mConfig.mMinSdkLevel = sdkLevel; + return this; + } + + /** + * Configure a "main" thread to be available for the duration of the test, as defined + * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. + */ + public Builder setProvideMainThread(boolean provideMainThread) { + mConfig.mProvideMainThread = provideMainThread; + return this; + } + + /** + * Configure the given system property as immutable for the duration of the test. + * Read access to the key is allowed, and write access will fail. When {@code value} is + * {@code null}, the value is left as undefined. + * + * All properties in the {@code debug.*} namespace are automatically mutable, with no + * developer action required. + * + * Has no effect on non-Ravenwood environments. + */ + public Builder setSystemPropertyImmutable(@NonNull String key, + @Nullable Object value) { + mConfig.mSystemProperties.setValue(key, value); + mConfig.mSystemProperties.setAccessReadOnly(key); + return this; + } + + /** + * Configure the given system property as mutable for the duration of the test. + * Both read and write access to the key is allowed, and its value will be reset between + * each test. When {@code value} is {@code null}, the value is left as undefined. + * + * All properties in the {@code debug.*} namespace are automatically mutable, with no + * developer action required. + * + * Has no effect on non-Ravenwood environments. + */ + public Builder setSystemPropertyMutable(@NonNull String key, + @Nullable Object value) { + mConfig.mSystemProperties.setValue(key, value); + mConfig.mSystemProperties.setAccessReadWrite(key); + return this; + } + + /** + * Configure the set of system services that are required for this test to operate. + * + * For example, passing {@code android.hardware.SerialManager.class} as an argument will + * ensure that the underlying service is created, initialized, and ready to use for the + * duration of the test. The {@code SerialManager} instance can be obtained via + * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and + * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}. + */ + public Builder setServicesRequired(@NonNull Class... services) { + mConfig.mServicesRequired.clear(); + for (Class service : services) { + mConfig.mServicesRequired.add(service); + } + return this; + } + + public RavenwoodConfig build() { + mConfig.setDefaults(); + return mConfig; + } + } +} diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 825c91a1b6dc..4196d8e22610 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -16,56 +16,43 @@ package android.platform.test.ravenwood; -import static android.os.Process.FIRST_APPLICATION_UID; -import static android.os.Process.SYSTEM_UID; -import static android.os.UserHandle.SYSTEM; - -import static org.junit.Assert.fail; +import static com.android.ravenwood.common.RavenwoodCommonUtils.log; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.annotations.DisabledOnNonRavenwood; import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.annotations.EnabledOnRavenwood; -import android.platform.test.annotations.IgnoreUnderRavenwood; import com.android.ravenwood.common.RavenwoodCommonUtils; -import org.junit.Assume; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; /** - * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when - * tests are run on non-Ravenwood test environments. - * - * This rule initializes and resets the Ravenwood environment between each test method to offer a - * hermetic testing environment. - * - * By default, all tests are executed on Ravenwood, but annotations such as - * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method - * and class level to "ignore" tests that may not be ready. When needed, a - * {@link RavenwoodClassRule} can be used in addition to a {@link RavenwoodRule} to ignore tests - * before a test class is fully initialized. + * @deprecated Use {@link RavenwoodConfig} to configure the ravenwood environment instead. + * A {@link RavenwoodRule} is no longer needed for {@link DisabledOnRavenwood}. To get the + * {@link Context} and {@link Instrumentation}, use + * {@link androidx.test.platform.app.InstrumentationRegistry} instead. */ -public class RavenwoodRule implements TestRule { +@Deprecated +public final class RavenwoodRule implements TestRule { + private static final String TAG = "RavenwoodRule"; + static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood(); /** - * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect - * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}. + * When this flag is enabled, all tests will be unconditionally run on Ravenwood to detect + * cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}. * * This is typically helpful for internal maintainers discovering tests that had previously * been ignored, but now have enough Ravenwood-supported functionality to be enabled. */ - static final boolean ENABLE_PROBE_IGNORED = "1".equals( + private static final boolean RUN_DISABLED_TESTS = "1".equals( System.getenv("RAVENWOOD_RUN_DISABLED_TESTS")); /** @@ -90,56 +77,34 @@ public class RavenwoodRule implements TestRule { * * Because we use a regex-find, setting "." would disable all tests. */ - private static final Pattern REALLY_DISABLE_PATTERN = Pattern.compile( - Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLE"), "")); + private static final Pattern REALLY_DISABLED_PATTERN = Pattern.compile( + Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLED"), "")); - private static final boolean ENABLE_REALLY_DISABLE_PATTERN = - !REALLY_DISABLE_PATTERN.pattern().isEmpty(); - - /** - * If true, enable optional validation on running tests. - */ - private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals( - System.getenv("RAVENWOOD_OPTIONAL_VALIDATION")); + private static final boolean HAS_REALLY_DISABLE_PATTERN = + !REALLY_DISABLED_PATTERN.pattern().isEmpty(); static { - if (ENABLE_PROBE_IGNORED) { - System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests"); - if (ENABLE_REALLY_DISABLE_PATTERN) { - System.out.println("$RAVENWOOD_REALLY_DISABLE=" + REALLY_DISABLE_PATTERN.pattern()); + if (RUN_DISABLED_TESTS) { + log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests"); + if (HAS_REALLY_DISABLE_PATTERN) { + log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern()); } } } - private static final int NOBODY_UID = 9999; - - private static final AtomicInteger sNextPid = new AtomicInteger(100); - - int mCurrentUser = SYSTEM.getIdentifier(); - - /** - * Unless the test author requests differently, run as "nobody", and give each collection of - * tests its own unique PID. - */ - int mUid = NOBODY_UID; - int mPid = sNextPid.getAndIncrement(); - - String mPackageName; - - boolean mProvideMainThread = false; - - final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); - - final List> mServicesRequired = new ArrayList<>(); - - volatile Context mContext; - volatile Instrumentation mInstrumentation; + private final RavenwoodConfig mConfiguration; public RavenwoodRule() { + mConfiguration = new RavenwoodConfig.Builder().build(); + } + + private RavenwoodRule(RavenwoodConfig config) { + mConfiguration = config; } public static class Builder { - private RavenwoodRule mRule = new RavenwoodRule(); + private final RavenwoodConfig.Builder mBuilder = + new RavenwoodConfig.Builder(); public Builder() { } @@ -149,7 +114,7 @@ public class RavenwoodRule implements TestRule { * test. Has no effect on non-Ravenwood environments. */ public Builder setProcessSystem() { - mRule.mUid = SYSTEM_UID; + mBuilder.setProcessSystem(); return this; } @@ -158,7 +123,7 @@ public class RavenwoodRule implements TestRule { * test. Has no effect on non-Ravenwood environments. */ public Builder setProcessApp() { - mRule.mUid = FIRST_APPLICATION_UID; + mBuilder.setProcessApp(); return this; } @@ -166,8 +131,8 @@ public class RavenwoodRule implements TestRule { * Configure the identity of this process to be the given package name for the duration * of the test. Has no effect on non-Ravenwood environments. */ - public Builder setPackageName(/* @NonNull */ String packageName) { - mRule.mPackageName = Objects.requireNonNull(packageName); + public Builder setPackageName(@NonNull String packageName) { + mBuilder.setPackageName(packageName); return this; } @@ -176,7 +141,7 @@ public class RavenwoodRule implements TestRule { * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. */ public Builder setProvideMainThread(boolean provideMainThread) { - mRule.mProvideMainThread = provideMainThread; + mBuilder.setProvideMainThread(provideMainThread); return this; } @@ -190,10 +155,8 @@ public class RavenwoodRule implements TestRule { * * Has no effect on non-Ravenwood environments. */ - public Builder setSystemPropertyImmutable(/* @NonNull */ String key, - /* @Nullable */ Object value) { - mRule.mSystemProperties.setValue(key, value); - mRule.mSystemProperties.setAccessReadOnly(key); + public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) { + mBuilder.setSystemPropertyImmutable(key, value); return this; } @@ -207,10 +170,8 @@ public class RavenwoodRule implements TestRule { * * Has no effect on non-Ravenwood environments. */ - public Builder setSystemPropertyMutable(/* @NonNull */ String key, - /* @Nullable */ Object value) { - mRule.mSystemProperties.setValue(key, value); - mRule.mSystemProperties.setAccessReadWrite(key); + public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) { + mBuilder.setSystemPropertyMutable(key, value); return this; } @@ -223,16 +184,13 @@ public class RavenwoodRule implements TestRule { * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}. */ - public Builder setServicesRequired(Class... services) { - mRule.mServicesRequired.clear(); - for (Class service : services) { - mRule.mServicesRequired.add(service); - } + public Builder setServicesRequired(@NonNull Class... services) { + mBuilder.setServicesRequired(services); return this; } public RavenwoodRule build() { - return mRule; + return new RavenwoodRule(mBuilder.build()); } } @@ -252,183 +210,49 @@ public class RavenwoodRule implements TestRule { } /** - * Return a {@code Context} available for usage during the currently running test case. - * - * Each test should obtain needed information or references via this method; - * references must not be stored beyond the scope of a test case. + * @deprecated Use + * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()} + * instead. */ + @Deprecated public Context getContext() { - return Objects.requireNonNull(mContext, + return Objects.requireNonNull(mConfiguration.mInstContext, "Context is only available during @Test execution"); } /** - * Return a {@code Instrumentation} available for usage during the currently running test case. - * - * Each test should obtain needed information or references via this method; - * references must not be stored beyond the scope of a test case. + * @deprecated Use + * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()} + * instead. */ + @Deprecated public Instrumentation getInstrumentation() { - return Objects.requireNonNull(mInstrumentation, + return Objects.requireNonNull(mConfiguration.mInstrumentation, "Instrumentation is only available during @Test execution"); } - static boolean shouldEnableOnDevice(Description description) { - if (description.isTest()) { - if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) { - return false; - } - } - final var clazz = description.getTestClass(); - if (clazz != null) { - if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) { - return false; - } - } - return true; - } - - /** - * Determine if the given {@link Description} should be enabled when running on the - * Ravenwood test environment. - * - * A more specific method-level annotation always takes precedence over any class-level - * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over - * an {@link DisabledOnRavenwood} annotation. - */ - static boolean shouldEnableOnRavenwood(Description description) { - // First, consult any method-level annotations - if (description.isTest()) { - // Stopgap for http://g/ravenwood/EPAD-N5ntxM - if (description.getMethodName().endsWith("$noRavenwood")) { - return false; - } - if (description.getAnnotation(EnabledOnRavenwood.class) != null) { - return true; - } - if (description.getAnnotation(DisabledOnRavenwood.class) != null) { - return false; - } - if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { - return false; - } - } - - // Otherwise, consult any class-level annotations - final var clazz = description.getTestClass(); - if (clazz != null) { - if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) { - return true; - } - if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) { - return false; - } - if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) { - return false; - } - } - - // When no annotations have been requested, assume test should be included - return true; - } - - static boolean shouldStillIgnoreInProbeIgnoreMode(Description description) { - if (!ENABLE_REALLY_DISABLE_PATTERN) { - return false; - } - - final var fullname = description.getTestClass().getName() - + (description.isTest() ? "#" + description.getMethodName() : ""); - - if (REALLY_DISABLE_PATTERN.matcher(fullname).find()) { - System.out.println("Still ignoring " + fullname); - return true; - } - return false; - } - @Override public Statement apply(Statement base, Description description) { - // No special treatment when running outside Ravenwood; run tests as-is - if (!IS_ON_RAVENWOOD) { - Assume.assumeTrue(shouldEnableOnDevice(description)); + if (!RavenwoodConfig.isOnRavenwood()) { return base; } - - if (ENABLE_PROBE_IGNORED) { - return applyProbeIgnored(base, description); - } else { - return applyDefault(base, description); - } - } - - private void commonPrologue(Statement base, Description description) { - RavenwoodRuleImpl.logTestRunner("started", description); - RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION); - RavenwoodRuleImpl.init(RavenwoodRule.this); - } - - /** - * Run the given {@link Statement} with no special treatment. - */ - private Statement applyDefault(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { - Assume.assumeTrue(shouldEnableOnRavenwood(description)); - - commonPrologue(base, description); + RavenwoodAwareTestRunnerHook.onRavenwoodRuleEnter( + RavenwoodAwareTestRunner.getCurrentRunner(), description, + RavenwoodRule.this); try { base.evaluate(); - RavenwoodRuleImpl.logTestRunner("finished", description); - } catch (Throwable t) { - RavenwoodRuleImpl.logTestRunner("failed", description); - throw t; } finally { - RavenwoodRuleImpl.reset(RavenwoodRule.this); - } - } - }; - } - - /** - * Run the given {@link Statement} with probing enabled. All tests will be unconditionally - * run on Ravenwood to detect cases where a test is able to pass despite being marked as - * {@code IgnoreUnderRavenwood}. - */ - private Statement applyProbeIgnored(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description)); - - commonPrologue(base, description); - try { - base.evaluate(); - } catch (Throwable t) { - // If the test isn't included, eat the exception and report the - // assumption failure that test authors expect; otherwise throw - Assume.assumeTrue(shouldEnableOnRavenwood(description)); - throw t; - } finally { - RavenwoodRuleImpl.logTestRunner("finished", description); - RavenwoodRuleImpl.reset(RavenwoodRule.this); - } - - if (!shouldEnableOnRavenwood(description)) { - fail("Test wasn't included under Ravenwood, but it actually " - + "passed under Ravenwood; consider updating annotations"); + RavenwoodAwareTestRunnerHook.onRavenwoodRuleExit( + RavenwoodAwareTestRunner.getCurrentRunner(), description, + RavenwoodRule.this); } } }; } - public static class _$RavenwoodPrivate { - public static boolean isOptionalValidationEnabled() { - return ENABLE_OPTIONAL_VALIDATION; - } - } - /** * Returns the "real" result from {@link System#currentTimeMillis()}. * @@ -439,4 +263,51 @@ public class RavenwoodRule implements TestRule { public long realCurrentTimeMillis() { return System.currentTimeMillis(); } + + // Below are internal to ravenwood. Don't use them from normal tests... + + public static class RavenwoodPrivate { + private RavenwoodPrivate() { + } + + private volatile Boolean mRunDisabledTestsOverride = null; + + private volatile Pattern mReallyDisabledPattern = null; + + public boolean isRunningDisabledTests() { + if (mRunDisabledTestsOverride != null) { + return mRunDisabledTestsOverride; + } + return RUN_DISABLED_TESTS; + } + + public Pattern getReallyDisabledPattern() { + if (mReallyDisabledPattern != null) { + return mReallyDisabledPattern; + } + return REALLY_DISABLED_PATTERN; + } + + public void overrideRunDisabledTest(boolean runDisabledTests, + @Nullable String reallyDisabledPattern) { + mRunDisabledTestsOverride = runDisabledTests; + mReallyDisabledPattern = + reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern); + } + + public void resetRunDisabledTest() { + mRunDisabledTestsOverride = null; + mReallyDisabledPattern = null; + } + } + + private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate(); + + public static RavenwoodPrivate private$ravenwood() { + return sRavenwoodPrivate; + } + + RavenwoodConfig getConfiguration() { + return mConfiguration; + } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java index 5f1b0c2c929f..ef8f5840f949 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -48,11 +48,13 @@ public class RavenwoodSystemProperties { switch (key) { case "gsm.version.baseband": case "no.such.thing": + case "qemu.sf.lcd_density": case "ro.bootloader": case "ro.debuggable": case "ro.hardware": case "ro.hw_timeout_multiplier": case "ro.odm.build.media_performance_class": + case "ro.sf.lcd_density": case "ro.treble.enabled": case "ro.vndk.version": return true; -- cgit v1.2.3-59-g8ed1b