TestGrouping.java revision e70f61b1160e953e5e4d18d30a463fa9ba821779
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.test.suitebuilder;
18
19import android.test.ClassPathPackageInfo;
20import android.test.ClassPathPackageInfoSource;
21import android.test.PackageInfoSources;
22import android.util.Log;
23import com.android.internal.util.Predicate;
24import junit.framework.TestCase;
25
26import java.io.Serializable;
27import java.lang.reflect.Constructor;
28import java.lang.reflect.Method;
29import java.lang.reflect.Modifier;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Collection;
33import java.util.Comparator;
34import java.util.List;
35import java.util.Set;
36import java.util.SortedSet;
37import java.util.TreeSet;
38
39/**
40 * Represents a collection of test classes present on the classpath. You can add individual classes
41 * or entire packages. By default sub-packages are included recursively, but methods are
42 * provided to allow for arbitrary inclusion or exclusion of sub-packages. Typically a
43 * {@link TestGrouping} will have only one root package, but this is not a requirement.
44 *
45 * {@hide} Not needed for 1.0 SDK.
46 */
47public class TestGrouping {
48
49    SortedSet<Class<? extends TestCase>> testCaseClasses;
50
51    public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
52            = new SortBySimpleName();
53
54    public static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME
55            = new SortByFullyQualifiedName();
56
57    protected String firstIncludedPackage = null;
58    private ClassLoader classLoader;
59
60    public TestGrouping(Comparator<Class<? extends TestCase>> comparator) {
61        testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
62    }
63
64    /**
65     * @return A list of all tests in the package, including small, medium, large,
66     *         flaky, and suppressed tests. Includes sub-packages recursively.
67     */
68    public List<TestMethod> getTests() {
69        List<TestMethod> testMethods = new ArrayList<TestMethod>();
70        for (Class<? extends TestCase> testCase : testCaseClasses) {
71            for (Method testMethod : getTestMethods(testCase)) {
72                testMethods.add(new TestMethod(testMethod, testCase));
73            }
74        }
75        return testMethods;
76    }
77
78    protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) {
79        List<Method> methods = Arrays.asList(testCaseClass.getMethods());
80        return select(methods, new TestMethodPredicate());
81    }
82
83    SortedSet<Class<? extends TestCase>> getTestCaseClasses() {
84        return testCaseClasses;
85    }
86
87    public boolean equals(Object o) {
88        if (this == o) {
89            return true;
90        }
91        if (o == null || getClass() != o.getClass()) {
92            return false;
93        }
94        TestGrouping other = (TestGrouping) o;
95        if (!this.testCaseClasses.equals(other.testCaseClasses)) {
96            return false;
97        }
98        return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator());
99    }
100
101    public int hashCode() {
102        return testCaseClasses.hashCode();
103    }
104
105    /**
106     * Include all tests in the given packages and all their sub-packages, unless otherwise
107     * specified. Each of the given packages must contain at least one test class, either directly
108     * or in a sub-package.
109     *
110     * @param packageNames Names of packages to add.
111     * @return The {@link TestGrouping} for method chaining.
112     */
113    public TestGrouping addPackagesRecursive(String... packageNames) {
114        for (String packageName : packageNames) {
115            List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
116            if (addedClasses.isEmpty()) {
117                Log.w("TestGrouping", "Invalid Package: '" + packageName
118                        + "' could not be found or has no tests");
119            }
120            testCaseClasses.addAll(addedClasses);
121            if (firstIncludedPackage == null) {
122                firstIncludedPackage = packageName;
123            }
124        }
125        return this;
126    }
127
128    /**
129     * Exclude all tests in the given packages and all their sub-packages, unless otherwise
130     * specified.
131     *
132     * @param packageNames Names of packages to remove.
133     * @return The {@link TestGrouping} for method chaining.
134     */
135    public TestGrouping removePackagesRecursive(String... packageNames) {
136        for (String packageName : packageNames) {
137            testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
138        }
139        return this;
140    }
141
142    /**
143     * @return The first package name passed to {@link #addPackagesRecursive(String[])}, or null
144     *         if that method was never called.
145     */
146    public String getFirstIncludedPackage() {
147        return firstIncludedPackage;
148    }
149
150    private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
151        ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
152        ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);
153
154        return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
155    }
156
157    @SuppressWarnings("unchecked")
158    private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) {
159        List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>();
160        for (Class<?> testClass : select(allClasses,
161                new TestCasePredicate())) {
162            testClasses.add((Class<? extends TestCase>) testClass);
163        }
164        return testClasses;
165    }
166
167    private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
168        ArrayList<T> selectedItems = new ArrayList<T>();
169        for (T item : items) {
170            if (predicate.apply(item)) {
171                selectedItems.add(item);
172            }
173        }
174        return selectedItems;
175    }
176
177    public void setClassLoader(ClassLoader classLoader) {
178        this.classLoader = classLoader;
179    }
180
181    /**
182     * Sort classes by their simple names (i.e. without the package prefix), using
183     * their packages to sort classes with the same name.
184     */
185    private static class SortBySimpleName
186            implements Comparator<Class<? extends TestCase>>, Serializable {
187
188        public int compare(Class<? extends TestCase> class1,
189                Class<? extends TestCase> class2) {
190            int result = class1.getSimpleName().compareTo(class2.getSimpleName());
191            if (result != 0) {
192                return result;
193            }
194            return class1.getName().compareTo(class2.getName());
195        }
196    }
197
198    /**
199     * Sort classes by their fully qualified names (i.e. with the package
200     * prefix).
201     */
202    private static class SortByFullyQualifiedName
203            implements Comparator<Class<? extends TestCase>>, Serializable {
204
205        public int compare(Class<? extends TestCase> class1,
206                Class<? extends TestCase> class2) {
207            return class1.getName().compareTo(class2.getName());
208        }
209    }
210
211    private static class TestCasePredicate implements Predicate<Class<?>> {
212
213        public boolean apply(Class aClass) {
214            int modifiers = ((Class<?>) aClass).getModifiers();
215            return TestCase.class.isAssignableFrom((Class<?>) aClass)
216                    && Modifier.isPublic(modifiers)
217                    && !Modifier.isAbstract(modifiers)
218                    && hasValidConstructor((Class<?>) aClass);
219        }
220
221        @SuppressWarnings("unchecked")
222        private boolean hasValidConstructor(java.lang.Class<?> aClass) {
223            // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler,
224            // where the return type of Class.getDeclaredConstructors() was changed
225            // from Constructor<T>[] to Constructor<?>[]
226            Constructor<? extends TestCase>[] constructors
227                    = (Constructor<? extends TestCase>[]) aClass.getConstructors();
228            for (Constructor<? extends TestCase> constructor : constructors) {
229                if (Modifier.isPublic(constructor.getModifiers())) {
230                    java.lang.Class[] parameterTypes = constructor.getParameterTypes();
231                    if (parameterTypes.length == 0 ||
232                            (parameterTypes.length == 1 && parameterTypes[0] == String.class)) {
233                        return true;
234                    }
235                }
236            }
237            return false;
238        }
239    }
240
241    private static class TestMethodPredicate implements Predicate<Method> {
242
243        public boolean apply(Method method) {
244            return ((method.getParameterTypes().length == 0) &&
245                    (method.getName().startsWith("test")) &&
246                    (method.getReturnType().getSimpleName().equals("void")));
247        }
248    }
249}
250