blob: a2b94ff4503500bc4d603717a1b3a705b19280ef [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Brett Chabotbb469fe2010-04-23 16:22:09 -070049 private static final String LOG_TAG = "TestGrouping";
50
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 SortedSet<Class<? extends TestCase>> testCaseClasses;
52
53 public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
54 = new SortBySimpleName();
55
56 public static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME
57 = new SortByFullyQualifiedName();
58
59 protected String firstIncludedPackage = null;
60 private ClassLoader classLoader;
61
62 public TestGrouping(Comparator<Class<? extends TestCase>> comparator) {
63 testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
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 protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) {
81 List<Method> methods = Arrays.asList(testCaseClass.getMethods());
82 return select(methods, new TestMethodPredicate());
83 }
84
85 SortedSet<Class<? extends TestCase>> getTestCaseClasses() {
86 return testCaseClasses;
87 }
88
89 public boolean equals(Object o) {
90 if (this == o) {
91 return true;
92 }
93 if (o == null || getClass() != o.getClass()) {
94 return false;
95 }
96 TestGrouping other = (TestGrouping) o;
97 if (!this.testCaseClasses.equals(other.testCaseClasses)) {
98 return false;
99 }
100 return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator());
101 }
102
103 public int hashCode() {
104 return testCaseClasses.hashCode();
105 }
106
107 /**
108 * Include all tests in the given packages and all their sub-packages, unless otherwise
109 * specified. Each of the given packages must contain at least one test class, either directly
110 * or in a sub-package.
111 *
112 * @param packageNames Names of packages to add.
113 * @return The {@link TestGrouping} for method chaining.
114 */
115 public TestGrouping addPackagesRecursive(String... packageNames) {
116 for (String packageName : packageNames) {
117 List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
118 if (addedClasses.isEmpty()) {
Brett Chabotbb469fe2010-04-23 16:22:09 -0700119 Log.w(LOG_TAG, "Invalid Package: '" + packageName
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 + "' could not be found or has no tests");
121 }
122 testCaseClasses.addAll(addedClasses);
123 if (firstIncludedPackage == null) {
124 firstIncludedPackage = packageName;
125 }
126 }
127 return this;
128 }
129
130 /**
131 * Exclude all tests in the given packages and all their sub-packages, unless otherwise
132 * specified.
133 *
134 * @param packageNames Names of packages to remove.
135 * @return The {@link TestGrouping} for method chaining.
136 */
137 public TestGrouping removePackagesRecursive(String... packageNames) {
138 for (String packageName : packageNames) {
139 testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
140 }
141 return this;
142 }
143
144 /**
145 * @return The first package name passed to {@link #addPackagesRecursive(String[])}, or null
146 * if that method was never called.
147 */
148 public String getFirstIncludedPackage() {
149 return firstIncludedPackage;
150 }
151
152 private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
153 ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
154 ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);
155
156 return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
157 }
158
159 @SuppressWarnings("unchecked")
160 private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) {
161 List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>();
162 for (Class<?> testClass : select(allClasses,
163 new TestCasePredicate())) {
164 testClasses.add((Class<? extends TestCase>) testClass);
165 }
166 return testClasses;
167 }
168
169 private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
170 ArrayList<T> selectedItems = new ArrayList<T>();
171 for (T item : items) {
172 if (predicate.apply(item)) {
173 selectedItems.add(item);
174 }
175 }
176 return selectedItems;
177 }
178
179 public void setClassLoader(ClassLoader classLoader) {
180 this.classLoader = classLoader;
181 }
182
183 /**
184 * Sort classes by their simple names (i.e. without the package prefix), using
185 * their packages to sort classes with the same name.
186 */
187 private static class SortBySimpleName
188 implements Comparator<Class<? extends TestCase>>, Serializable {
189
190 public int compare(Class<? extends TestCase> class1,
191 Class<? extends TestCase> class2) {
192 int result = class1.getSimpleName().compareTo(class2.getSimpleName());
193 if (result != 0) {
194 return result;
195 }
196 return class1.getName().compareTo(class2.getName());
197 }
198 }
199
200 /**
201 * Sort classes by their fully qualified names (i.e. with the package
202 * prefix).
203 */
204 private static class SortByFullyQualifiedName
205 implements Comparator<Class<? extends TestCase>>, Serializable {
206
207 public int compare(Class<? extends TestCase> class1,
208 Class<? extends TestCase> class2) {
209 return class1.getName().compareTo(class2.getName());
210 }
211 }
212
213 private static class TestCasePredicate implements Predicate<Class<?>> {
214
215 public boolean apply(Class aClass) {
216 int modifiers = ((Class<?>) aClass).getModifiers();
217 return TestCase.class.isAssignableFrom((Class<?>) aClass)
218 && Modifier.isPublic(modifiers)
219 && !Modifier.isAbstract(modifiers)
220 && hasValidConstructor((Class<?>) aClass);
221 }
222
223 @SuppressWarnings("unchecked")
224 private boolean hasValidConstructor(java.lang.Class<?> aClass) {
225 // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler,
226 // where the return type of Class.getDeclaredConstructors() was changed
227 // from Constructor<T>[] to Constructor<?>[]
228 Constructor<? extends TestCase>[] constructors
229 = (Constructor<? extends TestCase>[]) aClass.getConstructors();
230 for (Constructor<? extends TestCase> constructor : constructors) {
231 if (Modifier.isPublic(constructor.getModifiers())) {
232 java.lang.Class[] parameterTypes = constructor.getParameterTypes();
233 if (parameterTypes.length == 0 ||
234 (parameterTypes.length == 1 && parameterTypes[0] == String.class)) {
235 return true;
236 }
237 }
238 }
Brett Chabotbb469fe2010-04-23 16:22:09 -0700239 Log.i(LOG_TAG, String.format(
240 "TestCase class %s is missing a public constructor with no parameters " +
241 "or a single String parameter - skipping",
242 aClass.getName()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 return false;
244 }
245 }
246
247 private static class TestMethodPredicate implements Predicate<Method> {
248
249 public boolean apply(Method method) {
250 return ((method.getParameterTypes().length == 0) &&
251 (method.getName().startsWith("test")) &&
252 (method.getReturnType().getSimpleName().equals("void")));
253 }
254 }
255}