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.content.Context; 20 import android.test.AndroidTestRunner; 21 import android.test.TestCaseUtil; 22 import android.util.Log; 23 import com.android.internal.util.Predicate; 24 import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME; 25 import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED; 26 27 import junit.framework.Test; 28 import junit.framework.TestCase; 29 import junit.framework.TestSuite; 30 31 import java.util.List; 32 import java.util.Set; 33 import java.util.HashSet; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 37 /** 38 * Build suites based on a combination of included packages, excluded packages, 39 * and predicates that must be satisfied. 40 * 41 * @deprecated New tests should be written using the 42 * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>. 43 */ 44 @Deprecated 45 public class TestSuiteBuilder { 46 47 private final TestGrouping testGrouping; 48 private final Set<Predicate<TestMethod>> predicates = new HashSet<Predicate<TestMethod>>(); 49 private List<TestCase> testCases; 50 private TestSuite rootSuite; 51 private TestSuite suiteForCurrentClass; 52 private String currentClassname; 53 private String suiteName; 54 55 /** 56 * The given name is automatically prefixed with the package containing the tests to be run. 57 * If more than one package is specified, the first is used. 58 * 59 * @param clazz Use the class from your .apk. Use the class name for the test suite name. 60 * Use the class' classloader in order to load classes for testing. 61 * This is needed when running in the emulator. 62 */ TestSuiteBuilder(Class clazz)63 public TestSuiteBuilder(Class clazz) { 64 this(clazz.getName(), clazz.getClassLoader()); 65 } 66 TestSuiteBuilder(String name, ClassLoader classLoader)67 public TestSuiteBuilder(String name, ClassLoader classLoader) { 68 this.suiteName = name; 69 this.testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME, classLoader); 70 this.testCases = new ArrayList<>(); 71 addRequirements(REJECT_SUPPRESSED); 72 } 73 74 /** @hide pending API Council approval */ addTestClassByName(String testClassName, String testMethodName, Context context)75 public TestSuiteBuilder addTestClassByName(String testClassName, String testMethodName, 76 Context context) { 77 78 AndroidTestRunner atr = new AndroidTestRunner(); 79 atr.setContext(context); 80 atr.setTestClassName(testClassName, testMethodName); 81 82 this.testCases.addAll(atr.getTestCases()); 83 return this; 84 } 85 86 /** @hide pending API Council approval */ addTestSuite(TestSuite testSuite)87 public TestSuiteBuilder addTestSuite(TestSuite testSuite) { 88 for (TestCase testCase : (List<TestCase>) TestCaseUtil.getTests(testSuite, true)) { 89 this.testCases.add(testCase); 90 } 91 return this; 92 } 93 94 /** 95 * Include all tests that satisfy the requirements in the given packages and all sub-packages, 96 * unless otherwise specified. 97 * 98 * @param packageNames Names of packages to add. 99 * @return The builder for method chaining. 100 */ includePackages(String... packageNames)101 public TestSuiteBuilder includePackages(String... packageNames) { 102 testGrouping.addPackagesRecursive(packageNames); 103 return this; 104 } 105 106 /** 107 * Exclude all tests in the given packages and all sub-packages, unless otherwise specified. 108 * 109 * @param packageNames Names of packages to remove. 110 * @return The builder for method chaining. 111 */ excludePackages(String... packageNames)112 public TestSuiteBuilder excludePackages(String... packageNames) { 113 testGrouping.removePackagesRecursive(packageNames); 114 return this; 115 } 116 117 /** 118 * Exclude tests that fail to satisfy all of the given predicates. 119 * 120 * @param predicates Predicates to add to the list of requirements. 121 * @return The builder for method chaining. 122 * @hide 123 */ addRequirements(List<Predicate<TestMethod>> predicates)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 */ includeAllPackagesUnderHere()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 {@code 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 */ named(String newSuiteName)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 */ build()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 */ getSuiteName()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 * @hide 220 */ addRequirements(Predicate<TestMethod>.... predicates)221 public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) { 222 ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>(); 223 Collections.addAll(list, predicates); 224 return addRequirements(list); 225 } 226 227 /** 228 * A special {@link junit.framework.TestCase} used to indicate a failure during the build() 229 * step. 230 * 231 * @deprecated New tests should be written using the 232 * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>. 233 */ 234 @Deprecated 235 public static class FailedToCreateTests extends TestCase { 236 private final Exception exception; 237 FailedToCreateTests(Exception exception)238 public FailedToCreateTests(Exception exception) { 239 super("testSuiteConstructionFailed"); 240 this.exception = exception; 241 } 242 testSuiteConstructionFailed()243 public void testSuiteConstructionFailed() { 244 throw new RuntimeException("Exception during suite construction", exception); 245 } 246 } 247 satisfiesAllPredicates(TestMethod test)248 private boolean satisfiesAllPredicates(TestMethod test) { 249 for (Predicate<TestMethod> predicate : predicates) { 250 if (!predicate.apply(test)) { 251 return false; 252 } 253 } 254 return true; 255 } 256 addTest(TestMethod testMethod)257 private void addTest(TestMethod testMethod) throws Exception { 258 addSuiteIfNecessary(testMethod.getEnclosingClassname()); 259 suiteForCurrentClass.addTest(testMethod.createTest()); 260 } 261 addTest(Test test)262 private void addTest(Test test) { 263 addSuiteIfNecessary(test.getClass().getName()); 264 suiteForCurrentClass.addTest(test); 265 } 266 addSuiteIfNecessary(String parentClassname)267 private void addSuiteIfNecessary(String parentClassname) { 268 if (!parentClassname.equals(currentClassname)) { 269 currentClassname = parentClassname; 270 suiteForCurrentClass = new TestSuite(parentClassname); 271 rootSuite.addTest(suiteForCurrentClass); 272 } 273 } 274 parsePackageNameFromClassName(String className)275 private static String parsePackageNameFromClassName(String className) { 276 return className.substring(0, className.lastIndexOf('.')); 277 } 278 } 279