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 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 */ 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 protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) { 81 List<Method> methods = Arrays.asList(testCaseClass.getMethods()); 82 return select(methods, new TestMethodPredicate()); 83 } 84 85 SortedSet<Class<? extends TestCase>> getTestCaseClasses() { 86 return testCaseClasses; 87 } 88 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 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 */ 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 */ 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 */ 148 public String getFirstIncludedPackage() { 149 return firstIncludedPackage; 150 } 151 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") 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 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 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 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 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 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") 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 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