TestGrouping.java revision e2e557976f1d68bf4d6a147ec484e5455f15eeef
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 */
47class TestGrouping {
48
49    private static final String LOG_TAG = "TestGrouping";
50
51    private final SortedSet<Class<? extends TestCase>> testCaseClasses;
52
53    static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
54            = new SortBySimpleName();
55
56    static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME
57            = new SortByFullyQualifiedName();
58
59    private final ClassLoader classLoader;
60
61    TestGrouping(Comparator<Class<? extends TestCase>> comparator, ClassLoader classLoader) {
62        testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
63        this.classLoader = classLoader;
64    }
65
66    /**
67     * @return A list of all tests in the package, including small, medium, large,
68     *         flaky, and suppressed tests. Includes sub-packages recursively.
69     */
70    public List<TestMethod> getTests() {
71        List<TestMethod> testMethods = new ArrayList<TestMethod>();
72        for (Class<? extends TestCase> testCase : testCaseClasses) {
73            for (Method testMethod : getTestMethods(testCase)) {
74                testMethods.add(new TestMethod(testMethod, testCase));
75            }
76        }
77        return testMethods;
78    }
79
80    private List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) {
81        List<Method> methods = Arrays.asList(testCaseClass.getMethods());
82        return select(methods, new TestMethodPredicate());
83    }
84
85    public boolean equals(Object o) {
86        if (this == o) {
87            return true;
88        }
89        if (o == null || getClass() != o.getClass()) {
90            return false;
91        }
92        TestGrouping other = (TestGrouping) o;
93        if (!this.testCaseClasses.equals(other.testCaseClasses)) {
94            return false;
95        }
96        return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator());
97    }
98
99    public int hashCode() {
100        return testCaseClasses.hashCode();
101    }
102
103    /**
104     * Include all tests in the given packages and all their sub-packages, unless otherwise
105     * specified. Each of the given packages must contain at least one test class, either directly
106     * or in a sub-package.
107     *
108     * @param packageNames Names of packages to add.
109     */
110    void addPackagesRecursive(String... packageNames) {
111        for (String packageName : packageNames) {
112            List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
113            if (addedClasses.isEmpty()) {
114                Log.w(LOG_TAG, "Invalid Package: '" + packageName
115                        + "' could not be found or has no tests");
116            }
117            testCaseClasses.addAll(addedClasses);
118        }
119    }
120
121    /**
122     * Exclude all tests in the given packages and all their sub-packages, unless otherwise
123     * specified.
124     *
125     * @param packageNames Names of packages to remove.
126     */
127    void removePackagesRecursive(String... packageNames) {
128        for (String packageName : packageNames) {
129            testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
130        }
131    }
132
133    private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
134        ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
135        ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);
136
137        return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
138    }
139
140    @SuppressWarnings("unchecked")
141    private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) {
142        List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>();
143        for (Class<?> testClass : select(allClasses,
144                new TestCasePredicate())) {
145            testClasses.add((Class<? extends TestCase>) testClass);
146        }
147        return testClasses;
148    }
149
150    private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
151        ArrayList<T> selectedItems = new ArrayList<T>();
152        for (T item : items) {
153            if (predicate.apply(item)) {
154                selectedItems.add(item);
155            }
156        }
157        return selectedItems;
158    }
159
160    /**
161     * Sort classes by their simple names (i.e. without the package prefix), using
162     * their packages to sort classes with the same name.
163     */
164    private static class SortBySimpleName
165            implements Comparator<Class<? extends TestCase>>, Serializable {
166
167        public int compare(Class<? extends TestCase> class1,
168                Class<? extends TestCase> class2) {
169            int result = class1.getSimpleName().compareTo(class2.getSimpleName());
170            if (result != 0) {
171                return result;
172            }
173            return class1.getName().compareTo(class2.getName());
174        }
175    }
176
177    /**
178     * Sort classes by their fully qualified names (i.e. with the package
179     * prefix).
180     */
181    private static class SortByFullyQualifiedName
182            implements Comparator<Class<? extends TestCase>>, Serializable {
183
184        public int compare(Class<? extends TestCase> class1,
185                Class<? extends TestCase> class2) {
186            return class1.getName().compareTo(class2.getName());
187        }
188    }
189
190    private static class TestCasePredicate implements Predicate<Class<?>> {
191
192        public boolean apply(Class aClass) {
193            int modifiers = ((Class<?>) aClass).getModifiers();
194            return TestCase.class.isAssignableFrom((Class<?>) aClass)
195                    && Modifier.isPublic(modifiers)
196                    && !Modifier.isAbstract(modifiers)
197                    && hasValidConstructor((Class<?>) aClass);
198        }
199
200        @SuppressWarnings("unchecked")
201        private boolean hasValidConstructor(java.lang.Class<?> aClass) {
202            // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler,
203            // where the return type of Class.getDeclaredConstructors() was changed
204            // from Constructor<T>[] to Constructor<?>[]
205            Constructor<? extends TestCase>[] constructors
206                    = (Constructor<? extends TestCase>[]) aClass.getConstructors();
207            for (Constructor<? extends TestCase> constructor : constructors) {
208                if (Modifier.isPublic(constructor.getModifiers())) {
209                    java.lang.Class[] parameterTypes = constructor.getParameterTypes();
210                    if (parameterTypes.length == 0 ||
211                            (parameterTypes.length == 1 && parameterTypes[0] == String.class)) {
212                        return true;
213                    }
214                }
215            }
216            Log.i(LOG_TAG, String.format(
217                    "TestCase class %s is missing a public constructor with no parameters " +
218                    "or a single String parameter - skipping",
219                    aClass.getName()));
220            return false;
221        }
222    }
223
224    private static class TestMethodPredicate implements Predicate<Method> {
225
226        public boolean apply(Method method) {
227            return ((method.getParameterTypes().length == 0) &&
228                    (method.getName().startsWith("test")) &&
229                    (method.getReturnType().getSimpleName().equals("void")));
230        }
231    }
232}
233