Home | History | Annotate | Download | only in suitebuilder
      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      */
     63     public TestSuiteBuilder(Class clazz) {
     64         this(clazz.getName(), clazz.getClassLoader());
     65     }
     66 
     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 */
     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 */
     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      */
    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      */
    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      */
    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 {@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      */
    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      * @hide
    220      */
    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 
    238         public FailedToCreateTests(Exception exception) {
    239             super("testSuiteConstructionFailed");
    240             this.exception = exception;
    241         }
    242 
    243         public void testSuiteConstructionFailed() {
    244             throw new RuntimeException("Exception during suite construction", exception);
    245         }
    246     }
    247 
    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 
    257     private void addTest(TestMethod testMethod) throws Exception {
    258         addSuiteIfNecessary(testMethod.getEnclosingClassname());
    259         suiteForCurrentClass.addTest(testMethod.createTest());
    260     }
    261 
    262     private void addTest(Test test) {
    263         addSuiteIfNecessary(test.getClass().getName());
    264         suiteForCurrentClass.addTest(test);
    265     }
    266 
    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 
    275     private static String parsePackageNameFromClassName(String className) {
    276         return className.substring(0, className.lastIndexOf('.'));
    277     }
    278 }
    279