1/*
2 * Copyright (C) 2007 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
17package android.test;
18
19import com.android.internal.util.Predicate;
20import com.android.internal.util.Predicates;
21
22import android.app.Activity;
23import android.app.Instrumentation;
24import android.os.Bundle;
25import android.os.Debug;
26import android.os.Looper;
27import android.os.Parcelable;
28import android.os.PerformanceCollector;
29import android.os.PerformanceCollector.PerformanceResultsWriter;
30import android.test.suitebuilder.TestMethod;
31import android.test.suitebuilder.TestPredicates;
32import android.test.suitebuilder.TestSuiteBuilder;
33import android.test.suitebuilder.annotation.HasAnnotation;
34import android.test.suitebuilder.annotation.LargeTest;
35import android.util.Log;
36
37import java.io.ByteArrayOutputStream;
38import java.io.File;
39import java.io.PrintStream;
40import java.lang.annotation.Annotation;
41import java.lang.reflect.InvocationTargetException;
42import java.lang.reflect.Method;
43import java.util.ArrayList;
44import java.util.List;
45
46import junit.framework.AssertionFailedError;
47import junit.framework.Test;
48import junit.framework.TestCase;
49import junit.framework.TestListener;
50import junit.framework.TestResult;
51import junit.framework.TestSuite;
52import junit.runner.BaseTestRunner;
53import junit.textui.ResultPrinter;
54
55/**
56 * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against
57 * an Android package (application).
58 *
59 * <div class="special reference">
60 * <h3>Developer Guides</h3>
61 * <p>For more information about application testing, read the
62 * <a href="{@docRoot}guide/topics/testing/index.html">Testing</a> developer guide.</p>
63 * </div>
64 *
65 * <h3>Typical Usage</h3>
66 * <ol>
67 * <li>Write {@link junit.framework.TestCase}s that perform unit, functional, or performance tests
68 * against the classes in your package.  Typically these are subclassed from:
69 *   <ul><li>{@link android.test.ActivityInstrumentationTestCase2}</li>
70 *   <li>{@link android.test.ActivityUnitTestCase}</li>
71 *   <li>{@link android.test.AndroidTestCase}</li>
72 *   <li>{@link android.test.ApplicationTestCase}</li>
73 *   <li>{@link android.test.InstrumentationTestCase}</li>
74 *   <li>{@link android.test.ProviderTestCase}</li>
75 *   <li>{@link android.test.ServiceTestCase}</li>
76 *   <li>{@link android.test.SingleLaunchActivityTestCase}</li></ul>
77 * <li>Set the <code>android:targetPackage</code> attribute of the <code>&lt;instrumentation&gt;</code>
78 * element in the test package's manifest. You should set the attribute value
79 * to the package name of the target application under test.
80 * <li>Run the instrumentation using "adb shell am instrument -w",
81 * with no optional arguments, to run all tests (except performance tests).
82 * <li>Run the instrumentation using "adb shell am instrument -w",
83 * with the argument '-e func true' to run all functional tests. These are tests that derive from
84 * {@link android.test.InstrumentationTestCase}.
85 * <li>Run the instrumentation using "adb shell am instrument -w",
86 * with the argument '-e unit true' to run all unit tests. These are tests that <i>do not</i>derive
87 * from {@link android.test.InstrumentationTestCase} (and are not performance tests).
88 * <li>Run the instrumentation using "adb shell am instrument -w",
89 * with the argument '-e class' set to run an individual {@link junit.framework.TestCase}.
90 * </ol>
91 * <p/>
92 * <b>Running all tests:</b> adb shell am instrument -w
93 * com.android.foo/android.test.InstrumentationTestRunner
94 * <p/>
95 * <b>Running all small tests:</b> adb shell am instrument -w
96 * -e size small
97 * com.android.foo/android.test.InstrumentationTestRunner
98 * <p/>
99 * <b>Running all medium tests:</b> adb shell am instrument -w
100 * -e size medium
101 * com.android.foo/android.test.InstrumentationTestRunner
102 * <p/>
103 * <b>Running all large tests:</b> adb shell am instrument -w
104 * -e size large
105 * com.android.foo/android.test.InstrumentationTestRunner
106 * <p/>
107 * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
108 * -e annotation com.android.foo.MyAnnotation
109 * com.android.foo/android.test.InstrumentationTestRunner
110 * <p/>
111 * If used with other options, the resulting test run will contain the union of the two options.
112 * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
113 * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
114 * <p/>
115 * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
116 * -e notAnnotation com.android.foo.MyAnnotation
117 * com.android.foo/android.test.InstrumentationTestRunner
118 * <p/>
119 * <b>Running a single testcase:</b> adb shell am instrument -w
120 * -e class com.android.foo.FooTest
121 * com.android.foo/android.test.InstrumentationTestRunner
122 * <p/>
123 * <b>Running a single test:</b> adb shell am instrument -w
124 * -e class com.android.foo.FooTest#testFoo
125 * com.android.foo/android.test.InstrumentationTestRunner
126 * <p/>
127 * <b>Running multiple tests:</b> adb shell am instrument -w
128 * -e class com.android.foo.FooTest,com.android.foo.TooTest
129 * com.android.foo/android.test.InstrumentationTestRunner
130 * <p/>
131 * <b>Running all tests in a java package:</b> adb shell am instrument -w
132 * -e package com.android.foo.subpkg
133 *  com.android.foo/android.test.InstrumentationTestRunner
134 * <p/>
135 * <b>Including performance tests:</b> adb shell am instrument -w
136 * -e perf true
137 * com.android.foo/android.test.InstrumentationTestRunner
138 * <p/>
139 * <b>To debug your tests, set a break point in your code and pass:</b>
140 * -e debug true
141 * <p/>
142 * <b>To run in 'log only' mode</b>
143 * -e log true
144 * This option will load and iterate through all test classes and methods, but will bypass actual
145 * test execution. Useful for quickly obtaining info on the tests to be executed by an
146 * instrumentation command.
147 * <p/>
148 * <b>To generate EMMA code coverage:</b>
149 * -e coverage true
150 * Note: this requires an emma instrumented build. By default, the code coverage results file
151 * will be saved in a /data/<app>/coverage.ec file, unless overridden by coverageFile flag (see
152 * below)
153 * <p/>
154 * <b> To specify EMMA code coverage results file path:</b>
155 * -e coverageFile /sdcard/myFile.ec
156 * <br/>
157 * in addition to the other arguments.
158 * @deprecated Use
159 * <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">
160 * AndroidJUnitRunner</a> instead. New tests should be written using the
161 * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
162 */
163
164/* (not JavaDoc)
165 * Although not necessary in most case, another way to use this class is to extend it and have the
166 * derived class return the desired test suite from the {@link #getTestSuite()} method. The test
167 * suite returned from this method will be used if no target class is defined in the meta-data or
168 * command line argument parameters. If a derived class is used it needs to be added as an
169 * instrumentation to the AndroidManifest.xml and the command to run it would look like:
170 * <p/>
171 * adb shell am instrument -w com.android.foo/<i>com.android.FooInstrumentationTestRunner</i>
172 * <p/>
173 * Where <i>com.android.FooInstrumentationTestRunner</i> is the derived class.
174 *
175 * This model is used by many existing app tests, but can probably be deprecated.
176 */
177@Deprecated
178public class InstrumentationTestRunner extends Instrumentation implements TestSuiteProvider {
179
180    /** @hide */
181    public static final String ARGUMENT_TEST_CLASS = "class";
182    /** @hide */
183    public static final String ARGUMENT_TEST_PACKAGE = "package";
184    /** @hide */
185    public static final String ARGUMENT_TEST_SIZE_PREDICATE = "size";
186    /** @hide */
187    public static final String ARGUMENT_DELAY_MSEC = "delay_msec";
188
189    private static final String SMALL_SUITE = "small";
190    private static final String MEDIUM_SUITE = "medium";
191    private static final String LARGE_SUITE = "large";
192
193    private static final String ARGUMENT_LOG_ONLY = "log";
194    /** @hide */
195    static final String ARGUMENT_ANNOTATION = "annotation";
196    /** @hide */
197    static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
198
199    /**
200     * This constant defines the maximum allowed runtime (in ms) for a test included in the "small"
201     * suite. It is used to make an educated guess at what suite an unlabeled test belongs.
202     */
203    private static final float SMALL_SUITE_MAX_RUNTIME = 100;
204
205    /**
206     * This constant defines the maximum allowed runtime (in ms) for a test included in the
207     * "medium" suite. It is used to make an educated guess at what suite an unlabeled test belongs.
208     */
209    private static final float MEDIUM_SUITE_MAX_RUNTIME = 1000;
210
211    /**
212     * The following keys are used in the status bundle to provide structured reports to
213     * an IInstrumentationWatcher.
214     */
215
216    /**
217     * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER},
218     * identifies InstrumentationTestRunner as the source of the report.  This is sent with all
219     * status messages.
220     */
221    public static final String REPORT_VALUE_ID = "InstrumentationTestRunner";
222    /**
223     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
224     * identifies the total number of tests that are being run.  This is sent with all status
225     * messages.
226     */
227    public static final String REPORT_KEY_NUM_TOTAL = "numtests";
228    /**
229     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
230     * identifies the sequence number of the current test.  This is sent with any status message
231     * describing a specific test being started or completed.
232     */
233    public static final String REPORT_KEY_NUM_CURRENT = "current";
234    /**
235     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
236     * identifies the name of the current test class.  This is sent with any status message
237     * describing a specific test being started or completed.
238     */
239    public static final String REPORT_KEY_NAME_CLASS = "class";
240    /**
241     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
242     * identifies the name of the current test.  This is sent with any status message
243     * describing a specific test being started or completed.
244     */
245    public static final String REPORT_KEY_NAME_TEST = "test";
246    /**
247     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
248     * reports the run time in seconds of the current test.
249     */
250    private static final String REPORT_KEY_RUN_TIME = "runtime";
251    /**
252     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
253     * reports the number of total iterations of the current test.
254     */
255    private static final String REPORT_KEY_NUM_ITERATIONS = "numiterations";
256    /**
257     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
258     * reports the guessed suite assignment for the current test.
259     */
260    private static final String REPORT_KEY_SUITE_ASSIGNMENT = "suiteassignment";
261    /**
262     * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
263     * identifies the path to the generated code coverage file.
264     */
265    private static final String REPORT_KEY_COVERAGE_PATH = "coverageFilePath";
266
267    /**
268     * The test is starting.
269     */
270    public static final int REPORT_VALUE_RESULT_START = 1;
271    /**
272     * The test completed successfully.
273     */
274    public static final int REPORT_VALUE_RESULT_OK = 0;
275    /**
276     * The test completed with an error.
277     */
278    public static final int REPORT_VALUE_RESULT_ERROR = -1;
279    /**
280     * The test completed with a failure.
281     */
282    public static final int REPORT_VALUE_RESULT_FAILURE = -2;
283    /**
284     * If included in the status bundle sent to an IInstrumentationWatcher, this key
285     * identifies a stack trace describing an error or failure.  This is sent with any status
286     * message describing a specific test being completed.
287     */
288    public static final String REPORT_KEY_STACK = "stack";
289
290    // Default file name for code coverage
291    private static final String DEFAULT_COVERAGE_FILE_NAME = "coverage.ec";
292
293    private static final String LOG_TAG = "InstrumentationTestRunner";
294
295    private final Bundle mResults = new Bundle();
296    private Bundle mArguments;
297    private AndroidTestRunner mTestRunner;
298    private boolean mDebug;
299    private boolean mJustCount;
300    private boolean mSuiteAssignmentMode;
301    private int mTestCount;
302    private String mPackageOfTests;
303    private boolean mCoverage;
304    private String mCoverageFilePath;
305    private int mDelayMsec;
306
307    @Override
308    public void onCreate(Bundle arguments) {
309        super.onCreate(arguments);
310        mArguments = arguments;
311
312        // Apk paths used to search for test classes when using TestSuiteBuilders.
313        String[] apkPaths =
314                {getTargetContext().getPackageCodePath(), getContext().getPackageCodePath()};
315        ClassPathPackageInfoSource.setApkPaths(apkPaths);
316
317        Predicate<TestMethod> testSizePredicate = null;
318        Predicate<TestMethod> testAnnotationPredicate = null;
319        Predicate<TestMethod> testNotAnnotationPredicate = null;
320        String testClassesArg = null;
321        boolean logOnly = false;
322
323        if (arguments != null) {
324            // Test class name passed as an argument should override any meta-data declaration.
325            testClassesArg = arguments.getString(ARGUMENT_TEST_CLASS);
326            mDebug = getBooleanArgument(arguments, "debug");
327            mJustCount = getBooleanArgument(arguments, "count");
328            mSuiteAssignmentMode = getBooleanArgument(arguments, "suiteAssignment");
329            mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE);
330            testSizePredicate = getSizePredicateFromArg(
331                    arguments.getString(ARGUMENT_TEST_SIZE_PREDICATE));
332            testAnnotationPredicate = getAnnotationPredicate(
333                    arguments.getString(ARGUMENT_ANNOTATION));
334            testNotAnnotationPredicate = getNotAnnotationPredicate(
335                    arguments.getString(ARGUMENT_NOT_ANNOTATION));
336
337            logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY);
338            mCoverage = getBooleanArgument(arguments, "coverage");
339            mCoverageFilePath = arguments.getString("coverageFile");
340
341            try {
342                Object delay = arguments.get(ARGUMENT_DELAY_MSEC);  // Accept either string or int
343                if (delay != null) mDelayMsec = Integer.parseInt(delay.toString());
344            } catch (NumberFormatException e) {
345                Log.e(LOG_TAG, "Invalid delay_msec parameter", e);
346            }
347        }
348
349        TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(),
350                getTargetContext().getClassLoader());
351
352        if (testSizePredicate != null) {
353            testSuiteBuilder.addRequirements(testSizePredicate);
354        }
355        if (testAnnotationPredicate != null) {
356            testSuiteBuilder.addRequirements(testAnnotationPredicate);
357        }
358        if (testNotAnnotationPredicate != null) {
359            testSuiteBuilder.addRequirements(testNotAnnotationPredicate);
360        }
361
362        if (testClassesArg == null) {
363            if (mPackageOfTests != null) {
364                testSuiteBuilder.includePackages(mPackageOfTests);
365            } else {
366                TestSuite testSuite = getTestSuite();
367                if (testSuite != null) {
368                    testSuiteBuilder.addTestSuite(testSuite);
369                } else {
370                    // no package or class bundle arguments were supplied, and no test suite
371                    // provided so add all tests in application
372                    testSuiteBuilder.includePackages("");
373                }
374            }
375        } else {
376            parseTestClasses(testClassesArg, testSuiteBuilder);
377        }
378
379        testSuiteBuilder.addRequirements(getBuilderRequirements());
380
381        mTestRunner = getAndroidTestRunner();
382        mTestRunner.setContext(getTargetContext());
383        mTestRunner.setInstrumentation(this);
384        mTestRunner.setSkipExecution(logOnly);
385        mTestRunner.setTest(testSuiteBuilder.build());
386        mTestCount = mTestRunner.getTestCases().size();
387        if (mSuiteAssignmentMode) {
388            mTestRunner.addTestListener(new SuiteAssignmentPrinter());
389        } else {
390            WatcherResultPrinter resultPrinter = new WatcherResultPrinter(mTestCount);
391            mTestRunner.addTestListener(new TestPrinter("TestRunner", false));
392            mTestRunner.addTestListener(resultPrinter);
393            mTestRunner.setPerformanceResultsWriter(resultPrinter);
394        }
395        start();
396    }
397
398    /**
399     * Get the arguments passed to this instrumentation.
400     *
401     * @return the Bundle object
402     */
403    public Bundle getArguments() {
404        return mArguments;
405    }
406
407    /**
408     * Add a {@link TestListener}
409     * @hide
410     */
411    protected void addTestListener(TestListener listener){
412        if(mTestRunner!=null && listener!=null){
413            mTestRunner.addTestListener(listener);
414        }
415    }
416
417    List<Predicate<TestMethod>> getBuilderRequirements() {
418        return new ArrayList<Predicate<TestMethod>>();
419    }
420
421    /**
422     * Parses and loads the specified set of test classes
423     *
424     * @param testClassArg - comma-separated list of test classes and methods
425     * @param testSuiteBuilder - builder to add tests to
426     */
427    private void parseTestClasses(String testClassArg, TestSuiteBuilder testSuiteBuilder) {
428        String[] testClasses = testClassArg.split(",");
429        for (String testClass : testClasses) {
430            parseTestClass(testClass, testSuiteBuilder);
431        }
432    }
433
434    /**
435     * Parse and load the given test class and, optionally, method
436     *
437     * @param testClassName - full package name of test class and optionally method to add.
438     *        Expected format: com.android.TestClass#testMethod
439     * @param testSuiteBuilder - builder to add tests to
440     */
441    private void parseTestClass(String testClassName, TestSuiteBuilder testSuiteBuilder) {
442        int methodSeparatorIndex = testClassName.indexOf('#');
443        String testMethodName = null;
444
445        if (methodSeparatorIndex > 0) {
446            testMethodName = testClassName.substring(methodSeparatorIndex + 1);
447            testClassName = testClassName.substring(0, methodSeparatorIndex);
448        }
449        testSuiteBuilder.addTestClassByName(testClassName, testMethodName, getTargetContext());
450    }
451
452    protected AndroidTestRunner getAndroidTestRunner() {
453        return new AndroidTestRunner();
454    }
455
456    private boolean getBooleanArgument(Bundle arguments, String tag) {
457        String tagString = arguments.getString(tag);
458        return tagString != null && Boolean.parseBoolean(tagString);
459    }
460
461    /*
462     * Returns the size predicate object, corresponding to the "size" argument value.
463     */
464    private Predicate<TestMethod> getSizePredicateFromArg(String sizeArg) {
465
466        if (SMALL_SUITE.equals(sizeArg)) {
467            return TestPredicates.SELECT_SMALL;
468        } else if (MEDIUM_SUITE.equals(sizeArg)) {
469            return TestPredicates.SELECT_MEDIUM;
470        } else if (LARGE_SUITE.equals(sizeArg)) {
471            return TestPredicates.SELECT_LARGE;
472        } else {
473            return null;
474        }
475    }
476
477   /**
478    * Returns the test predicate object, corresponding to the annotation class value provided via
479    * the {@link ARGUMENT_ANNOTATION} argument.
480    *
481    * @return the predicate or <code>null</code>
482    */
483    private Predicate<TestMethod> getAnnotationPredicate(String annotationClassName) {
484        Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
485        if (annotationClass != null) {
486            return new HasAnnotation(annotationClass);
487        }
488        return null;
489    }
490
491    /**
492     * Returns the negative test predicate object, corresponding to the annotation class value
493     * provided via the {@link ARGUMENT_NOT_ANNOTATION} argument.
494     *
495     * @return the predicate or <code>null</code>
496     */
497     private Predicate<TestMethod> getNotAnnotationPredicate(String annotationClassName) {
498         Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
499         if (annotationClass != null) {
500             return Predicates.not(new HasAnnotation(annotationClass));
501         }
502         return null;
503     }
504
505    /**
506     * Helper method to return the annotation class with specified name
507     *
508     * @param annotationClassName the fully qualified name of the class
509     * @return the annotation class or <code>null</code>
510     */
511    private Class<? extends Annotation> getAnnotationClass(String annotationClassName) {
512        if (annotationClassName == null) {
513            return null;
514        }
515        try {
516           Class<?> annotationClass = Class.forName(annotationClassName);
517           if (annotationClass.isAnnotation()) {
518               return (Class<? extends Annotation>)annotationClass;
519           } else {
520               Log.e(LOG_TAG, String.format("Provided annotation value %s is not an Annotation",
521                       annotationClassName));
522           }
523        } catch (ClassNotFoundException e) {
524            Log.e(LOG_TAG, String.format("Could not find class for specified annotation %s",
525                    annotationClassName));
526        }
527        return null;
528    }
529
530    /**
531     * Initialize the current thread as a looper.
532     * <p/>
533     * Exposed for unit testing.
534     */
535    void prepareLooper() {
536        Looper.prepare();
537    }
538
539    @Override
540    public void onStart() {
541        prepareLooper();
542
543        if (mJustCount) {
544            mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
545            mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);
546            finish(Activity.RESULT_OK, mResults);
547        } else {
548            if (mDebug) {
549                Debug.waitForDebugger();
550            }
551
552            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
553            PrintStream writer = new PrintStream(byteArrayOutputStream);
554            try {
555                StringResultPrinter resultPrinter = new StringResultPrinter(writer);
556
557                mTestRunner.addTestListener(resultPrinter);
558
559                long startTime = System.currentTimeMillis();
560                mTestRunner.runTest();
561                long runTime = System.currentTimeMillis() - startTime;
562
563                resultPrinter.printResult(mTestRunner.getTestResult(), runTime);
564            } catch (Throwable t) {
565                // catch all exceptions so a more verbose error message can be outputted
566                writer.println(String.format("Test run aborted due to unexpected exception: %s",
567                                t.getMessage()));
568                t.printStackTrace(writer);
569            } finally {
570                mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
571                        String.format("\nTest results for %s=%s",
572                        mTestRunner.getTestClassName(),
573                        byteArrayOutputStream.toString()));
574
575                if (mCoverage) {
576                    generateCoverageReport();
577                }
578                writer.close();
579
580                finish(Activity.RESULT_OK, mResults);
581            }
582        }
583    }
584
585    public TestSuite getTestSuite() {
586        return getAllTests();
587    }
588
589    /**
590     * Override this to define all of the tests to run in your package.
591     */
592    public TestSuite getAllTests() {
593        return null;
594    }
595
596    /**
597     * Override this to provide access to the class loader of your package.
598     */
599    public ClassLoader getLoader() {
600        return null;
601    }
602
603    private void generateCoverageReport() {
604        // use reflection to call emma dump coverage method, to avoid
605        // always statically compiling against emma jar
606        String coverageFilePath = getCoverageFilePath();
607        java.io.File coverageFile = new java.io.File(coverageFilePath);
608        try {
609            Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
610            Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
611                    coverageFile.getClass(), boolean.class, boolean.class);
612
613            dumpCoverageMethod.invoke(null, coverageFile, false, false);
614            // output path to generated coverage file so it can be parsed by a test harness if
615            // needed
616            mResults.putString(REPORT_KEY_COVERAGE_PATH, coverageFilePath);
617            // also output a more user friendly msg
618            final String currentStream = mResults.getString(
619                    Instrumentation.REPORT_KEY_STREAMRESULT);
620            mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
621                String.format("%s\nGenerated code coverage data to %s", currentStream,
622                coverageFilePath));
623        } catch (ClassNotFoundException e) {
624            reportEmmaError("Is emma jar on classpath?", e);
625        } catch (SecurityException e) {
626            reportEmmaError(e);
627        } catch (NoSuchMethodException e) {
628            reportEmmaError(e);
629        } catch (IllegalArgumentException e) {
630            reportEmmaError(e);
631        } catch (IllegalAccessException e) {
632            reportEmmaError(e);
633        } catch (InvocationTargetException e) {
634            reportEmmaError(e);
635        }
636    }
637
638    private String getCoverageFilePath() {
639        if (mCoverageFilePath == null) {
640            return getTargetContext().getFilesDir().getAbsolutePath() + File.separator +
641                   DEFAULT_COVERAGE_FILE_NAME;
642        } else {
643            return mCoverageFilePath;
644        }
645    }
646
647    private void reportEmmaError(Exception e) {
648        reportEmmaError("", e);
649    }
650
651    private void reportEmmaError(String hint, Exception e) {
652        String msg = "Failed to generate emma coverage. " + hint;
653        Log.e(LOG_TAG, msg, e);
654        mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: " + msg);
655    }
656
657    // TODO kill this, use status() and prettyprint model for better output
658    private class StringResultPrinter extends ResultPrinter {
659
660        public StringResultPrinter(PrintStream writer) {
661            super(writer);
662        }
663
664        public synchronized void printResult(TestResult result, long runTime) {
665            printHeader(runTime);
666            printFooter(result);
667        }
668    }
669
670    /**
671     * This class sends status reports back to the IInstrumentationWatcher about
672     * which suite each test belongs.
673     */
674    private class SuiteAssignmentPrinter implements TestListener {
675
676        private Bundle mTestResult;
677        private long mStartTime;
678        private long mEndTime;
679        private boolean mTimingValid;
680
681        public SuiteAssignmentPrinter() {
682        }
683
684        /**
685         * send a status for the start of a each test, so long tests can be seen as "running"
686         */
687        public void startTest(Test test) {
688            mTimingValid = true;
689            mStartTime = System.currentTimeMillis();
690        }
691
692        /**
693         * @see junit.framework.TestListener#addError(Test, Throwable)
694         */
695        public void addError(Test test, Throwable t) {
696            mTimingValid = false;
697        }
698
699        /**
700         * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
701         */
702        public void addFailure(Test test, AssertionFailedError t) {
703            mTimingValid = false;
704        }
705
706        /**
707         * @see junit.framework.TestListener#endTest(Test)
708         */
709        public void endTest(Test test) {
710            float runTime;
711            String assignmentSuite;
712            mEndTime = System.currentTimeMillis();
713            mTestResult = new Bundle();
714
715            if (!mTimingValid || mStartTime < 0) {
716                assignmentSuite = "NA";
717                runTime = -1;
718            } else {
719                runTime = mEndTime - mStartTime;
720                if (runTime < SMALL_SUITE_MAX_RUNTIME
721                        && !InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
722                    assignmentSuite = SMALL_SUITE;
723                } else if (runTime < MEDIUM_SUITE_MAX_RUNTIME) {
724                    assignmentSuite = MEDIUM_SUITE;
725                } else {
726                    assignmentSuite = LARGE_SUITE;
727                }
728            }
729            // Clear mStartTime so that we can verify that it gets set next time.
730            mStartTime = -1;
731
732            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
733                    test.getClass().getName() + "#" + ((TestCase) test).getName()
734                    + "\nin " + assignmentSuite + " suite\nrunTime: "
735                    + String.valueOf(runTime) + "\n");
736            mTestResult.putFloat(REPORT_KEY_RUN_TIME, runTime);
737            mTestResult.putString(REPORT_KEY_SUITE_ASSIGNMENT, assignmentSuite);
738
739            sendStatus(0, mTestResult);
740        }
741    }
742
743    /**
744     * This class sends status reports back to the IInstrumentationWatcher
745     */
746    private class WatcherResultPrinter implements TestListener, PerformanceResultsWriter {
747        private final Bundle mResultTemplate;
748        Bundle mTestResult;
749        int mTestNum = 0;
750        int mTestResultCode = 0;
751        String mTestClass = null;
752        PerformanceCollector mPerfCollector = new PerformanceCollector();
753        boolean mIsTimedTest = false;
754        boolean mIncludeDetailedStats = false;
755
756        public WatcherResultPrinter(int numTests) {
757            mResultTemplate = new Bundle();
758            mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
759            mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
760        }
761
762        /**
763         * send a status for the start of a each test, so long tests can be seen
764         * as "running"
765         */
766        public void startTest(Test test) {
767            String testClass = test.getClass().getName();
768            String testName = ((TestCase)test).getName();
769            mTestResult = new Bundle(mResultTemplate);
770            mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
771            mTestResult.putString(REPORT_KEY_NAME_TEST, testName);
772            mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
773            // pretty printing
774            if (testClass != null && !testClass.equals(mTestClass)) {
775                mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
776                        String.format("\n%s:", testClass));
777                mTestClass = testClass;
778            } else {
779                mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "");
780            }
781
782            Method testMethod = null;
783            try {
784                testMethod = test.getClass().getMethod(testName);
785                // Report total number of iterations, if test is repetitive
786                if (testMethod.isAnnotationPresent(RepetitiveTest.class)) {
787                    int numIterations = testMethod.getAnnotation(
788                        RepetitiveTest.class).numIterations();
789                    mTestResult.putInt(REPORT_KEY_NUM_ITERATIONS, numIterations);
790                }
791            } catch (NoSuchMethodException e) {
792                // ignore- the test with given name does not exist. Will be handled during test
793                // execution
794            }
795
796            // The delay_msec parameter is normally used to provide buffers of idle time
797            // for power measurement purposes. To make sure there is a delay before and after
798            // every test in a suite, we delay *after* every test (see endTest below) and also
799            // delay *before* the first test. So, delay test1 delay test2 delay.
800
801            try {
802                if (mTestNum == 1) Thread.sleep(mDelayMsec);
803            } catch (InterruptedException e) {
804                throw new IllegalStateException(e);
805            }
806
807            sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
808            mTestResultCode = 0;
809
810            mIsTimedTest = false;
811            mIncludeDetailedStats = false;
812            try {
813                // Look for TimedTest annotation on both test class and test method
814                if (testMethod != null && testMethod.isAnnotationPresent(TimedTest.class)) {
815                    mIsTimedTest = true;
816                    mIncludeDetailedStats = testMethod.getAnnotation(
817                            TimedTest.class).includeDetailedStats();
818                } else if (test.getClass().isAnnotationPresent(TimedTest.class)) {
819                    mIsTimedTest = true;
820                    mIncludeDetailedStats = test.getClass().getAnnotation(
821                            TimedTest.class).includeDetailedStats();
822                }
823            } catch (SecurityException e) {
824                // ignore - the test with given name cannot be accessed. Will be handled during
825                // test execution
826            }
827
828            if (mIsTimedTest && mIncludeDetailedStats) {
829                mPerfCollector.beginSnapshot("");
830            } else if (mIsTimedTest) {
831                mPerfCollector.startTiming("");
832            }
833        }
834
835        /**
836         * @see junit.framework.TestListener#addError(Test, Throwable)
837         */
838        public void addError(Test test, Throwable t) {
839            mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
840            mTestResultCode = REPORT_VALUE_RESULT_ERROR;
841            // pretty printing
842            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
843                String.format("\nError in %s:\n%s",
844                    ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
845        }
846
847        /**
848         * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
849         */
850        public void addFailure(Test test, AssertionFailedError t) {
851            mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
852            mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
853            // pretty printing
854            mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
855                String.format("\nFailure in %s:\n%s",
856                    ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
857        }
858
859        /**
860         * @see junit.framework.TestListener#endTest(Test)
861         */
862        public void endTest(Test test) {
863            if (mIsTimedTest && mIncludeDetailedStats) {
864                mTestResult.putAll(mPerfCollector.endSnapshot());
865            } else if (mIsTimedTest) {
866                writeStopTiming(mPerfCollector.stopTiming(""));
867            }
868
869            if (mTestResultCode == 0) {
870                mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
871            }
872            sendStatus(mTestResultCode, mTestResult);
873
874            try { // Sleep after every test, if specified
875                Thread.sleep(mDelayMsec);
876            } catch (InterruptedException e) {
877                throw new IllegalStateException(e);
878            }
879        }
880
881        public void writeBeginSnapshot(String label) {
882            // Do nothing
883        }
884
885        public void writeEndSnapshot(Bundle results) {
886            // Copy all snapshot data fields into mResults, which is outputted
887            // via Instrumentation.finish
888            mResults.putAll(results);
889        }
890
891        public void writeStartTiming(String label) {
892            // Do nothing
893        }
894
895        public void writeStopTiming(Bundle results) {
896            // Copy results into mTestResult by flattening list of iterations,
897            // which is outputted via WatcherResultPrinter.endTest
898            int i = 0;
899            for (Parcelable p :
900                    results.getParcelableArrayList(PerformanceCollector.METRIC_KEY_ITERATIONS)) {
901                Bundle iteration = (Bundle)p;
902                String index = "iteration" + i + ".";
903                mTestResult.putString(index + PerformanceCollector.METRIC_KEY_LABEL,
904                        iteration.getString(PerformanceCollector.METRIC_KEY_LABEL));
905                mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_CPU_TIME,
906                        iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME));
907                mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_EXECUTION_TIME,
908                        iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
909                i++;
910            }
911        }
912
913        public void writeMeasurement(String label, long value) {
914            mTestResult.putLong(label, value);
915        }
916
917        public void writeMeasurement(String label, float value) {
918            mTestResult.putFloat(label, value);
919        }
920
921        public void writeMeasurement(String label, String value) {
922            mTestResult.putString(label, value);
923        }
924
925        // TODO report the end of the cycle
926    }
927}
928