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 
     17 package com.android.test.runner;
     18 
     19 import android.app.Activity;
     20 import android.app.Instrumentation;
     21 import android.os.Bundle;
     22 import android.os.Debug;
     23 import android.os.Looper;
     24 import android.test.suitebuilder.annotation.LargeTest;
     25 import android.util.Log;
     26 
     27 import org.junit.internal.TextListener;
     28 import org.junit.runner.Description;
     29 import org.junit.runner.JUnitCore;
     30 import org.junit.runner.Result;
     31 import org.junit.runner.notification.Failure;
     32 import org.junit.runner.notification.RunListener;
     33 
     34 import java.io.ByteArrayOutputStream;
     35 import java.io.PrintStream;
     36 
     37 /**
     38  * An {@link Instrumentation} that runs JUnit3 and JUnit4 tests against
     39  * an Android package (application).
     40  * <p/>
     41  * Currently experimental. Based on {@link android.test.InstrumentationTestRunner}.
     42  * <p/>
     43  * Will eventually support a superset of {@link android.test.InstrumentationTestRunner} features,
     44  * while maintaining command/output format compatibility with that class.
     45  *
     46  * <h3>Typical Usage</h3>
     47  * <p/>
     48  * Write JUnit3 style {@link junit.framework.TestCase}s and/or JUnit4 style
     49  * {@link org.junit.Test}s that perform tests against the classes in your package.
     50  * Make use of the {@link com.android.test.InjectContext} and
     51  * {@link com.android.test.InjectInstrumentation} annotations if needed.
     52  * <p/>
     53  * In an appropriate AndroidManifest.xml, define an instrumentation with android:name set to
     54  * {@link com.android.test.runner.AndroidJUnitRunner} and the appropriate android:targetPackage set.
     55  * <p/>
     56  * Execution options:
     57  * <p/>
     58  * <b>Running all tests:</b> adb shell am instrument -w
     59  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
     60  * <p/>
     61  * <b>Running all tests in a class:</b> adb shell am instrument -w
     62  * -e class com.android.foo.FooTest
     63  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
     64  * <p/>
     65  * <b>Running a single test:</b> adb shell am instrument -w
     66  * -e class com.android.foo.FooTest#testFoo
     67  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
     68  * <p/>
     69  * <b>Running all tests in multiple classes:</b> adb shell am instrument -w
     70  * -e class com.android.foo.FooTest,com.android.foo.TooTest
     71  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
     72  * <p/>
     73  * <b>To debug your tests, set a break point in your code and pass:</b>
     74  * -e debug true
     75  * <p/>
     76  * <b>Running a specific test size i.e. annotated with
     77  * {@link android.test.suitebuilder.annotation.SmallTest} or
     78  * {@link android.test.suitebuilder.annotation.MediumTest} or
     79  * {@link android.test.suitebuilder.annotation.LargeTest}:</b>
     80  * adb shell am instrument -w -e size [small|medium|large]
     81  * com.android.foo/android.test.InstrumentationTestRunner
     82  * <p/>
     83  * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
     84  * -e annotation com.android.foo.MyAnnotation
     85  * com.android.foo/android.test.InstrumentationTestRunner
     86  * <p/>
     87  * If used with other options, the resulting test run will contain the intersection of the two
     88  * options.
     89  * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
     90  * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
     91  * <p/>
     92  * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
     93  * -e notAnnotation com.android.foo.MyAnnotation
     94  * com.android.foo/android.test.InstrumentationTestRunner
     95  * <p/>
     96  * As above, if used with other options, the resulting test run will contain the intersection of
     97  * the two options.
     98  * e.g. "-e size large -e notAnnotation com.android.foo.MyAnnotation" will run tests with
     99  * the {@link LargeTest} annotation that do NOT have the "com.android.foo.MyAnnotation" annotations.
    100  * <p/>
    101  * <b>To run in 'log only' mode</b>
    102  * -e log true
    103  * This option will load and iterate through all test classes and methods, but will bypass actual
    104  * test execution. Useful for quickly obtaining info on the tests to be executed by an
    105  * instrumentation command.
    106  * <p/>
    107  */
    108 public class AndroidJUnitRunner extends Instrumentation {
    109 
    110     public static final String ARGUMENT_TEST_CLASS = "class";
    111 
    112     private static final String ARGUMENT_TEST_SIZE = "size";
    113     private static final String ARGUMENT_LOG_ONLY = "log";
    114     private static final String ARGUMENT_ANNOTATION = "annotation";
    115     private static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
    116 
    117     /**
    118      * The following keys are used in the status bundle to provide structured reports to
    119      * an IInstrumentationWatcher.
    120      */
    121 
    122     /**
    123      * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER},
    124      * identifies InstrumentationTestRunner as the source of the report.  This is sent with all
    125      * status messages.
    126      */
    127     public static final String REPORT_VALUE_ID = "InstrumentationTestRunner";
    128     /**
    129      * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
    130      * identifies the total number of tests that are being run.  This is sent with all status
    131      * messages.
    132      */
    133     public static final String REPORT_KEY_NUM_TOTAL = "numtests";
    134     /**
    135      * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
    136      * identifies the sequence number of the current test.  This is sent with any status message
    137      * describing a specific test being started or completed.
    138      */
    139     public static final String REPORT_KEY_NUM_CURRENT = "current";
    140     /**
    141      * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
    142      * identifies the name of the current test class.  This is sent with any status message
    143      * describing a specific test being started or completed.
    144      */
    145     public static final String REPORT_KEY_NAME_CLASS = "class";
    146     /**
    147      * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
    148      * identifies the name of the current test.  This is sent with any status message
    149      * describing a specific test being started or completed.
    150      */
    151     public static final String REPORT_KEY_NAME_TEST = "test";
    152 
    153     /**
    154      * The test is starting.
    155      */
    156     public static final int REPORT_VALUE_RESULT_START = 1;
    157     /**
    158      * The test completed successfully.
    159      */
    160     public static final int REPORT_VALUE_RESULT_OK = 0;
    161     /**
    162      * The test completed with an error.
    163      */
    164     public static final int REPORT_VALUE_RESULT_ERROR = -1;
    165     /**
    166      * The test completed with a failure.
    167      */
    168     public static final int REPORT_VALUE_RESULT_FAILURE = -2;
    169     /**
    170      * The test was ignored.
    171      */
    172     public static final int REPORT_VALUE_RESULT_IGNORED = -3;
    173     /**
    174      * If included in the status bundle sent to an IInstrumentationWatcher, this key
    175      * identifies a stack trace describing an error or failure.  This is sent with any status
    176      * message describing a specific test being completed.
    177      */
    178     public static final String REPORT_KEY_STACK = "stack";
    179 
    180     private static final String LOG_TAG = "InstrumentationTestRunner";
    181 
    182     private final Bundle mResults = new Bundle();
    183     private Bundle mArguments;
    184 
    185     @Override
    186     public void onCreate(Bundle arguments) {
    187         super.onCreate(arguments);
    188         mArguments = arguments;
    189 
    190         start();
    191     }
    192 
    193     /**
    194      * Get the Bundle object that contains the arguments passed to the instrumentation
    195      *
    196      * @return the Bundle object
    197      * @hide
    198      */
    199     public Bundle getArguments(){
    200         return mArguments;
    201     }
    202 
    203     private boolean getBooleanArgument(Bundle arguments, String tag) {
    204         String tagString = arguments.getString(tag);
    205         return tagString != null && Boolean.parseBoolean(tagString);
    206     }
    207 
    208     /**
    209      * Initialize the current thread as a looper.
    210      * <p/>
    211      * Exposed for unit testing.
    212      */
    213     void prepareLooper() {
    214         Looper.prepare();
    215     }
    216 
    217     @Override
    218     public void onStart() {
    219         prepareLooper();
    220 
    221         if (getBooleanArgument(getArguments(), "debug")) {
    222             Debug.waitForDebugger();
    223         }
    224 
    225         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    226         PrintStream writer = new PrintStream(byteArrayOutputStream);
    227         try {
    228             JUnitCore testRunner = new JUnitCore();
    229             testRunner.addListener(new TextListener(writer));
    230             WatcherResultPrinter detailedResultPrinter = new WatcherResultPrinter();
    231             testRunner.addListener(detailedResultPrinter);
    232 
    233             TestRequest testRequest = buildRequest(getArguments(), writer);
    234             Result result = testRunner.run(testRequest.getRequest());
    235             result.getFailures().addAll(testRequest.getFailures());
    236             Log.i(LOG_TAG, String.format("Test run complete. %d tests, %d failed, %d ignored",
    237                     result.getRunCount(), result.getFailureCount(), result.getIgnoreCount()));
    238         } catch (Throwable t) {
    239             // catch all exceptions so a more verbose error message can be displayed
    240             writer.println(String.format(
    241                     "Test run aborted due to unexpected exception: %s",
    242                     t.getMessage()));
    243             t.printStackTrace(writer);
    244 
    245         } finally {
    246             writer.close();
    247             mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
    248                     String.format("\n%s",
    249                             byteArrayOutputStream.toString()));
    250             finish(Activity.RESULT_OK, mResults);
    251         }
    252 
    253     }
    254 
    255     /**
    256      * Builds a {@link TestRequest} based on given input arguments.
    257      * <p/>
    258      * Exposed for unit testing.
    259      */
    260     TestRequest buildRequest(Bundle arguments, PrintStream writer) {
    261         // only load tests for current aka testContext
    262         // Note that this represents a change from InstrumentationTestRunner where
    263         // getTargetContext().getPackageCodePath() was also scanned
    264         TestRequestBuilder builder = createTestRequestBuilder(writer,
    265                 getContext().getPackageCodePath());
    266 
    267         String testClassName = arguments.getString(ARGUMENT_TEST_CLASS);
    268         if (testClassName != null) {
    269             for (String className : testClassName.split(",")) {
    270                 parseTestClass(className, builder);
    271             }
    272         }
    273 
    274         String testSize = arguments.getString(ARGUMENT_TEST_SIZE);
    275         if (testSize != null) {
    276             builder.addTestSizeFilter(testSize);
    277         }
    278 
    279         String annotation = arguments.getString(ARGUMENT_ANNOTATION);
    280         if (annotation != null) {
    281             builder.addAnnotationInclusionFilter(annotation);
    282         }
    283 
    284         String notAnnotation = arguments.getString(ARGUMENT_NOT_ANNOTATION);
    285         if (notAnnotation != null) {
    286             builder.addAnnotationExclusionFilter(notAnnotation);
    287         }
    288 
    289         boolean logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY);
    290         if (logOnly) {
    291             builder.setSkipExecution(true);
    292         }
    293         return builder.build(this);
    294     }
    295 
    296     /**
    297      * Factory method for {@link TestRequestBuilder}.
    298      * <p/>
    299      * Exposed for unit testing.
    300      */
    301     TestRequestBuilder createTestRequestBuilder(PrintStream writer, String... packageCodePaths) {
    302         return new TestRequestBuilder(writer, packageCodePaths);
    303     }
    304 
    305     /**
    306      * Parse and load the given test class and, optionally, method
    307      *
    308      * @param testClassName - full package name of test class and optionally method to add.
    309      *        Expected format: com.android.TestClass#testMethod
    310      * @param testSuiteBuilder - builder to add tests to
    311      */
    312     private void parseTestClass(String testClassName, TestRequestBuilder testRequestBuilder) {
    313         int methodSeparatorIndex = testClassName.indexOf('#');
    314 
    315         if (methodSeparatorIndex > 0) {
    316             String testMethodName = testClassName.substring(methodSeparatorIndex + 1);
    317             testClassName = testClassName.substring(0, methodSeparatorIndex);
    318             testRequestBuilder.addTestMethod(testClassName, testMethodName);
    319         } else {
    320             testRequestBuilder.addTestClass(testClassName);
    321         }
    322     }
    323 
    324     /**
    325      * This class sends status reports back to the IInstrumentationWatcher
    326      */
    327     private class WatcherResultPrinter extends RunListener {
    328         private final Bundle mResultTemplate;
    329         Bundle mTestResult;
    330         int mTestNum = 0;
    331         int mTestResultCode = 0;
    332         String mTestClass = null;
    333 
    334         public WatcherResultPrinter() {
    335             mResultTemplate = new Bundle();
    336         }
    337 
    338         @Override
    339         public void testRunStarted(Description description) throws Exception {
    340             mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
    341             mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, description.testCount());
    342         }
    343 
    344         @Override
    345         public void testRunFinished(Result result) throws Exception {
    346             // TODO: implement this
    347         }
    348 
    349         /**
    350          * send a status for the start of a each test, so long tests can be seen
    351          * as "running"
    352          */
    353         @Override
    354         public void testStarted(Description description) throws Exception {
    355             String testClass = description.getClassName();
    356             String testName = description.getMethodName();
    357             mTestResult = new Bundle(mResultTemplate);
    358             mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
    359             mTestResult.putString(REPORT_KEY_NAME_TEST, testName);
    360             mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
    361             // pretty printing
    362             if (testClass != null && !testClass.equals(mTestClass)) {
    363                 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
    364                         String.format("\n%s:", testClass));
    365                 mTestClass = testClass;
    366             } else {
    367                 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "");
    368             }
    369 
    370             sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
    371             mTestResultCode = 0;
    372         }
    373 
    374         @Override
    375         public void testFinished(Description description) throws Exception {
    376             if (mTestResultCode == 0) {
    377                 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
    378             }
    379             sendStatus(mTestResultCode, mTestResult);
    380         }
    381 
    382         @Override
    383         public void testFailure(Failure failure) throws Exception {
    384             mTestResultCode = REPORT_VALUE_RESULT_ERROR;
    385             reportFailure(failure);
    386         }
    387 
    388 
    389         @Override
    390         public void testAssumptionFailure(Failure failure) {
    391             mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
    392             reportFailure(failure);
    393         }
    394 
    395         private void reportFailure(Failure failure) {
    396             mTestResult.putString(REPORT_KEY_STACK, failure.getTrace());
    397             // pretty printing
    398             mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
    399                     String.format("\nError in %s:\n%s",
    400                             failure.getDescription().getDisplayName(), failure.getTrace()));
    401         }
    402 
    403         @Override
    404         public void testIgnored(Description description) throws Exception {
    405             testStarted(description);
    406             mTestResultCode = REPORT_VALUE_RESULT_IGNORED;
    407             testFinished(description);
    408         }
    409     }
    410 }
    411