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