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

Commit 9a15ab28 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add a way to run specific test classes/methods" into main

parents 07472b30 c2da71cf
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -102,7 +102,8 @@ java_library {
    sdk_version: "core_current",
    srcs: ["common-src/**/*.java"],
    static_libs: [
        "framework-annotations-lib", // should it be "libs" instead?
        "junit",
        "framework-annotations-lib",
        "modules-utils-ravenwood",
    ],
    visibility: ["//visibility:private"],
+64 −14
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.annotation.Nullable;

import com.android.modules.utils.ravenwood.RavenwoodHelper;

import org.junit.runner.Description;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -43,7 +45,7 @@ public class RavenwoodInternalUtils {

    /**
     * If set to "1", we enable the verbose logging.
     *
     * <p>
     * (See also InitLogging() in http://ac/system/libbase/logging.cpp)
     */
    public static final boolean RAVENWOOD_VERBOSE_LOGGING =
@@ -69,21 +71,24 @@ public class RavenwoodInternalUtils {
        return sEnableExtraRuntimeCheck;
    }

    /** Simple logging method. */
    /**
     * Simple logging method.
     */
    public static void log(String tag, String message) {
        // Avoid using Android's Log class, which could be broken for various reasons.
        // (e.g. the JNI file doesn't exist for whatever reason)
        System.out.print(tag + ": " + message + "\n");
    }

    /** Simple logging method. */
    /**
     * Simple logging method.
     */
    private void log(String tag, String format, Object... args) {
        log(tag, String.format(format, args));
    }

    /**
     * Internal implementation of
     * {@link android.platform.test.ravenwood.RavenwoodRule#loadJniLibrary(String)}
     * Internal implementation of {@link android.platform.test.ravenwood.RavenwoodRule#loadJniLibrary(String)}
     */
    public static void loadJniLibrary(String libname) {
        if (isOnRavenwood()) {
@@ -171,14 +176,16 @@ public class RavenwoodInternalUtils {

    /**
     * @return the full directory path that contains the "ravenwood-runtime" files.
     *
     * <p>
     * This method throws if called on the device side.
     */
    public static String getRavenwoodRuntimePath() {
        return RavenwoodHelper.getRavenwoodRuntimePath();
    }

    /** Close an {@link AutoCloseable}. */
    /**
     * Close an {@link AutoCloseable}.
     */
    public static void closeQuietly(AutoCloseable c) {
        if (c != null) {
            try {
@@ -189,7 +196,9 @@ public class RavenwoodInternalUtils {
        }
    }

    /** Close a {@link FileDescriptor}. */
    /**
     * Close a {@link FileDescriptor}.
     */
    public static void closeQuietly(FileDescriptor fd) {
        var is = new FileInputStream(fd);
        closeQuietly(is);
@@ -222,7 +231,7 @@ public class RavenwoodInternalUtils {

    /**
     * Run a supplier and swallow the exception, if any.
     *
     * <p>
     * It's a dangerous function. Only use it in an exception handler where we don't want to crash.
     */
    @Nullable
@@ -237,7 +246,7 @@ public class RavenwoodInternalUtils {

    /**
     * Run a runnable and swallow the exception, if any.
     *
     * <p>
     * It's a dangerous function. Only use it in an exception handler where we don't want to crash.
     */
    public static void runIgnoringException(@NonNull Runnable r) {
@@ -255,7 +264,9 @@ public class RavenwoodInternalUtils {
        return stringWriter.toString();
    }

    /** Same as {@link Integer#parseInt(String)} but accepts null and returns null. */
    /**
     * Same as {@link Integer#parseInt(String)} but accepts null and returns null.
     */
    @Nullable
    public static Integer parseNullableInt(@Nullable String value) {
        if (value == null) {
@@ -278,7 +289,7 @@ public class RavenwoodInternalUtils {

    /**
     * Convert a wildcard string using "*" and "**" to match Java classnames.
     *
     * <p>
     * The input string can only contain alnum, "$", "." and "*".
     */
    public static Pattern parseClassNameWildcard(String pattern) {
@@ -299,4 +310,43 @@ public class RavenwoodInternalUtils {
        temp = temp.replace("@@", ".*");
        return Pattern.compile(temp);
    }

    /**
     * @return true if it's a junit4 test method.
     */
    public static Boolean isTestMethod(@NonNull Class<?> clazz, @NonNull Method method) {
        return method.getAnnotation(org.junit.Test.class) != null;
    }

    private static final Pattern sParamsPattern = Pattern.compile("\\[.*$");

    /**
     * Take a {@link Description#getMethodName()} result and extract the "real" method name,
     * i.e. without the parameters.
     */
    public static String getMethodNameFromMethodDescription(@NonNull String methodDescriptionName) {
        return sParamsPattern.matcher(methodDescriptionName).replaceFirst("");
    }

    /**
     * @return a canonical name of a test method. {@code clazz} may be a subclass of
     * {@link Method#getDeclaringClass()}.
     */
    public static String toCanonicalTestName(@NonNull Class<?> clazz, @NonNull Method method) {
        return clazz.getName() + "#" + method.getName();
    }

    /**
     * @return a canonical name of a test method.
     */
    public static String toCanonicalTestName(@NonNull Description description) {
        if (!description.isTest()) {
            // It's a test class.
            return description.getClassName();
        } else {
            // It's a test method
            return description.getClassName() + "#" + getMethodNameFromMethodDescription(
                    description.getMethodName());
        }
    }
}
 No newline at end of file
+96 −12
Original line number Diff line number Diff line
@@ -15,7 +15,9 @@
 */
package android.platform.test.ravenwood;

import static com.android.ravenwood.common.RavenwoodInternalUtils.isTestMethod;
import static com.android.ravenwood.common.RavenwoodInternalUtils.parseClassNameWildcard;
import static com.android.ravenwood.common.RavenwoodInternalUtils.toCanonicalTestName;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -54,6 +56,11 @@ import java.util.regex.Pattern;
 *
 *   This feature is only used locally. On CI server, we never use it.
 *
 * - You can override the above logic and run exact test classes / test methods by
 *   setting a case-insensitive regex to $RAVENWOOD_FORCE_FILTER_REGEX. If it's set,
 *   only the tests that are mathing it would be executed. For example,
 *   running with RAVENWOOD_FORCE_FILTER_REGEX='(MyTestClass1|MyTestClass2#testFoo)' would
 *   execute all tests in MyTestClass1, and only testFoo() in MyTestClass2.
 */
public abstract class RavenwoodEnablementChecker {
    private static final String TAG = RavenwoodInternalUtils.TAG;
@@ -161,20 +168,26 @@ public abstract class RavenwoodEnablementChecker {
     */
    @VisibleForTesting
    public static void setDefaultInstance() {
        sInstance = new RavenwoodEnablementCheckerImpl(getRunMode(), getPolicyFiles());
        sInstance = new RavenwoodEnablementCheckerImpl(getRunMode(), getPolicyFiles(),
                RavenwoodEnvironment.getInstance().getEnvVar("RAVENWOOD_FORCE_FILTER_REGEX", null));
    }

    /**
     * Force set a checker for testing.
     */
    @VisibleForTesting
    public static void overrideInstance(@NonNull RunMode runMode, @Nullable String policyText)  {
    public static void overrideInstance(
            @NonNull RunMode runMode,
            @Nullable String policyText,
            @Nullable String overridingPattern
            )  {
        try {
            var parser = new EnablementTextPolicyParser();
            if (policyText != null && !policyText.isEmpty()) {
                parser.parse("[in-memory]", policyText);
            }
            sInstance = new RavenwoodEnablementCheckerImpl(runMode, parser.getResult());
            sInstance = new RavenwoodEnablementCheckerImpl(
                    runMode, parser.getResult(), overridingPattern);
        } catch (IOException e) {
            SneakyThrow.sneakyThrow(e); // IOException shouldn't happen, but just in case
        }
@@ -193,24 +206,39 @@ public abstract class RavenwoodEnablementChecker {
    /**
     * Actual logic. This combines the annotation policy with the text policy.
     */
    @VisibleForTesting
    public static class RavenwoodEnablementCheckerImpl extends RavenwoodEnablementChecker {
    static class RavenwoodEnablementCheckerImpl extends RavenwoodEnablementChecker {
        private final RunMode mRunMode;
        private final PolicyChecker mChecker;

        public RavenwoodEnablementCheckerImpl(RunMode runMode, String[] policyFiles) {
            this(runMode, EnablementTextPolicyParser.parsePolicyFiles(policyFiles));
        RavenwoodEnablementCheckerImpl(
                @NonNull RunMode runMode,
                @NonNull String[] policyFiles,
                @Nullable String overridingPattern) {
            this(runMode, EnablementTextPolicyParser.parsePolicyFiles(policyFiles),
                    overridingPattern);
        }

        RavenwoodEnablementCheckerImpl(RunMode runMode, PolicyChecker subChecker) {
        RavenwoodEnablementCheckerImpl(
                @NonNull RunMode runMode,
                @NonNull PolicyChecker subChecker,
                @Nullable String overridingPattern) {
            this.mRunMode = runMode;
            var chain = new PolicyCheckerChain();

            if (overridingPattern == null || overridingPattern.isEmpty()) {
                // Annotations always win.
                chain.add(new AnnotationPolicyChecker());

                // Text policy changes the default behavior.
                chain.add(subChecker);
            } else {
                // Use a regex-based filter, and only run the exact matching tests.
                chain.add(new RegexRunFilter(
                        Pattern.compile(overridingPattern, Pattern.CASE_INSENSITIVE),
                        RunPolicy.Enabled,
                        RunPolicy.NeverRun
                ));
            }

            mChecker = chain;
        }
@@ -592,3 +620,59 @@ class EnablementTextPolicyParser {
        return true;
    }
}

/**
 * {@link PolicyChecker} based on a {@link Pattern}.
 */
class RegexRunFilter implements PolicyChecker {
    private static final String TAG = "RegexRunFilter";
    private final Pattern mRegex;
    private final RunPolicy mMatchingPolicy;
    private final RunPolicy mNonMatchingPolicy;

    /**
     * Constructor.
     *
     * @param regex regex to match. We apply it on both classes and methods.
     * @param matchingPolicy policy for matching items
     * @param nonMatchingPolicy  policy for non-matching items
     */
    public RegexRunFilter(
            @NonNull Pattern regex,
            @NonNull RunPolicy matchingPolicy,
            @NonNull RunPolicy nonMatchingPolicy) {
        mRegex = regex;
        mMatchingPolicy = matchingPolicy;
        mNonMatchingPolicy = nonMatchingPolicy;
    }

    private boolean classMatches(Class<?> testClass) {
        return mRegex.matcher(testClass.getName()).find();
    }

    @Override
    public RunPolicy getClassPolicy(Class<?> testClass) {
        if (classMatches(testClass)) {
            return mMatchingPolicy;
        }
        // If the class has any test matching test method, still return "enabled".
        // (but unmatched methods would be "unspecified".)
        for (var m : testClass.getMethods()) {
            if (!isTestMethod(testClass, m)) {
                continue;
            }
            if (mRegex.matcher(toCanonicalTestName(testClass, m)).find()) {
                return mMatchingPolicy;
            }
        }
        return mNonMatchingPolicy;
    }

    @Override
    public RunPolicy getMethodPolicy(Description description) {
        if (mRegex.matcher(toCanonicalTestName(description)).find()) {
            return mMatchingPolicy;
        }
        return classMatches(description.getTestClass()) ? mMatchingPolicy : mNonMatchingPolicy;
    }
}
+112 −2
Original line number Diff line number Diff line
@@ -308,8 +308,6 @@ public class RavenwoodEnablementTest extends RavenwoodRunnerTestBase {
        }
    }

    //--

    @RunWith(BlockJUnit4ClassRunner.class)
    // CHECKSTYLE:OFF Generated code
    @Expected(value = """
@@ -504,4 +502,116 @@ public class RavenwoodEnablementTest extends RavenwoodRunnerTestBase {
        public void test3() {
        }
    }

    @RunWith(BlockJUnit4ClassRunner.class)
    // CHECKSTYLE:OFF Generated code
    @Expected(value = """
    testRunStarted: classes
    testSuiteStarted: classes
    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel
    testStarted: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel)
    testAssumptionFailure: got: <false>, expected: is <true>
    testFinished: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel)
    testStarted: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel)
    testAssumptionFailure: got: <false>, expected: is <true>
    testFinished: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel)
    testStarted: test3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel)
    testFinished: test3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel)
    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_methodLevel
    testSuiteFinished: classes
    testRunFinished: 3,0,2,0
    """,runMode = RunMode.AlsoDisabledTests, enablementPolicy = """
    !module RavenwoodCoreTest
    ** false # disable all classes
    """, overridingRegex="testwith.*#test3"
    )
    // CHECKSTYLE:ON
    public static class TestWithPolicyWithForceRegex_methodLevel {
        public TestWithPolicyWithForceRegex_methodLevel() {
        }

        @Test
        public void test1() {
        }

        @Test
        public void test2() {
        }

        @Test
        @DisabledOnRavenwood
        public void test3() {
        }
    }

    @RunWith(BlockJUnit4ClassRunner.class)
    // CHECKSTYLE:OFF Generated code
    @Expected(value = """
    testRunStarted: classes
    testSuiteStarted: classes
    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled
    testStarted: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled)
    testFinished: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled)
    testStarted: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled)
    testFinished: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled)
    testStarted: test3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled)
    testFinished: test3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled)
    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelEnabled
    testSuiteFinished: classes
    testRunFinished: 3,0,0,0
    """,runMode = RunMode.AlsoDisabledTests, enablementPolicy = """
    !module RavenwoodCoreTest
    ** false # disable all classes
    """, overridingRegex="TestWithPolicy" // Should run all the tests
    )
    // CHECKSTYLE:ON
    public static class TestWithPolicyWithForceRegex_classLevelEnabled {
        public TestWithPolicyWithForceRegex_classLevelEnabled() {
        }

        @Test
        public void test1() {
        }

        @Test
        public void test2() {
        }

        @Test
        @DisabledOnRavenwood
        public void test3() {
        }
    }

    @RunWith(BlockJUnit4ClassRunner.class)
    // CHECKSTYLE:OFF Generated code
    @Expected(value = """
    testRunStarted: classes
    testSuiteStarted: classes
    testSuiteStarted: <init>(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelDisabled)
    testIgnored: <init>(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelDisabled)
    testSuiteFinished: <init>(com.android.ravenwoodtest.runnercallbacktests.RavenwoodEnablementTest$TestWithPolicyWithForceRegex_classLevelDisabled)
    testSuiteFinished: classes
    testRunFinished: 0,0,0,1
    """,runMode = RunMode.AlsoDisabledTests, enablementPolicy = """
    """, overridingRegex="NotMatchingAnyTests" // no tests should be executed
    )
    // CHECKSTYLE:ON
    public static class TestWithPolicyWithForceRegex_classLevelDisabled {
        public TestWithPolicyWithForceRegex_classLevelDisabled() {
        }

        @Test
        public void test1() {
        }

        @Test
        public void test2() {
        }

        @Test
        @DisabledOnRavenwood
        public void test3() {
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ public abstract class RavenwoodRunnerTestBase {
        String value();
        RunMode runMode() default RunMode.Normal;
        String enablementPolicy() default "";
        String overridingRegex() default "";
    }

    private static final AtomicReference<Throwable> sError = new AtomicReference<>();
@@ -153,7 +154,7 @@ public abstract class RavenwoodRunnerTestBase {

        // Oevrride enablement policy
        RavenwoodEnablementChecker.overrideInstance(
                expected.runMode(), expected.enablementPolicy());
                expected.runMode(), expected.enablementPolicy(), expected.overridingRegex());

        sError.set(null);