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 
17 package android.test.suitebuilder;
18 
19 import android.test.ClassPathPackageInfo;
20 import android.test.ClassPathPackageInfoSource;
21 import android.test.PackageInfoSources;
22 import android.util.Log;
23 import com.android.internal.util.Predicate;
24 import junit.framework.TestCase;
25 
26 import java.io.Serializable;
27 import java.lang.reflect.Constructor;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Comparator;
34 import java.util.List;
35 import java.util.Set;
36 import java.util.SortedSet;
37 import 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  */
47 public class TestGrouping {
48 
49     private static final String LOG_TAG = "TestGrouping";
50 
51     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 
TestGrouping(Comparator<Class<? extends TestCase>> comparator)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      */
getTests()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 
getTestMethods(Class<? extends TestCase> testCaseClass)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 
getTestCaseClasses()85     SortedSet<Class<? extends TestCase>> getTestCaseClasses() {
86         return testCaseClasses;
87     }
88 
equals(Object o)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 
hashCode()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      */
addPackagesRecursive(String... packageNames)115     public TestGrouping addPackagesRecursive(String... packageNames) {
116         for (String packageName : packageNames) {
117             List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
118             if (addedClasses.isEmpty()) {
119                 Log.w(LOG_TAG, "Invalid Package: '" + packageName
120                         + "' 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      */
removePackagesRecursive(String... packageNames)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      */
getFirstIncludedPackage()148     public String getFirstIncludedPackage() {
149         return firstIncludedPackage;
150     }
151 
testCaseClassesInPackage(String packageName)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")
selectTestClasses(Set<Class<?>> allClasses)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 
select(Collection<T> items, Predicate<T> predicate)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 
setClassLoader(ClassLoader classLoader)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 
compare(Class<? extends TestCase> class1, Class<? extends TestCase> class2)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 
compare(Class<? extends TestCase> class1, Class<? extends TestCase> class2)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 
apply(Class aClass)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")
hasValidConstructor(java.lang.Class<?> aClass)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             }
239             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()));
243             return false;
244         }
245     }
246 
247     private static class TestMethodPredicate implements Predicate<Method> {
248 
apply(Method method)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 }
256