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