• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle arguments)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      */
getArguments()199     public Bundle getArguments(){
200         return mArguments;
201     }
202 
getBooleanArgument(Bundle arguments, String tag)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      */
prepareLooper()213     void prepareLooper() {
214         Looper.prepare();
215     }
216 
217     @Override
onStart()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      */
buildRequest(Bundle arguments, PrintStream writer)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      */
createTestRequestBuilder(PrintStream writer, String... packageCodePaths)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      */
parseTestClass(String testClassName, TestRequestBuilder testRequestBuilder)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 
WatcherResultPrinter()334         public WatcherResultPrinter() {
335             mResultTemplate = new Bundle();
336         }
337 
338         @Override
testRunStarted(Description description)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
testRunFinished(Result result)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
testStarted(Description description)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
testFinished(Description description)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
testFailure(Failure failure)383         public void testFailure(Failure failure) throws Exception {
384             mTestResultCode = REPORT_VALUE_RESULT_ERROR;
385             reportFailure(failure);
386         }
387 
388 
389         @Override
testAssumptionFailure(Failure failure)390         public void testAssumptionFailure(Failure failure) {
391             mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
392             reportFailure(failure);
393         }
394 
reportFailure(Failure failure)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
testIgnored(Description description)404         public void testIgnored(Description description) throws Exception {
405             testStarted(description);
406             mTestResultCode = REPORT_VALUE_RESULT_IGNORED;
407             testFinished(description);
408         }
409     }
410 }
411