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