TestSuiteBuilder.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.content.Context; 20import android.test.AndroidTestRunner; 21import android.test.TestCaseUtil; 22import android.util.Log; 23import com.android.internal.util.Predicate; 24import com.google.android.collect.Lists; 25import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME; 26import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED; 27import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE; 28 29import junit.framework.Test; 30import junit.framework.TestCase; 31import junit.framework.TestSuite; 32 33import java.lang.reflect.InvocationTargetException; 34import java.util.Enumeration; 35import java.util.List; 36import java.util.Set; 37import java.util.HashSet; 38import java.util.ArrayList; 39import java.util.Collections; 40 41/** 42 * Build suites based on a combination of included packages, excluded packages, 43 * and predicates that must be satisfied. 44 */ 45public class TestSuiteBuilder { 46 47 private Context context; 48 private final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME); 49 private final Set<Predicate<TestMethod>> predicates = new HashSet<Predicate<TestMethod>>(); 50 private List<TestCase> testCases; 51 private TestSuite rootSuite; 52 private TestSuite suiteForCurrentClass; 53 private String currentClassname; 54 private String suiteName; 55 56 /** 57 * The given name is automatically prefixed with the package containing the tests to be run. 58 * If more than one package is specified, the first is used. 59 * 60 * @param clazz Use the class from your .apk. Use the class name for the test suite name. 61 * Use the class' classloader in order to load classes for testing. 62 * This is needed when running in the emulator. 63 */ 64 public TestSuiteBuilder(Class clazz) { 65 this(clazz.getName(), clazz.getClassLoader()); 66 } 67 68 public TestSuiteBuilder(String name, ClassLoader classLoader) { 69 this.suiteName = name; 70 this.testGrouping.setClassLoader(classLoader); 71 this.testCases = Lists.newArrayList(); 72 addRequirements(REJECT_SUPPRESSED); 73 } 74 75 /** @hide pending API Council approval */ 76 public TestSuiteBuilder addTestClassByName(String testClassName, String testMethodName, 77 Context context) { 78 79 AndroidTestRunner atr = new AndroidTestRunner(); 80 atr.setContext(context); 81 atr.setTestClassName(testClassName, testMethodName); 82 83 this.testCases.addAll(atr.getTestCases()); 84 return this; 85 } 86 87 /** @hide pending API Council approval */ 88 public TestSuiteBuilder addTestSuite(TestSuite testSuite) { 89 for (TestCase testCase : (List<TestCase>) TestCaseUtil.getTests(testSuite, true)) { 90 this.testCases.add(testCase); 91 } 92 return this; 93 } 94 95 /** 96 * Include all tests that satisfy the requirements in the given packages and all sub-packages, 97 * unless otherwise specified. 98 * 99 * @param packageNames Names of packages to add. 100 * @return The builder for method chaining. 101 */ 102 public TestSuiteBuilder includePackages(String... packageNames) { 103 testGrouping.addPackagesRecursive(packageNames); 104 return this; 105 } 106 107 /** 108 * Exclude all tests in the given packages and all sub-packages, unless otherwise specified. 109 * 110 * @param packageNames Names of packages to remove. 111 * @return The builder for method chaining. 112 */ 113 public TestSuiteBuilder excludePackages(String... packageNames) { 114 testGrouping.removePackagesRecursive(packageNames); 115 return this; 116 } 117 118 /** 119 * Exclude tests that fail to satisfy all of the given predicates. 120 * 121 * @param predicates Predicates to add to the list of requirements. 122 * @return The builder for method chaining. 123 */ 124 public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) { 125 this.predicates.addAll(predicates); 126 return this; 127 } 128 129 /** 130 * Include all junit tests that satisfy the requirements in the calling class' package and all 131 * sub-packages. 132 * 133 * @return The builder for method chaining. 134 */ 135 public final TestSuiteBuilder includeAllPackagesUnderHere() { 136 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); 137 138 String callingClassName = null; 139 String thisClassName = TestSuiteBuilder.class.getName(); 140 141 // We want to get the package of this method's calling class. This method's calling class 142 // should be one level below this class in the stack trace. 143 for (int i = 0; i < stackTraceElements.length; i++) { 144 StackTraceElement element = stackTraceElements[i]; 145 if (thisClassName.equals(element.getClassName()) 146 && "includeAllPackagesUnderHere".equals(element.getMethodName())) { 147 // We've found this class in the call stack. The calling class must be the 148 // next class in the stack. 149 callingClassName = stackTraceElements[i + 1].getClassName(); 150 break; 151 } 152 } 153 154 String packageName = parsePackageNameFromClassName(callingClassName); 155 return includePackages(packageName); 156 } 157 158 /** 159 * Override the default name for the suite being built. This should generally be called if you 160 * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which 161 * tests will be included. The name you specify is automatically prefixed with the package 162 * containing the tests to be run. If more than one package is specified, the first is used. 163 * 164 * @param newSuiteName Prefix of name to give the suite being built. 165 * @return The builder for method chaining. 166 */ 167 public TestSuiteBuilder named(String newSuiteName) { 168 suiteName = newSuiteName; 169 return this; 170 } 171 172 /** 173 * Call this method once you've configured your builder as desired. 174 * 175 * @return The suite containing the requested tests. 176 */ 177 public final TestSuite build() { 178 rootSuite = new TestSuite(getSuiteName()); 179 180 // Keep track of current class so we know when to create a new sub-suite. 181 currentClassname = null; 182 try { 183 for (TestMethod test : testGrouping.getTests()) { 184 if (satisfiesAllPredicates(test)) { 185 addTest(test); 186 } 187 } 188 if (testCases.size() > 0) { 189 for (TestCase testCase : testCases) { 190 if (satisfiesAllPredicates(new TestMethod(testCase))) { 191 addTest(testCase); 192 } 193 } 194 } 195 } catch (Exception exception) { 196 Log.i("TestSuiteBuilder", "Failed to create test.", exception); 197 TestSuite suite = new TestSuite(getSuiteName()); 198 suite.addTest(new FailedToCreateTests(exception)); 199 return suite; 200 } 201 return rootSuite; 202 } 203 204 /** 205 * Subclasses use this method to determine the name of the suite. 206 * 207 * @return The package and suite name combined. 208 */ 209 protected String getSuiteName() { 210 return suiteName; 211 } 212 213 /** 214 * Exclude tests that fail to satisfy all of the given predicates. If you call this method, you 215 * probably also want to call {@link #named(String)} to override the default suite name. 216 * 217 * @param predicates Predicates to add to the list of requirements. 218 * @return The builder for method chaining. 219 */ 220 public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) { 221 ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>(); 222 Collections.addAll(list, predicates); 223 return addRequirements(list); 224 } 225 226 /** 227 * A special {@link junit.framework.TestCase} used to indicate a failure during the build() 228 * step. 229 */ 230 public static class FailedToCreateTests extends TestCase { 231 private final Exception exception; 232 233 public FailedToCreateTests(Exception exception) { 234 super("testSuiteConstructionFailed"); 235 this.exception = exception; 236 } 237 238 public void testSuiteConstructionFailed() { 239 throw new RuntimeException("Exception during suite construction", exception); 240 } 241 } 242 243 /** 244 * @return the test package that represents the packages that were included for our test suite. 245 * 246 * {@hide} Not needed for 1.0 SDK. 247 */ 248 protected TestGrouping getTestGrouping() { 249 return testGrouping; 250 } 251 252 private boolean satisfiesAllPredicates(TestMethod test) { 253 for (Predicate<TestMethod> predicate : predicates) { 254 if (!predicate.apply(test)) { 255 return false; 256 } 257 } 258 return true; 259 } 260 261 private void addTest(TestMethod testMethod) throws Exception { 262 addSuiteIfNecessary(testMethod.getEnclosingClassname()); 263 suiteForCurrentClass.addTest(testMethod.createTest()); 264 } 265 266 private void addTest(Test test) { 267 addSuiteIfNecessary(test.getClass().getName()); 268 suiteForCurrentClass.addTest(test); 269 } 270 271 private void addSuiteIfNecessary(String parentClassname) { 272 if (!parentClassname.equals(currentClassname)) { 273 currentClassname = parentClassname; 274 suiteForCurrentClass = new TestSuite(parentClassname); 275 rootSuite.addTest(suiteForCurrentClass); 276 } 277 } 278 279 private static String parsePackageNameFromClassName(String className) { 280 return className.substring(0, className.lastIndexOf('.')); 281 } 282} 283