Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2012 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 package com.android.test.runner;
     17 
     18 import android.app.Instrumentation;
     19 import android.test.suitebuilder.annotation.LargeTest;
     20 import android.test.suitebuilder.annotation.MediumTest;
     21 import android.test.suitebuilder.annotation.SmallTest;
     22 import android.test.suitebuilder.annotation.Suppress;
     23 import android.util.Log;
     24 
     25 import com.android.test.runner.ClassPathScanner.ChainedClassNameFilter;
     26 import com.android.test.runner.ClassPathScanner.ExcludePackageNameFilter;
     27 import com.android.test.runner.ClassPathScanner.ExternalClassNameFilter;
     28 
     29 import org.junit.runner.Computer;
     30 import org.junit.runner.Description;
     31 import org.junit.runner.Request;
     32 import org.junit.runner.Runner;
     33 import org.junit.runner.manipulation.Filter;
     34 import org.junit.runners.model.InitializationError;
     35 
     36 import java.io.IOException;
     37 import java.io.PrintStream;
     38 import java.lang.annotation.Annotation;
     39 import java.util.Arrays;
     40 import java.util.Collection;
     41 import java.util.Collections;
     42 
     43 /**
     44  * Builds a {@link Request} from test classes in given apk paths, filtered on provided set of
     45  * restrictions.
     46  */
     47 public class TestRequestBuilder {
     48 
     49     private static final String LOG_TAG = "TestRequestBuilder";
     50 
     51     private String[] mApkPaths;
     52     private TestLoader mTestLoader;
     53     private Filter mFilter = new AnnotationExclusionFilter(Suppress.class);
     54     private PrintStream mWriter;
     55     private boolean mSkipExecution = false;
     56 
     57     /**
     58      * Filter that only runs tests whose method or class has been annotated with given filter.
     59      */
     60     private static class AnnotationInclusionFilter extends Filter {
     61 
     62         private final Class<? extends Annotation> mAnnotationClass;
     63 
     64         AnnotationInclusionFilter(Class<? extends Annotation> annotation) {
     65             mAnnotationClass = annotation;
     66         }
     67 
     68         /**
     69          * {@inheritDoc}
     70          */
     71         @Override
     72         public boolean shouldRun(Description description) {
     73             if (description.isTest()) {
     74                 return description.getAnnotation(mAnnotationClass) != null ||
     75                         description.getTestClass().isAnnotationPresent(mAnnotationClass);
     76             } else {
     77                 // don't filter out any test classes/suites, because their methods may have correct
     78                 // annotation
     79                 return true;
     80             }
     81         }
     82 
     83         /**
     84          * {@inheritDoc}
     85          */
     86         @Override
     87         public String describe() {
     88             return String.format("annotation %s", mAnnotationClass.getName());
     89         }
     90     }
     91 
     92     /**
     93      * Filter out tests whose method or class has been annotated with given filter.
     94      */
     95     private static class AnnotationExclusionFilter extends Filter {
     96 
     97         private final Class<? extends Annotation> mAnnotationClass;
     98 
     99         AnnotationExclusionFilter(Class<? extends Annotation> annotation) {
    100             mAnnotationClass = annotation;
    101         }
    102 
    103         /**
    104          * {@inheritDoc}
    105          */
    106         @Override
    107         public boolean shouldRun(Description description) {
    108             if (description.getTestClass().isAnnotationPresent(mAnnotationClass) ||
    109                     description.getAnnotation(mAnnotationClass) != null) {
    110                 return false;
    111             } else {
    112                 return true;
    113             }
    114         }
    115 
    116         /**
    117          * {@inheritDoc}
    118          */
    119         @Override
    120         public String describe() {
    121             return String.format("not annotation %s", mAnnotationClass.getName());
    122         }
    123     }
    124 
    125     public TestRequestBuilder(PrintStream writer, String... apkPaths) {
    126         mApkPaths = apkPaths;
    127         mTestLoader = new TestLoader(writer);
    128     }
    129 
    130     /**
    131      * Add a test class to be executed. All test methods in this class will be executed.
    132      *
    133      * @param className
    134      */
    135     public void addTestClass(String className) {
    136         mTestLoader.loadClass(className);
    137     }
    138 
    139     /**
    140      * Adds a test method to run.
    141      * <p/>
    142      * Currently only supports one test method to be run.
    143      */
    144     public void addTestMethod(String testClassName, String testMethodName) {
    145         Class<?> clazz = mTestLoader.loadClass(testClassName);
    146         if (clazz != null) {
    147             mFilter = mFilter.intersect(Filter.matchMethodDescription(
    148                     Description.createTestDescription(clazz, testMethodName)));
    149         }
    150     }
    151 
    152     /**
    153      * Run only tests with given size
    154      * @param testSize
    155      */
    156     public void addTestSizeFilter(String testSize) {
    157         if ("small".equals(testSize)) {
    158             mFilter = mFilter.intersect(new AnnotationInclusionFilter(SmallTest.class));
    159         } else if ("medium".equals(testSize)) {
    160             mFilter = mFilter.intersect(new AnnotationInclusionFilter(MediumTest.class));
    161         } else if ("large".equals(testSize)) {
    162             mFilter = mFilter.intersect(new AnnotationInclusionFilter(LargeTest.class));
    163         } else {
    164             Log.e(LOG_TAG, String.format("Unrecognized test size '%s'", testSize));
    165         }
    166     }
    167 
    168     /**
    169      * Only run tests annotated with given annotation class.
    170      *
    171      * @param annotation the full class name of annotation
    172      */
    173     public void addAnnotationInclusionFilter(String annotation) {
    174         Class<? extends Annotation> annotationClass = loadAnnotationClass(annotation);
    175         if (annotationClass != null) {
    176             mFilter = mFilter.intersect(new AnnotationInclusionFilter(annotationClass));
    177         }
    178     }
    179 
    180     /**
    181      * Skip tests annotated with given annotation class.
    182      *
    183      * @param notAnnotation the full class name of annotation
    184      */
    185     public void addAnnotationExclusionFilter(String notAnnotation) {
    186         Class<? extends Annotation> annotationClass = loadAnnotationClass(notAnnotation);
    187         if (annotationClass != null) {
    188             mFilter = mFilter.intersect(new AnnotationExclusionFilter(annotationClass));
    189         }
    190     }
    191 
    192     /**
    193      * Build a request that will generate test started and test ended events, but will skip actual
    194      * test execution.
    195      */
    196     public void setSkipExecution(boolean b) {
    197         mSkipExecution = b;
    198     }
    199 
    200     /**
    201      * Builds the {@link TestRequest} based on current contents of added classes and methods.
    202      * <p/>
    203      * If no classes have been explicitly added, will scan the classpath for all tests.
    204      *
    205      */
    206     public TestRequest build(Instrumentation instr) {
    207         if (mTestLoader.isEmpty()) {
    208             // no class restrictions have been specified. Load all classes
    209             loadClassesFromClassPath();
    210         }
    211 
    212         Request request = classes(instr, mSkipExecution, new Computer(),
    213                 mTestLoader.getLoadedClasses().toArray(new Class[0]));
    214         return new TestRequest(mTestLoader.getLoadFailures(), request.filterWith(mFilter));
    215     }
    216 
    217     /**
    218      * Create a <code>Request</code> that, when processed, will run all the tests
    219      * in a set of classes.
    220      *
    221      * @param instr the {@link Instrumentation} to inject into any tests that require it
    222      * @param computer Helps construct Runners from classes
    223      * @param classes the classes containing the tests
    224      * @return a <code>Request</code> that will cause all tests in the classes to be run
    225      */
    226     private static Request classes(Instrumentation instr, boolean skipExecution,
    227             Computer computer, Class<?>... classes) {
    228         try {
    229             AndroidRunnerBuilder builder = new AndroidRunnerBuilder(true, instr, skipExecution);
    230             Runner suite = computer.getSuite(builder, classes);
    231             return Request.runner(suite);
    232         } catch (InitializationError e) {
    233             throw new RuntimeException(
    234                     "Suite constructor, called as above, should always complete");
    235         }
    236     }
    237 
    238     private void loadClassesFromClassPath() {
    239         Collection<String> classNames = getClassNamesFromClassPath();
    240         for (String className : classNames) {
    241             mTestLoader.loadIfTest(className);
    242         }
    243     }
    244 
    245     private Collection<String> getClassNamesFromClassPath() {
    246         Log.i(LOG_TAG, String.format("Scanning classpath to find tests in apks %s",
    247                 Arrays.toString(mApkPaths)));
    248         ClassPathScanner scanner = new ClassPathScanner(mApkPaths);
    249         try {
    250             // exclude inner classes, and classes from junit and this lib namespace
    251             return scanner.getClassPathEntries(new ChainedClassNameFilter(
    252                     new ExcludePackageNameFilter("junit"),
    253                     new ExcludePackageNameFilter("org.junit"),
    254                     new ExcludePackageNameFilter("org.hamcrest"),
    255                     new ExternalClassNameFilter(),
    256                     new ExcludePackageNameFilter("com.android.test.runner.junit3")));
    257         } catch (IOException e) {
    258             mWriter.println("failed to scan classes");
    259             Log.e(LOG_TAG, "Failed to scan classes", e);
    260         }
    261         return Collections.emptyList();
    262     }
    263 
    264     /**
    265      * Factory method for {@link ClassPathScanner}.
    266      * <p/>
    267      * Exposed so unit tests can mock.
    268      */
    269     ClassPathScanner createClassPathScanner(String... apkPaths) {
    270         return new ClassPathScanner(apkPaths);
    271     }
    272 
    273     @SuppressWarnings("unchecked")
    274     private Class<? extends Annotation> loadAnnotationClass(String className) {
    275         try {
    276             Class<?> clazz = Class.forName(className);
    277             return (Class<? extends Annotation>)clazz;
    278         } catch (ClassNotFoundException e) {
    279             Log.e(LOG_TAG, String.format("Could not find annotation class: %s", className));
    280         } catch (ClassCastException e) {
    281             Log.e(LOG_TAG, String.format("Class %s is not an annotation", className));
    282         }
    283         return null;
    284     }
    285 }
    286