• 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 com.android.test.runner.listener.CoverageListener;
28 import com.android.test.runner.listener.DelayInjector;
29 import com.android.test.runner.listener.InstrumentationResultPrinter;
30 import com.android.test.runner.listener.InstrumentationRunListener;
31 import com.android.test.runner.listener.SuiteAssignmentPrinter;
32 
33 import org.junit.internal.TextListener;
34 import org.junit.runner.JUnitCore;
35 import org.junit.runner.Result;
36 import org.junit.runner.notification.RunListener;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.PrintStream;
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 /**
44  * An {@link Instrumentation} that runs JUnit3 and JUnit4 tests against
45  * an Android package (application).
46  * <p/>
47  * Currently experimental. Based on {@link android.test.InstrumentationTestRunner}.
48  * <p/>
49  * Will eventually support a superset of {@link android.test.InstrumentationTestRunner} features,
50  * while maintaining command/output format compatibility with that class.
51  *
52  * <h3>Typical Usage</h3>
53  * <p/>
54  * Write JUnit3 style {@link junit.framework.TestCase}s and/or JUnit4 style
55  * {@link org.junit.Test}s that perform tests against the classes in your package.
56  * Make use of the {@link com.android.test.InjectContext} and
57  * {@link com.android.test.InjectInstrumentation} annotations if needed.
58  * <p/>
59  * In an appropriate AndroidManifest.xml, define an instrumentation with android:name set to
60  * {@link com.android.test.runner.AndroidJUnitRunner} and the appropriate android:targetPackage set.
61  * <p/>
62  * Execution options:
63  * <p/>
64  * <b>Running all tests:</b> adb shell am instrument -w
65  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
66  * <p/>
67  * <b>Running all tests in a class:</b> adb shell am instrument -w
68  * -e class com.android.foo.FooTest
69  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
70  * <p/>
71  * <b>Running a single test:</b> adb shell am instrument -w
72  * -e class com.android.foo.FooTest#testFoo
73  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
74  * <p/>
75  * <b>Running all tests in multiple classes:</b> adb shell am instrument -w
76  * -e class com.android.foo.FooTest,com.android.foo.TooTest
77  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
78  * <p/>
79  * <b>Running all tests in a java package:</b> adb shell am instrument -w
80  * -e package com.android.foo.bar
81  * com.android.foo/com.android.test.runner.AndroidJUnitRunner
82  * <b>To debug your tests, set a break point in your code and pass:</b>
83  * -e debug true
84  * <p/>
85  * <b>Running a specific test size i.e. annotated with
86  * {@link android.test.suitebuilder.annotation.SmallTest} or
87  * {@link android.test.suitebuilder.annotation.MediumTest} or
88  * {@link android.test.suitebuilder.annotation.LargeTest}:</b>
89  * adb shell am instrument -w -e size [small|medium|large]
90  * com.android.foo/android.test.InstrumentationTestRunner
91  * <p/>
92  * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
93  * -e annotation com.android.foo.MyAnnotation
94  * com.android.foo/android.test.InstrumentationTestRunner
95  * <p/>
96  * If used with other options, the resulting test run will contain the intersection of the two
97  * options.
98  * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
99  * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
100  * <p/>
101  * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
102  * -e notAnnotation com.android.foo.MyAnnotation
103  * com.android.foo/android.test.InstrumentationTestRunner
104  * <p/>
105  * As above, if used with other options, the resulting test run will contain the intersection of
106  * the two options.
107  * e.g. "-e size large -e notAnnotation com.android.foo.MyAnnotation" will run tests with
108  * the {@link LargeTest} annotation that do NOT have the "com.android.foo.MyAnnotation" annotations.
109  * <p/>
110  * <b>To run in 'log only' mode</b>
111  * -e log true
112  * This option will load and iterate through all test classes and methods, but will bypass actual
113  * test execution. Useful for quickly obtaining info on the tests to be executed by an
114  * instrumentation command.
115  * <p/>
116  * <b>To generate EMMA code coverage:</b>
117  * -e coverage true
118  * Note: this requires an emma instrumented build. By default, the code coverage results file
119  * will be saved in a /data/<app>/coverage.ec file, unless overridden by coverageFile flag (see
120  * below)
121  * <p/>
122  * <b> To specify EMMA code coverage results file path:</b>
123  * -e coverageFile /sdcard/myFile.ec
124  * <p/>
125  */
126 public class AndroidJUnitRunner extends Instrumentation {
127 
128     // constants for supported instrumentation arguments
129     public static final String ARGUMENT_TEST_CLASS = "class";
130     private static final String ARGUMENT_TEST_SIZE = "size";
131     private static final String ARGUMENT_LOG_ONLY = "log";
132     private static final String ARGUMENT_ANNOTATION = "annotation";
133     private static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
134     private static final String ARGUMENT_DELAY_MSEC = "delay_msec";
135     private static final String ARGUMENT_COVERAGE = "coverage";
136     private static final String ARGUMENT_COVERAGE_PATH = "coverageFile";
137     private static final String ARGUMENT_SUITE_ASSIGNMENT = "suiteAssignment";
138     private static final String ARGUMENT_DEBUG = "debug";
139     private static final String ARGUMENT_EXTRA_LISTENER = "extraListener";
140     private static final String ARGUMENT_TEST_PACKAGE = "package";
141     // TODO: consider supporting 'count' from InstrumentationTestRunner
142 
143     private static final String LOG_TAG = "AndroidJUnitRunner";
144 
145     private Bundle mArguments;
146 
147     @Override
onCreate(Bundle arguments)148     public void onCreate(Bundle arguments) {
149         super.onCreate(arguments);
150         mArguments = arguments;
151 
152         start();
153     }
154 
155     /**
156      * Get the Bundle object that contains the arguments passed to the instrumentation
157      *
158      * @return the Bundle object
159      * @hide
160      */
getArguments()161     public Bundle getArguments(){
162         return mArguments;
163     }
164 
165     /**
166      * Set the arguments.
167      *
168      * @VisibleForTesting
169      */
setArguments(Bundle args)170     void setArguments(Bundle args) {
171         mArguments = args;
172     }
173 
getBooleanArgument(String tag)174     private boolean getBooleanArgument(String tag) {
175         String tagString = getArguments().getString(tag);
176         return tagString != null && Boolean.parseBoolean(tagString);
177     }
178 
179     /**
180      * Initialize the current thread as a looper.
181      * <p/>
182      * Exposed for unit testing.
183      */
prepareLooper()184     void prepareLooper() {
185         Looper.prepare();
186     }
187 
188     @Override
onStart()189     public void onStart() {
190         prepareLooper();
191 
192         if (getBooleanArgument(ARGUMENT_DEBUG)) {
193             Debug.waitForDebugger();
194         }
195 
196         setupDexmaker();
197 
198         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
199         PrintStream writer = new PrintStream(byteArrayOutputStream);
200         List<RunListener> listeners = new ArrayList<RunListener>();
201 
202         try {
203             JUnitCore testRunner = new JUnitCore();
204             addListeners(listeners, testRunner, writer);
205 
206             TestRequest testRequest = buildRequest(getArguments(), writer);
207             Result result = testRunner.run(testRequest.getRequest());
208             result.getFailures().addAll(testRequest.getFailures());
209             Log.i(LOG_TAG, String.format("Test run complete. %d tests, %d failed, %d ignored",
210                     result.getRunCount(), result.getFailureCount(), result.getIgnoreCount()));
211         } catch (Throwable t) {
212             // catch all exceptions so a more verbose error message can be displayed
213             writer.println(String.format(
214                     "Test run aborted due to unexpected exception: %s",
215                     t.getMessage()));
216             t.printStackTrace(writer);
217 
218         } finally {
219             Bundle results = new Bundle();
220             reportRunEnded(listeners, writer, results);
221             writer.close();
222             results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
223                     String.format("\n%s",
224                             byteArrayOutputStream.toString()));
225             finish(Activity.RESULT_OK, results);
226         }
227 
228     }
229 
addListeners(List<RunListener> listeners, JUnitCore testRunner, PrintStream writer)230     private void addListeners(List<RunListener> listeners, JUnitCore testRunner,
231             PrintStream writer) {
232         if (getBooleanArgument(ARGUMENT_SUITE_ASSIGNMENT)) {
233             addListener(listeners, testRunner, new SuiteAssignmentPrinter(writer));
234         } else {
235             addListener(listeners, testRunner, new TextListener(writer));
236             addListener(listeners, testRunner, new InstrumentationResultPrinter(this));
237             addDelayListener(listeners, testRunner);
238             addCoverageListener(listeners, testRunner);
239         }
240 
241         addExtraListeners(listeners, testRunner, writer);
242     }
243 
addListener(List<RunListener> list, JUnitCore testRunner, RunListener listener)244     private void addListener(List<RunListener> list, JUnitCore testRunner, RunListener listener) {
245         list.add(listener);
246         testRunner.addListener(listener);
247     }
248 
addCoverageListener(List<RunListener> list, JUnitCore testRunner)249     private void addCoverageListener(List<RunListener> list, JUnitCore testRunner) {
250         if (getBooleanArgument(ARGUMENT_COVERAGE)) {
251             String coverageFilePath = getArguments().getString(ARGUMENT_COVERAGE_PATH);
252             addListener(list, testRunner, new CoverageListener(this, coverageFilePath));
253         }
254     }
255 
256     /**
257      * Sets up listener to inject {@link #ARGUMENT_DELAY_MSEC}, if specified.
258      * @param testRunner
259      */
addDelayListener(List<RunListener> list, JUnitCore testRunner)260     private void addDelayListener(List<RunListener> list, JUnitCore testRunner) {
261         try {
262             Object delay = getArguments().get(ARGUMENT_DELAY_MSEC);  // Accept either string or int
263             if (delay != null) {
264                 int delayMsec = Integer.parseInt(delay.toString());
265                 addListener(list, testRunner, new DelayInjector(delayMsec));
266             }
267         } catch (NumberFormatException e) {
268             Log.e(LOG_TAG, "Invalid delay_msec parameter", e);
269         }
270     }
271 
addExtraListeners(List<RunListener> listeners, JUnitCore testRunner, PrintStream writer)272     private void addExtraListeners(List<RunListener> listeners, JUnitCore testRunner,
273             PrintStream writer) {
274         String extraListenerList = getArguments().getString(ARGUMENT_EXTRA_LISTENER);
275         if (extraListenerList == null) {
276             return;
277         }
278 
279         for (String listenerName : extraListenerList.split(",")) {
280             addExtraListener(listeners, testRunner, writer, listenerName);
281         }
282     }
283 
addExtraListener(List<RunListener> listeners, JUnitCore testRunner, PrintStream writer, String extraListener)284     private void addExtraListener(List<RunListener> listeners, JUnitCore testRunner,
285             PrintStream writer, String extraListener) {
286         if (extraListener == null || extraListener.length() == 0) {
287             return;
288         }
289 
290         final Class<?> klass;
291         try {
292             klass = Class.forName(extraListener);
293         } catch (ClassNotFoundException e) {
294             writer.println("Could not find extra RunListener class " + extraListener);
295             return;
296         }
297 
298         if (!RunListener.class.isAssignableFrom(klass)) {
299             writer.println("Extra listeners must extend RunListener class " + extraListener);
300             return;
301         }
302 
303         try {
304             klass.getConstructor().setAccessible(true);
305         } catch (NoSuchMethodException e) {
306             writer.println("Must have no argument constructor for class " + extraListener);
307             return;
308         }
309 
310         final RunListener l;
311         try {
312             l = (RunListener) klass.newInstance();
313         } catch (Throwable t) {
314             writer.println("Could not instantiate extra RunListener class " + extraListener);
315             t.printStackTrace(writer);
316             return;
317         }
318 
319         addListener(listeners, testRunner, l);
320     }
321 
reportRunEnded(List<RunListener> listeners, PrintStream writer, Bundle results)322     private void reportRunEnded(List<RunListener> listeners, PrintStream writer, Bundle results) {
323         for (RunListener listener : listeners) {
324             if (listener instanceof InstrumentationRunListener) {
325                 ((InstrumentationRunListener)listener).instrumentationRunFinished(writer, results);
326             }
327         }
328     }
329 
330     /**
331      * Builds a {@link TestRequest} based on given input arguments.
332      * <p/>
333      * Exposed for unit testing.
334      */
buildRequest(Bundle arguments, PrintStream writer)335     TestRequest buildRequest(Bundle arguments, PrintStream writer) {
336         // only load tests for current aka testContext
337         // Note that this represents a change from InstrumentationTestRunner where
338         // getTargetContext().getPackageCodePath() was also scanned
339         TestRequestBuilder builder = createTestRequestBuilder(writer,
340                 getContext().getPackageCodePath());
341 
342         String testClassName = arguments.getString(ARGUMENT_TEST_CLASS);
343         if (testClassName != null) {
344             for (String className : testClassName.split(",")) {
345                 parseTestClass(className, builder);
346             }
347         }
348 
349         String testPackage = arguments.getString(ARGUMENT_TEST_PACKAGE);
350         if (testPackage != null) {
351             builder.addTestPackageFilter(testPackage);
352         }
353 
354         String testSize = arguments.getString(ARGUMENT_TEST_SIZE);
355         if (testSize != null) {
356             builder.addTestSizeFilter(testSize);
357         }
358 
359         String annotation = arguments.getString(ARGUMENT_ANNOTATION);
360         if (annotation != null) {
361             builder.addAnnotationInclusionFilter(annotation);
362         }
363 
364         String notAnnotation = arguments.getString(ARGUMENT_NOT_ANNOTATION);
365         if (notAnnotation != null) {
366             builder.addAnnotationExclusionFilter(notAnnotation);
367         }
368 
369         if (getBooleanArgument(ARGUMENT_LOG_ONLY)) {
370             builder.setSkipExecution(true);
371         }
372         return builder.build(this, arguments);
373     }
374 
375     /**
376      * Factory method for {@link TestRequestBuilder}.
377      * <p/>
378      * Exposed for unit testing.
379      */
createTestRequestBuilder(PrintStream writer, String... packageCodePaths)380     TestRequestBuilder createTestRequestBuilder(PrintStream writer, String... packageCodePaths) {
381         return new TestRequestBuilder(writer, packageCodePaths);
382     }
383 
384     /**
385      * Parse and load the given test class and, optionally, method
386      *
387      * @param testClassName - full package name of test class and optionally method to add.
388      *        Expected format: com.android.TestClass#testMethod
389      * @param testSuiteBuilder - builder to add tests to
390      */
parseTestClass(String testClassName, TestRequestBuilder testRequestBuilder)391     private void parseTestClass(String testClassName, TestRequestBuilder testRequestBuilder) {
392         int methodSeparatorIndex = testClassName.indexOf('#');
393 
394         if (methodSeparatorIndex > 0) {
395             String testMethodName = testClassName.substring(methodSeparatorIndex + 1);
396             testClassName = testClassName.substring(0, methodSeparatorIndex);
397             testRequestBuilder.addTestMethod(testClassName, testMethodName);
398         } else {
399             testRequestBuilder.addTestClass(testClassName);
400         }
401     }
402 
setupDexmaker()403     private void setupDexmaker() {
404         // Explicitly set the Dexmaker cache, so tests that use mocking frameworks work
405         String dexCache = getTargetContext().getCacheDir().getPath();
406         Log.i(LOG_TAG, "Setting dexmaker.dexcache to " + dexCache);
407         System.setProperty("dexmaker.dexcache", getTargetContext().getCacheDir().getPath());
408     }
409 }
410