blob: 030bc426ac198f0be6f6baf1cca1f569fd3baa66 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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.ClassPathPackageInfoSource;
20import android.util.Log;
21import com.android.internal.util.Predicate;
22import junit.framework.TestCase;
23
24import java.io.Serializable;
25import java.lang.reflect.Constructor;
26import java.lang.reflect.Method;
27import java.lang.reflect.Modifier;
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.Collection;
31import java.util.Comparator;
32import java.util.List;
33import java.util.Set;
34import java.util.SortedSet;
35import java.util.TreeSet;
36
37/**
38 * Represents a collection of test classes present on the classpath. You can add individual classes
39 * or entire packages. By default sub-packages are included recursively, but methods are
40 * provided to allow for arbitrary inclusion or exclusion of sub-packages. Typically a
41 * {@link TestGrouping} will have only one root package, but this is not a requirement.
42 *
43 * {@hide} Not needed for 1.0 SDK.
44 */
45class TestGrouping {
46
47 private static final String LOG_TAG = "TestGrouping";
48
49 private final SortedSet<Class<? extends TestCase>> testCaseClasses;
50
51 static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
52 = new SortBySimpleName();
53
54 static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME
55 = new SortByFullyQualifiedName();
56
57 private final ClassLoader classLoader;
58
59 TestGrouping(Comparator<Class<? extends TestCase>> comparator, ClassLoader classLoader) {
60 testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
61 this.classLoader = classLoader;
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 private List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) {
79 List<Method> methods = Arrays.asList(testCaseClass.getMethods());
80 return select(methods, new TestMethodPredicate());
81 }
82
83 public boolean equals(Object o) {
84 if (this == o) {
85 return true;
86 }
87 if (o == null || getClass() != o.getClass()) {
88 return false;
89 }
90 TestGrouping other = (TestGrouping) o;
91 if (!this.testCaseClasses.equals(other.testCaseClasses)) {
92 return false;
93 }
94 return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator());
95 }
96
97 public int hashCode() {
98 return testCaseClasses.hashCode();
99 }
100
101 /**
102 * Include all tests in the given packages and all their sub-packages, unless otherwise
103 * specified. Each of the given packages must contain at least one test class, either directly
104 * or in a sub-package.
105 *
106 * @param packageNames Names of packages to add.
107 */
108 void addPackagesRecursive(String... packageNames) {
109 for (String packageName : packageNames) {
110 List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
111 if (addedClasses.isEmpty()) {
112 Log.w(LOG_TAG, "Invalid Package: '" + packageName
113 + "' could not be found or has no tests");
114 }
115 testCaseClasses.addAll(addedClasses);
116 }
117 }
118
119 /**
120 * Exclude all tests in the given packages and all their sub-packages, unless otherwise
121 * specified.
122 *
123 * @param packageNames Names of packages to remove.
124 */
125 void removePackagesRecursive(String... packageNames) {
126 for (String packageName : packageNames) {
127 testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
128 }
129 }
130
131 private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
132 ClassPathPackageInfoSource source = ClassPathPackageInfoSource.forClassPath(classLoader);
133
134 return selectTestClasses(source.getTopLevelClassesRecursive(packageName));
135 }
136
137 @SuppressWarnings("unchecked")
138 private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) {
139 List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>();
140 for (Class<?> testClass : select(allClasses,
141 new TestCasePredicate())) {
142 testClasses.add((Class<? extends TestCase>) testClass);
143 }
144 return testClasses;
145 }
146
147 private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
148 ArrayList<T> selectedItems = new ArrayList<T>();
149 for (T item : items) {
150 if (predicate.apply(item)) {
151 selectedItems.add(item);
152 }
153 }
154 return selectedItems;
155 }
156
157 /**
158 * Sort classes by their simple names (i.e. without the package prefix), using
159 * their packages to sort classes with the same name.
160 */
161 private static class SortBySimpleName
162 implements Comparator<Class<? extends TestCase>>, Serializable {
163
164 public int compare(Class<? extends TestCase> class1,
165 Class<? extends TestCase> class2) {
166 int result = class1.getSimpleName().compareTo(class2.getSimpleName());
167 if (result != 0) {
168 return result;
169 }
170 return class1.getName().compareTo(class2.getName());
171 }
172 }
173
174 /**
175 * Sort classes by their fully qualified names (i.e. with the package
176 * prefix).
177 */
178 private static class SortByFullyQualifiedName
179 implements Comparator<Class<? extends TestCase>>, Serializable {
180
181 public int compare(Class<? extends TestCase> class1,
182 Class<? extends TestCase> class2) {
183 return class1.getName().compareTo(class2.getName());
184 }
185 }
186
187 private static class TestCasePredicate implements Predicate<Class<?>> {
188
189 public boolean apply(Class aClass) {
190 int modifiers = ((Class<?>) aClass).getModifiers();
191 return TestCase.class.isAssignableFrom((Class<?>) aClass)
192 && Modifier.isPublic(modifiers)
193 && !Modifier.isAbstract(modifiers)
194 && hasValidConstructor((Class<?>) aClass);
195 }
196
197 @SuppressWarnings("unchecked")
198 private boolean hasValidConstructor(java.lang.Class<?> aClass) {
199 // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler,
200 // where the return type of Class.getDeclaredConstructors() was changed
201 // from Constructor<T>[] to Constructor<?>[]
202 Constructor<? extends TestCase>[] constructors
203 = (Constructor<? extends TestCase>[]) aClass.getConstructors();
204 for (Constructor<? extends TestCase> constructor : constructors) {
205 if (Modifier.isPublic(constructor.getModifiers())) {
206 java.lang.Class[] parameterTypes = constructor.getParameterTypes();
207 if (parameterTypes.length == 0 ||
208 (parameterTypes.length == 1 && parameterTypes[0] == String.class)) {
209 return true;
210 }
211 }
212 }
213 Log.i(LOG_TAG, String.format(
214 "TestCase class %s is missing a public constructor with no parameters " +
215 "or a single String parameter - skipping",
216 aClass.getName()));
217 return false;
218 }
219 }
220
221 private static class TestMethodPredicate implements Predicate<Method> {
222
223 public boolean apply(Method method) {
224 return ((method.getParameterTypes().length == 0) &&
225 (method.getName().startsWith("test")) &&
226 (method.getReturnType().getSimpleName().equals("void")));
227 }
228 }
229}