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