summaryrefslogtreecommitdiff
path: root/ravenwood/junit-src
diff options
context:
space:
mode:
author John Wu <topjohnwu@google.com> 2024-09-26 22:59:40 +0000
committer John Wu <topjohnwu@google.com> 2024-09-26 22:59:40 +0000
commit983461633b96db0bc58205a657edeffad3ce4080 (patch)
tree8df76979756f92a675ea35ac852917a2369d152f /ravenwood/junit-src
parent6f7665370b368b3f4164cdce501ae224b3351c9b (diff)
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
Diffstat (limited to 'ravenwood/junit-src')
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java (renamed from ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java)25
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java733
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java30
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java217
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java341
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java2
6 files changed, 1067 insertions, 281 deletions
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
index 2fb8074c850a..a84f16f619d3 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
@@ -23,29 +23,14 @@ 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.)
+ * 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.METHOD})
+@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
-public @interface DisabledOnNonRavenwood {
- /**
- * General free-form description of why this test is being ignored.
- */
- String reason() default "";
+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<? 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/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<Class<?>> 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<Class<?>> 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;