diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
commit | f013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch) | |
tree | 7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /test-runner | |
parent | e70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff) | |
download | frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'test-runner')
14 files changed, 783 insertions, 105 deletions
diff --git a/test-runner/android/test/ActivityInstrumentationTestCase.java b/test-runner/android/test/ActivityInstrumentationTestCase.java index b198a7a..e5a9991 100644 --- a/test-runner/android/test/ActivityInstrumentationTestCase.java +++ b/test-runner/android/test/ActivityInstrumentationTestCase.java @@ -27,7 +27,12 @@ import java.lang.reflect.Field; * automatically here by {@link #setUp} and {@link #tearDown}. * * <p>If you prefer an isolated unit test, see {@link android.test.ActivityUnitTestCase}. + * + * @deprecated new tests should be written using + * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for + * configuring the Activity under test */ +@Deprecated public abstract class ActivityInstrumentationTestCase<T extends Activity> extends ActivityTestCase { String mPackage; diff --git a/test-runner/android/test/ActivityInstrumentationTestCase2.java b/test-runner/android/test/ActivityInstrumentationTestCase2.java new file mode 100644 index 0000000..7a84eca --- /dev/null +++ b/test-runner/android/test/ActivityInstrumentationTestCase2.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Activity; +import android.content.Intent; + +import java.lang.reflect.Method; + +/** + * This class provides functional testing of a single activity. The activity under test will + * be created using the system infrastructure (by calling InstrumentationTestCase.launchActivity()) + * and you will then be able to manipulate your Activity directly. + * + * <p>Other options supported by this test case include: + * <ul> + * <li>You can run any test method on the UI thread (see {@link android.test.UiThreadTest}).</li> + * <li>You can inject custom Intents into your Activity (see + * {@link #setActivityIntent(Intent)}).</li> + * </ul> + * + * <p>This class replaces {@link android.test.ActivityInstrumentationTestCase}, which is deprecated. + * New tests should be written using this base class. + * + * <p>If you prefer an isolated unit test, see {@link android.test.ActivityUnitTestCase}. + */ +public abstract class ActivityInstrumentationTestCase2<T extends Activity> + extends ActivityTestCase { + String mPackage; + Class<T> mActivityClass; + boolean mInitialTouchMode = false; + Intent mActivityIntent = null; + + /** + * @param pkg The package of the instrumentation. + * @param activityClass The activity to test. + */ + public ActivityInstrumentationTestCase2(String pkg, Class<T> activityClass) { + mPackage = pkg; + mActivityClass = activityClass; + } + + /** + * Get the Activity under test, starting it if necessary. + * + * For each test method invocation, the Activity will not actually be created until the first + * time this method is called. + * + * <p>If you wish to provide custom setup values to your Activity, you may call + * {@link #setActivityIntent(Intent)} and/or {@link #setActivityInitialTouchMode(boolean)} + * before your first call to getActivity(). Calling them after your Activity has + * started will have no effect. + * + * <p><b>NOTE:</b> Activities under test may not be started from within the UI thread. + * If your test method is annotated with {@link android.test.UiThreadTest}, then your Activity + * will be started automatically just before your test method is run. You still call this + * method in order to get the Activity under test. + * + * @return the Activity under test + */ + @Override + public T getActivity() { + Activity a = super.getActivity(); + if (a == null) { + // set initial touch mode + getInstrumentation().setInTouchMode(mInitialTouchMode); + // inject custom intent, if provided + if (mActivityIntent == null) { + a = launchActivity(mPackage, mActivityClass, null); + } else { + a = launchActivityWithIntent(mPackage, mActivityClass, mActivityIntent); + } + setActivity(a); + } + return (T) a; + } + + /** + * Call this method before the first call to {@link #getActivity} to inject a customized Intent + * into the Activity under test. + * + * <p>If you do not call this, the default intent will be provided. If you call this after + * your Activity has been started, it will have no effect. + * + * <p><b>NOTE:</b> Activities under test may not be started from within the UI thread. + * If your test method is annotated with {@link android.test.UiThreadTest}, then you must call + * {@link #setActivityIntent(Intent)} from {@link #setUp()}. + * + * <p>The default Intent (if this method is not called) is: + * action = {@link Intent#ACTION_MAIN} + * flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK} + * All other fields are null or empty. + * + * @param i The Intent to start the Activity with, or null to reset to the default Intent. + */ + public void setActivityIntent(Intent i) { + mActivityIntent = i; + } + + /** + * Call this method before the first call to {@link #getActivity} to set the initial touch + * mode for the Activity under test. + * + * <p>If you do not call this, the touch mode will be false. If you call this after + * your Activity has been started, it will have no effect. + * + * <p><b>NOTE:</b> Activities under test may not be started from within the UI thread. + * If your test method is annotated with {@link android.test.UiThreadTest}, then you must call + * {@link #setActivityInitialTouchMode(boolean)} from {@link #setUp()}. + * + * @param initialTouchMode true if the Activity should be placed into "touch mode" when started + */ + public void setActivityInitialTouchMode(boolean initialTouchMode) { + mInitialTouchMode = initialTouchMode; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + boolean mInitialTouchMode = false; + Intent mActivityIntent = null; + } + + @Override + protected void tearDown() throws Exception { + // Finish the Activity off (unless was never launched anyway) + Activity a = super.getActivity(); + if (a != null) { + a.finish(); + setActivity(null); + } + + // Scrub out members - protects against memory leaks in the case where someone + // creates a non-static inner class (thus referencing the test case) and gives it to + // someone else to hold onto + scrubClass(ActivityInstrumentationTestCase2.class); + + super.tearDown(); + } + + /** + * Runs the current unit test. If the unit test is annotated with + * {@link android.test.UiThreadTest}, force the Activity to be created before switching to + * the UI thread. + */ + @Override + protected void runTest() throws Throwable { + try { + Method method = getClass().getMethod(getName(), (Class[]) null); + if (method.isAnnotationPresent(UiThreadTest.class)) { + getActivity(); + } + } catch (Exception e) { + // eat the exception here; super.runTest() will catch it again and handle it properly + } + super.runTest(); + } + +} diff --git a/test-runner/android/test/AndroidTestRunner.java b/test-runner/android/test/AndroidTestRunner.java index 353255e..79cedb0 100644 --- a/test-runner/android/test/AndroidTestRunner.java +++ b/test-runner/android/test/AndroidTestRunner.java @@ -35,6 +35,7 @@ public class AndroidTestRunner extends BaseTestRunner { private String mTestClassName; private List<TestCase> mTestCases; private Context mContext; + private boolean mSkipExecution = false; private List<TestListener> mTestListeners = Lists.newArrayList(); private Instrumentation mInstrumentation; @@ -124,8 +125,15 @@ public class AndroidTestRunner extends BaseTestRunner { } protected TestResult createTestResult() { + if (mSkipExecution) { + return new NoExecTestResult(); + } return new TestResult(); } + + void setSkipExecution(boolean skip) { + mSkipExecution = skip; + } public List<TestCase> getTestCases() { return mTestCases; diff --git a/test-runner/android/test/DatabaseTestUtils.java b/test-runner/android/test/DatabaseTestUtils.java new file mode 100644 index 0000000..23e0aba --- /dev/null +++ b/test-runner/android/test/DatabaseTestUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import com.google.android.collect.Sets; + +import android.database.sqlite.SQLiteDatabase; +import android.database.Cursor; + +import java.util.Set; + +/** + * A collection of utilities for writing unit tests for database code. + * @hide pending API council approval + */ +public class DatabaseTestUtils { + + /** + * Compares the schema of two databases and asserts that they are equal. + * @param expectedDb the db that is known to have the correct schema + * @param db the db whose schema should be checked + */ + public static void assertSchemaEquals(SQLiteDatabase expectedDb, SQLiteDatabase db) { + Set<String> expectedSchema = getSchemaSet(expectedDb); + Set<String> schema = getSchemaSet(db); + MoreAsserts.assertEquals(expectedSchema, schema); + } + + private static Set<String> getSchemaSet(SQLiteDatabase db) { + Set<String> schemaSet = Sets.newHashSet(); + + Cursor entityCursor = db.rawQuery("SELECT sql FROM sqlite_master", null); + try { + while (entityCursor.moveToNext()) { + String sql = entityCursor.getString(0); + schemaSet.add(sql); + } + } finally { + entityCursor.close(); + } + return schemaSet; + } +} diff --git a/test-runner/android/test/InstrumentationCoreTestRunner.java b/test-runner/android/test/InstrumentationCoreTestRunner.java new file mode 100644 index 0000000..6b1a4e4 --- /dev/null +++ b/test-runner/android/test/InstrumentationCoreTestRunner.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import java.io.File; + +import android.os.Bundle; + +/** + * This test runner extends the default InstrumentationTestRunner. It overrides + * the {@code onCreate(Bundle)} method and sets the system properties necessary + * for many core tests to run. This is needed because there are some core tests + * that need writing access to the filesystem. + * + * @hide + */ +public class InstrumentationCoreTestRunner extends InstrumentationTestRunner { + + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + + File cacheDir = getTargetContext().getCacheDir(); + + System.setProperty("user.language", "en"); + System.setProperty("user.region", "US"); + System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath()); + } +} diff --git a/test-runner/android/test/InstrumentationTestRunner.java b/test-runner/android/test/InstrumentationTestRunner.java index 930da12..8b8d472 100644 --- a/test-runner/android/test/InstrumentationTestRunner.java +++ b/test-runner/android/test/InstrumentationTestRunner.java @@ -16,6 +16,19 @@ package android.test; +import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE; +import android.app.Activity; +import android.app.Instrumentation; +import android.os.Bundle; +import android.os.Debug; +import android.os.Looper; +import android.test.suitebuilder.TestMethod; +import android.test.suitebuilder.TestPredicates; +import android.test.suitebuilder.TestSuiteBuilder; +import android.util.Log; + +import com.android.internal.util.Predicate; + import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestCase; @@ -25,18 +38,12 @@ import junit.framework.TestSuite; import junit.runner.BaseTestRunner; import junit.textui.ResultPrinter; -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Context; -import android.os.Bundle; -import android.os.Debug; -import android.os.Looper; -import android.test.suitebuilder.InstrumentationTestSuiteBuilder; -import android.test.suitebuilder.TestSuiteBuilder; -import android.test.suitebuilder.UnitTestSuiteBuilder; - import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + /** * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against @@ -69,10 +76,16 @@ import java.io.PrintStream; * <b>Running all tests:</b> adb shell am instrument -w * com.android.foo/android.test.InstrumentationTestRunner * <p/> - * <b>Running unit tests:</b> adb shell am instrument -w -e unit true + * <b>Running all small tests:</b> adb shell am instrument -w + * -e size small * com.android.foo/android.test.InstrumentationTestRunner * <p/> - * <b>Running instrumentation tests:</b> adb shell am instrument -w -e func true + * <b>Running all medium tests:</b> adb shell am instrument -w + * -e size medium + * com.android.foo/android.test.InstrumentationTestRunner + * <p/> + * <b>Running all large tests:</b> adb shell am instrument -w + * -e size large * com.android.foo/android.test.InstrumentationTestRunner * <p/> * <b>Running a single testcase:</b> adb shell am instrument -w @@ -83,8 +96,30 @@ import java.io.PrintStream; * -e class com.android.foo.FooTest#testFoo * com.android.foo/android.test.InstrumentationTestRunner * <p/> + * <b>Running multiple tests:</b> adb shell am instrument -w + * -e class com.android.foo.FooTest,com.android.foo.TooTest + * com.android.foo/android.test.InstrumentationTestRunner + * <p/> + * <b>Including performance tests:</b> adb shell am instrument -w + * -e perf true + * com.android.foo/android.test.InstrumentationTestRunner + * <p/> * <b>To debug your tests, set a break point in your code and pass:</b> * -e debug true + * <p/> + * <b>To run in 'log only' mode</b> + * -e log true + * This option will load and iterate through all test classes and methods, but will bypass actual + * test execution. Useful for quickly obtaining info on the tests to be executed by an + * instrumentation command. + * <p/> + * <b>To generate EMMA code coverage:</b> + * -e coverage true + * Note: this requires an emma instrumented build. By default, the code coverage results file + * will be saved as /sdcard/coverage.ec, unless overridden by coverageFile flag (see below) + * <p/> + * <b> To specify EMMA code coverage results file path:</b> + * -e coverageFile /sdcard/myFile.ec * <br/> * in addition to the other arguments. */ @@ -108,17 +143,36 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu /** @hide */ public static final String ARGUMENT_TEST_CLASS = "class"; /** @hide */ - public static final String ARGUMENT_UNIT_CLASS = "unit"; + public static final String ARGUMENT_TEST_PACKAGE = "package"; /** @hide */ - public static final String ARGUMENT_FUNC_CLASS = "func"; + public static final String ARGUMENT_TEST_SIZE_PREDICATE = "size"; /** @hide */ - public static final String ARGUMENT_TEST_PACKAGE = "package"; + public static final String ARGUMENT_INCLUDE_PERF = "perf"; + + private static final String SMALL_SUITE = "small"; + private static final String MEDIUM_SUITE = "medium"; + private static final String LARGE_SUITE = "large"; + + private static final String ARGUMENT_LOG_ONLY = "log"; + + + /** + * This constant defines the maximum allowed runtime (in ms) for a test included in the "small" suite. + * It is used to make an educated guess at what suite an unlabeled test belongs. + */ + private static final float SMALL_SUITE_MAX_RUNTIME = 100; + + /** + * This constant defines the maximum allowed runtime (in ms) for a test included in the "medium" suite. + * It is used to make an educated guess at what suite an unlabeled test belongs. + */ + private static final float MEDIUM_SUITE_MAX_RUNTIME = 1000; /** * The following keys are used in the status bundle to provide structured reports to * an IInstrumentationWatcher. */ - + /** * This value, if stored with key {@link android.app.Instrumentation#REPORT_KEY_IDENTIFIER}, * identifies InstrumentationTestRunner as the source of the report. This is sent with all @@ -150,6 +204,16 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu */ public static final String REPORT_KEY_NAME_TEST = "test"; /** + * If included in the status or final bundle sent to an IInstrumentationWatcher, this key + * reports the run time in seconds of the current test. + */ + private static final String REPORT_KEY_RUN_TIME = "runtime"; + /** + * If included in the status or final bundle sent to an IInstrumentationWatcher, this key + * reports the guessed suite assignment for the current test. + */ + private static final String REPORT_KEY_SUITE_ASSIGNMENT = "suiteassignment"; + /** * The test is starting. */ public static final int REPORT_VALUE_RESULT_START = 1; @@ -172,14 +236,19 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu */ public static final String REPORT_KEY_STACK = "stack"; + private static final String DEFAULT_COVERAGE_FILE_PATH = "/sdcard/coverage.ec"; + + private static final String LOG_TAG = "InstrumentationTestRunner"; + private final Bundle mResults = new Bundle(); private AndroidTestRunner mTestRunner; private boolean mDebug; private boolean mJustCount; + private boolean mSuiteAssignmentMode; private int mTestCount; - private boolean mBundleOutput; - private boolean mDatabaseOutput; private String mPackageOfTests; + private boolean mCoverage; + private String mCoverageFilePath; @Override public void onCreate(Bundle arguments) { @@ -190,90 +259,95 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu {getTargetContext().getPackageCodePath(), getContext().getPackageCodePath()}; ClassPathPackageInfoSource.setApkPaths(apkPaths); - boolean useUnitTestSuite = false; - boolean useFunctionalTestSuite = false; - - String testClassName = null; - Test test = null; + Predicate<TestMethod> testSizePredicate = null; + boolean includePerformance = false; + String testClassesArg = null; + boolean logOnly = false; if (arguments != null) { // Test class name passed as an argument should override any meta-data declaration. - testClassName = arguments.getString(ARGUMENT_TEST_CLASS); + testClassesArg = arguments.getString(ARGUMENT_TEST_CLASS); mDebug = getBooleanArgument(arguments, "debug"); mJustCount = getBooleanArgument(arguments, "count"); - mBundleOutput = getBooleanArgument(arguments, "bundle"); - mDatabaseOutput = getBooleanArgument(arguments, "database"); - useUnitTestSuite = getBooleanArgument(arguments, ARGUMENT_UNIT_CLASS); - useFunctionalTestSuite = getBooleanArgument(arguments, ARGUMENT_FUNC_CLASS); + mSuiteAssignmentMode = getBooleanArgument(arguments, "suiteAssignment"); mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE); + testSizePredicate = getSizePredicateFromArg( + arguments.getString(ARGUMENT_TEST_SIZE_PREDICATE)); + includePerformance = getBooleanArgument(arguments, ARGUMENT_INCLUDE_PERF); + logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY); + mCoverage = getBooleanArgument(arguments, "coverage"); + mCoverageFilePath = arguments.getString("coverageFile"); + } + + TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(), + getTargetContext().getClassLoader()); + + if (testSizePredicate != null) { + testSuiteBuilder.addRequirements(testSizePredicate); + } + if (!includePerformance) { + testSuiteBuilder.addRequirements(REJECT_PERFORMANCE); } - if (testClassName == null) { + if (testClassesArg == null) { + TestSuite testSuite = null; if (mPackageOfTests != null) { - test = createPackageTestSuite( - getTargetContext(), useUnitTestSuite, - useFunctionalTestSuite, mPackageOfTests); + testSuiteBuilder.includePackages(mPackageOfTests); } else { - test = getTestSuite(); + testSuite = getTestSuite(); + testSuiteBuilder.addTestSuite(testSuite); } - if (test == null) { - test = createDefaultTestSuite(getTargetContext(), useUnitTestSuite, - useFunctionalTestSuite); + if (testSuite == null) { + testSuiteBuilder.includePackages(getTargetContext().getPackageName()); } - mTestCount = test.countTestCases(); + } else { + parseTestClasses(testClassesArg, testSuiteBuilder); } mTestRunner = getAndroidTestRunner(); mTestRunner.setContext(getTargetContext()); mTestRunner.setInstrumentaiton(this); - mTestRunner.addTestListener(new TestPrinter("TestRunner", false)); - if (mDatabaseOutput) { - mTestRunner.addTestListener(new TestRecorder()); - } - - if (testClassName != null) { - int methodSeparatorIndex = testClassName.indexOf('#'); - String testMethodName = null; - - if (methodSeparatorIndex > 0) { - testMethodName = testClassName.substring(methodSeparatorIndex + 1); - testClassName = testClassName.substring(0, methodSeparatorIndex); - } - mTestRunner.setTestClassName(testClassName, testMethodName); - mTestCount = mTestRunner.getTestCases().size(); + mTestRunner.setSkipExecution(logOnly); + mTestRunner.setTest(testSuiteBuilder.build()); + mTestCount = mTestRunner.getTestCases().size(); + if (mSuiteAssignmentMode) { + mTestRunner.addTestListener(new SuiteAssignmentPrinter()); } else { - mTestRunner.setTest(test); - } - // add this now that we know the count - if (!mBundleOutput) { + mTestRunner.addTestListener(new TestPrinter("TestRunner", false)); mTestRunner.addTestListener(new WatcherResultPrinter(mTestCount)); } - start(); } - private TestSuite createDefaultTestSuite(Context context, boolean useUnitTestSuite, - boolean useFunctionalTestSuite) { - return createPackageTestSuite(context, useUnitTestSuite, useFunctionalTestSuite, context.getPackageName()); + /** + * Parses and loads the specified set of test classes + * @param testClassArg - comma-separated list of test classes and methods + * @param testSuiteBuilder - builder to add tests to + */ + private void parseTestClasses(String testClassArg, TestSuiteBuilder testSuiteBuilder) { + String[] testClasses = testClassArg.split(","); + for (String testClass : testClasses) { + parseTestClass(testClass, testSuiteBuilder); + } } - private TestSuite createPackageTestSuite(Context context, boolean useUnitTestSuite, - boolean useFunctionalTestSuite, String packageName) { - TestSuiteBuilder testSuiteBuilder = null; + /** + * Parse and load the given test class and, optionally, method + * @param testClassName - full package name of test class and optionally method to add. Expected + * format: com.android.TestClass#testMethod + * @param testSuiteBuilder - builder to add tests to + */ + private void parseTestClass(String testClassName, TestSuiteBuilder testSuiteBuilder) { + int methodSeparatorIndex = testClassName.indexOf('#'); + String testMethodName = null; - if (useUnitTestSuite) { - testSuiteBuilder = new UnitTestSuiteBuilder(getClass().getName(), - context.getClassLoader()); - } else if (useFunctionalTestSuite) { - testSuiteBuilder = new InstrumentationTestSuiteBuilder(getClass().getName(), - context.getClassLoader()); - } else { - testSuiteBuilder = new TestSuiteBuilder(getClass().getName(), - context.getClassLoader()); + if (methodSeparatorIndex > 0) { + testMethodName = testClassName.substring(methodSeparatorIndex + 1); + testClassName = testClassName.substring(0, methodSeparatorIndex); } - - return testSuiteBuilder.includePackages(packageName).build(); + testSuiteBuilder.addTestClassByName(testClassName, testMethodName, + getTargetContext()); } protected AndroidTestRunner getAndroidTestRunner() { @@ -284,7 +358,23 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu String tagString = arguments.getString(tag); return tagString != null && Boolean.parseBoolean(tagString); } - + + /* + * Returns the size predicate object, corresponding to the "size" argument value. + */ + private Predicate<TestMethod> getSizePredicateFromArg(String sizeArg) { + + if (SMALL_SUITE.equals(sizeArg)) { + return TestPredicates.SELECT_SMALL; + } else if (MEDIUM_SUITE.equals(sizeArg)) { + return TestPredicates.SELECT_MEDIUM; + } else if (LARGE_SUITE.equals(sizeArg)) { + return TestPredicates.SELECT_LARGE; + } else { + return null; + } + } + @Override public void onStart() { Looper.prepare(); @@ -303,25 +393,24 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu try { StringResultPrinter resultPrinter = new StringResultPrinter(writer); - if (mBundleOutput) { - mTestRunner.addTestListener(new BundleTestListener(mResults)); - } else { - mTestRunner.addTestListener(resultPrinter); - } - + mTestRunner.addTestListener(resultPrinter); + long startTime = System.currentTimeMillis(); mTestRunner.runTest(); long runTime = System.currentTimeMillis() - startTime; resultPrinter.print(mTestRunner.getTestResult(), runTime); } finally { - if (!mBundleOutput) { - mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, - String.format("\nTest results for %s=%s", - mTestRunner.getTestClassName(), - byteArrayOutputStream.toString())); + mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("\nTest results for %s=%s", + mTestRunner.getTestClassName(), + byteArrayOutputStream.toString())); + + if (mCoverage) { + generateCoverageReport(); } writer.close(); + finish(Activity.RESULT_OK, mResults); } } @@ -344,6 +433,51 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu public ClassLoader getLoader() { return null; } + + private void generateCoverageReport() { + // use reflection to call emma dump coverage method, to avoid + // always statically compiling against emma jar + java.io.File coverageFile = new java.io.File(getCoverageFilePath()); + try { + Class emmaRTClass = Class.forName("com.vladium.emma.rt.RT"); + Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData", + coverageFile.getClass(), boolean.class, boolean.class); + + dumpCoverageMethod.invoke(null, coverageFile, false, false); + + } catch (ClassNotFoundException e) { + reportEmmaError("Is emma jar on classpath?", e); + } catch (SecurityException e) { + reportEmmaError(e); + } catch (NoSuchMethodException e) { + reportEmmaError(e); + } catch (IllegalArgumentException e) { + reportEmmaError(e); + } catch (IllegalAccessException e) { + reportEmmaError(e); + } catch (InvocationTargetException e) { + reportEmmaError(e); + } + } + + private String getCoverageFilePath() { + if (mCoverageFilePath == null) { + return DEFAULT_COVERAGE_FILE_PATH; + } + else { + return mCoverageFilePath; + } + } + + private void reportEmmaError(Exception e) { + reportEmmaError("", e); + } + + private void reportEmmaError(String hint, Exception e) { + String msg = "Failed to generate emma coverage. " + hint; + Log.e(LOG_TAG, msg, e); + mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: " + msg); + } // TODO kill this, use status() and prettyprint model for better output private class StringResultPrinter extends ResultPrinter { @@ -354,15 +488,85 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu synchronized void print(TestResult result, long runTime) { printHeader(runTime); - if (mBundleOutput) { - printErrors(result); - printFailures(result); - } printFooter(result); } } /** + * This class sends status reports back to the IInstrumentationWatcher about + * which suite each test belongs. + */ + private class SuiteAssignmentPrinter implements TestListener + { + + private Bundle mTestResult; + private long mStartTime; + private long mEndTime; + private boolean mTimingValid; + + public SuiteAssignmentPrinter() { + } + + /** + * send a status for the start of a each test, so long tests can be seen as "running" + */ + public void startTest(Test test) { + mTimingValid = true; + mStartTime = System.currentTimeMillis(); + } + + /** + * @see junit.framework.TestListener#addError(Test, Throwable) + */ + public void addError(Test test, Throwable t) { + mTimingValid = false; + } + + /** + * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError) + */ + public void addFailure(Test test, AssertionFailedError t) { + mTimingValid = false; + } + + /** + * @see junit.framework.TestListener#endTest(Test) + */ + public void endTest(Test test) { + float runTime; + String assignmentSuite; + mEndTime = System.currentTimeMillis(); + mTestResult = new Bundle(); + + if (!mTimingValid || mStartTime < 0) { + assignmentSuite = "NA"; + runTime = -1; + } else { + runTime = mEndTime - mStartTime; + if (runTime < SMALL_SUITE_MAX_RUNTIME + && !InstrumentationTestCase.class.isAssignableFrom(test.getClass())) { + assignmentSuite = SMALL_SUITE; + } else if (runTime < MEDIUM_SUITE_MAX_RUNTIME) { + assignmentSuite = MEDIUM_SUITE; + } else { + assignmentSuite = LARGE_SUITE; + } + } + // Clear mStartTime so that we can verify that it gets set next time. + mStartTime = -1; + + mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + test.getClass().getName() + "#" + ((TestCase) test).getName() + + "\nin " + assignmentSuite + " suite\nrunTime: " + + String.valueOf(runTime) + "\n"); + mTestResult.putFloat(REPORT_KEY_RUN_TIME, runTime); + mTestResult.putString(REPORT_KEY_SUITE_ASSIGNMENT, assignmentSuite); + + sendStatus(0, mTestResult); + } + } + + /** * This class sends status reports back to the IInstrumentationWatcher */ private class WatcherResultPrinter implements TestListener @@ -437,6 +641,5 @@ public class InstrumentationTestRunner extends Instrumentation implements TestSu // TODO report the end of the cycle // TODO report runtime for each test } - - } + diff --git a/test-runner/android/test/NoExecTestResult.java b/test-runner/android/test/NoExecTestResult.java new file mode 100755 index 0000000..1ee62c1 --- /dev/null +++ b/test-runner/android/test/NoExecTestResult.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.test; + +import junit.framework.TestCase; +import junit.framework.TestResult; + +/** + * A benign test result that does no actually test execution, just runs + * through the motions + * + * {@hide} Not needed for SDK. + */ +class NoExecTestResult extends TestResult { + + /** + * Override parent to just inform listeners of test, + * and skip test execution. + */ + @Override + protected void run(final TestCase test) { + startTest(test); + endTest(test); + } + +} diff --git a/test-runner/android/test/ProviderTestCase.java b/test-runner/android/test/ProviderTestCase.java index 5882387..445b4eb 100644 --- a/test-runner/android/test/ProviderTestCase.java +++ b/test-runner/android/test/ProviderTestCase.java @@ -11,9 +11,12 @@ import android.database.DatabaseUtils; * If you would like to test a single content provider with an * {@link InstrumentationTestCase}, this provides some of the boiler plate in {@link #setUp} and * {@link #tearDown}. + * + * @deprecated this class extends InstrumentationTestCase but should extend AndroidTestCase. Use + * ProviderTestCase2, which corrects this problem, instead. */ public abstract class ProviderTestCase<T extends ContentProvider> - extends InstrumentationTestCase { + extends InstrumentationTestCase { Class<T> mProviderClass; String mProviderAuthority; @@ -66,18 +69,17 @@ public abstract class ProviderTestCase<T extends ContentProvider> String databaseName, int databaseVersion, String sql) throws IllegalAccessException, InstantiationException { final String filenamePrefix = "test."; - MockContentResolver mockContentResolver = new MockContentResolver(); + MockContentResolver resolver = new MockContentResolver(); RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( new MockContext(), // The context that most methods are delegated to targetContext, // The context that file methods are delegated to filenamePrefix); Context context = new IsolatedContext( - mockContentResolver, targetContextWrapper); + resolver, targetContextWrapper); DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql); T provider = providerClass.newInstance(); provider.attachInfo(context, null); - MockContentResolver resolver = new MockContentResolver(); resolver.addProvider(authority, provider); return resolver; diff --git a/test-runner/android/test/ProviderTestCase2.java b/test-runner/android/test/ProviderTestCase2.java new file mode 100644 index 0000000..714b77b --- /dev/null +++ b/test-runner/android/test/ProviderTestCase2.java @@ -0,0 +1,82 @@ +package android.test; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.test.mock.MockContext; +import android.test.mock.MockContentResolver; +import android.database.DatabaseUtils; + +/** + * If you would like to test a single content provider with an + * {@link android.test.InstrumentationTestCase}, this provides some of the boiler plate in + * {@link #setUp} and {@link #tearDown}. + * @hide pending API council approval + */ +public abstract class ProviderTestCase2<T extends ContentProvider> extends AndroidTestCase { + + Class<T> mProviderClass; + String mProviderAuthority; + + private IsolatedContext mProviderContext; + private MockContentResolver mResolver; + + public ProviderTestCase2(Class<T> providerClass, String providerAuthority) { + mProviderClass = providerClass; + mProviderAuthority = providerAuthority; + } + + /** + * The content provider that will be set up for use in each test method. + */ + private T mProvider; + + public T getProvider() { + return mProvider; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mResolver = new MockContentResolver(); + final String filenamePrefix = "test."; + RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( + new MockContext(), // The context that most methods are delegated to + getContext(), // The context that file methods are delegated to + filenamePrefix); + mProviderContext = new IsolatedContext(mResolver, targetContextWrapper); + + mProvider = mProviderClass.newInstance(); + mProvider.attachInfo(mProviderContext, null); + assertNotNull(mProvider); + mResolver.addProvider(mProviderAuthority, getProvider()); + } + + public MockContentResolver getMockContentResolver() { + return mResolver; + } + + public IsolatedContext getMockContext() { + return mProviderContext; + } + + public static <T extends ContentProvider> ContentResolver newResolverWithContentProviderFromSql( + Context targetContext, String filenamePrefix, Class<T> providerClass, String authority, + String databaseName, int databaseVersion, String sql) + throws IllegalAccessException, InstantiationException { + MockContentResolver resolver = new MockContentResolver(); + RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( + new MockContext(), // The context that most methods are delegated to + targetContext, // The context that file methods are delegated to + filenamePrefix); + Context context = new IsolatedContext(resolver, targetContextWrapper); + DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql); + + T provider = providerClass.newInstance(); + provider.attachInfo(context, null); + resolver.addProvider(authority, provider); + + return resolver; + } +}
\ No newline at end of file diff --git a/test-runner/android/test/mock/MockContentResolver.java b/test-runner/android/test/mock/MockContentResolver.java index 66840a1..3a1dc36c 100644 --- a/test-runner/android/test/mock/MockContentResolver.java +++ b/test-runner/android/test/mock/MockContentResolver.java @@ -50,7 +50,12 @@ public class MockContentResolver extends ContentResolver { /** @hide */ @Override protected IContentProvider acquireProvider(Context context, String name) { - return mProviders.get(name).getIContentProvider(); + final ContentProvider provider = mProviders.get(name); + if (provider != null) { + return provider.getIContentProvider(); + } else { + return null; + } } /** @hide */ diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java index 4f7745b..34ebd78 100644 --- a/test-runner/android/test/mock/MockPackageManager.java +++ b/test-runner/android/test/mock/MockPackageManager.java @@ -140,6 +140,14 @@ public class MockPackageManager extends PackageManager { public String getNameForUid(int uid) { throw new UnsupportedOperationException(); } + + /** + * @hide - to match hiding in superclass + */ + @Override + public int getUidForSharedUser(String sharedUserName) { + throw new UnsupportedOperationException(); + } @Override public List<ApplicationInfo> getInstalledApplications(int flags) { diff --git a/test-runner/android/test/suitebuilder/TestMethod.java b/test-runner/android/test/suitebuilder/TestMethod.java index 3a936c2..08568d5 100644 --- a/test-runner/android/test/suitebuilder/TestMethod.java +++ b/test-runner/android/test/suitebuilder/TestMethod.java @@ -26,8 +26,6 @@ import java.lang.reflect.Method; /** * Represents a test to be run. Can be constructed without instantiating the TestCase or even * loading the class. - * - * {@hide} Not needed for 1.0 SDK. */ public class TestMethod { @@ -36,9 +34,17 @@ public class TestMethod { private final Class<? extends TestCase> enclosingClass; public TestMethod(Method method, Class<? extends TestCase> enclosingClass) { + this(method.getName(), enclosingClass); + } + + public TestMethod(String methodName, Class<? extends TestCase> enclosingClass) { this.enclosingClass = enclosingClass; this.enclosingClassname = enclosingClass.getName(); - this.testMethodName = method.getName(); + this.testMethodName = methodName; + } + + public TestMethod(TestCase testCase) { + this(testCase.getName(), testCase.getClass()); } public String getName() { @@ -53,7 +59,7 @@ public class TestMethod { try { return getEnclosingClass().getMethod(getName()).getAnnotation(annotationClass); } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + return null; } } diff --git a/test-runner/android/test/suitebuilder/TestPredicates.java b/test-runner/android/test/suitebuilder/TestPredicates.java index ff75217..d814e0b 100644 --- a/test-runner/android/test/suitebuilder/TestPredicates.java +++ b/test-runner/android/test/suitebuilder/TestPredicates.java @@ -20,6 +20,9 @@ import android.test.InstrumentationTestCase; import android.test.PerformanceTestBase; import android.test.suitebuilder.annotation.HasAnnotation; import android.test.suitebuilder.annotation.Suppress; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Smoke; import com.android.internal.util.Predicate; import com.android.internal.util.Predicates; @@ -35,6 +38,9 @@ public class TestPredicates { Predicates.not(SELECT_INSTRUMENTATION); public static final Predicate<TestMethod> SELECT_SMOKE = new HasAnnotation(Smoke.class); + public static final Predicate<TestMethod> SELECT_SMALL = new HasAnnotation(SmallTest.class); + public static final Predicate<TestMethod> SELECT_MEDIUM = new HasAnnotation(MediumTest.class); + public static final Predicate<TestMethod> SELECT_LARGE = new HasAnnotation(LargeTest.class); public static final Predicate<TestMethod> REJECT_SUPPRESSED = Predicates.not(new HasAnnotation(Suppress.class)); public static final Predicate<TestMethod> REJECT_PERFORMANCE = diff --git a/test-runner/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java index 99d0eb9..428905e 100644 --- a/test-runner/android/test/suitebuilder/TestSuiteBuilder.java +++ b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java @@ -16,15 +16,22 @@ package android.test.suitebuilder; +import android.content.Context; +import android.test.AndroidTestRunner; +import android.test.TestCaseUtil; import android.util.Log; import com.android.internal.util.Predicate; +import com.google.android.collect.Lists; import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME; import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED; import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE; +import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import java.lang.reflect.InvocationTargetException; +import java.util.Enumeration; import java.util.List; import java.util.Set; import java.util.HashSet; @@ -37,8 +44,10 @@ import java.util.Collections; */ public class TestSuiteBuilder { + private Context context; private final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME); private final Set<Predicate<TestMethod>> predicates = new HashSet<Predicate<TestMethod>>(); + private List<TestCase> testCases; private TestSuite rootSuite; private TestSuite suiteForCurrentClass; private String currentClassname; @@ -59,8 +68,28 @@ public class TestSuiteBuilder { public TestSuiteBuilder(String name, ClassLoader classLoader) { this.suiteName = name; this.testGrouping.setClassLoader(classLoader); + this.testCases = Lists.newArrayList(); addRequirements(REJECT_SUPPRESSED); - addRequirements(REJECT_PERFORMANCE); + } + + /** @hide pending API Council approval */ + public TestSuiteBuilder addTestClassByName(String testClassName, String testMethodName, + Context context) { + + AndroidTestRunner atr = new AndroidTestRunner(); + atr.setContext(context); + atr.setTestClassName(testClassName, testMethodName); + + this.testCases.addAll(atr.getTestCases()); + return this; + } + + /** @hide pending API Council approval */ + public TestSuiteBuilder addTestSuite(TestSuite testSuite) { + for (TestCase testCase : (List<TestCase>) TestCaseUtil.getTests(testSuite, true)) { + this.testCases.add(testCase); + } + return this; } /** @@ -156,6 +185,13 @@ public class TestSuiteBuilder { addTest(test); } } + if (testCases.size() > 0) { + for (TestCase testCase : testCases) { + if (satisfiesAllPredicates(new TestMethod(testCase))) { + addTest(testCase); + } + } + } } catch (Exception exception) { Log.i("TestSuiteBuilder", "Failed to create test.", exception); TestSuite suite = new TestSuite(getSuiteName()); @@ -223,12 +259,16 @@ public class TestSuiteBuilder { } private void addTest(TestMethod testMethod) throws Exception { - addSuiteIfNecessary(testMethod); + addSuiteIfNecessary(testMethod.getEnclosingClassname()); suiteForCurrentClass.addTest(testMethod.createTest()); } + + private void addTest(Test test) { + addSuiteIfNecessary(test.getClass().getName()); + suiteForCurrentClass.addTest(test); + } - private void addSuiteIfNecessary(TestMethod test) { - String parentClassname = test.getEnclosingClassname(); + private void addSuiteIfNecessary(String parentClassname) { if (!parentClassname.equals(currentClassname)) { currentClassname = parentClassname; suiteForCurrentClass = new TestSuite(parentClassname); |