Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a84549b authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Offer `RavenwoodClassRule`.

Some tests interact with yet-unsupported APIs under Ravenwood in
their test class constructors, and `RavenwoodRule` arrives too late
to effectively apply annotations like `@IgnoreUnderRavenwood`.

This change adds `RavenwoodClassRule` which can be marked as an
`@ClassRule` so that it runs in a static context before each test
class is constructed, giving it a chance to respect annotations.

We also adjust our annotation naming to match the naming style in
JUnit Jupiter, which has annotations like `DisabledOnJre` and
`EnabledOnOs`.

Bug: 319647875
Test: atest SystemUiRoboTests
Test: atest SystemUiRavenTests
Change-Id: I7d834b8e74e961bb3d4befcad6204d99f8fe80e9
parent eb649022
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ import android.os.Looper;
public class RavenwoodRuleImpl {
    private static final String MAIN_THREAD_NAME = "RavenwoodMain";

    public static boolean isUnderRavenwood() {
    public static boolean isOnRavenwood() {
        return true;
    }

+7 −6
Original line number Diff line number Diff line
@@ -23,15 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Tests marked with this annotation are excluded when running under a Ravenwood test environment.
 * Tests marked with this annotation are disabled when running under a Ravenwood test environment.
 *
 * A more specific method-level annotation always takes precedence over any class-level
 * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
 * an {@link ExcludeUnderRavenwood} annotation.
 * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
 * an {@link DisabledOnRavenwood} annotation.
 *
 * This annotation only takes effect when the containing class has a {@code
 * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
 * .AssumptionViolatedException} which test infrastructure treats as being ignored.
 * RavenwoodRule} or {@code RavenwoodClassRule} configured. Ignoring is accomplished by
 * throwing an {@code org.junit.AssumptionViolatedException} which test infrastructure treats as
 * being ignored.
 *
 * This annotation has no effect on any other non-Ravenwood test environments.
 *
@@ -40,5 +41,5 @@ import java.lang.annotation.Target;
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcludeUnderRavenwood {
public @interface DisabledOnRavenwood {
}
+7 −6
Original line number Diff line number Diff line
@@ -23,15 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Tests marked with this annotation are included when running under a Ravenwood test environment.
 * Tests marked with this annotation are enabled when running under a Ravenwood test environment.
 *
 * A more specific method-level annotation always takes precedence over any class-level
 * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
 * an {@link ExcludeUnderRavenwood} annotation.
 * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
 * an {@link DisabledOnRavenwood} annotation.
 *
 * This annotation only takes effect when the containing class has a {@code
 * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
 * .AssumptionViolatedException} which test infrastructure treats as being ignored.
 * RavenwoodRule} or {@code RavenwoodClassRule} configured. Ignoring is accomplished by
 * throwing an {@code org.junit.AssumptionViolatedException} which test infrastructure treats as
 * being ignored.
 *
 * This annotation has no effect on any other non-Ravenwood test environments.
 *
@@ -40,5 +41,5 @@ import java.lang.annotation.Target;
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IncludeUnderRavenwood {
public @interface EnabledOnRavenwood {
}
+63 −0
Original line number Diff line number Diff line
/*
 * 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.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;

import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;

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.
 *
 * 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.
 */
public class RavenwoodClassRule implements TestRule {
    @Override
    public Statement apply(Statement base, Description description) {
        // No special treatment when running outside Ravenwood; run tests as-is
        if (!IS_ON_RAVENWOOD) {
            return base;
        }

        if (ENABLE_PROBE_IGNORED) {
            // Pass through to possible underlying RavenwoodRule for both environment
            // configuration and handling method-level annotations
            return base;
        } else {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    Assume.assumeTrue(shouldEnableOnRavenwood(description));
                    // Pass through to possible underlying RavenwoodRule for both environment
                    // configuration and handling method-level annotations
                    base.evaluate();
                }
            };
        }
    }
}
+57 −41
Original line number Diff line number Diff line
@@ -18,9 +18,9 @@ package android.platform.test.ravenwood;

import static org.junit.Assert.fail;

import android.platform.test.annotations.ExcludeUnderRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.annotations.IncludeUnderRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;

import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -30,29 +30,36 @@ import org.junit.runners.model.Statement;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
 * QUESTIONS ABOUT IT.
 * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when
 * tests are run on non-Ravenwood test environments.
 *
 * @hide
 * 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.
 */
public class RavenwoodRule implements TestRule {
    private static AtomicInteger sNextPid = new AtomicInteger(100);

    private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood();
    static final boolean IS_ON_RAVENWOOD = RavenwoodRuleImpl.isOnRavenwood();

    /**
     * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect
     * 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}.
     *
     * This is typically helpful for internal maintainers discovering tests that had previously
     * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
     */
    private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE
    static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE

    private static final int SYSTEM_UID = 1000;
    private static final int NOBODY_UID = 9999;
    private static final int FIRST_APPLICATION_UID = 10000;

    private static final AtomicInteger sNextPid = new AtomicInteger(100);

    /**
     * Unless the test author requests differently, run as "nobody", and give each collection of
     * tests its own unique PID.
@@ -75,7 +82,7 @@ public class RavenwoodRule implements TestRule {

        /**
         * Configure the identity of this process to be the system UID for the duration of the
         * test. Has no effect under non-Ravenwood environments.
         * test. Has no effect on non-Ravenwood environments.
         */
        public Builder setProcessSystem() {
            mRule.mUid = SYSTEM_UID;
@@ -84,7 +91,7 @@ public class RavenwoodRule implements TestRule {

        /**
         * Configure the identity of this process to be an app UID for the duration of the
         * test. Has no effect under non-Ravenwood environments.
         * test. Has no effect on non-Ravenwood environments.
         */
        public Builder setProcessApp() {
            mRule.mUid = FIRST_APPLICATION_UID;
@@ -93,7 +100,7 @@ public class RavenwoodRule implements TestRule {

        /**
         * Configure a "main" thread to be available for the duration of the test, as defined
         * by {@code Looper.getMainLooper()}. Has no effect under non-Ravenwood environments.
         * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
         */
        public Builder setProvideMainThread(boolean provideMainThread) {
            mRule.mProvideMainThread = provideMainThread;
@@ -108,7 +115,7 @@ public class RavenwoodRule implements TestRule {
         * All properties in the {@code debug.*} namespace are automatically mutable, with no
         * developer action required.
         *
         * Has no effect under non-Ravenwood environments.
         * Has no effect on non-Ravenwood environments.
         */
        public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
                /* @Nullable */ Object value) {
@@ -125,7 +132,7 @@ public class RavenwoodRule implements TestRule {
         * All properties in the {@code debug.*} namespace are automatically mutable, with no
         * developer action required.
         *
         * Has no effect under non-Ravenwood environments.
         * Has no effect on non-Ravenwood environments.
         */
        public Builder setSystemPropertyMutable(/* @NonNull */ String key,
                /* @Nullable */ Object value) {
@@ -140,42 +147,51 @@ public class RavenwoodRule implements TestRule {
    }

    /**
     * Return if the current process is running under a Ravenwood test environment.
     * @deprecated replaced by {@link #isOnRavenwood()}
     */
    @Deprecated
    public static boolean isUnderRavenwood() {
        return IS_UNDER_RAVENWOOD;
        return IS_ON_RAVENWOOD;
    }

    /**
     * Determine if the given {@link Description} should be included when running under the
     * Return if the current process is running on a Ravenwood test environment.
     */
    public static boolean isOnRavenwood() {
        return IS_ON_RAVENWOOD;
    }

    /**
     * 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 IncludeUnderRavenwood} annotation always takes precedence over
     * an {@link ExcludeUnderRavenwood} annotation.
     * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
     * an {@link DisabledOnRavenwood} annotation.
     */
    private boolean shouldIncludeUnderRavenwood(Description description) {
    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;
            }

        // First, consult any method-level annotations
        if (description.getAnnotation(IncludeUnderRavenwood.class) != null) {
            if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
                return true;
            }
        if (description.getAnnotation(ExcludeUnderRavenwood.class) != null) {
            if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
                return false;
            }
            if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
                return false;
            }
        }

        // Otherwise, consult any class-level annotations
        if (description.getTestClass().getAnnotation(IncludeUnderRavenwood.class) != null) {
        if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
            return true;
        }
        if (description.getTestClass().getAnnotation(ExcludeUnderRavenwood.class) != null) {
        if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
            return false;
        }
        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
@@ -189,7 +205,7 @@ public class RavenwoodRule implements TestRule {
    @Override
    public Statement apply(Statement base, Description description) {
        // No special treatment when running outside Ravenwood; run tests as-is
        if (!IS_UNDER_RAVENWOOD) {
        if (!IS_ON_RAVENWOOD) {
            return base;
        }

@@ -207,7 +223,7 @@ public class RavenwoodRule implements TestRule {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
                Assume.assumeTrue(shouldEnableOnRavenwood(description));

                RavenwoodRuleImpl.init(RavenwoodRule.this);
                try {
@@ -221,7 +237,7 @@ public class RavenwoodRule implements TestRule {

    /**
     * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
     * run under Ravenwood to detect cases where a test is able to pass despite being marked as
     * 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) {
@@ -234,13 +250,13 @@ public class RavenwoodRule implements TestRule {
                } 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(shouldIncludeUnderRavenwood(description));
                    Assume.assumeTrue(shouldEnableOnRavenwood(description));
                    throw t;
                } finally {
                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
                }

                if (!shouldIncludeUnderRavenwood(description)) {
                if (!shouldEnableOnRavenwood(description)) {
                    fail("Test wasn't included under Ravenwood, but it actually "
                            + "passed under Ravenwood; consider updating annotations");
                }
Loading