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

Commit 4864bfa7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add generic SelectTest JUnit filter and CoreTestsFilter"

parents d49a6c86 0f224efb
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 com.android.server.wm.test.filters;

import android.os.Bundle;

import com.android.test.filters.SelectTest;

/**
 * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests.
 *
 * <p>Use this filter when running FrameworksCoreTests as
 * <pre>
 * adb shell am instrument -w \
 *     -e filter com.android.server.wm.test.filters.CoreTestsFilter  \
 *     -e selectTest_verbose true \
 *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
 * </pre>
 */
public final class CoreTestsFilter extends SelectTest {

    private static final String[] SELECTED_CORE_TESTS = {
            "android.app.servertransaction.", // all tests under the package.
            "android.view.DisplayCutoutTest",
    };

    public CoreTestsFilter(Bundle testArgs) {
        super(addSelectTest(testArgs, SELECTED_CORE_TESTS));
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ android_test {
    ],

    static_libs: [
        "frameworks-base-testutils",
        "androidx.test.runner",
        "mockito-target-minus-junit4",
        "platform-test-annotations",
+338 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 com.android.test.filters;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;

/**
 * JUnit filter to select tests.
 *
 * <p>This filter selects tests specified by package name, class name, and method name. With this
 * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the
 * restriction that prevents using the package and the class options can be mitigated.
 *
 * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option.
 * <pre>
 * adb shell am instrument -w \
 *     -e filter com.android.test.filters.SelectTest \
 *     -e selectTest package1.,package2. \
 *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
 * </pre>
 * Note that the ending {@code .} in package name is mandatory.
 *
 * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option.
 * <pre>
 * adb shell am instrument -w \
 *     -e filter com.android.test.filters.SelectTest      \
 *     -e selectTest package1.ClassA,package2.ClassB \
 *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
 * </pre>
 *
 * <p><b>Select out test methods from Java classes:</b>
 * <pre>
 * adb shell am instrument -w \
 *     -e filter com.android.test.filters.SelectTest                      \
 *     -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \
 *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
 * </pre>
 *
 * Those options can be used simultaneously. For example
 * <pre>
 * adb shell am instrument -w \
 *     -e filter com.android.test.filters.SelectTest                        \
 *     -e selectTest package1.,package2.classA,package3.ClassB#methodZ \
 *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
 * </pre>
 * will select out all tests in package1, all tests in classA, and ClassB#methodZ test.
 *
 * <p>Note that when this option is specified with either {@code -e package} or {@code -e class}
 * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage},
 * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected
 * with this SelectTest option.
 *
 * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely
 * logs to logcat while parsing {@code -e selectTest} option.
 */
public class SelectTest extends Filter {

    private static final String TAG = SelectTest.class.getSimpleName();

    @VisibleForTesting
    static final String OPTION_SELECT_TEST = "selectTest";
    @VisibleForTesting
    static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose";

    private static final String ARGUMENT_ITEM_SEPARATOR = ",";
    private static final String PACKAGE_NAME_SEPARATOR = ".";
    private static final String METHOD_SEPARATOR = "#";

    @Nullable
    private final PackageSet mPackageSet;

    /**
     * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}.
     *
     * @param testArgs instrumentation test arguments.
     */
    public SelectTest(@NonNull Bundle testArgs) {
        mPackageSet = parseSelectTest(testArgs);
    }

    @Override
    public boolean shouldRun(Description description) {
        if (mPackageSet == null) {
            // Accept all tests because this filter is disabled.
            return true;
        }
        String testClassName = description.getClassName();
        String testMethodName = description.getMethodName();
        return mPackageSet.accept(testClassName, testMethodName);
    }

    @Override
    public String describe() {
        return OPTION_SELECT_TEST + "=" + mPackageSet;
    }

    /**
     * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}.
     *
     * <p>This method is intended to be used at constructor of extended {@link Filter} class.
     *
     * @param testArgs instrumentation test arguments.
     * @param selectTests array of class name to be selected to run.
     * @return modified instrumentation test arguments.
     */
    @NonNull
    protected static Bundle addSelectTest(
            @NonNull Bundle testArgs, @NonNull String... selectTests) {
        if (selectTests.length == 0) {
            return testArgs;
        }
        testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests)));
        return testArgs;
    }

    /**
     * Parse {@code -e selectTest} argument.
     * @param testArgs instrumentation test arguments.
     * @return {@link PackageSet} that will filter tests. Returns {@code null} when no
     *     {@code -e selectTest} option is specified, thus this filter gets disabled.
     */
    @Nullable
    private static PackageSet parseSelectTest(Bundle testArgs) {
        final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST);
        if (selectTestArgs == null) {
            Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified");
            return null;
        }

        final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE));
        final PackageSet packageSet = new PackageSet(verbose);
        for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) {
            packageSet.add(selectTestArg);
        }
        return packageSet;
    }

    private static String getPackageName(String selectTestArg) {
        int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR);
        return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos);
    }

    @Nullable
    private static String getClassName(String selectTestArg) {
        if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) {
            return null;
        }
        int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
        return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos);
    }

    @Nullable
    private static String getMethodName(String selectTestArg) {
        int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
        return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1);
    }

    /** Package level filter */
    private static class PackageSet {
        private final boolean mVerbose;
        /**
         * Java package name to {@link ClassSet} map. To represent package filtering, a map value
         * can be {@code null}.
         */
        private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>();

        PackageSet(boolean verbose) {
            mVerbose = verbose;
        }

        void add(final String selectTestArg) {
            final String packageName = getPackageName(selectTestArg);
            final String className = getClassName(selectTestArg);

            if (className == null) {
                ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering.
                if (mVerbose) {
                    logging("Select package " + selectTestArg, classSet != null,
                            "; supersede " + classSet);
                }
                return;
            }

            ClassSet classSet = mClassSetMap.get(packageName);
            if (classSet == null) {
                if (mClassSetMap.containsKey(packageName)) {
                    if (mVerbose) {
                        logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true,
                                " ignore " + selectTestArg);
                    }
                    return;
                }
                classSet = new ClassSet(mVerbose);
                mClassSetMap.put(packageName, classSet);
            }
            classSet.add(selectTestArg);
        }

        boolean accept(String className, @Nullable String methodName) {
            String packageName = getPackageName(className);
            if (!mClassSetMap.containsKey(packageName)) {
                return false;
            }
            ClassSet classSet = mClassSetMap.get(packageName);
            return classSet == null || classSet.accept(className, methodName);
        }

        @Override
        public String toString() {
            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
            for (String packageName : mClassSetMap.keySet()) {
                ClassSet classSet = mClassSetMap.get(packageName);
                joiner.add(classSet == null
                        ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString());
            }
            return joiner.toString();
        }
    }

    /** Class level filter */
    private static class ClassSet {
        private final boolean mVerbose;
        /**
         * Java class name to set of method names map. To represent class filtering, a map value
         * can be {@code null}.
         */
        private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>();

        ClassSet(boolean verbose) {
            mVerbose = verbose;
        }

        void add(String selectTestArg) {
            final String className = getClassName(selectTestArg);
            final String methodName = getMethodName(selectTestArg);

            if (methodName == null) {
                Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering.
                if (mVerbose) {
                    logging("Select class " + selectTestArg, methodSet != null,
                            "; supersede " + toString(className, methodSet));
                }
                return;
            }

            Set<String> methodSet = mMethodSetMap.get(className);
            if (methodSet == null) {
                if (mMethodSetMap.containsKey(className)) {
                    if (mVerbose) {
                        logging("Select class " + className, true, "; ignore " + selectTestArg);
                    }
                    return;
                }
                methodSet = new LinkedHashSet<>();
                mMethodSetMap.put(className, methodSet);
            }

            methodSet.add(methodName);
            if (mVerbose) {
                logging("Select method " + selectTestArg, false, null);
            }
        }

        boolean accept(String className, @Nullable String methodName) {
            if (!mMethodSetMap.containsKey(className)) {
                return false;
            }
            Set<String> methodSet = mMethodSetMap.get(className);
            return methodName == null || methodSet == null || methodSet.contains(methodName);
        }

        @Override
        public String toString() {
            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
            for (String className : mMethodSetMap.keySet()) {
                joiner.add(toString(className, mMethodSetMap.get(className)));
            }
            return joiner.toString();
        }

        private static String toString(String className, @Nullable Set<String> methodSet) {
            if (methodSet == null) {
                return className;
            }
            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
            for (String methodName : methodSet) {
                joiner.add(className + METHOD_SEPARATOR + methodName);
            }
            return joiner.toString();
        }
    }

    private static void logging(String infoLog, boolean isWarning, String warningLog) {
        if (isWarning) {
            Log.w(TAG, infoLog + warningLog);
        } else {
            Log.i(TAG, infoLog);
        }
    }

    private static String join(Collection<String> list) {
        StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
        for (String text : list) {
            joiner.add(text);
        }
        return joiner.toString();
    }
}
+220 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 com.android.test.filters;

import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST;
import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.os.Bundle;
import android.util.ArraySet;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.StringJoiner;

public class SelectTestTests {

    private static final String PACKAGE_A = "packageA.";
    private static final String PACKAGE_B = "packageB.";
    private static final String PACKAGE_C = "packageC.";
    private static final String CLASS_A1 = PACKAGE_A + "Class1";
    private static final String CLASS_A2 = PACKAGE_A + "Class2";
    private static final String CLASS_B3 = PACKAGE_B + "Class3";
    private static final String CLASS_B4 = PACKAGE_B + "Class4";
    private static final String CLASS_C5 = PACKAGE_C + "Class5";
    private static final String CLASS_C6 = PACKAGE_C + "Class6";
    private static final String METHOD_A1K = CLASS_A1 + "#methodK";
    private static final String METHOD_A1L = CLASS_A1 + "#methodL";
    private static final String METHOD_A2M = CLASS_A2 + "#methodM";
    private static final String METHOD_A2N = CLASS_A2 + "#methodN";
    private static final String METHOD_B3P = CLASS_B3 + "#methodP";
    private static final String METHOD_B3Q = CLASS_B3 + "#methodQ";
    private static final String METHOD_B4R = CLASS_B4 + "#methodR";
    private static final String METHOD_B4S = CLASS_B4 + "#methodS";
    private static final String METHOD_C5W = CLASS_C5 + "#methodW";
    private static final String METHOD_C5X = CLASS_C5 + "#methodX";
    private static final String METHOD_C6Y = CLASS_C6 + "#methodY";
    private static final String METHOD_C6Z = CLASS_C6 + "#methodZ";

    private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K);
    private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L);
    private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M);
    private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N);
    private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P);
    private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q);
    private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R);
    private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S);
    private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W);
    private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X);
    private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y);
    private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z);
    private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L);
    private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N);
    private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q);
    private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S);
    private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X);
    private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z);
    private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2);
    private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4);
    private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6);
    private static final Set<Description> TEST_ALL =
            merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C);

    private SelectTestBuilder mBuilder;

    @Before
    public void setUp() {
        mBuilder = new SelectTestBuilder();
    }

    private static class SelectTestBuilder {
        private final Bundle mTestArgs = new Bundle();

        Filter build() {
            mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString());
            return new SelectTest(mTestArgs);
        }

        SelectTestBuilder withSelectTest(String... selectTestArgs) {
            putTestOption(OPTION_SELECT_TEST, selectTestArgs);
            return this;
        }

        private void putTestOption(String option, String... args) {
            if (args.length > 0) {
                StringJoiner joiner = new StringJoiner(",");
                for (String arg : args) {
                    joiner.add(arg);
                }
                mTestArgs.putString(option, joiner.toString());
            }
        }
    }

    private static Set<Description> methodTest(String testName) {
        int methodSep = testName.indexOf("#");
        String className = testName.substring(0, methodSep);
        String methodName = testName.substring(methodSep + 1);
        final Set<Description> tests = new ArraySet<>();
        tests.add(Description.createSuiteDescription(className));
        tests.add(Description.createTestDescription(className, methodName));
        return Collections.unmodifiableSet(tests);
    }

    @SafeVarargs
    private static Set<Description> merge(Set<Description>... testSpecs) {
        final Set<Description> merged = new LinkedHashSet<>();
        for (Set<Description> testSet : testSpecs) {
            merged.addAll(testSet);
        }
        return Collections.unmodifiableSet(merged);
    }

    @SafeVarargs
    private static void acceptTests(Filter filter, Set<Description>... testSpecs) {
        final Set<Description> accepts = merge(testSpecs);
        for (Description test : TEST_ALL) {
            if (accepts.contains(test)) {
                assertTrue("accept " + test, filter.shouldRun(test));
            } else {
                assertFalse("reject " + test, filter.shouldRun(test));
            }
        }
    }

    @Test
    public void testFilterDisabled() {
        final Filter filter = mBuilder.build();
        acceptTests(filter, TEST_ALL);
    }

    @Test
    public void testSelectPackage() {
        final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build();
        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
    }

    @Test
    public void testSelectClass() {
        final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build();
        acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3);
    }

    @Test
    public void testSelectMethod() {
        final Filter filter = mBuilder
                .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build();
        acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P);
    }

    @Test
    public void testSelectClassAndPackage() {
        final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build();
        acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5);
    }

    @Test
    public void testSelectMethodAndPackage() {
        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build();
        acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W);
    }

    @Test
    public void testSelectMethodAndClass() {
        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build();
        acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P);
    }

    @Test
    public void testSelectClassAndSamePackage() {
        final Filter filter = mBuilder.withSelectTest(
                CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build();
        acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C);
    }

    @Test
    public void testSelectMethodAndSameClass() {
        final Filter filter = mBuilder.withSelectTest(
                METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build();
        acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R);
    }

    @Test
    public void testSelectMethodAndSamePackage() {
        final Filter filter = mBuilder.withSelectTest(
                METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
                PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
    }

    @Test
    public void testSelectMethodAndClassAndPackage() {
        final Filter filter = mBuilder.withSelectTest(
                METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A,
                PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build();
        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
    }
}