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