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 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      */
     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