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.ClassPathPackageInfoSource;
20 import android.util.Log;
21 import com.android.internal.util.Predicate;
22 import junit.framework.TestCase;
23 
24 import java.io.Serializable;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Comparator;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.SortedSet;
35 import 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  */
45 class 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 
TestGrouping(Comparator<Class<? extends TestCase>> comparator, ClassLoader classLoader)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      */
getTests()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 
getTestMethods(Class<? extends TestCase> testCaseClass)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 
equals(Object o)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 
hashCode()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      */
addPackagesRecursive(String... packageNames)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      */
removePackagesRecursive(String... packageNames)125     void removePackagesRecursive(String... packageNames) {
126         for (String packageName : packageNames) {
127             testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
128         }
129     }
130 
testCaseClassesInPackage(String packageName)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")
selectTestClasses(Set<Class<?>> allClasses)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 
select(Collection<T> items, Predicate<T> predicate)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 
compare(Class<? extends TestCase> class1, Class<? extends TestCase> class2)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 
compare(Class<? extends TestCase> class1, Class<? extends TestCase> class2)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 
apply(Class aClass)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")
hasValidConstructor(java.lang.Class<?> aClass)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 
apply(Method method)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 }
230