blob: 70d164338ead1c302f4e3c13e94baa6df94cc895 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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 static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE;
Jack Wangff1df692009-08-26 17:19:13 -070020
21import com.android.internal.util.Predicate;
Brett Chabot88e03a92010-02-19 09:57:11 -080022import com.android.internal.util.Predicates;
Jack Wangff1df692009-08-26 17:19:13 -070023
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.app.Activity;
25import android.app.Instrumentation;
26import android.os.Bundle;
27import android.os.Debug;
28import android.os.Looper;
Jack Wangff1df692009-08-26 17:19:13 -070029import android.os.Parcelable;
30import android.os.PerformanceCollector;
Jack Wangff1df692009-08-26 17:19:13 -070031import android.os.PerformanceCollector.PerformanceResultsWriter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.test.suitebuilder.TestMethod;
33import android.test.suitebuilder.TestPredicates;
34import android.test.suitebuilder.TestSuiteBuilder;
Brett Chabot88e03a92010-02-19 09:57:11 -080035import android.test.suitebuilder.annotation.HasAnnotation;
Doug Zongker0375fa12010-02-23 12:36:06 -080036import android.test.suitebuilder.annotation.LargeTest;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.util.Log;
38
Jack Wangff1df692009-08-26 17:19:13 -070039import java.io.ByteArrayOutputStream;
40import java.io.File;
41import java.io.PrintStream;
Brett Chabot88e03a92010-02-19 09:57:11 -080042import java.lang.annotation.Annotation;
Jack Wangff1df692009-08-26 17:19:13 -070043import java.lang.reflect.InvocationTargetException;
44import java.lang.reflect.Method;
45import java.util.ArrayList;
46import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047
48import junit.framework.AssertionFailedError;
49import junit.framework.Test;
50import junit.framework.TestCase;
51import junit.framework.TestListener;
52import junit.framework.TestResult;
53import junit.framework.TestSuite;
54import junit.runner.BaseTestRunner;
55import junit.textui.ResultPrinter;
56
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057/**
58 * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against
59 * an Android package (application). Typical usage:
60 * <ol>
61 * <li>Write {@link junit.framework.TestCase}s that perform unit, functional, or performance tests
62 * against the classes in your package. Typically these are subclassed from:
Jack Wangff1df692009-08-26 17:19:13 -070063 * <ul><li>{@link android.test.ActivityInstrumentationTestCase2}</li>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 * <li>{@link android.test.ActivityUnitTestCase}</li>
65 * <li>{@link android.test.AndroidTestCase}</li>
66 * <li>{@link android.test.ApplicationTestCase}</li>
67 * <li>{@link android.test.InstrumentationTestCase}</li>
68 * <li>{@link android.test.ProviderTestCase}</li>
69 * <li>{@link android.test.ServiceTestCase}</li>
70 * <li>{@link android.test.SingleLaunchActivityTestCase}</li></ul>
71 * <li>In an appropriate AndroidManifest.xml, define the this instrumentation with
72 * the appropriate android:targetPackage set.
73 * <li>Run the instrumentation using "adb shell am instrument -w",
74 * with no optional arguments, to run all tests (except performance tests).
75 * <li>Run the instrumentation using "adb shell am instrument -w",
76 * with the argument '-e func true' to run all functional tests. These are tests that derive from
77 * {@link android.test.InstrumentationTestCase}.
78 * <li>Run the instrumentation using "adb shell am instrument -w",
79 * with the argument '-e unit true' to run all unit tests. These are tests that <i>do not</i>derive
80 * from {@link android.test.InstrumentationTestCase} (and are not performance tests).
81 * <li>Run the instrumentation using "adb shell am instrument -w",
82 * with the argument '-e class' set to run an individual {@link junit.framework.TestCase}.
83 * </ol>
84 * <p/>
85 * <b>Running all tests:</b> adb shell am instrument -w
86 * com.android.foo/android.test.InstrumentationTestRunner
87 * <p/>
88 * <b>Running all small tests:</b> adb shell am instrument -w
89 * -e size small
90 * com.android.foo/android.test.InstrumentationTestRunner
91 * <p/>
92 * <b>Running all medium tests:</b> adb shell am instrument -w
93 * -e size medium
94 * com.android.foo/android.test.InstrumentationTestRunner
95 * <p/>
96 * <b>Running all large tests:</b> adb shell am instrument -w
97 * -e size large
98 * com.android.foo/android.test.InstrumentationTestRunner
99 * <p/>
Brett Chabot88e03a92010-02-19 09:57:11 -0800100 * <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
101 * -e annotation com.android.foo.MyAnnotation
102 * com.android.foo/android.test.InstrumentationTestRunner
103 * <p/>
104 * If used with other options, the resulting test run will contain the union of the two options.
105 * e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
106 * the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
107 * <p/>
108 * <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
109 * -e notAnnotation com.android.foo.MyAnnotation
110 * com.android.foo/android.test.InstrumentationTestRunner
111 * <p/>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 * <b>Running a single testcase:</b> adb shell am instrument -w
113 * -e class com.android.foo.FooTest
114 * com.android.foo/android.test.InstrumentationTestRunner
115 * <p/>
116 * <b>Running a single test:</b> adb shell am instrument -w
117 * -e class com.android.foo.FooTest#testFoo
118 * com.android.foo/android.test.InstrumentationTestRunner
119 * <p/>
120 * <b>Running multiple tests:</b> adb shell am instrument -w
121 * -e class com.android.foo.FooTest,com.android.foo.TooTest
122 * com.android.foo/android.test.InstrumentationTestRunner
123 * <p/>
Brett Chabot89c0ef42010-03-18 20:03:31 -0700124 * <b>Running all tests in a java package:</b> adb shell am instrument -w
125 * -e package com.android.foo.subpkg
126 * com.android.foo/android.test.InstrumentationTestRunner
127 * <p/>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 * <b>Including performance tests:</b> adb shell am instrument -w
129 * -e perf true
130 * com.android.foo/android.test.InstrumentationTestRunner
131 * <p/>
132 * <b>To debug your tests, set a break point in your code and pass:</b>
133 * -e debug true
134 * <p/>
135 * <b>To run in 'log only' mode</b>
136 * -e log true
Jack Wangff1df692009-08-26 17:19:13 -0700137 * This option will load and iterate through all test classes and methods, but will bypass actual
138 * test execution. Useful for quickly obtaining info on the tests to be executed by an
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 * instrumentation command.
140 * <p/>
141 * <b>To generate EMMA code coverage:</b>
142 * -e coverage true
Jack Wangff1df692009-08-26 17:19:13 -0700143 * Note: this requires an emma instrumented build. By default, the code coverage results file
Brett Chabot51e03642009-05-28 18:18:15 -0700144 * will be saved in a /data/<app>/coverage.ec file, unless overridden by coverageFile flag (see
145 * below)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 * <p/>
147 * <b> To specify EMMA code coverage results file path:</b>
148 * -e coverageFile /sdcard/myFile.ec
149 * <br/>
150 * in addition to the other arguments.
151 */
152
153/* (not JavaDoc)
154 * Although not necessary in most case, another way to use this class is to extend it and have the
Jack Wangff1df692009-08-26 17:19:13 -0700155 * derived class return the desired test suite from the {@link #getTestSuite()} method. The test
156 * suite returned from this method will be used if no target class is defined in the meta-data or
157 * command line argument parameters. If a derived class is used it needs to be added as an
158 * instrumentation to the AndroidManifest.xml and the command to run it would look like:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 * <p/>
160 * adb shell am instrument -w com.android.foo/<i>com.android.FooInstrumentationTestRunner</i>
161 * <p/>
162 * Where <i>com.android.FooInstrumentationTestRunner</i> is the derived class.
163 *
164 * This model is used by many existing app tests, but can probably be deprecated.
165 */
166public class InstrumentationTestRunner extends Instrumentation implements TestSuiteProvider {
167
168 /** @hide */
169 public static final String ARGUMENT_TEST_CLASS = "class";
170 /** @hide */
171 public static final String ARGUMENT_TEST_PACKAGE = "package";
172 /** @hide */
173 public static final String ARGUMENT_TEST_SIZE_PREDICATE = "size";
174 /** @hide */
175 public static final String ARGUMENT_INCLUDE_PERF = "perf";
176 /** @hide */
177 public static final String ARGUMENT_DELAY_MSEC = "delay_msec";
178
179 private static final String SMALL_SUITE = "small";
Jack Wangff1df692009-08-26 17:19:13 -0700180 private static final String MEDIUM_SUITE = "medium";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 private static final String LARGE_SUITE = "large";
Jack Wangff1df692009-08-26 17:19:13 -0700182
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 private static final String ARGUMENT_LOG_ONLY = "log";
Brett Chabot88e03a92010-02-19 09:57:11 -0800184 /** @hide */
185 static final String ARGUMENT_ANNOTATION = "annotation";
186 /** @hide */
187 static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 /**
Jack Wangff1df692009-08-26 17:19:13 -0700190 * This constant defines the maximum allowed runtime (in ms) for a test included in the "small"
191 * suite. It is used to make an educated guess at what suite an unlabeled test belongs.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 */
193 private static final float SMALL_SUITE_MAX_RUNTIME = 100;
Jack Wangff1df692009-08-26 17:19:13 -0700194
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 /**
Jack Wangff1df692009-08-26 17:19:13 -0700196 * This constant defines the maximum allowed runtime (in ms) for a test included in the
197 * "medium" suite. It is used to make an educated guess at what suite an unlabeled test belongs.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 */
199 private static final float MEDIUM_SUITE_MAX_RUNTIME = 1000;
Jack Wangff1df692009-08-26 17:19:13 -0700200
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 /**
Jack Wangff1df692009-08-26 17:19:13 -0700202 * The following keys are used in the status bundle to provide structured reports to
203 * an IInstrumentationWatcher.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800204 */
205
206 /**
Jack Wangff1df692009-08-26 17:19:13 -0700207 * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER},
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 * identifies InstrumentationTestRunner as the source of the report. This is sent with all
209 * status messages.
210 */
211 public static final String REPORT_VALUE_ID = "InstrumentationTestRunner";
212 /**
Jack Wangff1df692009-08-26 17:19:13 -0700213 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 * identifies the total number of tests that are being run. This is sent with all status
215 * messages.
216 */
217 public static final String REPORT_KEY_NUM_TOTAL = "numtests";
218 /**
Jack Wangff1df692009-08-26 17:19:13 -0700219 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 * identifies the sequence number of the current test. This is sent with any status message
221 * describing a specific test being started or completed.
222 */
223 public static final String REPORT_KEY_NUM_CURRENT = "current";
224 /**
Jack Wangff1df692009-08-26 17:19:13 -0700225 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 * identifies the name of the current test class. This is sent with any status message
227 * describing a specific test being started or completed.
228 */
229 public static final String REPORT_KEY_NAME_CLASS = "class";
230 /**
Jack Wangff1df692009-08-26 17:19:13 -0700231 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 * identifies the name of the current test. This is sent with any status message
233 * describing a specific test being started or completed.
234 */
235 public static final String REPORT_KEY_NAME_TEST = "test";
236 /**
Jack Wangff1df692009-08-26 17:19:13 -0700237 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 * reports the run time in seconds of the current test.
239 */
240 private static final String REPORT_KEY_RUN_TIME = "runtime";
241 /**
Jack Wangff1df692009-08-26 17:19:13 -0700242 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 * reports the guessed suite assignment for the current test.
244 */
245 private static final String REPORT_KEY_SUITE_ASSIGNMENT = "suiteassignment";
246 /**
Brett Chabot51e03642009-05-28 18:18:15 -0700247 * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
248 * identifies the path to the generated code coverage file.
249 */
250 private static final String REPORT_KEY_COVERAGE_PATH = "coverageFilePath";
Jack Wangff1df692009-08-26 17:19:13 -0700251
252 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 * The test is starting.
254 */
255 public static final int REPORT_VALUE_RESULT_START = 1;
256 /**
257 * The test completed successfully.
258 */
259 public static final int REPORT_VALUE_RESULT_OK = 0;
260 /**
261 * The test completed with an error.
262 */
263 public static final int REPORT_VALUE_RESULT_ERROR = -1;
264 /**
265 * The test completed with a failure.
266 */
267 public static final int REPORT_VALUE_RESULT_FAILURE = -2;
268 /**
Jack Wangff1df692009-08-26 17:19:13 -0700269 * If included in the status bundle sent to an IInstrumentationWatcher, this key
270 * identifies a stack trace describing an error or failure. This is sent with any status
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 * message describing a specific test being completed.
272 */
273 public static final String REPORT_KEY_STACK = "stack";
274
Brett Chabot51e03642009-05-28 18:18:15 -0700275 // Default file name for code coverage
276 private static final String DEFAULT_COVERAGE_FILE_NAME = "coverage.ec";
Jack Wangff1df692009-08-26 17:19:13 -0700277
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 private static final String LOG_TAG = "InstrumentationTestRunner";
279
280 private final Bundle mResults = new Bundle();
281 private AndroidTestRunner mTestRunner;
282 private boolean mDebug;
283 private boolean mJustCount;
284 private boolean mSuiteAssignmentMode;
285 private int mTestCount;
286 private String mPackageOfTests;
287 private boolean mCoverage;
288 private String mCoverageFilePath;
289 private int mDelayMsec;
290
291 @Override
292 public void onCreate(Bundle arguments) {
293 super.onCreate(arguments);
294
295 // Apk paths used to search for test classes when using TestSuiteBuilders.
296 String[] apkPaths =
297 {getTargetContext().getPackageCodePath(), getContext().getPackageCodePath()};
298 ClassPathPackageInfoSource.setApkPaths(apkPaths);
299
300 Predicate<TestMethod> testSizePredicate = null;
Brett Chabot88e03a92010-02-19 09:57:11 -0800301 Predicate<TestMethod> testAnnotationPredicate = null;
302 Predicate<TestMethod> testNotAnnotationPredicate = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 boolean includePerformance = false;
304 String testClassesArg = null;
305 boolean logOnly = false;
306
307 if (arguments != null) {
308 // Test class name passed as an argument should override any meta-data declaration.
309 testClassesArg = arguments.getString(ARGUMENT_TEST_CLASS);
310 mDebug = getBooleanArgument(arguments, "debug");
311 mJustCount = getBooleanArgument(arguments, "count");
312 mSuiteAssignmentMode = getBooleanArgument(arguments, "suiteAssignment");
313 mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE);
314 testSizePredicate = getSizePredicateFromArg(
315 arguments.getString(ARGUMENT_TEST_SIZE_PREDICATE));
Brett Chabot88e03a92010-02-19 09:57:11 -0800316 testAnnotationPredicate = getAnnotationPredicate(
317 arguments.getString(ARGUMENT_ANNOTATION));
318 testNotAnnotationPredicate = getNotAnnotationPredicate(
319 arguments.getString(ARGUMENT_NOT_ANNOTATION));
320
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 includePerformance = getBooleanArgument(arguments, ARGUMENT_INCLUDE_PERF);
322 logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY);
323 mCoverage = getBooleanArgument(arguments, "coverage");
324 mCoverageFilePath = arguments.getString("coverageFile");
325
326 try {
327 Object delay = arguments.get(ARGUMENT_DELAY_MSEC); // Accept either string or int
328 if (delay != null) mDelayMsec = Integer.parseInt(delay.toString());
329 } catch (NumberFormatException e) {
330 Log.e(LOG_TAG, "Invalid delay_msec parameter", e);
331 }
332 }
333
334 TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(),
335 getTargetContext().getClassLoader());
336
337 if (testSizePredicate != null) {
338 testSuiteBuilder.addRequirements(testSizePredicate);
339 }
Brett Chabot88e03a92010-02-19 09:57:11 -0800340 if (testAnnotationPredicate != null) {
341 testSuiteBuilder.addRequirements(testAnnotationPredicate);
342 }
343 if (testNotAnnotationPredicate != null) {
344 testSuiteBuilder.addRequirements(testNotAnnotationPredicate);
345 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 if (!includePerformance) {
347 testSuiteBuilder.addRequirements(REJECT_PERFORMANCE);
348 }
349
350 if (testClassesArg == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 if (mPackageOfTests != null) {
352 testSuiteBuilder.includePackages(mPackageOfTests);
353 } else {
Brett Chabot61b10ac2009-03-31 17:04:34 -0700354 TestSuite testSuite = getTestSuite();
355 if (testSuite != null) {
356 testSuiteBuilder.addTestSuite(testSuite);
357 } else {
Jack Wangff1df692009-08-26 17:19:13 -0700358 // no package or class bundle arguments were supplied, and no test suite
Brett Chabot61b10ac2009-03-31 17:04:34 -0700359 // provided so add all tests in application
360 testSuiteBuilder.includePackages("");
361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 }
363 } else {
364 parseTestClasses(testClassesArg, testSuiteBuilder);
365 }
Jack Wangff1df692009-08-26 17:19:13 -0700366
Urs Grobda13ef52009-04-17 11:30:14 -0700367 testSuiteBuilder.addRequirements(getBuilderRequirements());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368
369 mTestRunner = getAndroidTestRunner();
370 mTestRunner.setContext(getTargetContext());
Jack Wang7aba54b2009-08-20 19:20:54 -0700371 mTestRunner.setInstrumentation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 mTestRunner.setSkipExecution(logOnly);
373 mTestRunner.setTest(testSuiteBuilder.build());
374 mTestCount = mTestRunner.getTestCases().size();
375 if (mSuiteAssignmentMode) {
376 mTestRunner.addTestListener(new SuiteAssignmentPrinter());
377 } else {
Jack Wangff1df692009-08-26 17:19:13 -0700378 WatcherResultPrinter resultPrinter = new WatcherResultPrinter(mTestCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 mTestRunner.addTestListener(new TestPrinter("TestRunner", false));
Jack Wangff1df692009-08-26 17:19:13 -0700380 mTestRunner.addTestListener(resultPrinter);
381 mTestRunner.setPerformanceResultsWriter(resultPrinter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 }
383 start();
384 }
385
Urs Grobda13ef52009-04-17 11:30:14 -0700386 List<Predicate<TestMethod>> getBuilderRequirements() {
387 return new ArrayList<Predicate<TestMethod>>();
388 }
389
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800390 /**
Jack Wangff1df692009-08-26 17:19:13 -0700391 * Parses and loads the specified set of test classes
392 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 * @param testClassArg - comma-separated list of test classes and methods
394 * @param testSuiteBuilder - builder to add tests to
395 */
396 private void parseTestClasses(String testClassArg, TestSuiteBuilder testSuiteBuilder) {
397 String[] testClasses = testClassArg.split(",");
398 for (String testClass : testClasses) {
399 parseTestClass(testClass, testSuiteBuilder);
400 }
401 }
402
403 /**
404 * Parse and load the given test class and, optionally, method
Jack Wangff1df692009-08-26 17:19:13 -0700405 *
406 * @param testClassName - full package name of test class and optionally method to add.
407 * Expected format: com.android.TestClass#testMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 * @param testSuiteBuilder - builder to add tests to
409 */
410 private void parseTestClass(String testClassName, TestSuiteBuilder testSuiteBuilder) {
411 int methodSeparatorIndex = testClassName.indexOf('#');
412 String testMethodName = null;
413
414 if (methodSeparatorIndex > 0) {
415 testMethodName = testClassName.substring(methodSeparatorIndex + 1);
416 testClassName = testClassName.substring(0, methodSeparatorIndex);
417 }
Jack Wangff1df692009-08-26 17:19:13 -0700418 testSuiteBuilder.addTestClassByName(testClassName, testMethodName, getTargetContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 }
420
421 protected AndroidTestRunner getAndroidTestRunner() {
422 return new AndroidTestRunner();
423 }
424
425 private boolean getBooleanArgument(Bundle arguments, String tag) {
426 String tagString = arguments.getString(tag);
427 return tagString != null && Boolean.parseBoolean(tagString);
428 }
Jack Wangff1df692009-08-26 17:19:13 -0700429
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 /*
431 * Returns the size predicate object, corresponding to the "size" argument value.
432 */
433 private Predicate<TestMethod> getSizePredicateFromArg(String sizeArg) {
Jack Wangff1df692009-08-26 17:19:13 -0700434
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435 if (SMALL_SUITE.equals(sizeArg)) {
436 return TestPredicates.SELECT_SMALL;
437 } else if (MEDIUM_SUITE.equals(sizeArg)) {
438 return TestPredicates.SELECT_MEDIUM;
439 } else if (LARGE_SUITE.equals(sizeArg)) {
440 return TestPredicates.SELECT_LARGE;
441 } else {
442 return null;
443 }
444 }
Jack Wangff1df692009-08-26 17:19:13 -0700445
Brett Chabot88e03a92010-02-19 09:57:11 -0800446 /**
447 * Returns the test predicate object, corresponding to the annotation class value provided via
448 * the {@link ARGUMENT_ANNOTATION} argument.
449 *
450 * @return the predicate or <code>null</code>
451 */
452 private Predicate<TestMethod> getAnnotationPredicate(String annotationClassName) {
453 Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
454 if (annotationClass != null) {
455 return new HasAnnotation(annotationClass);
456 }
457 return null;
458 }
459
460 /**
461 * Returns the negative test predicate object, corresponding to the annotation class value
462 * provided via the {@link ARGUMENT_NOT_ANNOTATION} argument.
463 *
464 * @return the predicate or <code>null</code>
465 */
466 private Predicate<TestMethod> getNotAnnotationPredicate(String annotationClassName) {
467 Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
468 if (annotationClass != null) {
469 return Predicates.not(new HasAnnotation(annotationClass));
470 }
471 return null;
472 }
473
474 /**
475 * Helper method to return the annotation class with specified name
476 *
477 * @param annotationClassName the fully qualified name of the class
478 * @return the annotation class or <code>null</code>
479 */
480 private Class<? extends Annotation> getAnnotationClass(String annotationClassName) {
481 if (annotationClassName == null) {
482 return null;
483 }
484 try {
485 Class<?> annotationClass = Class.forName(annotationClassName);
486 if (annotationClass.isAnnotation()) {
487 return (Class<? extends Annotation>)annotationClass;
488 } else {
489 Log.e(LOG_TAG, String.format("Provided annotation value %s is not an Annotation",
490 annotationClassName));
491 }
492 } catch (ClassNotFoundException e) {
493 Log.e(LOG_TAG, String.format("Could not find class for specified annotation %s",
494 annotationClassName));
495 }
496 return null;
497 }
498
Brett Chabot31e7ce72010-07-07 17:19:08 -0700499 /**
500 * Initialize the current thread as a looper.
501 * <p/>
502 * Exposed for unit testing.
503 */
504 void prepareLooper() {
505 Looper.prepare();
506 }
507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 @Override
509 public void onStart() {
Brett Chabot31e7ce72010-07-07 17:19:08 -0700510 prepareLooper();
Jack Wangff1df692009-08-26 17:19:13 -0700511
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 if (mJustCount) {
513 mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
514 mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);
515 finish(Activity.RESULT_OK, mResults);
516 } else {
517 if (mDebug) {
518 Debug.waitForDebugger();
519 }
Jack Wangff1df692009-08-26 17:19:13 -0700520
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
522 PrintStream writer = new PrintStream(byteArrayOutputStream);
523 try {
524 StringResultPrinter resultPrinter = new StringResultPrinter(writer);
Jack Wangff1df692009-08-26 17:19:13 -0700525
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 mTestRunner.addTestListener(resultPrinter);
Jack Wangff1df692009-08-26 17:19:13 -0700527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 long startTime = System.currentTimeMillis();
529 mTestRunner.runTest();
530 long runTime = System.currentTimeMillis() - startTime;
Jack Wangff1df692009-08-26 17:19:13 -0700531
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 resultPrinter.print(mTestRunner.getTestResult(), runTime);
Brett Chabot31e7ce72010-07-07 17:19:08 -0700533 } catch (Throwable t) {
534 // catch all exceptions so a more verbose error message can be outputted
535 writer.println(String.format("Test run aborted due to unexpected exception: %s",
536 t.getMessage()));
537 t.printStackTrace(writer);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538 } finally {
Jack Wangff1df692009-08-26 17:19:13 -0700539 mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
540 String.format("\nTest results for %s=%s",
541 mTestRunner.getTestClassName(),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 byteArrayOutputStream.toString()));
543
544 if (mCoverage) {
545 generateCoverageReport();
546 }
547 writer.close();
Jack Wangff1df692009-08-26 17:19:13 -0700548
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 finish(Activity.RESULT_OK, mResults);
550 }
551 }
552 }
553
554 public TestSuite getTestSuite() {
555 return getAllTests();
556 }
557
558 /**
559 * Override this to define all of the tests to run in your package.
560 */
561 public TestSuite getAllTests() {
562 return null;
563 }
564
565 /**
566 * Override this to provide access to the class loader of your package.
567 */
568 public ClassLoader getLoader() {
569 return null;
570 }
Jack Wangff1df692009-08-26 17:19:13 -0700571
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 private void generateCoverageReport() {
573 // use reflection to call emma dump coverage method, to avoid
574 // always statically compiling against emma jar
Brett Chabot51e03642009-05-28 18:18:15 -0700575 String coverageFilePath = getCoverageFilePath();
576 java.io.File coverageFile = new java.io.File(coverageFilePath);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 try {
Brett Chabot88e03a92010-02-19 09:57:11 -0800578 Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
Jack Wangff1df692009-08-26 17:19:13 -0700579 Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 coverageFile.getClass(), boolean.class, boolean.class);
Jack Wangff1df692009-08-26 17:19:13 -0700581
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 dumpCoverageMethod.invoke(null, coverageFile, false, false);
Brett Chabot51e03642009-05-28 18:18:15 -0700583 // output path to generated coverage file so it can be parsed by a test harness if
584 // needed
585 mResults.putString(REPORT_KEY_COVERAGE_PATH, coverageFilePath);
586 // also output a more user friendly msg
Brett Chabot08d13c32010-02-18 15:42:13 -0800587 final String currentStream = mResults.getString(
588 Instrumentation.REPORT_KEY_STREAMRESULT);
Brett Chabot51e03642009-05-28 18:18:15 -0700589 mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
Brett Chabot08d13c32010-02-18 15:42:13 -0800590 String.format("%s\nGenerated code coverage data to %s", currentStream,
591 coverageFilePath));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 } catch (ClassNotFoundException e) {
593 reportEmmaError("Is emma jar on classpath?", e);
594 } catch (SecurityException e) {
595 reportEmmaError(e);
596 } catch (NoSuchMethodException e) {
597 reportEmmaError(e);
598 } catch (IllegalArgumentException e) {
599 reportEmmaError(e);
600 } catch (IllegalAccessException e) {
601 reportEmmaError(e);
602 } catch (InvocationTargetException e) {
603 reportEmmaError(e);
604 }
605 }
606
607 private String getCoverageFilePath() {
608 if (mCoverageFilePath == null) {
Brett Chabot51e03642009-05-28 18:18:15 -0700609 return getTargetContext().getFilesDir().getAbsolutePath() + File.separator +
Jack Wangff1df692009-08-26 17:19:13 -0700610 DEFAULT_COVERAGE_FILE_NAME;
611 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 return mCoverageFilePath;
613 }
614 }
615
616 private void reportEmmaError(Exception e) {
Jack Wangff1df692009-08-26 17:19:13 -0700617 reportEmmaError("", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 }
619
620 private void reportEmmaError(String hint, Exception e) {
621 String msg = "Failed to generate emma coverage. " + hint;
622 Log.e(LOG_TAG, msg, e);
623 mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: " + msg);
624 }
625
626 // TODO kill this, use status() and prettyprint model for better output
627 private class StringResultPrinter extends ResultPrinter {
628
629 public StringResultPrinter(PrintStream writer) {
630 super(writer);
631 }
632
633 synchronized void print(TestResult result, long runTime) {
634 printHeader(runTime);
635 printFooter(result);
636 }
637 }
Jack Wangff1df692009-08-26 17:19:13 -0700638
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 /**
Jack Wangff1df692009-08-26 17:19:13 -0700640 * This class sends status reports back to the IInstrumentationWatcher about
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 * which suite each test belongs.
642 */
Jack Wangff1df692009-08-26 17:19:13 -0700643 private class SuiteAssignmentPrinter implements TestListener {
644
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645 private Bundle mTestResult;
646 private long mStartTime;
647 private long mEndTime;
648 private boolean mTimingValid;
Jack Wangff1df692009-08-26 17:19:13 -0700649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 public SuiteAssignmentPrinter() {
651 }
Jack Wangff1df692009-08-26 17:19:13 -0700652
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653 /**
654 * send a status for the start of a each test, so long tests can be seen as "running"
655 */
656 public void startTest(Test test) {
657 mTimingValid = true;
Jack Wangff1df692009-08-26 17:19:13 -0700658 mStartTime = System.currentTimeMillis();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800659 }
Jack Wangff1df692009-08-26 17:19:13 -0700660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 /**
662 * @see junit.framework.TestListener#addError(Test, Throwable)
663 */
664 public void addError(Test test, Throwable t) {
665 mTimingValid = false;
666 }
667
668 /**
669 * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
670 */
671 public void addFailure(Test test, AssertionFailedError t) {
672 mTimingValid = false;
673 }
674
675 /**
676 * @see junit.framework.TestListener#endTest(Test)
677 */
678 public void endTest(Test test) {
679 float runTime;
680 String assignmentSuite;
681 mEndTime = System.currentTimeMillis();
682 mTestResult = new Bundle();
683
684 if (!mTimingValid || mStartTime < 0) {
685 assignmentSuite = "NA";
686 runTime = -1;
687 } else {
688 runTime = mEndTime - mStartTime;
Jack Wangff1df692009-08-26 17:19:13 -0700689 if (runTime < SMALL_SUITE_MAX_RUNTIME
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 && !InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
691 assignmentSuite = SMALL_SUITE;
692 } else if (runTime < MEDIUM_SUITE_MAX_RUNTIME) {
693 assignmentSuite = MEDIUM_SUITE;
694 } else {
695 assignmentSuite = LARGE_SUITE;
696 }
697 }
698 // Clear mStartTime so that we can verify that it gets set next time.
699 mStartTime = -1;
700
Jack Wangff1df692009-08-26 17:19:13 -0700701 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
702 test.getClass().getName() + "#" + ((TestCase) test).getName()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 + "\nin " + assignmentSuite + " suite\nrunTime: "
704 + String.valueOf(runTime) + "\n");
705 mTestResult.putFloat(REPORT_KEY_RUN_TIME, runTime);
706 mTestResult.putString(REPORT_KEY_SUITE_ASSIGNMENT, assignmentSuite);
707
708 sendStatus(0, mTestResult);
709 }
710 }
Jack Wangff1df692009-08-26 17:19:13 -0700711
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 /**
713 * This class sends status reports back to the IInstrumentationWatcher
714 */
Jack Wangff1df692009-08-26 17:19:13 -0700715 private class WatcherResultPrinter implements TestListener, PerformanceResultsWriter {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 private final Bundle mResultTemplate;
717 Bundle mTestResult;
718 int mTestNum = 0;
719 int mTestResultCode = 0;
720 String mTestClass = null;
Jack Wang4f414bd2009-11-06 20:53:47 -0800721 PerformanceCollector mPerfCollector = new PerformanceCollector();
Jack Wangff1df692009-08-26 17:19:13 -0700722 boolean mIsTimedTest = false;
Jack Wang4f414bd2009-11-06 20:53:47 -0800723 boolean mIncludeDetailedStats = false;
Jack Wangff1df692009-08-26 17:19:13 -0700724
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 public WatcherResultPrinter(int numTests) {
726 mResultTemplate = new Bundle();
727 mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
728 mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
729 }
Jack Wangff1df692009-08-26 17:19:13 -0700730
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 /**
Jack Wangff1df692009-08-26 17:19:13 -0700732 * send a status for the start of a each test, so long tests can be seen
733 * as "running"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800734 */
735 public void startTest(Test test) {
736 String testClass = test.getClass().getName();
Jack Wangff1df692009-08-26 17:19:13 -0700737 String testName = ((TestCase)test).getName();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 mTestResult = new Bundle(mResultTemplate);
739 mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
Jack Wangff1df692009-08-26 17:19:13 -0700740 mTestResult.putString(REPORT_KEY_NAME_TEST, testName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
742 // pretty printing
743 if (testClass != null && !testClass.equals(mTestClass)) {
Jack Wangff1df692009-08-26 17:19:13 -0700744 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745 String.format("\n%s:", testClass));
746 mTestClass = testClass;
747 } else {
748 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "");
749 }
750
751 // The delay_msec parameter is normally used to provide buffers of idle time
Jack Wangff1df692009-08-26 17:19:13 -0700752 // for power measurement purposes. To make sure there is a delay before and after
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 // every test in a suite, we delay *after* every test (see endTest below) and also
Jack Wangff1df692009-08-26 17:19:13 -0700754 // delay *before* the first test. So, delay test1 delay test2 delay.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755
756 try {
757 if (mTestNum == 1) Thread.sleep(mDelayMsec);
758 } catch (InterruptedException e) {
759 throw new IllegalStateException(e);
760 }
761
762 sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
763 mTestResultCode = 0;
Jack Wangff1df692009-08-26 17:19:13 -0700764
765 mIsTimedTest = false;
Jack Wang4f414bd2009-11-06 20:53:47 -0800766 mIncludeDetailedStats = false;
Jack Wangff1df692009-08-26 17:19:13 -0700767 try {
Jack Wang4f414bd2009-11-06 20:53:47 -0800768 // Look for TimedTest annotation on both test class and test method
769 if (test.getClass().getMethod(testName).isAnnotationPresent(TimedTest.class)) {
770 mIsTimedTest = true;
771 mIncludeDetailedStats = test.getClass().getMethod(testName).getAnnotation(
772 TimedTest.class).includeDetailedStats();
773 } else if (test.getClass().isAnnotationPresent(TimedTest.class)) {
774 mIsTimedTest = true;
775 mIncludeDetailedStats = test.getClass().getAnnotation(
776 TimedTest.class).includeDetailedStats();
777 }
Jack Wangff1df692009-08-26 17:19:13 -0700778 } catch (SecurityException e) {
Brett Chabot31e7ce72010-07-07 17:19:08 -0700779 // ignore - the test with given name cannot be accessed. Will be handled during
780 // test execution
Jack Wangff1df692009-08-26 17:19:13 -0700781 } catch (NoSuchMethodException e) {
Brett Chabot31e7ce72010-07-07 17:19:08 -0700782 // ignore- the test with given name does not exist. Will be handled during test
783 // execution
Jack Wangff1df692009-08-26 17:19:13 -0700784 }
785
Jack Wang4f414bd2009-11-06 20:53:47 -0800786 if (mIsTimedTest && mIncludeDetailedStats) {
787 mPerfCollector.beginSnapshot("");
788 } else if (mIsTimedTest) {
789 mPerfCollector.startTiming("");
Jack Wangff1df692009-08-26 17:19:13 -0700790 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 }
Jack Wangff1df692009-08-26 17:19:13 -0700792
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 /**
794 * @see junit.framework.TestListener#addError(Test, Throwable)
795 */
796 public void addError(Test test, Throwable t) {
797 mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
798 mTestResultCode = REPORT_VALUE_RESULT_ERROR;
799 // pretty printing
Jack Wangff1df692009-08-26 17:19:13 -0700800 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
801 String.format("\nError in %s:\n%s",
802 ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800803 }
804
805 /**
806 * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
807 */
808 public void addFailure(Test test, AssertionFailedError t) {
809 mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
810 mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
811 // pretty printing
Jack Wangff1df692009-08-26 17:19:13 -0700812 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
813 String.format("\nFailure in %s:\n%s",
814 ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 }
816
817 /**
818 * @see junit.framework.TestListener#endTest(Test)
819 */
820 public void endTest(Test test) {
Jack Wang4f414bd2009-11-06 20:53:47 -0800821 if (mIsTimedTest && mIncludeDetailedStats) {
822 mTestResult.putAll(mPerfCollector.endSnapshot());
823 } else if (mIsTimedTest) {
824 writeStopTiming(mPerfCollector.stopTiming(""));
Jack Wangff1df692009-08-26 17:19:13 -0700825 }
826
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 if (mTestResultCode == 0) {
828 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
829 }
830 sendStatus(mTestResultCode, mTestResult);
831
Jack Wangff1df692009-08-26 17:19:13 -0700832 try { // Sleep after every test, if specified
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833 Thread.sleep(mDelayMsec);
834 } catch (InterruptedException e) {
835 throw new IllegalStateException(e);
836 }
837 }
838
Jack Wangff1df692009-08-26 17:19:13 -0700839 public void writeBeginSnapshot(String label) {
840 // Do nothing
841 }
842
843 public void writeEndSnapshot(Bundle results) {
Jack Wang075997f2009-10-27 22:01:09 -0700844 // Copy all snapshot data fields into mResults, which is outputted
845 // via Instrumentation.finish
846 mResults.putAll(results);
Jack Wangff1df692009-08-26 17:19:13 -0700847 }
848
849 public void writeStartTiming(String label) {
850 // Do nothing
851 }
852
853 public void writeStopTiming(Bundle results) {
854 // Copy results into mTestResult by flattening list of iterations,
855 // which is outputted via WatcherResultPrinter.endTest
856 int i = 0;
857 for (Parcelable p :
858 results.getParcelableArrayList(PerformanceCollector.METRIC_KEY_ITERATIONS)) {
859 Bundle iteration = (Bundle)p;
Jack Wang4f414bd2009-11-06 20:53:47 -0800860 String index = "iteration" + i + ".";
Jack Wangff1df692009-08-26 17:19:13 -0700861 mTestResult.putString(index + PerformanceCollector.METRIC_KEY_LABEL,
862 iteration.getString(PerformanceCollector.METRIC_KEY_LABEL));
863 mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_CPU_TIME,
864 iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME));
865 mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_EXECUTION_TIME,
866 iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
867 i++;
868 }
869 }
870
Jack Wang075997f2009-10-27 22:01:09 -0700871 public void writeMeasurement(String label, long value) {
Jack Wang4f414bd2009-11-06 20:53:47 -0800872 mTestResult.putLong(label, value);
Jack Wang075997f2009-10-27 22:01:09 -0700873 }
874
875 public void writeMeasurement(String label, float value) {
Jack Wang4f414bd2009-11-06 20:53:47 -0800876 mTestResult.putFloat(label, value);
Jack Wang075997f2009-10-27 22:01:09 -0700877 }
878
879 public void writeMeasurement(String label, String value) {
Jack Wang4f414bd2009-11-06 20:53:47 -0800880 mTestResult.putString(label, value);
Jack Wang075997f2009-10-27 22:01:09 -0700881 }
882
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 // TODO report the end of the cycle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800884 }
885}