• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.google.android.collect.Lists;
25 import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME;
26 import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED;
27 import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE;
28 
29 import junit.framework.Test;
30 import junit.framework.TestCase;
31 import junit.framework.TestSuite;
32 
33 import java.lang.reflect.InvocationTargetException;
34 import java.util.Enumeration;
35 import java.util.List;
36 import java.util.Set;
37 import java.util.HashSet;
38 import java.util.ArrayList;
39 import 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  */
45 public 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      */
TestSuiteBuilder(Class clazz)64     public TestSuiteBuilder(Class clazz) {
65         this(clazz.getName(), clazz.getClassLoader());
66     }
67 
TestSuiteBuilder(String name, ClassLoader classLoader)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 */
addTestClassByName(String testClassName, String testMethodName, Context context)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 */
addTestSuite(TestSuite testSuite)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      */
includePackages(String... packageNames)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      */
excludePackages(String... packageNames)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      */
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 {@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      */
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      */
addRequirements(Predicate<TestMethod>.... predicates)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 
FailedToCreateTests(Exception exception)233         public FailedToCreateTests(Exception exception) {
234             super("testSuiteConstructionFailed");
235             this.exception = exception;
236         }
237 
testSuiteConstructionFailed()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      */
getTestGrouping()248     protected TestGrouping getTestGrouping() {
249         return testGrouping;
250     }
251 
satisfiesAllPredicates(TestMethod test)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 
addTest(TestMethod testMethod)261     private void addTest(TestMethod testMethod) throws Exception {
262         addSuiteIfNecessary(testMethod.getEnclosingClassname());
263         suiteForCurrentClass.addTest(testMethod.createTest());
264     }
265 
addTest(Test test)266     private void addTest(Test test) {
267         addSuiteIfNecessary(test.getClass().getName());
268         suiteForCurrentClass.addTest(test);
269     }
270 
addSuiteIfNecessary(String parentClassname)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 
parsePackageNameFromClassName(String className)279     private static String parsePackageNameFromClassName(String className) {
280         return className.substring(0, className.lastIndexOf('.'));
281     }
282 }
283