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 
     28 import junit.framework.Test;
     29 import junit.framework.TestCase;
     30 import junit.framework.TestSuite;
     31 
     32 import java.lang.reflect.InvocationTargetException;
     33 import java.util.Enumeration;
     34 import java.util.List;
     35 import java.util.Set;
     36 import java.util.HashSet;
     37 import java.util.ArrayList;
     38 import java.util.Collections;
     39 
     40 /**
     41  * Build suites based on a combination of included packages, excluded packages,
     42  * and predicates that must be satisfied.
     43  */
     44 public class TestSuiteBuilder {
     45 
     46     private Context context;
     47     private final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME);
     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.setClassLoader(classLoader);
     70         this.testCases = Lists.newArrayList();
     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      */
    123     public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) {
    124         this.predicates.addAll(predicates);
    125         return this;
    126     }
    127 
    128     /**
    129      * Include all junit tests that satisfy the requirements in the calling class' package and all
    130      * sub-packages.
    131      *
    132      * @return The builder for method chaining.
    133      */
    134     public final TestSuiteBuilder includeAllPackagesUnderHere() {
    135         StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
    136 
    137         String callingClassName = null;
    138         String thisClassName = TestSuiteBuilder.class.getName();
    139 
    140         // We want to get the package of this method's calling class. This method's calling class
    141         // should be one level below this class in the stack trace.
    142         for (int i = 0; i < stackTraceElements.length; i++) {
    143             StackTraceElement element = stackTraceElements[i];
    144             if (thisClassName.equals(element.getClassName())
    145                     && "includeAllPackagesUnderHere".equals(element.getMethodName())) {
    146                 // We've found this class in the call stack. The calling class must be the
    147                 // next class in the stack.
    148                 callingClassName = stackTraceElements[i + 1].getClassName();
    149                 break;
    150             }
    151         }
    152 
    153         String packageName = parsePackageNameFromClassName(callingClassName);
    154         return includePackages(packageName);
    155     }
    156 
    157     /**
    158      * Override the default name for the suite being built. This should generally be called if you
    159      * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which
    160      * tests will be included. The name you specify is automatically prefixed with the package
    161      * containing the tests to be run. If more than one package is specified, the first is used.
    162      *
    163      * @param newSuiteName Prefix of name to give the suite being built.
    164      * @return The builder for method chaining.
    165      */
    166     public TestSuiteBuilder named(String newSuiteName) {
    167         suiteName = newSuiteName;
    168         return this;
    169     }
    170 
    171     /**
    172      * Call this method once you've configured your builder as desired.
    173      *
    174      * @return The suite containing the requested tests.
    175      */
    176     public final TestSuite build() {
    177         rootSuite = new TestSuite(getSuiteName());
    178 
    179         // Keep track of current class so we know when to create a new sub-suite.
    180         currentClassname = null;
    181         try {
    182             for (TestMethod test : testGrouping.getTests()) {
    183                 if (satisfiesAllPredicates(test)) {
    184                     addTest(test);
    185                 }
    186             }
    187             if (testCases.size() > 0) {
    188                 for (TestCase testCase : testCases) {
    189                     if (satisfiesAllPredicates(new TestMethod(testCase))) {
    190                         addTest(testCase);
    191                     }
    192                 }
    193             }
    194         } catch (Exception exception) {
    195             Log.i("TestSuiteBuilder", "Failed to create test.", exception);
    196             TestSuite suite = new TestSuite(getSuiteName());
    197             suite.addTest(new FailedToCreateTests(exception));
    198             return suite;
    199         }
    200         return rootSuite;
    201     }
    202 
    203     /**
    204      * Subclasses use this method to determine the name of the suite.
    205      *
    206      * @return The package and suite name combined.
    207      */
    208     protected String getSuiteName() {
    209         return suiteName;
    210     }
    211 
    212     /**
    213      * Exclude tests that fail to satisfy all of the given predicates. If you call this method, you
    214      * probably also want to call {@link #named(String)} to override the default suite name.
    215      *
    216      * @param predicates Predicates to add to the list of requirements.
    217      * @return The builder for method chaining.
    218      */
    219     public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) {
    220         ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>();
    221         Collections.addAll(list, predicates);
    222         return addRequirements(list);
    223     }
    224 
    225     /**
    226      * A special {@link junit.framework.TestCase} used to indicate a failure during the build()
    227      * step.
    228      */
    229     public static class FailedToCreateTests extends TestCase {
    230         private final Exception exception;
    231 
    232         public FailedToCreateTests(Exception exception) {
    233             super("testSuiteConstructionFailed");
    234             this.exception = exception;
    235         }
    236 
    237         public void testSuiteConstructionFailed() {
    238             throw new RuntimeException("Exception during suite construction", exception);
    239         }
    240     }
    241 
    242     /**
    243      * @return the test package that represents the packages that were included for our test suite.
    244      *
    245      * {@hide} Not needed for 1.0 SDK.
    246      */
    247     protected TestGrouping getTestGrouping() {
    248         return testGrouping;
    249     }
    250 
    251     private boolean satisfiesAllPredicates(TestMethod test) {
    252         for (Predicate<TestMethod> predicate : predicates) {
    253             if (!predicate.apply(test)) {
    254                 return false;
    255             }
    256         }
    257         return true;
    258     }
    259 
    260     private void addTest(TestMethod testMethod) throws Exception {
    261         addSuiteIfNecessary(testMethod.getEnclosingClassname());
    262         suiteForCurrentClass.addTest(testMethod.createTest());
    263     }
    264 
    265     private void addTest(Test test) {
    266         addSuiteIfNecessary(test.getClass().getName());
    267         suiteForCurrentClass.addTest(test);
    268     }
    269 
    270     private void addSuiteIfNecessary(String parentClassname) {
    271         if (!parentClassname.equals(currentClassname)) {
    272             currentClassname = parentClassname;
    273             suiteForCurrentClass = new TestSuite(parentClassname);
    274             rootSuite.addTest(suiteForCurrentClass);
    275         }
    276     }
    277 
    278     private static String parsePackageNameFromClassName(String className) {
    279         return className.substring(0, className.lastIndexOf('.'));
    280     }
    281 }
    282