summaryrefslogtreecommitdiffstats
path: root/test-runner
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
commit9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch)
treed88beb88001f2482911e3d28e43833b50e4b4e97 /test-runner
parentd83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff)
downloadframeworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip
frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz
frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'test-runner')
-rw-r--r--test-runner/Android.mk27
-rw-r--r--test-runner/android/test/ActivityInstrumentationTestCase.java91
-rw-r--r--test-runner/android/test/ActivityInstrumentationTestCase2.java174
-rw-r--r--test-runner/android/test/ActivityTestCase.java82
-rw-r--r--test-runner/android/test/ActivityUnitTestCase.java340
-rw-r--r--test-runner/android/test/AndroidTestRunner.java206
-rw-r--r--test-runner/android/test/ApplicationTestCase.java177
-rw-r--r--test-runner/android/test/AssertionFailedError.java36
-rw-r--r--test-runner/android/test/BundlePrinter.java63
-rw-r--r--test-runner/android/test/BundleTestListener.java63
-rw-r--r--test-runner/android/test/ClassPathPackageInfo.java79
-rw-r--r--test-runner/android/test/ClassPathPackageInfoSource.java320
-rw-r--r--test-runner/android/test/ComparisonFailure.java35
-rw-r--r--test-runner/android/test/DatabaseTestUtils.java57
-rw-r--r--test-runner/android/test/InstrumentationCoreTestRunner.java43
-rw-r--r--test-runner/android/test/InstrumentationTestRunner.java672
-rw-r--r--test-runner/android/test/InstrumentationUtils.java50
-rw-r--r--test-runner/android/test/IsolatedContext.java85
-rw-r--r--test-runner/android/test/LaunchPerformanceBase.java59
-rw-r--r--test-runner/android/test/MoreAsserts.java552
-rwxr-xr-xtest-runner/android/test/NoExecTestResult.java39
-rw-r--r--test-runner/android/test/PackageInfoSources.java37
-rw-r--r--test-runner/android/test/PerformanceTestBase.java42
-rw-r--r--test-runner/android/test/ProviderTestCase.java87
-rw-r--r--test-runner/android/test/ProviderTestCase2.java82
-rw-r--r--test-runner/android/test/RenamingDelegatingContext.java198
-rw-r--r--test-runner/android/test/ServiceLocator.java35
-rw-r--r--test-runner/android/test/ServiceTestCase.java280
-rw-r--r--test-runner/android/test/SimpleCache.java35
-rw-r--r--test-runner/android/test/SingleLaunchActivityTestCase.java83
-rw-r--r--test-runner/android/test/SyncBaseInstrumentation.java121
-rw-r--r--test-runner/android/test/TestBrowserActivity.java127
-rw-r--r--test-runner/android/test/TestBrowserController.java38
-rw-r--r--test-runner/android/test/TestBrowserControllerImpl.java110
-rw-r--r--test-runner/android/test/TestBrowserView.java27
-rw-r--r--test-runner/android/test/TestCase.java48
-rw-r--r--test-runner/android/test/TestCaseUtil.java171
-rw-r--r--test-runner/android/test/TestListActivity.java253
-rw-r--r--test-runner/android/test/TestLocationProvider.java139
-rw-r--r--test-runner/android/test/TestPrinter.java112
-rw-r--r--test-runner/android/test/TestRecorder.java186
-rw-r--r--test-runner/android/test/TestRunner.java723
-rw-r--r--test-runner/android/test/TestRunnerView.java41
-rw-r--r--test-runner/android/test/TestSuiteProvider.java27
-rw-r--r--test-runner/android/test/TouchUtils.java793
-rw-r--r--test-runner/android/test/ViewAsserts.java373
-rw-r--r--test-runner/android/test/mock/MockApplication.java46
-rw-r--r--test-runner/android/test/mock/MockContentProvider.java107
-rw-r--r--test-runner/android/test/mock/MockContentResolver.java70
-rw-r--r--test-runner/android/test/mock/MockContext.java389
-rw-r--r--test-runner/android/test/mock/MockDialogInterface.java20
-rw-r--r--test-runner/android/test/mock/MockPackageManager.java413
-rw-r--r--test-runner/android/test/mock/MockResources.java222
-rw-r--r--test-runner/android/test/mock/package.html5
-rw-r--r--test-runner/android/test/suitebuilder/AssignableFrom.java32
-rw-r--r--test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java35
-rw-r--r--test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java35
-rw-r--r--test-runner/android/test/suitebuilder/TestGrouping.java249
-rw-r--r--test-runner/android/test/suitebuilder/TestMethod.java146
-rw-r--r--test-runner/android/test/suitebuilder/TestPredicates.java49
-rw-r--r--test-runner/android/test/suitebuilder/TestSuiteBuilder.java282
-rw-r--r--test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java36
-rw-r--r--test-runner/android/test/suitebuilder/annotation/HasAnnotation.java44
-rw-r--r--test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java41
-rw-r--r--test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java41
-rw-r--r--test-runner/android/test/suitebuilder/annotation/package.html5
-rw-r--r--test-runner/android/test/suitebuilder/package.html5
-rw-r--r--test-runner/junit/MODULE_LICENSE_CPL1
-rw-r--r--test-runner/junit/runner/BaseTestRunner.java326
-rw-r--r--test-runner/junit/runner/ClassPathTestCollector.java81
-rw-r--r--test-runner/junit/runner/FailureDetailView.java28
-rw-r--r--test-runner/junit/runner/LoadingTestCollector.java70
-rw-r--r--test-runner/junit/runner/ReloadingTestSuiteLoader.java20
-rw-r--r--test-runner/junit/runner/SimpleTestCollector.java21
-rw-r--r--test-runner/junit/runner/Sorter.java39
-rw-r--r--test-runner/junit/runner/StandardTestSuiteLoader.java20
-rw-r--r--test-runner/junit/runner/TestCaseClassLoader.java225
-rw-r--r--test-runner/junit/runner/TestCollector.java17
-rw-r--r--test-runner/junit/runner/TestRunListener.java20
-rw-r--r--test-runner/junit/runner/TestSuiteLoader.java9
-rw-r--r--test-runner/junit/runner/Version.java14
-rw-r--r--test-runner/junit/runner/excluded.properties12
-rw-r--r--test-runner/junit/runner/logo.gifbin0 -> 964 bytes
-rw-r--r--test-runner/junit/runner/package.html5
-rw-r--r--test-runner/junit/runner/smalllogo.gifbin0 -> 883 bytes
-rw-r--r--test-runner/junit/textui/ResultPrinter.java142
-rw-r--r--test-runner/junit/textui/TestRunner.java189
-rw-r--r--test-runner/junit/textui/package.html6
88 files changed, 10935 insertions, 0 deletions
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
new file mode 100644
index 0000000..42167c7
--- /dev/null
+++ b/test-runner/Android.mk
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAVA_LIBRARIES := core framework
+
+LOCAL_MODULE:= android.test.runner
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/test-runner/android/test/ActivityInstrumentationTestCase.java b/test-runner/android/test/ActivityInstrumentationTestCase.java
new file mode 100644
index 0000000..e5a9991
--- /dev/null
+++ b/test-runner/android/test/ActivityInstrumentationTestCase.java
@@ -0,0 +1,91 @@
+/*
+ * 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 java.lang.reflect.Field;
+
+/**
+ * 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. Most of the work is handled
+ * 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;
+ Class<T> mActivityClass;
+ boolean mInitialTouchMode = false;
+
+ /**
+ * @param pkg The package of the instrumentation.
+ * @param activityClass The activity to test.
+ */
+ public ActivityInstrumentationTestCase(String pkg, Class<T> activityClass) {
+ this(pkg, activityClass, false);
+ }
+
+ /**
+ * @param pkg The package of the instrumentation.
+ * @param activityClass The activity to test.
+ * @param initialTouchMode true = in touch mode
+ */
+ public ActivityInstrumentationTestCase(String pkg, Class<T> activityClass,
+ boolean initialTouchMode) {
+ mPackage = pkg;
+ mActivityClass = activityClass;
+ mInitialTouchMode = initialTouchMode;
+ }
+
+ @Override
+ public T getActivity() {
+ return (T) super.getActivity();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // set initial touch mode
+ getInstrumentation().setInTouchMode(mInitialTouchMode);
+ setActivity(launchActivity(mPackage, mActivityClass, null));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getActivity().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(ActivityInstrumentationTestCase.class);
+
+ super.tearDown();
+ }
+
+ public void testActivityTestCaseSetUpProperly() throws Exception {
+ assertNotNull("activity should be launched successfully", getActivity());
+ }
+}
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/ActivityTestCase.java b/test-runner/android/test/ActivityTestCase.java
new file mode 100644
index 0000000..18bfccc
--- /dev/null
+++ b/test-runner/android/test/ActivityTestCase.java
@@ -0,0 +1,82 @@
+/*
+ * 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 java.lang.reflect.Field;
+
+/**
+ * This is common code used to support Activity test cases. For more useful classes, please see
+ * {@link android.test.ActivityUnitTestCase} and
+ * {@link android.test.ActivityInstrumentationTestCase}.
+ */
+public abstract class ActivityTestCase extends InstrumentationTestCase {
+
+ /**
+ * The activity that will be set up for use in each test method.
+ */
+ private Activity mActivity;
+
+ /**
+ * @return Returns the activity under test.
+ */
+ protected Activity getActivity() {
+ return mActivity;
+ }
+
+ /**
+ * Set the activity under test.
+ * @param testActivity The activity under test
+ */
+ protected void setActivity(Activity testActivity) {
+ mActivity = testActivity;
+ }
+
+ /**
+ * This function is called by various TestCase implementations, at tearDown() time, in order
+ * to scrub out any class variables. This protects against memory leaks in the case where a
+ * test case creates a non-static inner class (thus referencing the test case) and gives it to
+ * someone else to hold onto.
+ *
+ * @param testCaseClass The class of the derived TestCase implementation.
+ *
+ * @throws IllegalAccessException
+ */
+ protected void scrubClass(final Class<?> testCaseClass)
+ throws IllegalAccessException {
+ final Field[] fields = getClass().getDeclaredFields();
+ for (Field field : fields) {
+ final Class<?> fieldClass = field.getDeclaringClass();
+ if (testCaseClass.isAssignableFrom(fieldClass) && !field.getType().isPrimitive()) {
+ try {
+ field.setAccessible(true);
+ field.set(this, null);
+ } catch (Exception e) {
+ android.util.Log.d("TestCase", "Error: Could not nullify field!");
+ }
+
+ if (field.get(this) != null) {
+ android.util.Log.d("TestCase", "Error: Could not nullify field!");
+ }
+ }
+ }
+ }
+
+
+
+}
diff --git a/test-runner/android/test/ActivityUnitTestCase.java b/test-runner/android/test/ActivityUnitTestCase.java
new file mode 100644
index 0000000..dfd8fc2
--- /dev/null
+++ b/test-runner/android/test/ActivityUnitTestCase.java
@@ -0,0 +1,340 @@
+/*
+ * 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.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.test.mock.MockApplication;
+import android.view.Window;
+
+/**
+ * This class provides isolated testing of a single activity. The activity under test will
+ * be created with minimal connection to the system infrastructure, and you can inject mocked or
+ * wrappered versions of many of Activity's dependencies. Most of the work is handled
+ * automatically here by {@link #setUp} and {@link #tearDown}.
+ *
+ * <p>If you prefer a functional test, see {@link android.test.ActivityInstrumentationTestCase}.
+ *
+ * <p>It must be noted that, as a true unit test, your Activity will not be running in the
+ * normal system and will not participate in the normal interactions with other Activities.
+ * The following methods should not be called in this configuration - most of them will throw
+ * exceptions:
+ * <ul>
+ * <li>{@link android.app.Activity#createPendingResult(int, Intent, int)}</li>
+ * <li>{@link android.app.Activity#startActivityIfNeeded(Intent, int)}</li>
+ * <li>{@link android.app.Activity#startActivityFromChild(Activity, Intent, int)}</li>
+ * <li>{@link android.app.Activity#startNextMatchingActivity(Intent)}</li>
+ * <li>{@link android.app.Activity#getCallingActivity()}</li>
+ * <li>{@link android.app.Activity#getCallingPackage()}</li>
+ * <li>{@link android.app.Activity#createPendingResult(int, Intent, int)}</li>
+ * <li>{@link android.app.Activity#getTaskId()}</li>
+ * <li>{@link android.app.Activity#isTaskRoot()}</li>
+ * <li>{@link android.app.Activity#moveTaskToBack(boolean)}</li>
+ * <li>{@link android.app.Activity#setPersistent(boolean)}</li>
+ * </ul>
+ *
+ * <p>The following methods may be called but will not do anything. For test purposes, you can use
+ * the methods {@link #getStartedActivityIntent()} and {@link #getStartedActivityRequest()} to
+ * inspect the parameters that they were called with.
+ * <ul>
+ * <li>{@link android.app.Activity#startActivity(Intent)}</li>
+ * <li>{@link android.app.Activity#startActivityForResult(Intent, int)}</li>
+ * </ul>
+ *
+ * <p>The following methods may be called but will not do anything. For test purposes, you can use
+ * the methods {@link #isFinishCalled()} and {@link #getFinishedActivityRequest()} to inspect the
+ * parameters that they were called with.
+ * <ul>
+ * <li>{@link android.app.Activity#finish()}</li>
+ * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li>
+ * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li>
+ * </ul>
+ *
+ */
+public abstract class ActivityUnitTestCase<T extends Activity>
+ extends ActivityTestCase {
+
+ private Class<T> mActivityClass;
+
+ private Context mActivityContext;
+ private Application mApplication;
+ private MockParent mMockParent;
+
+ private boolean mAttached = false;
+ private boolean mCreated = false;
+
+ public ActivityUnitTestCase(Class<T> activityClass) {
+ mActivityClass = activityClass;
+ }
+
+ @Override
+ public T getActivity() {
+ return (T) super.getActivity();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // default value for target context, as a default
+ mActivityContext = getInstrumentation().getTargetContext();
+ }
+
+ /**
+ * Start the activity under test, in the same way as if it was started by
+ * {@link android.content.Context#startActivity Context.startActivity()}, providing the
+ * arguments it supplied. When you use this method to start the activity, it will automatically
+ * be stopped by {@link #tearDown}.
+ *
+ * <p>This method will call onCreate(), but if you wish to further exercise Activity life
+ * cycle methods, you must call them yourself from your test case.
+ *
+ * <p><i>Do not call from your setUp() method. You must call this method from each of your
+ * test methods.</i>
+ *
+ * @param intent The Intent as if supplied to {@link android.content.Context#startActivity}.
+ * @param savedInstanceState The instance state, if you are simulating this part of the life
+ * cycle. Typically null.
+ * @param lastNonConfigurationInstance This Object will be available to the
+ * Activity if it calls {@link android.app.Activity#getLastNonConfigurationInstance()}.
+ * Typically null.
+ * @return Returns the Activity that was created
+ */
+ protected T startActivity(Intent intent, Bundle savedInstanceState,
+ Object lastNonConfigurationInstance) {
+ assertFalse("Activity already created", mCreated);
+
+ if (!mAttached) {
+ assertNotNull(mActivityClass);
+ setActivity(null);
+ T newActivity = null;
+ try {
+ IBinder token = null;
+ if (mApplication == null) {
+ setApplication(new MockApplication());
+ }
+ ComponentName cn = new ComponentName(mActivityClass.getPackage().getName(),
+ mActivityClass.getName());
+ intent.setComponent(cn);
+ ActivityInfo info = null;
+ CharSequence title = mActivityClass.getName();
+ mMockParent = new MockParent();
+ String id = null;
+
+ newActivity = (T) getInstrumentation().newActivity(mActivityClass, mActivityContext,
+ token, mApplication, intent, info, title, mMockParent, id,
+ lastNonConfigurationInstance);
+ } catch (Exception e) {
+ assertNotNull(newActivity);
+ }
+
+ assertNotNull(newActivity);
+ setActivity(newActivity);
+
+ mAttached = true;
+ }
+
+ T result = getActivity();
+ if (result != null) {
+ getInstrumentation().callActivityOnCreate(getActivity(), savedInstanceState);
+ mCreated = true;
+ }
+ return result;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+
+ 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(ActivityInstrumentationTestCase.class);
+
+ super.tearDown();
+ }
+
+ /**
+ * Set the application for use during the test. You must call this function before calling
+ * {@link #startActivity}. If your test does not call this method,
+ * @param application The Application object that will be injected into the Activity under test.
+ */
+ public void setApplication(Application application) {
+ mApplication = application;
+ }
+
+ /**
+ * If you wish to inject a Mock, Isolated, or otherwise altered context, you can do so
+ * here. You must call this function before calling {@link #startActivity}. If you wish to
+ * obtain a real Context, as a building block, use getInstrumentation().getTargetContext().
+ */
+ public void setActivityContext(Context activityContext) {
+ mActivityContext = activityContext;
+ }
+
+ /**
+ * This method will return the value if your Activity under test calls
+ * {@link android.app.Activity#setRequestedOrientation}.
+ */
+ public int getRequestedOrientation() {
+ if (mMockParent != null) {
+ return mMockParent.mRequestedOrientation;
+ }
+ return 0;
+ }
+
+ /**
+ * This method will return the launch intent if your Activity under test calls
+ * {@link android.app.Activity#startActivity(Intent)} or
+ * {@link android.app.Activity#startActivityForResult(Intent, int)}.
+ * @return The Intent provided in the start call, or null if no start call was made.
+ */
+ public Intent getStartedActivityIntent() {
+ if (mMockParent != null) {
+ return mMockParent.mStartedActivityIntent;
+ }
+ return null;
+ }
+
+ /**
+ * This method will return the launch request code if your Activity under test calls
+ * {@link android.app.Activity#startActivityForResult(Intent, int)}.
+ * @return The request code provided in the start call, or -1 if no start call was made.
+ */
+ public int getStartedActivityRequest() {
+ if (mMockParent != null) {
+ return mMockParent.mStartedActivityRequest;
+ }
+ return 0;
+ }
+
+ /**
+ * This method will notify you if the Activity under test called
+ * {@link android.app.Activity#finish()},
+ * {@link android.app.Activity#finishFromChild(Activity)}, or
+ * {@link android.app.Activity#finishActivity(int)}.
+ * @return Returns true if one of the listed finish methods was called.
+ */
+ public boolean isFinishCalled() {
+ if (mMockParent != null) {
+ return mMockParent.mFinished;
+ }
+ return false;
+ }
+
+ /**
+ * This method will return the request code if the Activity under test called
+ * {@link android.app.Activity#finishActivity(int)}.
+ * @return The request code provided in the start call, or -1 if no finish call was made.
+ */
+ public int getFinishedActivityRequest() {
+ if (mMockParent != null) {
+ return mMockParent.mFinishedActivityRequest;
+ }
+ return 0;
+ }
+
+ /**
+ * This mock Activity represents the "parent" activity. By injecting this, we allow the user
+ * to call a few more Activity methods, including:
+ * <ul>
+ * <li>{@link android.app.Activity#getRequestedOrientation()}</li>
+ * <li>{@link android.app.Activity#setRequestedOrientation(int)}</li>
+ * <li>{@link android.app.Activity#finish()}</li>
+ * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li>
+ * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li>
+ * </ul>
+ *
+ * TODO: Make this overrideable, and the unit test can look for calls to other methods
+ */
+ private static class MockParent extends Activity {
+
+ public int mRequestedOrientation = 0;
+ public Intent mStartedActivityIntent = null;
+ public int mStartedActivityRequest = -1;
+ public boolean mFinished = false;
+ public int mFinishedActivityRequest = -1;
+
+ /**
+ * Implementing in the parent allows the user to call this function on the tested activity.
+ */
+ @Override
+ public void setRequestedOrientation(int requestedOrientation) {
+ mRequestedOrientation = requestedOrientation;
+ }
+
+ /**
+ * Implementing in the parent allows the user to call this function on the tested activity.
+ */
+ @Override
+ public int getRequestedOrientation() {
+ return mRequestedOrientation;
+ }
+
+ /**
+ * By returning null here, we inhibit the creation of any "container" for the window.
+ */
+ @Override
+ public Window getWindow() {
+ return null;
+ }
+
+ /**
+ * By defining this in the parent, we allow the tested activity to call
+ * <ul>
+ * <li>{@link android.app.Activity#startActivity(Intent)}</li>
+ * <li>{@link android.app.Activity#startActivityForResult(Intent, int)}</li>
+ * </ul>
+ */
+ @Override
+ public void startActivityFromChild(Activity child, Intent intent, int requestCode) {
+ mStartedActivityIntent = intent;
+ mStartedActivityRequest = requestCode;
+ }
+
+ /**
+ * By defining this in the parent, we allow the tested activity to call
+ * <ul>
+ * <li>{@link android.app.Activity#finish()}</li>
+ * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li>
+ * </ul>
+ */
+ @Override
+ public void finishFromChild(Activity child) {
+ mFinished = true;
+ }
+
+ /**
+ * By defining this in the parent, we allow the tested activity to call
+ * <ul>
+ * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li>
+ * </ul>
+ */
+ @Override
+ public void finishActivityFromChild(Activity child, int requestCode) {
+ mFinished = true;
+ mFinishedActivityRequest = requestCode;
+ }
+ }
+}
diff --git a/test-runner/android/test/AndroidTestRunner.java b/test-runner/android/test/AndroidTestRunner.java
new file mode 100644
index 0000000..79cedb0
--- /dev/null
+++ b/test-runner/android/test/AndroidTestRunner.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2007 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.Instrumentation;
+import android.content.Context;
+import com.google.android.collect.Lists;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestListener;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import junit.runner.BaseTestRunner;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+public class AndroidTestRunner extends BaseTestRunner {
+
+ private TestResult mTestResult;
+ private String mTestClassName;
+ private List<TestCase> mTestCases;
+ private Context mContext;
+ private boolean mSkipExecution = false;
+
+ private List<TestListener> mTestListeners = Lists.newArrayList();
+ private Instrumentation mInstrumentation;
+
+ @SuppressWarnings("unchecked")
+ public void setTestClassName(String testClassName, String testMethodName) {
+ Class testClass = loadTestClass(testClassName);
+
+ if (shouldRunSingleTestMethod(testMethodName, testClass)) {
+ TestCase testCase = buildSingleTestMethod(testClass, testMethodName);
+ mTestCases = Lists.newArrayList(testCase);
+ mTestClassName = testClass.getSimpleName();
+ } else {
+ setTest(getTest(testClass), testClass);
+ }
+ }
+
+ public void setTest(Test test) {
+ setTest(test, test.getClass());
+ }
+
+ private void setTest(Test test, Class<? extends Test> testClass) {
+ mTestCases = (List<TestCase>) TestCaseUtil.getTests(test, true);
+ if (TestSuite.class.isAssignableFrom(testClass)) {
+ mTestClassName = TestCaseUtil.getTestName(test);
+ } else {
+ mTestClassName = testClass.getSimpleName();
+ }
+ }
+
+ public void clearTestListeners() {
+ mTestListeners.clear();
+ }
+
+ public void addTestListener(TestListener testListener) {
+ if (testListener != null) {
+ mTestListeners.add(testListener);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Class<? extends Test> loadTestClass(String testClassName) {
+ try {
+ return (Class<? extends Test>) mContext.getClassLoader().loadClass(testClassName);
+ } catch (ClassNotFoundException e) {
+ runFailed("Could not find test class. Class: " + testClassName);
+ }
+ return null;
+ }
+
+ private TestCase buildSingleTestMethod(Class testClass, String testMethodName) {
+ try {
+ TestCase testCase = (TestCase) testClass.newInstance();
+ testCase.setName(testMethodName);
+ return testCase;
+ } catch (IllegalAccessException e) {
+ runFailed("Could not access test class. Class: " + testClass.getName());
+ } catch (InstantiationException e) {
+ runFailed("Could not instantiate test class. Class: " + testClass.getName());
+ }
+
+ return null;
+ }
+
+ private boolean shouldRunSingleTestMethod(String testMethodName,
+ Class<? extends Test> testClass) {
+ return testMethodName != null && TestCase.class.isAssignableFrom(testClass);
+ }
+
+ private Test getTest(Class clazz) {
+ if (TestSuiteProvider.class.isAssignableFrom(clazz)) {
+ try {
+ TestSuiteProvider testSuiteProvider =
+ (TestSuiteProvider) clazz.getConstructor().newInstance();
+ return testSuiteProvider.getTestSuite();
+ } catch (InstantiationException e) {
+ runFailed("Could not instantiate test suite provider. Class: " + clazz.getName());
+ } catch (IllegalAccessException e) {
+ runFailed("Illegal access of test suite provider. Class: " + clazz.getName());
+ } catch (InvocationTargetException e) {
+ runFailed("Invocation exception test suite provider. Class: " + clazz.getName());
+ } catch (NoSuchMethodException e) {
+ runFailed("No such method on test suite provider. Class: " + clazz.getName());
+ }
+ }
+ return getTest(clazz.getName());
+ }
+
+ protected TestResult createTestResult() {
+ if (mSkipExecution) {
+ return new NoExecTestResult();
+ }
+ return new TestResult();
+ }
+
+ void setSkipExecution(boolean skip) {
+ mSkipExecution = skip;
+ }
+
+ public List<TestCase> getTestCases() {
+ return mTestCases;
+ }
+
+ public String getTestClassName() {
+ return mTestClassName;
+ }
+
+ public TestResult getTestResult() {
+ return mTestResult;
+ }
+
+ public void runTest() {
+ runTest(createTestResult());
+ }
+
+ public void runTest(TestResult testResult) {
+ mTestResult = testResult;
+
+ for (TestListener testListener : mTestListeners) {
+ mTestResult.addListener(testListener);
+ }
+
+ for (TestCase testCase : mTestCases) {
+ setContextIfAndroidTestCase(testCase, mContext);
+ setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);
+ testCase.run(mTestResult);
+ }
+ }
+
+ private void setContextIfAndroidTestCase(Test test, Context context) {
+ if (AndroidTestCase.class.isAssignableFrom(test.getClass())) {
+ ((AndroidTestCase) test).setContext(context);
+ }
+ }
+
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ private void setInstrumentationIfInstrumentationTestCase(
+ Test test, Instrumentation instrumentation) {
+ if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
+ ((InstrumentationTestCase) test).injectInsrumentation(instrumentation);
+ }
+ }
+
+ public void setInstrumentaiton(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ @Override
+ protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
+ return mContext.getClassLoader().loadClass(suiteClassName);
+ }
+
+ public void testStarted(String testName) {
+ }
+
+ public void testEnded(String testName) {
+ }
+
+ public void testFailed(int status, Test test, Throwable t) {
+ }
+
+ protected void runFailed(String message) {
+ throw new RuntimeException(message);
+ }
+}
diff --git a/test-runner/android/test/ApplicationTestCase.java b/test-runner/android/test/ApplicationTestCase.java
new file mode 100644
index 0000000..31b226a
--- /dev/null
+++ b/test-runner/android/test/ApplicationTestCase.java
@@ -0,0 +1,177 @@
+/*
+ * 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.lang.reflect.Field;
+
+import android.app.Application;
+import android.app.Instrumentation;
+import android.content.Context;
+
+/**
+ * This test case provides a framework in which you can test Application classes in
+ * a controlled environment. It provides basic support for the lifecycle of a
+ * Application, and hooks by which you can inject various dependencies and control
+ * the environment in which your Application is tested.
+ *
+ * <p><b>Lifecycle Support.</b>
+ * Every Application is designed to be accessed within a specific sequence of
+ * method calls (see {@link android.app.Application} for more details).
+ * In order to support the lifecycle of a Application, this test case will make the
+ * following calls at the following times.
+ *
+ * <ul><li>The test case will not call onCreate() until your test calls
+ * {@link #createApplication()}. This gives you a chance
+ * to set up or adjust any additional framework or test logic before
+ * onCreate().</li>
+ * <li>After your test completes, the test case {@link #tearDown} method is
+ * automatically called, and it will stop & destroy your application by calling its
+ * onDestroy() method.</li>
+ * </ul>
+ *
+ * <p><b>Dependency Injection.</b>
+ * Every Application has one inherent dependency, the {@link android.content.Context Context} in
+ * which it runs.
+ * This framework allows you to inject a modified, mock, or isolated replacement for this
+ * dependencies, and thus perform a true unit test.
+ *
+ * <p>If simply run your tests as-is, your Application will be injected with a fully-functional
+ * Context.
+ * You can create and inject alternative types of Contexts by calling
+ * {@link AndroidTestCase#setContext(Context) setContext()}. You must do this <i>before</i> calling
+ * startApplication(). The test framework provides a
+ * number of alternatives for Context, including {@link android.test.mock.MockContext MockContext},
+ * {@link android.test.RenamingDelegatingContext RenamingDelegatingContext}, and
+ * {@link android.content.ContextWrapper ContextWrapper}.
+ */
+public abstract class ApplicationTestCase<T extends Application> extends AndroidTestCase {
+
+ Class<T> mApplicationClass;
+
+ private Context mSystemContext;
+
+ public ApplicationTestCase(Class<T> applicationClass) {
+ mApplicationClass = applicationClass;
+ }
+
+ private T mApplication;
+ private boolean mAttached = false;
+ private boolean mCreated = false;
+
+ /**
+ * @return Returns the actual Application under test.
+ */
+ public T getApplication() {
+ return mApplication;
+ }
+
+ /**
+ * This will do the work to instantiate the Application under test. After this, your test
+ * code must also start and stop the Application.
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // get the real context, before the individual tests have a chance to muck with it
+ mSystemContext = getContext();
+ }
+
+ /**
+ * Load and attach the application under test.
+ */
+ private void setupApplication() {
+ mApplication = null;
+ try {
+ mApplication = (T) Instrumentation.newApplication(mApplicationClass, getContext());
+ } catch (Exception e) {
+ assertNotNull(mApplication);
+ }
+ mAttached = true;
+ }
+
+ /**
+ * Start the Application under test, in the same way as if it was started by the system.
+ * If you use this method to start the Application, it will automatically
+ * be stopped by {@link #tearDown}. If you wish to inject a specialized Context for your
+ * test, by calling {@link AndroidTestCase#setContext(Context) setContext()},
+ * you must do so before calling this method.
+ */
+ final protected void createApplication() {
+ assertFalse(mCreated);
+
+ if (!mAttached) {
+ setupApplication();
+ }
+ assertNotNull(mApplication);
+
+ mApplication.onCreate();
+ mCreated = true;
+ }
+
+ /**
+ * This will make the necessary calls to terminate the Application under test (it will
+ * call onTerminate(). Ordinarily this will be called automatically (by {@link #tearDown}, but
+ * you can call it directly from your test in order to check for proper shutdown behaviors.
+ */
+ final protected void terminateApplication() {
+ if (mCreated) {
+ mApplication.onTerminate();
+ }
+ }
+
+ /**
+ * Shuts down the Application under test. Also makes sure all resources are cleaned up and
+ * garbage collected before moving on to the next
+ * test. Subclasses that override this method should make sure they call super.tearDown()
+ * at the end of the overriding method.
+ *
+ * @throws Exception
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ terminateApplication();
+ mApplication = 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(ApplicationTestCase.class);
+
+ super.tearDown();
+ }
+
+ /**
+ * Return a real (not mocked or instrumented) system Context that can be used when generating
+ * Mock or other Context objects for your Application under test.
+ *
+ * @return Returns a reference to a normal Context.
+ */
+ public Context getSystemContext() {
+ return mSystemContext;
+ }
+
+ /**
+ * This test simply confirms that the Application class can be instantiated properly.
+ *
+ * @throws Exception
+ */
+ final public void testApplicationTestCaseSetUpProperly() throws Exception {
+ setupApplication();
+ assertNotNull("Application class could not be instantiated successfully", mApplication);
+ }
+}
diff --git a/test-runner/android/test/AssertionFailedError.java b/test-runner/android/test/AssertionFailedError.java
new file mode 100644
index 0000000..7af5806
--- /dev/null
+++ b/test-runner/android/test/AssertionFailedError.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Thrown when an assertion failed.
+ *
+ * Note: Most users of this class should simply use junit.framework.AssertionFailedError,
+ * which provides the same functionality.
+ */
+public class AssertionFailedError extends Error {
+
+ /**
+ * It is more typical to call {@link #AssertionFailedError(String)}.
+ */
+ public AssertionFailedError() {
+ }
+
+ public AssertionFailedError(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/test-runner/android/test/BundlePrinter.java b/test-runner/android/test/BundlePrinter.java
new file mode 100644
index 0000000..96213e7
--- /dev/null
+++ b/test-runner/android/test/BundlePrinter.java
@@ -0,0 +1,63 @@
+package android.test;
+
+import java.io.PrintStream;
+
+import android.os.Bundle;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.runner.BaseTestRunner;
+import junit.textui.ResultPrinter;
+
+
+/**
+ * Subclass of ResultPrinter that adds test case results to a bundle.
+ *
+ * {@hide} - This class is deprecated, and will be going away. Please don't use it.
+ */
+public class BundlePrinter extends ResultPrinter {
+
+ private Bundle mResults;
+ private boolean mFailure;
+ private boolean mError;
+
+ public BundlePrinter(PrintStream writer, Bundle result) {
+ super(writer);
+ mResults = result;
+ }
+
+ @Override
+ public void addError(Test test, Throwable t) {
+ mResults.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t));
+ mFailure = true;
+ super.addError(test, t);
+ }
+
+ @Override
+ public void addFailure(Test test, AssertionFailedError t) {
+ mResults.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t));
+ mError = true;
+ super.addFailure(test, t);
+ }
+
+ @Override
+ public void endTest(Test test) {
+ if (!mFailure && !mError) {
+ mResults.putString(getComboName(test), "passed");
+ }
+ super.endTest(test);
+ }
+
+ @Override
+ public void startTest(Test test) {
+ mFailure = false;
+ mError = false;
+ super.startTest(test);
+ }
+
+ private String getComboName(Test test) {
+ return test.getClass().getName() + ":" + ((TestCase) test).getName();
+ }
+
+}
diff --git a/test-runner/android/test/BundleTestListener.java b/test-runner/android/test/BundleTestListener.java
new file mode 100644
index 0000000..772713f
--- /dev/null
+++ b/test-runner/android/test/BundleTestListener.java
@@ -0,0 +1,63 @@
+/*
+ * 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.*;
+import junit.framework.TestCase;
+import junit.runner.BaseTestRunner;
+import android.os.Bundle;
+
+/**
+ * A {@link TestListener} that adds test case results to a bundle.
+ *
+ * {@hide} - This class is deprecated, and will be going away. Please don't use it.
+ */
+public class BundleTestListener implements TestListener {
+
+ private Bundle mBundle;
+ private boolean mFailed;
+
+ public BundleTestListener(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+
+ public void addError(Test test, Throwable t) {
+ mBundle.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t));
+ mFailed = true;
+ }
+
+ public void addFailure(Test test, junit.framework.AssertionFailedError t) {
+ mBundle.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t));
+ mFailed = true;
+ }
+
+ public void endTest(Test test) {
+ if (!mFailed) {
+ mBundle.putString(getComboName(test), "passed");
+ }
+ }
+
+ public void startTest(Test test) {
+ mFailed = false;
+ }
+
+ private String getComboName(Test test) {
+ return test.getClass().getName() + ":" + ((TestCase) test).getName();
+ }
+
+}
diff --git a/test-runner/android/test/ClassPathPackageInfo.java b/test-runner/android/test/ClassPathPackageInfo.java
new file mode 100644
index 0000000..1f6e647
--- /dev/null
+++ b/test-runner/android/test/ClassPathPackageInfo.java
@@ -0,0 +1,79 @@
+/*
+ * 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 java.util.Collections;
+import java.util.Set;
+
+/**
+ * The Package object doesn't allow you to iterate over the contained
+ * classes and subpackages of that package. This is a version that does.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class ClassPathPackageInfo {
+
+ private final ClassPathPackageInfoSource source;
+ private final String packageName;
+ private final Set<String> subpackageNames;
+ private final Set<Class<?>> topLevelClasses;
+
+ ClassPathPackageInfo(ClassPathPackageInfoSource source, String packageName,
+ Set<String> subpackageNames, Set<Class<?>> topLevelClasses) {
+ this.source = source;
+ this.packageName = packageName;
+ this.subpackageNames = Collections.unmodifiableSet(subpackageNames);
+ this.topLevelClasses = Collections.unmodifiableSet(topLevelClasses);
+ }
+
+ public Set<ClassPathPackageInfo> getSubpackages() {
+ Set<ClassPathPackageInfo> info = Sets.newHashSet();
+ for (String name : subpackageNames) {
+ info.add(source.getPackageInfo(name));
+ }
+ return info;
+ }
+
+ public Set<Class<?>> getTopLevelClassesRecursive() {
+ Set<Class<?>> set = Sets.newHashSet();
+ addTopLevelClassesTo(set);
+ return set;
+ }
+
+ private void addTopLevelClassesTo(Set<Class<?>> set) {
+ set.addAll(topLevelClasses);
+ for (ClassPathPackageInfo info : getSubpackages()) {
+ info.addTopLevelClassesTo(set);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ClassPathPackageInfo) {
+ ClassPathPackageInfo that = (ClassPathPackageInfo) obj;
+ return (this.packageName).equals(that.packageName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode();
+ }
+}
diff --git a/test-runner/android/test/ClassPathPackageInfoSource.java b/test-runner/android/test/ClassPathPackageInfoSource.java
new file mode 100644
index 0000000..12bc7f3
--- /dev/null
+++ b/test-runner/android/test/ClassPathPackageInfoSource.java
@@ -0,0 +1,320 @@
+/*
+ * 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.util.Config;
+import android.util.Log;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+import dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Generate {@link ClassPathPackageInfo}s by scanning apk paths.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class ClassPathPackageInfoSource {
+
+ private static final String CLASS_EXTENSION = ".class";
+
+ private static final ClassLoader CLASS_LOADER
+ = ClassPathPackageInfoSource.class.getClassLoader();
+
+ private final SimpleCache<String, ClassPathPackageInfo> cache =
+ new SimpleCache<String, ClassPathPackageInfo>() {
+ @Override
+ protected ClassPathPackageInfo load(String pkgName) {
+ return createPackageInfo(pkgName);
+ }
+ };
+
+ // The class path of the running application
+ private final String[] classPath;
+ private static String[] apkPaths;
+
+ // A cache of jar file contents
+ private final Map<File, Set<String>> jarFiles = Maps.newHashMap();
+ private ClassLoader classLoader;
+
+ ClassPathPackageInfoSource() {
+ classPath = getClassPath();
+ }
+
+
+ public static void setApkPaths(String[] apkPaths) {
+ ClassPathPackageInfoSource.apkPaths = apkPaths;
+ }
+
+ public ClassPathPackageInfo getPackageInfo(String pkgName) {
+ return cache.get(pkgName);
+ }
+
+ private ClassPathPackageInfo createPackageInfo(String packageName) {
+ Set<String> subpackageNames = new TreeSet<String>();
+ Set<String> classNames = new TreeSet<String>();
+ Set<Class<?>> topLevelClasses = Sets.newHashSet();
+ findClasses(packageName, classNames, subpackageNames);
+ for (String className : classNames) {
+ if (className.endsWith(".R") || className.endsWith(".Manifest")) {
+ // Don't try to load classes that are generated. They usually aren't in test apks.
+ continue;
+ }
+
+ try {
+ // We get errors in the emulator if we don't use the caller's class loader.
+ topLevelClasses.add(Class.forName(className, false,
+ (classLoader != null) ? classLoader : CLASS_LOADER));
+ } catch (ClassNotFoundException e) {
+ // Should not happen unless there is a generated class that is not included in
+ // the .apk.
+ Log.w("ClassPathPackageInfoSource", "Cannot load class. "
+ + "Make sure it is in your apk. Class name: '" + className
+ + "'. Message: " + e.getMessage(), e);
+ }
+ }
+ return new ClassPathPackageInfo(this, packageName, subpackageNames,
+ topLevelClasses);
+ }
+
+ /**
+ * Finds all classes and sub packages that are below the packageName and
+ * add them to the respective sets. Searches the package on the whole class
+ * path.
+ */
+ private void findClasses(String packageName, Set<String> classNames,
+ Set<String> subpackageNames) {
+ String packagePrefix = packageName + '.';
+ String pathPrefix = packagePrefix.replace('.', '/');
+
+ for (String entryName : classPath) {
+ File classPathEntry = new File(entryName);
+
+ // Forge may not have brought over every item in the classpath. Be
+ // polite and ignore missing entries.
+ if (classPathEntry.exists()) {
+ try {
+ if (entryName.endsWith(".apk")) {
+ findClassesInApk(entryName, packageName, classNames, subpackageNames);
+ } else if ("true".equals(System.getProperty("android.vm.dexfile", "false"))) {
+ // If the vm supports dex files then scan the directories that contain
+ // apk files.
+ for (String apkPath : apkPaths) {
+ File file = new File(apkPath);
+ scanForApkFiles(file, packageName, classNames, subpackageNames);
+ }
+ } else if (entryName.endsWith(".jar")) {
+ findClassesInJar(classPathEntry, pathPrefix,
+ classNames, subpackageNames);
+ } else if (classPathEntry.isDirectory()) {
+ findClassesInDirectory(classPathEntry, packagePrefix, pathPrefix,
+ classNames, subpackageNames);
+ } else {
+ throw new AssertionError("Don't understand classpath entry " +
+ classPathEntry);
+ }
+ } catch (IOException e) {
+ throw new AssertionError("Can't read classpath entry " +
+ entryName + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ private void scanForApkFiles(File source, String packageName,
+ Set<String> classNames, Set<String> subpackageNames) throws IOException {
+ if (source.getPath().endsWith(".apk")) {
+ findClassesInApk(source.getPath(), packageName, classNames, subpackageNames);
+ } else {
+ File[] files = source.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ scanForApkFiles(file, packageName, classNames, subpackageNames);
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds all classes and sub packages that are below the packageName and
+ * add them to the respective sets. Searches the package in a class directory.
+ */
+ private void findClassesInDirectory(File classDir,
+ String packagePrefix, String pathPrefix, Set<String> classNames,
+ Set<String> subpackageNames)
+ throws IOException {
+ File directory = new File(classDir, pathPrefix);
+
+ if (directory.exists()) {
+ for (File f : directory.listFiles()) {
+ String name = f.getName();
+ if (name.endsWith(CLASS_EXTENSION) && isToplevelClass(name)) {
+ classNames.add(packagePrefix + getClassName(name));
+ } else if (f.isDirectory()) {
+ subpackageNames.add(packagePrefix + name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds all classes and sub packages that are below the packageName and
+ * add them to the respective sets. Searches the package in a single jar file.
+ */
+ private void findClassesInJar(File jarFile, String pathPrefix,
+ Set<String> classNames, Set<String> subpackageNames)
+ throws IOException {
+ Set<String> entryNames = getJarEntries(jarFile);
+ // check if the Jar contains the package.
+ if (!entryNames.contains(pathPrefix)) {
+ return;
+ }
+ int prefixLength = pathPrefix.length();
+ for (String entryName : entryNames) {
+ if (entryName.startsWith(pathPrefix)) {
+ if (entryName.endsWith(CLASS_EXTENSION)) {
+ // check if the class is in the package itself or in one of its
+ // subpackages.
+ int index = entryName.indexOf('/', prefixLength);
+ if (index >= 0) {
+ String p = entryName.substring(0, index).replace('/', '.');
+ subpackageNames.add(p);
+ } else if (isToplevelClass(entryName)) {
+ classNames.add(getClassName(entryName).replace('/', '.'));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds all classes and sub packages that are below the packageName and
+ * add them to the respective sets. Searches the package in a single apk file.
+ */
+ private void findClassesInApk(String apkPath, String packageName,
+ Set<String> classNames, Set<String> subpackageNames)
+ throws IOException {
+
+ DexFile dexFile = null;
+ try {
+ dexFile = new DexFile(apkPath);
+ Enumeration<String> apkClassNames = dexFile.entries();
+ while (apkClassNames.hasMoreElements()) {
+ String className = apkClassNames.nextElement();
+
+ if (className.startsWith(packageName)) {
+ int lastPackageSeparator = className.lastIndexOf('.');
+ String subPackageName = className.substring(0, lastPackageSeparator);
+ if (subPackageName.length() > packageName.length()) {
+ subpackageNames.add(subPackageName);
+ } else if (isToplevelClass(className)) {
+ classNames.add(className);
+ }
+ }
+ }
+ } catch (IOException e) {
+ if (Config.LOGV) {
+ Log.w("ClassPathPackageInfoSource",
+ "Error finding classes at apk path: " + apkPath, e);
+ }
+ } finally {
+ if (dexFile != null) {
+ // Todo: figure out why closing causes a dalvik error resulting in vm shutdown.
+// dexFile.close();
+ }
+ }
+ }
+
+ /**
+ * Gets the class and package entries from a Jar.
+ */
+ private Set<String> getJarEntries(File jarFile)
+ throws IOException {
+ Set<String> entryNames = jarFiles.get(jarFile);
+ if (entryNames == null) {
+ entryNames = Sets.newHashSet();
+ ZipFile zipFile = new ZipFile(jarFile);
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ String entryName = entries.nextElement().getName();
+ if (entryName.endsWith(CLASS_EXTENSION)) {
+ // add the entry name of the class
+ entryNames.add(entryName);
+
+ // add the entry name of the classes package, i.e. the entry name of
+ // the directory that the class is in. Used to quickly skip jar files
+ // if they do not contain a certain package.
+ //
+ // Also add parent packages so that a JAR that contains
+ // pkg1/pkg2/Foo.class will be marked as containing pkg1/ in addition
+ // to pkg1/pkg2/ and pkg1/pkg2/Foo.class. We're still interested in
+ // JAR files that contains subpackages of a given package, even if
+ // an intermediate package contains no direct classes.
+ //
+ // Classes in the default package will cause a single package named
+ // "" to be added instead.
+ int lastIndex = entryName.lastIndexOf('/');
+ do {
+ String packageName = entryName.substring(0, lastIndex + 1);
+ entryNames.add(packageName);
+ lastIndex = entryName.lastIndexOf('/', lastIndex - 1);
+ } while (lastIndex > 0);
+ }
+ }
+ jarFiles.put(jarFile, entryNames);
+ }
+ return entryNames;
+ }
+
+ /**
+ * Checks if a given file name represents a toplevel class.
+ */
+ private static boolean isToplevelClass(String fileName) {
+ return fileName.indexOf('$') < 0;
+ }
+
+ /**
+ * Given the absolute path of a class file, return the class name.
+ */
+ private static String getClassName(String className) {
+ int classNameEnd = className.length() - CLASS_EXTENSION.length();
+ return className.substring(0, classNameEnd);
+ }
+
+ /**
+ * Gets the class path from the System Property "java.class.path" and splits
+ * it up into the individual elements.
+ */
+ private static String[] getClassPath() {
+ String classPath = System.getProperty("java.class.path");
+ String separator = System.getProperty("path.separator", ":");
+ return classPath.split(Pattern.quote(separator));
+ }
+
+ public void setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+}
diff --git a/test-runner/android/test/ComparisonFailure.java b/test-runner/android/test/ComparisonFailure.java
new file mode 100644
index 0000000..e7e9698
--- /dev/null
+++ b/test-runner/android/test/ComparisonFailure.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Thrown when an assert equals for Strings failed.
+ *
+ * Note: Most users of this class should simply use junit.framework.ComparisonFailure,
+ * which provides the same functionality at a lighter weight.
+ */
+public class ComparisonFailure extends AssertionFailedError {
+ private junit.framework.ComparisonFailure mComparison;
+
+ public ComparisonFailure(String message, String expected, String actual) {
+ mComparison = new junit.framework.ComparisonFailure(message, expected, actual);
+ }
+
+ public String getMessage() {
+ return mComparison.getMessage();
+ }
+}
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
new file mode 100644
index 0000000..f038612
--- /dev/null
+++ b/test-runner/android/test/InstrumentationTestRunner.java
@@ -0,0 +1,672 @@
+/*
+ * Copyright (C) 2007 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 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;
+import junit.framework.TestListener;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import junit.runner.BaseTestRunner;
+import junit.textui.ResultPrinter;
+
+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
+ * an Android package (application). Typical usage:
+ * <ol>
+ * <li>Write {@link junit.framework.TestCase}s that perform unit, functional, or performance tests
+ * against the classes in your package. Typically these are subclassed from:
+ * <ul><li>{@link android.test.ActivityInstrumentationTestCase}</li>
+ * <li>{@link android.test.ActivityUnitTestCase}</li>
+ * <li>{@link android.test.AndroidTestCase}</li>
+ * <li>{@link android.test.ApplicationTestCase}</li>
+ * <li>{@link android.test.InstrumentationTestCase}</li>
+ * <li>{@link android.test.ProviderTestCase}</li>
+ * <li>{@link android.test.ServiceTestCase}</li>
+ * <li>{@link android.test.SingleLaunchActivityTestCase}</li></ul>
+ * <li>In an appropriate AndroidManifest.xml, define the this instrumentation with
+ * the appropriate android:targetPackage set.
+ * <li>Run the instrumentation using "adb shell am instrument -w",
+ * with no optional arguments, to run all tests (except performance tests).
+ * <li>Run the instrumentation using "adb shell am instrument -w",
+ * with the argument '-e func true' to run all functional tests. These are tests that derive from
+ * {@link android.test.InstrumentationTestCase}.
+ * <li>Run the instrumentation using "adb shell am instrument -w",
+ * with the argument '-e unit true' to run all unit tests. These are tests that <i>do not</i>derive
+ * from {@link android.test.InstrumentationTestCase} (and are not performance tests).
+ * <li>Run the instrumentation using "adb shell am instrument -w",
+ * with the argument '-e class' set to run an individual {@link junit.framework.TestCase}.
+ * </ol>
+ * <p/>
+ * <b>Running all tests:</b> adb shell am instrument -w
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
+ * <b>Running all small tests:</b> adb shell am instrument -w
+ * -e size small
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
+ * <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
+ * -e class com.android.foo.FooTest
+ * com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
+ * <b>Running a single test:</b> adb shell am instrument -w
+ * -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.
+ */
+
+/* (not JavaDoc)
+ * Although not necessary in most case, another way to use this class is to extend it and have the
+ * derived class return
+ * the desired test suite from the {@link #getTestSuite()} method. The test suite returned from this
+ * method will be used if no target class is defined in the meta-data or command line argument
+ * parameters. If a derived class is used it needs to be added as an instrumentation to the
+ * AndroidManifest.xml and the command to run it would look like:
+ * <p/>
+ * adb shell am instrument -w com.android.foo/<i>com.android.FooInstrumentationTestRunner</i>
+ * <p/>
+ * Where <i>com.android.FooInstrumentationTestRunner</i> is the derived class.
+ *
+ * This model is used by many existing app tests, but can probably be deprecated.
+ */
+public class InstrumentationTestRunner extends Instrumentation implements TestSuiteProvider {
+
+ /** @hide */
+ public static final String ARGUMENT_TEST_CLASS = "class";
+ /** @hide */
+ public static final String ARGUMENT_TEST_PACKAGE = "package";
+ /** @hide */
+ public static final String ARGUMENT_TEST_SIZE_PREDICATE = "size";
+ /** @hide */
+ public static final String ARGUMENT_INCLUDE_PERF = "perf";
+ /** @hide */
+ public static final String ARGUMENT_DELAY_MSEC = "delay_msec";
+
+ 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
+ * status messages.
+ */
+ public static final String REPORT_VALUE_ID = "InstrumentationTestRunner";
+ /**
+ * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
+ * identifies the total number of tests that are being run. This is sent with all status
+ * messages.
+ */
+ public static final String REPORT_KEY_NUM_TOTAL = "numtests";
+ /**
+ * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
+ * identifies the sequence number of the current test. This is sent with any status message
+ * describing a specific test being started or completed.
+ */
+ public static final String REPORT_KEY_NUM_CURRENT = "current";
+ /**
+ * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
+ * identifies the name of the current test class. This is sent with any status message
+ * describing a specific test being started or completed.
+ */
+ public static final String REPORT_KEY_NAME_CLASS = "class";
+ /**
+ * If included in the status or final bundle sent to an IInstrumentationWatcher, this key
+ * identifies the name of the current test. This is sent with any status message
+ * describing a specific test being started or completed.
+ */
+ 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;
+ /**
+ * The test completed successfully.
+ */
+ public static final int REPORT_VALUE_RESULT_OK = 0;
+ /**
+ * The test completed with an error.
+ */
+ public static final int REPORT_VALUE_RESULT_ERROR = -1;
+ /**
+ * The test completed with a failure.
+ */
+ public static final int REPORT_VALUE_RESULT_FAILURE = -2;
+ /**
+ * If included in the status bundle sent to an IInstrumentationWatcher, this key
+ * identifies a stack trace describing an error or failure. This is sent with any status
+ * message describing a specific test being completed.
+ */
+ 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 String mPackageOfTests;
+ private boolean mCoverage;
+ private String mCoverageFilePath;
+ private int mDelayMsec;
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+
+ // Apk paths used to search for test classes when using TestSuiteBuilders.
+ String[] apkPaths =
+ {getTargetContext().getPackageCodePath(), getContext().getPackageCodePath()};
+ ClassPathPackageInfoSource.setApkPaths(apkPaths);
+
+ 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.
+ testClassesArg = arguments.getString(ARGUMENT_TEST_CLASS);
+ mDebug = getBooleanArgument(arguments, "debug");
+ mJustCount = getBooleanArgument(arguments, "count");
+ 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");
+
+ try {
+ Object delay = arguments.get(ARGUMENT_DELAY_MSEC); // Accept either string or int
+ if (delay != null) mDelayMsec = Integer.parseInt(delay.toString());
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Invalid delay_msec parameter", e);
+ }
+ }
+
+ TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(),
+ getTargetContext().getClassLoader());
+
+ if (testSizePredicate != null) {
+ testSuiteBuilder.addRequirements(testSizePredicate);
+ }
+ if (!includePerformance) {
+ testSuiteBuilder.addRequirements(REJECT_PERFORMANCE);
+ }
+
+ if (testClassesArg == null) {
+ TestSuite testSuite = null;
+ if (mPackageOfTests != null) {
+ testSuiteBuilder.includePackages(mPackageOfTests);
+ } else {
+ testSuite = getTestSuite();
+ testSuiteBuilder.addTestSuite(testSuite);
+ }
+
+ if (testSuite == null) {
+ testSuiteBuilder.includePackages(getTargetContext().getPackageName());
+ }
+ } else {
+ parseTestClasses(testClassesArg, testSuiteBuilder);
+ }
+
+ mTestRunner = getAndroidTestRunner();
+ mTestRunner.setContext(getTargetContext());
+ mTestRunner.setInstrumentaiton(this);
+ mTestRunner.setSkipExecution(logOnly);
+ mTestRunner.setTest(testSuiteBuilder.build());
+ mTestCount = mTestRunner.getTestCases().size();
+ if (mSuiteAssignmentMode) {
+ mTestRunner.addTestListener(new SuiteAssignmentPrinter());
+ } else {
+ mTestRunner.addTestListener(new TestPrinter("TestRunner", false));
+ mTestRunner.addTestListener(new WatcherResultPrinter(mTestCount));
+ }
+ start();
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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 (methodSeparatorIndex > 0) {
+ testMethodName = testClassName.substring(methodSeparatorIndex + 1);
+ testClassName = testClassName.substring(0, methodSeparatorIndex);
+ }
+ testSuiteBuilder.addTestClassByName(testClassName, testMethodName,
+ getTargetContext());
+ }
+
+ protected AndroidTestRunner getAndroidTestRunner() {
+ return new AndroidTestRunner();
+ }
+
+ private boolean getBooleanArgument(Bundle arguments, String tag) {
+ 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();
+
+ if (mJustCount) {
+ mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
+ mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);
+ finish(Activity.RESULT_OK, mResults);
+ } else {
+ if (mDebug) {
+ Debug.waitForDebugger();
+ }
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream writer = new PrintStream(byteArrayOutputStream);
+ try {
+ StringResultPrinter resultPrinter = new StringResultPrinter(writer);
+
+ mTestRunner.addTestListener(resultPrinter);
+
+ long startTime = System.currentTimeMillis();
+ mTestRunner.runTest();
+ long runTime = System.currentTimeMillis() - startTime;
+
+ resultPrinter.print(mTestRunner.getTestResult(), runTime);
+ } finally {
+ 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);
+ }
+ }
+ }
+
+ public TestSuite getTestSuite() {
+ return getAllTests();
+ }
+
+ /**
+ * Override this to define all of the tests to run in your package.
+ */
+ public TestSuite getAllTests() {
+ return null;
+ }
+
+ /**
+ * Override this to provide access to the class loader of your package.
+ */
+ 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 {
+
+ public StringResultPrinter(PrintStream writer) {
+ super(writer);
+ }
+
+ synchronized void print(TestResult result, long runTime) {
+ printHeader(runTime);
+ 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
+ {
+ private final Bundle mResultTemplate;
+ Bundle mTestResult;
+ int mTestNum = 0;
+ int mTestResultCode = 0;
+ String mTestClass = null;
+
+ public WatcherResultPrinter(int numTests) {
+ mResultTemplate = new Bundle();
+ mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
+ mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
+ }
+
+ /**
+ * send a status for the start of a each test, so long tests can be seen as "running"
+ */
+ public void startTest(Test test) {
+ String testClass = test.getClass().getName();
+ mTestResult = new Bundle(mResultTemplate);
+ mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass);
+ mTestResult.putString(REPORT_KEY_NAME_TEST, ((TestCase) test).getName());
+ mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum);
+ // pretty printing
+ if (testClass != null && !testClass.equals(mTestClass)) {
+ mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+ String.format("\n%s:", testClass));
+ mTestClass = testClass;
+ } else {
+ mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "");
+ }
+
+ // The delay_msec parameter is normally used to provide buffers of idle time
+ // for power measurement purposes. To make sure there is a delay before and after
+ // every test in a suite, we delay *after* every test (see endTest below) and also
+ // delay *before* the first test. So, delay test1 delay test2 delay.
+
+ try {
+ if (mTestNum == 1) Thread.sleep(mDelayMsec);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+
+ sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
+ mTestResultCode = 0;
+ }
+
+ /**
+ * @see junit.framework.TestListener#addError(Test, Throwable)
+ */
+ public void addError(Test test, Throwable t) {
+ mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
+ mTestResultCode = REPORT_VALUE_RESULT_ERROR;
+ // pretty printing
+ mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+ String.format("\nError in %s:\n%s",
+ ((TestCase) test).getName(), BaseTestRunner.getFilteredTrace(t)));
+ }
+
+ /**
+ * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
+ */
+ public void addFailure(Test test, AssertionFailedError t) {
+ mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t));
+ mTestResultCode = REPORT_VALUE_RESULT_FAILURE;
+ // pretty printing
+ mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+ String.format("\nFailure in %s:\n%s",
+ ((TestCase) test).getName(), BaseTestRunner.getFilteredTrace(t)));
+ }
+
+ /**
+ * @see junit.framework.TestListener#endTest(Test)
+ */
+ public void endTest(Test test) {
+ if (mTestResultCode == 0) {
+ mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
+ }
+ sendStatus(mTestResultCode, mTestResult);
+
+ try { // Sleep after every test, if specified
+ Thread.sleep(mDelayMsec);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ // TODO report the end of the cycle
+ // TODO report runtime for each test
+ }
+}
diff --git a/test-runner/android/test/InstrumentationUtils.java b/test-runner/android/test/InstrumentationUtils.java
new file mode 100644
index 0000000..4c59097
--- /dev/null
+++ b/test-runner/android/test/InstrumentationUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 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.lang.reflect.Field;
+
+/**
+ *
+ * The InstrumentationUtils class has all the utility functions needed for
+ * instrumentation tests.
+ *
+ * {@hide} - Not currently used.
+ */
+public class InstrumentationUtils {
+ /**
+ * An utility function that returns the menu identifier for a particular
+ * menu item.
+ *
+ * @param cls Class object of the class that handles the menu ite,.
+ * @param identifier Menu identifier.
+ * @return The integer corresponding to the menu item.
+ */
+ public static int getMenuIdentifier(Class cls, String identifier) {
+ int id = -1;
+ try {
+ Integer field = (Integer)cls.getDeclaredField(identifier).get(cls);
+ id = field.intValue();
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return id;
+ }
+
+}
diff --git a/test-runner/android/test/IsolatedContext.java b/test-runner/android/test/IsolatedContext.java
new file mode 100644
index 0000000..2866666
--- /dev/null
+++ b/test-runner/android/test/IsolatedContext.java
@@ -0,0 +1,85 @@
+package android.test;
+
+import com.google.android.collect.Lists;
+
+import android.content.ContextWrapper;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+
+import java.util.List;
+
+/**
+ * A mock context which prevents its users from talking to the rest of the device while
+ * stubbing enough methods to satify code that tries to talk to other packages.
+ */
+public class IsolatedContext extends ContextWrapper {
+
+ private ContentResolver mResolver;
+
+ private List<Intent> mBroadcastIntents = Lists.newArrayList();
+
+ public IsolatedContext(
+ ContentResolver resolver, Context targetContext) {
+ super(targetContext);
+ mResolver = resolver;
+ }
+
+ /** Returns the list of intents that were broadcast since the last call to this method. */
+ public List<Intent> getAndClearBroadcastIntents() {
+ List<Intent> intents = mBroadcastIntents;
+ mBroadcastIntents = Lists.newArrayList();
+ return intents;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ // We need to return the real resolver so that MailEngine.makeRight can get to the
+ // subscribed feeds provider. TODO: mock out subscribed feeds too.
+ return mResolver;
+ }
+
+ @Override
+ public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+ return false;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ return null;
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ mBroadcastIntents.add(intent);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
+ mBroadcastIntents.add(intent);
+ }
+
+ @Override
+ public int checkUriPermission(
+ Uri uri, String readPermission, String writePermission, int pid,
+ int uid, int modeFlags) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ // No services exist in this context.
+ return null;
+ }
+
+}
diff --git a/test-runner/android/test/LaunchPerformanceBase.java b/test-runner/android/test/LaunchPerformanceBase.java
new file mode 100644
index 0000000..c324446
--- /dev/null
+++ b/test-runner/android/test/LaunchPerformanceBase.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 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.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Debug;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+
+import java.util.ArrayList;
+
+
+/**
+ * Base class for all launch performance Instrumentation classes.
+ *
+ * @hide
+ */
+public class LaunchPerformanceBase extends Instrumentation {
+
+ public static final String LOG_TAG = "Launch Performance";
+
+ protected Bundle mResults;
+ protected Intent mIntent;
+
+ public LaunchPerformanceBase() {
+ mResults = new Bundle();
+ mIntent = new Intent(Intent.ACTION_MAIN);
+ mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ setAutomaticPerformanceSnapshots();
+ }
+
+ /**
+ * Launches intent, and waits for idle before returning.
+ *
+ * @hide
+ */
+ protected void LaunchApp() {
+ startActivitySync(mIntent);
+ waitForIdleSync();
+ }
+}
diff --git a/test-runner/android/test/MoreAsserts.java b/test-runner/android/test/MoreAsserts.java
new file mode 100644
index 0000000..2e74644
--- /dev/null
+++ b/test-runner/android/test/MoreAsserts.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright (C) 2007 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.Lists;
+import junit.framework.Assert;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Contains additional assertion methods not found in JUnit.
+ */
+public final class MoreAsserts {
+
+ private MoreAsserts() { }
+
+ /**
+ * Asserts that the class {@code expected} is assignable from the object
+ * {@code actual}. This verifies {@code expected} is a parent class or a
+ * interface that {@code actual} implements.
+ */
+ public static void assertAssignableFrom(Class<?> expected, Object actual) {
+ assertAssignableFrom(expected, actual.getClass());
+ }
+
+ /**
+ * Asserts that class {@code expected} is assignable from the class
+ * {@code actual}. This verifies {@code expected} is a parent class or a
+ * interface that {@code actual} implements.
+ */
+ public static void assertAssignableFrom(Class<?> expected, Class<?> actual) {
+ Assert.assertTrue(
+ "Expected " + expected.getCanonicalName() +
+ " to be assignable from actual class " + actual.getCanonicalName(),
+ expected.isAssignableFrom(actual));
+ }
+
+ /**
+ * Asserts that {@code actual} is not equal {@code unexpected}, according
+ * to both {@code ==} and {@link Object#equals}.
+ */
+ public static void assertNotEqual(
+ String message, Object unexpected, Object actual) {
+ if (equal(unexpected, actual)) {
+ failEqual(message, unexpected);
+ }
+ }
+
+ /**
+ * Variant of {@link #assertNotEqual(String,Object,Object)} using a
+ * generic message.
+ */
+ public static void assertNotEqual(Object unexpected, Object actual) {
+ assertNotEqual(null, unexpected, actual);
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element equals
+ * those in array {@code expected}. On failure, message indicates specific
+ * element mismatch.
+ */
+ public static void assertEquals(
+ String message, byte[] expected, byte[] actual) {
+ if (expected.length != actual.length) {
+ failWrongLength(message, expected.length, actual.length);
+ }
+ for (int i = 0; i < expected.length; i++) {
+ if (expected[i] != actual[i]) {
+ failWrongElement(message, i, expected[i], actual[i]);
+ }
+ }
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element equals
+ * those in array {@code expected}. On failure, message indicates specific
+ * element mismatch.
+ */
+ public static void assertEquals(byte[] expected, byte[] actual) {
+ assertEquals(null, expected, actual);
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element equals
+ * those in array {@code expected}. On failure, message indicates first
+ * specific element mismatch.
+ */
+ public static void assertEquals(
+ String message, int[] expected, int[] actual) {
+ if (expected.length != actual.length) {
+ failWrongLength(message, expected.length, actual.length);
+ }
+ for (int i = 0; i < expected.length; i++) {
+ if (expected[i] != actual[i]) {
+ failWrongElement(message, i, expected[i], actual[i]);
+ }
+ }
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element equals
+ * those in array {@code expected}. On failure, message indicates first
+ * specific element mismatch.
+ */
+ public static void assertEquals(int[] expected, int[] actual) {
+ assertEquals(null, expected, actual);
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element equals
+ * those in array {@code expected}. On failure, message indicates first
+ * specific element mismatch.
+ */
+ public static void assertEquals(
+ String message, double[] expected, double[] actual) {
+ if (expected.length != actual.length) {
+ failWrongLength(message, expected.length, actual.length);
+ }
+ for (int i = 0; i < expected.length; i++) {
+ if (expected[i] != actual[i]) {
+ failWrongElement(message, i, expected[i], actual[i]);
+ }
+ }
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element equals
+ * those in array {@code expected}. On failure, message indicates first
+ * specific element mismatch.
+ */
+ public static void assertEquals(double[] expected, double[] actual) {
+ assertEquals(null, expected, actual);
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element
+ * is the same as those in array {@code expected}. Note that this uses
+ * {@code equals()} instead of {@code ==} to compare the objects.
+ * {@code null} will be considered equal to {code null} (unlike SQL).
+ * On failure, message indicates first specific element mismatch.
+ */
+ public static void assertEquals(
+ String message, Object[] expected, Object[] actual) {
+ if (expected.length != actual.length) {
+ failWrongLength(message, expected.length, actual.length);
+ }
+ for (int i = 0; i < expected.length; i++) {
+ Object exp = expected[i];
+ Object act = actual[i];
+ // The following borrowed from java.util.equals(Object[], Object[]).
+ if (!((exp==null) ? act==null : exp.equals(act))) {
+ failWrongElement(message, i, exp, act);
+ }
+ }
+ }
+
+ /**
+ * Asserts that array {@code actual} is the same size and every element
+ * is the same as those in array {@code expected}. Note that this uses
+ * {@code ==} instead of {@code equals()} to compare the objects.
+ * On failure, message indicates first specific element mismatch.
+ */
+ public static void assertEquals(Object[] expected, Object[] actual) {
+ assertEquals(null, expected, actual);
+ }
+
+ /** Asserts that two sets contain the same elements. */
+ public static void assertEquals(
+ String message, Set<? extends Object> expected, Set<? extends Object> actual) {
+ Set<Object> onlyInExpected = new HashSet<Object>(expected);
+ onlyInExpected.removeAll(actual);
+ Set<Object> onlyInActual = new HashSet<Object>(actual);
+ onlyInActual.removeAll(expected);
+ if (onlyInExpected.size() != 0 || onlyInActual.size() != 0) {
+ Set<Object> intersection = new HashSet<Object>(expected);
+ intersection.retainAll(actual);
+ failWithMessage(
+ message,
+ "Sets do not match.\nOnly in expected: " + onlyInExpected
+ + "\nOnly in actual: " + onlyInActual
+ + "\nIntersection: " + intersection);
+ }
+ }
+
+ /** Asserts that two sets contain the same elements. */
+ public static void assertEquals(Set<? extends Object> expected, Set<? extends Object> actual) {
+ assertEquals(null, expected, actual);
+ }
+
+ /**
+ * Asserts that {@code expectedRegex} exactly matches {@code actual} and
+ * fails with {@code message} if it does not. The MatchResult is returned
+ * in case the test needs access to any captured groups. Note that you can
+ * also use this for a literal string, by wrapping your expected string in
+ * {@link Pattern#quote}.
+ */
+ public static MatchResult assertMatchesRegex(
+ String message, String expectedRegex, String actual) {
+ if (actual == null) {
+ failNotMatches(message, expectedRegex, actual);
+ }
+ Matcher matcher = getMatcher(expectedRegex, actual);
+ if (!matcher.matches()) {
+ failNotMatches(message, expectedRegex, actual);
+ }
+ return matcher;
+ }
+
+ /**
+ * Variant of {@link #assertMatchesRegex(String,String,String)} using a
+ * generic message.
+ */
+ public static MatchResult assertMatchesRegex(
+ String expectedRegex, String actual) {
+ return assertMatchesRegex(null, expectedRegex, actual);
+ }
+
+ /**
+ * Asserts that {@code expectedRegex} matches any substring of {@code actual}
+ * and fails with {@code message} if it does not. The Matcher is returned in
+ * case the test needs access to any captured groups. Note that you can also
+ * use this for a literal string, by wrapping your expected string in
+ * {@link Pattern#quote}.
+ */
+ public static MatchResult assertContainsRegex(
+ String message, String expectedRegex, String actual) {
+ if (actual == null) {
+ failNotContains(message, expectedRegex, actual);
+ }
+ Matcher matcher = getMatcher(expectedRegex, actual);
+ if (!matcher.find()) {
+ failNotContains(message, expectedRegex, actual);
+ }
+ return matcher;
+ }
+
+ /**
+ * Variant of {@link #assertContainsRegex(String,String,String)} using a
+ * generic message.
+ */
+ public static MatchResult assertContainsRegex(
+ String expectedRegex, String actual) {
+ return assertContainsRegex(null, expectedRegex, actual);
+ }
+
+ /**
+ * Asserts that {@code expectedRegex} does not exactly match {@code actual},
+ * and fails with {@code message} if it does. Note that you can also use
+ * this for a literal string, by wrapping your expected string in
+ * {@link Pattern#quote}.
+ */
+ public static void assertNotMatchesRegex(
+ String message, String expectedRegex, String actual) {
+ Matcher matcher = getMatcher(expectedRegex, actual);
+ if (matcher.matches()) {
+ failMatch(message, expectedRegex, actual);
+ }
+ }
+
+ /**
+ * Variant of {@link #assertNotMatchesRegex(String,String,String)} using a
+ * generic message.
+ */
+ public static void assertNotMatchesRegex(
+ String expectedRegex, String actual) {
+ assertNotMatchesRegex(null, expectedRegex, actual);
+ }
+
+ /**
+ * Asserts that {@code expectedRegex} does not match any substring of
+ * {@code actual}, and fails with {@code message} if it does. Note that you
+ * can also use this for a literal string, by wrapping your expected string
+ * in {@link Pattern#quote}.
+ */
+ public static void assertNotContainsRegex(
+ String message, String expectedRegex, String actual) {
+ Matcher matcher = getMatcher(expectedRegex, actual);
+ if (matcher.find()) {
+ failContains(message, expectedRegex, actual);
+ }
+ }
+
+ /**
+ * Variant of {@link #assertNotContainsRegex(String,String,String)} using a
+ * generic message.
+ */
+ public static void assertNotContainsRegex(
+ String expectedRegex, String actual) {
+ assertNotContainsRegex(null, expectedRegex, actual);
+ }
+
+ /**
+ * Asserts that {@code actual} contains precisely the elements
+ * {@code expected}, and in the same order.
+ */
+ public static void assertContentsInOrder(
+ String message, Iterable<?> actual, Object... expected) {
+ Assert.assertEquals(message,
+ Arrays.asList(expected), Lists.newArrayList(actual));
+ }
+
+ /**
+ * Variant of assertContentsInOrder(String, Iterable<?>, Object...)
+ * using a generic message.
+ */
+ public static void assertContentsInOrder(
+ Iterable<?> actual, Object... expected) {
+ assertContentsInOrder((String) null, actual, expected);
+ }
+
+ /**
+ * Asserts that {@code actual} contains precisely the elements
+ * {@code expected}, but in any order.
+ */
+ public static void assertContentsInAnyOrder(String message, Iterable<?> actual,
+ Object... expected) {
+ HashMap<Object, Object> expectedMap = new HashMap<Object, Object>(expected.length);
+ for (Object expectedObj : expected) {
+ expectedMap.put(expectedObj, expectedObj);
+ }
+
+ for (Object actualObj : actual) {
+ if (expectedMap.remove(actualObj) == null) {
+ failWithMessage(message, "Extra object in actual: (" + actualObj.toString() + ")");
+ }
+ }
+
+ if (expectedMap.size() > 0) {
+ failWithMessage(message, "Extra objects in expected.");
+ }
+ }
+
+ /**
+ * Variant of assertContentsInAnyOrder(String, Iterable<?>, Object...)
+ * using a generic message.
+ */
+ public static void assertContentsInAnyOrder(Iterable<?> actual, Object... expected) {
+ assertContentsInAnyOrder((String)null, actual, expected);
+ }
+
+ /**
+ * Asserts that {@code iterable} is empty.
+ */
+ public static void assertEmpty(String message, Iterable<?> iterable) {
+ if (iterable.iterator().hasNext()) {
+ failNotEmpty(message, iterable.toString());
+ }
+ }
+
+ /**
+ * Variant of {@link #assertEmpty(String, Iterable)} using a
+ * generic message.
+ */
+ public static void assertEmpty(Iterable<?> iterable) {
+ assertEmpty(null, iterable);
+ }
+
+ /**
+ * Asserts that {@code map} is empty.
+ */
+ public static void assertEmpty(String message, Map<?,?> map) {
+ if (!map.isEmpty()) {
+ failNotEmpty(message, map.toString());
+ }
+ }
+
+ /**
+ * Variant of {@link #assertEmpty(String, Map)} using a generic
+ * message.
+ */
+ public static void assertEmpty(Map<?,?> map) {
+ assertEmpty(null, map);
+ }
+
+ /**
+ * Asserts that {@code iterable} is not empty.
+ */
+ public static void assertNotEmpty(String message, Iterable<?> iterable) {
+ if (!iterable.iterator().hasNext()) {
+ failEmpty(message);
+ }
+ }
+
+ /**
+ * Variant of assertNotEmpty(String, Iterable<?>)
+ * using a generic message.
+ */
+ public static void assertNotEmpty(Iterable<?> iterable) {
+ assertNotEmpty(null, iterable);
+ }
+
+ /**
+ * Asserts that {@code map} is not empty.
+ */
+ public static void assertNotEmpty(String message, Map<?,?> map) {
+ if (map.isEmpty()) {
+ failEmpty(message);
+ }
+ }
+
+ /**
+ * Variant of {@link #assertNotEmpty(String, Map)} using a generic
+ * message.
+ */
+ public static void assertNotEmpty(Map<?,?> map) {
+ assertNotEmpty(null, map);
+ }
+
+ /**
+ * Utility for testing equals() and hashCode() results at once.
+ * Tests that lhs.equals(rhs) matches expectedResult, as well as
+ * rhs.equals(lhs). Also tests that hashCode() return values are
+ * equal if expectedResult is true. (hashCode() is not tested if
+ * expectedResult is false, as unequal objects can have equal hashCodes.)
+ *
+ * @param lhs An Object for which equals() and hashCode() are to be tested.
+ * @param rhs As lhs.
+ * @param expectedResult True if the objects should compare equal,
+ * false if not.
+ */
+ public static void checkEqualsAndHashCodeMethods(
+ String message, Object lhs, Object rhs, boolean expectedResult) {
+
+ if ((lhs == null) && (rhs == null)) {
+ Assert.assertTrue(
+ "Your check is dubious...why would you expect null != null?",
+ expectedResult);
+ return;
+ }
+
+ if ((lhs == null) || (rhs == null)) {
+ Assert.assertFalse(
+ "Your check is dubious...why would you expect an object "
+ + "to be equal to null?", expectedResult);
+ }
+
+ if (lhs != null) {
+ Assert.assertEquals(message, expectedResult, lhs.equals(rhs));
+ }
+ if (rhs != null) {
+ Assert.assertEquals(message, expectedResult, rhs.equals(lhs));
+ }
+
+ if (expectedResult) {
+ String hashMessage =
+ "hashCode() values for equal objects should be the same";
+ if (message != null) {
+ hashMessage += ": " + message;
+ }
+ Assert.assertTrue(hashMessage, lhs.hashCode() == rhs.hashCode());
+ }
+ }
+
+ /**
+ * Variant of
+ * checkEqualsAndHashCodeMethods(String,Object,Object,boolean...)}
+ * using a generic message.
+ */
+ public static void checkEqualsAndHashCodeMethods(Object lhs, Object rhs,
+ boolean expectedResult) {
+ checkEqualsAndHashCodeMethods((String) null, lhs, rhs, expectedResult);
+ }
+
+ private static Matcher getMatcher(String expectedRegex, String actual) {
+ Pattern pattern = Pattern.compile(expectedRegex);
+ return pattern.matcher(actual);
+ }
+
+ private static void failEqual(String message, Object unexpected) {
+ failWithMessage(message, "expected not to be:<" + unexpected + ">");
+ }
+
+ private static void failWrongLength(
+ String message, int expected, int actual) {
+ failWithMessage(message, "expected array length:<" + expected
+ + "> but was:<" + actual + '>');
+ }
+
+ private static void failWrongElement(
+ String message, int index, Object expected, Object actual) {
+ failWithMessage(message, "expected array element[" + index + "]:<"
+ + expected + "> but was:<" + actual + '>');
+ }
+
+ private static void failNotMatches(
+ String message, String expectedRegex, String actual) {
+ String actualDesc = (actual == null) ? "null" : ('<' + actual + '>');
+ failWithMessage(message, "expected to match regex:<" + expectedRegex
+ + "> but was:" + actualDesc);
+ }
+
+ private static void failNotContains(
+ String message, String expectedRegex, String actual) {
+ String actualDesc = (actual == null) ? "null" : ('<' + actual + '>');
+ failWithMessage(message, "expected to contain regex:<" + expectedRegex
+ + "> but was:" + actualDesc);
+ }
+
+ private static void failMatch(
+ String message, String expectedRegex, String actual) {
+ failWithMessage(message, "expected not to match regex:<" + expectedRegex
+ + "> but was:<" + actual + '>');
+ }
+
+ private static void failContains(
+ String message, String expectedRegex, String actual) {
+ failWithMessage(message, "expected not to contain regex:<" + expectedRegex
+ + "> but was:<" + actual + '>');
+ }
+
+ private static void failNotEmpty(
+ String message, String actual) {
+ failWithMessage(message, "expected to be empty, but contained: <"
+ + actual + ">");
+ }
+
+ private static void failEmpty(String message) {
+ failWithMessage(message, "expected not to be empty, but was");
+ }
+
+ private static void failWithMessage(String userMessage, String ourMessage) {
+ Assert.fail((userMessage == null)
+ ? ourMessage
+ : userMessage + ' ' + ourMessage);
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+}
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/PackageInfoSources.java b/test-runner/android/test/PackageInfoSources.java
new file mode 100644
index 0000000..ef37449
--- /dev/null
+++ b/test-runner/android/test/PackageInfoSources.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * {@hide} Not needed for SDK.
+ */
+public class PackageInfoSources {
+
+ private static ClassPathPackageInfoSource classPathSource;
+
+ private PackageInfoSources() {
+ }
+
+ public static ClassPathPackageInfoSource forClassPath(ClassLoader classLoader) {
+ if (classPathSource == null) {
+ classPathSource = new ClassPathPackageInfoSource();
+ classPathSource.setClassLoader(classLoader);
+ }
+ return classPathSource;
+ }
+
+}
diff --git a/test-runner/android/test/PerformanceTestBase.java b/test-runner/android/test/PerformanceTestBase.java
new file mode 100644
index 0000000..93ac90c
--- /dev/null
+++ b/test-runner/android/test/PerformanceTestBase.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 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.test.PerformanceTestCase;
+import junit.framework.TestCase;
+
+/**
+ * {@hide} Not needed for SDK.
+ */
+public abstract class PerformanceTestBase extends TestCase implements PerformanceTestCase {
+
+ public int startPerformance(PerformanceTestCase.Intermediates intermediates) {
+ return 0;
+ }
+
+ public boolean isPerformanceOnly() {
+ return true;
+ }
+
+ /*
+ * Temporary hack to get some things working again.
+ */
+ public void testRun() {
+ throw new RuntimeException("test implementation not provided");
+ }
+}
+
diff --git a/test-runner/android/test/ProviderTestCase.java b/test-runner/android/test/ProviderTestCase.java
new file mode 100644
index 0000000..445b4eb
--- /dev/null
+++ b/test-runner/android/test/ProviderTestCase.java
@@ -0,0 +1,87 @@
+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 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 {
+
+ Class<T> mProviderClass;
+ String mProviderAuthority;
+
+ private IsolatedContext mProviderContext;
+ private MockContentResolver mResolver;
+
+ public ProviderTestCase(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
+ getInstrumentation().getTargetContext(), // 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, Class<T> providerClass, String authority,
+ String databaseName, int databaseVersion, String sql)
+ throws IllegalAccessException, InstantiationException {
+ final String filenamePrefix = "test.";
+ 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;
+ }
+}
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/RenamingDelegatingContext.java b/test-runner/android/test/RenamingDelegatingContext.java
new file mode 100644
index 0000000..3f64340
--- /dev/null
+++ b/test-runner/android/test/RenamingDelegatingContext.java
@@ -0,0 +1,198 @@
+package android.test;
+
+import com.google.android.collect.Sets;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.ContentProvider;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.Set;
+
+/**
+ * This is a class which delegates to the given context, but performs database
+ * and file operations with a renamed database/file name (prefixes default
+ * names with a given prefix).
+ */
+public class RenamingDelegatingContext extends ContextWrapper {
+
+ private Context mFileContext;
+ private String mFilePrefix = null;
+
+ private Set<String> mDatabaseNames = Sets.newHashSet();
+ private Set<String> mFileNames = Sets.newHashSet();
+
+ public static <T extends ContentProvider> T providerWithRenamedContext(
+ Class<T> contentProvider, Context c, String filePrefix)
+ throws IllegalAccessException, InstantiationException {
+ return providerWithRenamedContext(contentProvider, c, filePrefix, false);
+ }
+
+ public static <T extends ContentProvider> T providerWithRenamedContext(
+ Class<T> contentProvider, Context c, String filePrefix,
+ boolean allowAccessToExistingFilesAndDbs)
+ throws IllegalAccessException, InstantiationException {
+ Class<T> mProviderClass = contentProvider;
+ T mProvider = mProviderClass.newInstance();
+ RenamingDelegatingContext mContext = new RenamingDelegatingContext(c, filePrefix);
+ if (allowAccessToExistingFilesAndDbs) {
+ mContext.makeExistingFilesAndDbsAccessible();
+ }
+ mProvider.attachInfo(mContext, null);
+ return mProvider;
+ }
+
+ /**
+ * Makes accessible all files and databases whose names match the filePrefix that was passed to
+ * the constructor. Normally only files and databases that were created through this context are
+ * accessible.
+ */
+ public void makeExistingFilesAndDbsAccessible() {
+ String[] databaseList = mFileContext.databaseList();
+ for (String diskName : databaseList) {
+ if (shouldDiskNameBeVisible(diskName)) {
+ mDatabaseNames.add(publicNameFromDiskName(diskName));
+ }
+ }
+ String[] fileList = mFileContext.fileList();
+ for (String diskName : fileList) {
+ if (shouldDiskNameBeVisible(diskName)) {
+ mFileNames.add(publicNameFromDiskName(diskName));
+ }
+ }
+ }
+
+ /**
+ * Returns if the given diskName starts with the given prefix or not.
+ * @param diskName name of the database/file.
+ */
+ boolean shouldDiskNameBeVisible(String diskName) {
+ return diskName.startsWith(mFilePrefix);
+ }
+
+ /**
+ * Returns the public name (everything following the prefix) of the given diskName.
+ * @param diskName name of the database/file.
+ */
+ String publicNameFromDiskName(String diskName) {
+ if (!shouldDiskNameBeVisible(diskName)) {
+ throw new IllegalArgumentException("disk file should not be visible: " + diskName);
+ }
+ return diskName.substring(mFilePrefix.length(), diskName.length());
+ }
+
+ /**
+ * @param context : the context that will be delagated.
+ * @param filePrefix : a prefix with which database and file names will be
+ * prefixed.
+ */
+ public RenamingDelegatingContext(Context context, String filePrefix) {
+ super(context);
+ mFileContext = context;
+ mFilePrefix = filePrefix;
+ }
+
+ /**
+ * @param context : the context that will be delagated.
+ * @param fileContext : the context that file and db methods will be delgated to
+ * @param filePrefix : a prefix with which database and file names will be
+ * prefixed.
+ */
+ public RenamingDelegatingContext(Context context, Context fileContext, String filePrefix) {
+ super(context);
+ mFileContext = fileContext;
+ mFilePrefix = filePrefix;
+ }
+
+ public String getDatabasePrefix() {
+ return mFilePrefix;
+ }
+
+ private String renamedFileName(String name) {
+ return mFilePrefix + name;
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name,
+ int mode, SQLiteDatabase.CursorFactory factory) {
+ final String internalName = renamedFileName(name);
+ if (!mDatabaseNames.contains(name)) {
+ mDatabaseNames.add(name);
+ mFileContext.deleteDatabase(internalName);
+ }
+ return mFileContext.openOrCreateDatabase(internalName, mode, factory);
+ }
+
+ @Override
+ public boolean deleteDatabase(String name) {
+ if (mDatabaseNames.contains(name)) {
+ mDatabaseNames.remove(name);
+ return mFileContext.deleteDatabase(renamedFileName(name));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String[] databaseList() {
+ return mDatabaseNames.toArray(new String[]{});
+ }
+
+ @Override
+ public FileInputStream openFileInput(String name)
+ throws FileNotFoundException {
+ final String internalName = renamedFileName(name);
+ if (mFileNames.contains(name)) {
+ return mFileContext.openFileInput(internalName);
+ } else {
+ throw new FileNotFoundException(internalName);
+ }
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String name, int mode)
+ throws FileNotFoundException {
+ mFileNames.add(name);
+ return mFileContext.openFileOutput(renamedFileName(name), mode);
+ }
+
+ @Override
+ public File getFileStreamPath(String name) {
+ return mFileContext.getFileStreamPath(renamedFileName(name));
+ }
+
+ @Override
+ public boolean deleteFile(String name) {
+ if (mFileNames.contains(name)) {
+ mFileNames.remove(name);
+ return mFileContext.deleteFile(renamedFileName(name));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String[] fileList() {
+ return mFileNames.toArray(new String[]{});
+ }
+
+// /**
+// * Given an array of files returns only those whose names indicate that they belong to this
+// * context.
+// * @param allFiles the original list of files
+// * @return the pruned list of files
+// */
+// private String[] prunedFileList(String[] allFiles) {
+// List<String> files = Lists.newArrayList();
+// for (String file : allFiles) {
+// if (file.startsWith(mFilePrefix)) {
+// files.add(file);
+// }
+// }
+// return files.toArray(new String[]{});
+// }
+} \ No newline at end of file
diff --git a/test-runner/android/test/ServiceLocator.java b/test-runner/android/test/ServiceLocator.java
new file mode 100644
index 0000000..3324008
--- /dev/null
+++ b/test-runner/android/test/ServiceLocator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public class ServiceLocator {
+
+ private static TestBrowserController mTestBrowserController =
+ new TestBrowserControllerImpl();
+
+ public static TestBrowserController getTestBrowserController() {
+ return mTestBrowserController;
+ }
+
+ static void setTestBrowserController(TestBrowserController testBrowserController) {
+ mTestBrowserController = testBrowserController;
+ }
+}
diff --git a/test-runner/android/test/ServiceTestCase.java b/test-runner/android/test/ServiceTestCase.java
new file mode 100644
index 0000000..fcb9d55
--- /dev/null
+++ b/test-runner/android/test/ServiceTestCase.java
@@ -0,0 +1,280 @@
+/*
+ * 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.Application;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.test.mock.MockApplication;
+
+import java.lang.reflect.Field;
+import java.util.Random;
+
+/**
+ * This test case provides a framework in which you can test Service classes in
+ * a controlled environment. It provides basic support for the lifecycle of a
+ * Service, and hooks by which you can inject various dependencies and control
+ * the environment in which your Service is tested.
+ *
+ * <p><b>Lifecycle Support.</b>
+ * Every Service is designed to be accessed within a specific sequence of
+ * calls. <insert link to Service lifecycle doc here>.
+ * In order to support the lifecycle of a Service, this test case will make the
+ * following calls at the following times.
+ *
+ * <ul><li>The test case will not call onCreate() until your test calls
+ * {@link #startService} or {@link #bindService}. This gives you a chance
+ * to set up or adjust any additional framework or test logic before
+ * onCreate().</li>
+ * <li>When your test calls {@link #startService} or {@link #bindService}
+ * the test case will call onCreate(), and then call the corresponding entry point in your service.
+ * It will record any parameters or other support values necessary to support the lifecycle.</li>
+ * <li>After your test completes, the test case {@link #tearDown} function is
+ * automatically called, and it will stop and destroy your service with the appropriate
+ * calls (depending on how your test invoked the service.)</li>
+ * </ul>
+ *
+ * <p><b>Dependency Injection.</b>
+ * Every service has two inherent dependencies, the {@link android.content.Context Context} in
+ * which it runs, and the {@link android.app.Application Application} with which it is associated.
+ * This framework allows you to inject modified, mock, or isolated replacements for these
+ * dependencies, and thus perform a true unit test.
+ *
+ * <p>If simply run your tests as-is, your Service will be injected with a fully-functional
+ * Context, and a generic {@link android.test.mock.MockApplication MockApplication} object.
+ * You can create and inject alternatives to either of these by calling
+ * {@link AndroidTestCase#setContext(Context) setContext()} or
+ * {@link #setApplication setApplication()}. You must do this <i>before</i> calling
+ * startService() or bindService(). The test framework provides a
+ * number of alternatives for Context, including {link android.test.mock.MockContext MockContext},
+ * {@link android.test.RenamingDelegatingContext RenamingDelegatingContext}, and
+ * {@link android.content.ContextWrapper ContextWrapper}.
+ */
+public abstract class ServiceTestCase<T extends Service> extends AndroidTestCase {
+
+ Class<T> mServiceClass;
+
+ private Context mSystemContext;
+ private Application mApplication;
+
+ public ServiceTestCase(Class<T> serviceClass) {
+ mServiceClass = serviceClass;
+ }
+
+ private T mService;
+ private boolean mServiceAttached = false;
+ private boolean mServiceCreated = false;
+ private boolean mServiceStarted = false;
+ private boolean mServiceBound = false;
+ private Intent mServiceIntent = null;
+ private int mServiceId;
+
+ /**
+ * @return Returns the actual service under test.
+ */
+ public T getService() {
+ return mService;
+ }
+
+ /**
+ * This will do the work to instantiate the Service under test. After this, your test
+ * code must also start and stop the service.
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // get the real context, before the individual tests have a chance to muck with it
+ mSystemContext = getContext();
+
+ }
+
+ /**
+ * Create the service under test and attach all injected dependencies (Context, Application) to
+ * it. This will be called automatically by {@link #startService} or by {@link #bindService}.
+ * If you wish to call {@link AndroidTestCase#setContext(Context) setContext()} or
+ * {@link #setApplication setApplication()}, you must do so before calling this function.
+ */
+ protected void setupService() {
+ mService = null;
+ try {
+ mService = mServiceClass.newInstance();
+ } catch (Exception e) {
+ assertNotNull(mService);
+ }
+ if (getApplication() == null) {
+ setApplication(new MockApplication());
+ }
+ mService.attach(
+ getContext(),
+ null, // ActivityThread not actually used in Service
+ mServiceClass.getName(),
+ null, // token not needed when not talking with the activity manager
+ getApplication(),
+ null // mocked services don't talk with the activity manager
+ );
+
+ assertNotNull(mService);
+
+ mServiceId = new Random().nextInt();
+ mServiceAttached = true;
+ }
+
+ /**
+ * Start the service under test, in the same way as if it was started by
+ * {@link android.content.Context#startService Context.startService()}, providing the
+ * arguments it supplied. If you use this method to start the service, it will automatically
+ * be stopped by {@link #tearDown}.
+ *
+ * @param intent The Intent as if supplied to {@link android.content.Context#startService}.
+ */
+ protected void startService(Intent intent) {
+ assertFalse(mServiceStarted);
+ assertFalse(mServiceBound);
+
+ if (!mServiceAttached) {
+ setupService();
+ }
+ assertNotNull(mService);
+
+ if (!mServiceCreated) {
+ mService.onCreate();
+ mServiceCreated = true;
+ }
+ mService.onStart(intent, mServiceId);
+
+ mServiceStarted = true;
+ }
+
+ /**
+ * Start the service under test, in the same way as if it was started by
+ * {@link android.content.Context#bindService Context.bindService()}, providing the
+ * arguments it supplied.
+ *
+ * Return the communication channel to the service. May return null if
+ * clients can not bind to the service. The returned
+ * {@link android.os.IBinder} is usually for a complex interface
+ * that has been <a href="{@docRoot}guide/developing/tools/aidl.html">described using
+ * aidl</a>.
+ *
+ * Note: In order to test with this interface, your service must implement a getService()
+ * method, as shown in samples.ApiDemos.app.LocalService.
+
+ * @param intent The Intent as if supplied to {@link android.content.Context#bindService}.
+ *
+ * @return Return an IBinder for making further calls into the Service.
+ */
+ protected IBinder bindService(Intent intent) {
+ assertFalse(mServiceStarted);
+ assertFalse(mServiceBound);
+
+ if (!mServiceAttached) {
+ setupService();
+ }
+ assertNotNull(mService);
+
+ if (!mServiceCreated) {
+ mService.onCreate();
+ mServiceCreated = true;
+ }
+ // no extras are expected by unbind
+ mServiceIntent = intent.cloneFilter();
+ IBinder result = mService.onBind(intent);
+
+ mServiceBound = true;
+ return result;
+ }
+
+ /**
+ * This will make the necessary calls to stop (or unbind) the Service under test, and
+ * call onDestroy(). Ordinarily this will be called automatically (by {@link #tearDown}, but
+ * you can call it directly from your test in order to check for proper shutdown behaviors.
+ */
+ protected void shutdownService() {
+ if (mServiceStarted) {
+ mService.stopSelf();
+ mServiceStarted = false;
+ } else if (mServiceBound) {
+ mService.onUnbind(mServiceIntent);
+ mServiceBound = false;
+ }
+ if (mServiceCreated) {
+ mService.onDestroy();
+ }
+ }
+
+ /**
+ * Shuts down the Service under test. Also makes sure all resources are cleaned up and
+ * garbage collected before moving on to the next
+ * test. Subclasses that override this method should make sure they call super.tearDown()
+ * at the end of the overriding method.
+ *
+ * @throws Exception
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ shutdownService();
+ mService = 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(ServiceTestCase.class);
+
+ super.tearDown();
+ }
+
+ /**
+ * Set the application for use during the test. If your test does not call this function,
+ * a new {@link android.test.mock.MockApplication MockApplication} object will be generated.
+ *
+ * @param application The Application object that will be injected into the Service under test.
+ */
+ public void setApplication(Application application) {
+ mApplication = application;
+ }
+
+ /**
+ * Return the Application object being used by the Service under test.
+ *
+ * @return Returns the application object.
+ *
+ * @see #setApplication
+ */
+ public Application getApplication() {
+ return mApplication;
+ }
+
+ /**
+ * Return a real (not mocked or instrumented) system Context that can be used when generating
+ * Mock or other Context objects for your Service under test.
+ *
+ * @return Returns a reference to a normal Context.
+ */
+ public Context getSystemContext() {
+ return mSystemContext;
+ }
+
+ public void testServiceTestCaseSetUpProperly() throws Exception {
+ setupService();
+ assertNotNull("service should be launched successfully", mService);
+ }
+}
diff --git a/test-runner/android/test/SimpleCache.java b/test-runner/android/test/SimpleCache.java
new file mode 100644
index 0000000..44424ec
--- /dev/null
+++ b/test-runner/android/test/SimpleCache.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util.HashMap;
+import java.util.Map;
+
+abstract class SimpleCache<K, V> {
+ private Map<K, V> map = new HashMap<K, V>();
+
+ protected abstract V load(K key);
+
+ final V get(K key) {
+ if (map.containsKey(key)) {
+ return map.get(key);
+ }
+ V value = load(key);
+ map.put(key, value);
+ return value;
+ }
+}
diff --git a/test-runner/android/test/SingleLaunchActivityTestCase.java b/test-runner/android/test/SingleLaunchActivityTestCase.java
new file mode 100644
index 0000000..8d43b73
--- /dev/null
+++ b/test-runner/android/test/SingleLaunchActivityTestCase.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 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.view.IWindowManager;
+import android.os.ServiceManager;
+
+/**
+ * If you would like to test a single activity with an
+ * {@link android.test.InstrumentationTestCase}, this provides some of the boiler plate to
+ * launch and finish the activity in {@link #setUp} and {@link #tearDown}.
+ *
+ * This launches the activity only once for the entire class instead of doing it
+ * in every setup / teardown call.
+ */
+public abstract class SingleLaunchActivityTestCase<T extends Activity>
+ extends InstrumentationTestCase {
+
+ String mPackage;
+ Class<T> mActivityClass;
+ private static int sTestCaseCounter = 0;
+ private static boolean sActivityLaunchedFlag = false;
+
+ /**
+ * @param pkg The package of the instrumentation.
+ * @param activityClass The activity to test.
+ */
+ public SingleLaunchActivityTestCase(String pkg, Class<T> activityClass) {
+ mPackage = pkg;
+ mActivityClass = activityClass;
+ sTestCaseCounter ++;
+ }
+
+ /**
+ * The activity that will be set up for use in each test method.
+ */
+ private static Activity sActivity;
+
+ public T getActivity() {
+ return (T) sActivity;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // If it is the first test case, launch the activity.
+ if (!sActivityLaunchedFlag) {
+ // by default, not in touch mode
+ getInstrumentation().setInTouchMode(false);
+ sActivity = launchActivity(mPackage, mActivityClass, null);
+ sActivityLaunchedFlag = true;
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // If it is the last test case, call finish on the activity.
+ sTestCaseCounter --;
+ if (sTestCaseCounter == 1) {
+ sActivity.finish();
+ }
+ super.tearDown();
+ }
+
+ public void testActivityTestCaseSetUpProperly() throws Exception {
+ assertNotNull("activity should be launched successfully", sActivity);
+ }
+}
diff --git a/test-runner/android/test/SyncBaseInstrumentation.java b/test-runner/android/test/SyncBaseInstrumentation.java
new file mode 100644
index 0000000..c1d2507
--- /dev/null
+++ b/test-runner/android/test/SyncBaseInstrumentation.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 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.content.ContentResolver;
+import android.content.Context;
+import android.content.ContentValues;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.Sync;
+import android.net.Uri;
+import java.util.Map;
+
+/**
+ * If you would like to test sync a single provider with an
+ * {@link InstrumentationTestCase}, this provides some of the boiler plate in {@link #setUp} and
+ * {@link #tearDown}.
+ */
+public class SyncBaseInstrumentation extends InstrumentationTestCase {
+ private Context mTargetContext;
+ ContentResolver mContentResolver;
+ private static final int MAX_TIME_FOR_SYNC_IN_MINS = 20;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTargetContext = getInstrumentation().getTargetContext();
+ mContentResolver = mTargetContext.getContentResolver();
+ }
+
+ /**
+ * Syncs the specified provider.
+ * @throws Exception
+ */
+ protected void syncProvider(Uri uri, String account, String authority) throws Exception {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
+ extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, account);
+
+ mContentResolver.startSync(uri, extras);
+ long startTimeInMillis = SystemClock.elapsedRealtime();
+ long endTimeInMillis = startTimeInMillis + MAX_TIME_FOR_SYNC_IN_MINS * 60000;
+
+ int counter = 0;
+ // Making sure race condition does not occur when en entry have been removed from pending
+ // and active tables and loaded in memory (therefore sync might be still in progress)
+ while (counter < 2) {
+ // Sleep for 1 second.
+ Thread.sleep(1000);
+ // Finish test if time to sync has exceeded max time.
+ if (SystemClock.elapsedRealtime() > endTimeInMillis) {
+ break;
+ }
+
+ if (isSyncActive(account, authority)) {
+ counter = 0;
+ continue;
+ }
+ counter++;
+ }
+ }
+
+ protected void cancelSyncsandDisableAutoSync() {
+ Sync.Settings.QueryMap mSyncSettings =
+ new Sync.Settings.QueryMap(mContentResolver, true, null);
+ mSyncSettings.setListenForNetworkTickles(false);
+ mContentResolver.cancelSync(null);
+ mSyncSettings.close();
+ }
+
+ /**
+ * This method tests if any sync is active or not. Sync is considered to be active if the
+ * entry is in either the Pending or Active tables.
+ * @return
+ */
+ private boolean isSyncActive(String account, String authority) {
+ Sync.Pending.QueryMap pendingQueryMap = null;
+ Sync.Active.QueryMap activeQueryMap = null;
+ try {
+ pendingQueryMap = new Sync.Pending.QueryMap(mContentResolver, false, null);
+ activeQueryMap = new Sync.Active.QueryMap(mContentResolver, false, null);
+
+ if (pendingQueryMap.isPending(account, authority)) {
+ return true;
+ }
+ if (isActiveInActiveQueryMap(activeQueryMap, account, authority)) {
+ return true;
+ }
+ return false;
+ } finally {
+ activeQueryMap.close();
+ pendingQueryMap.close();
+ }
+ }
+
+ private boolean isActiveInActiveQueryMap(Sync.Active.QueryMap activemap, String account,
+ String authority) {
+ Map<String, ContentValues> rows = activemap.getRows();
+ for (ContentValues values : rows.values()) {
+ if (values.getAsString("account").equals(account)
+ && values.getAsString("authority").equals(authority)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/test-runner/android/test/TestBrowserActivity.java b/test-runner/android/test/TestBrowserActivity.java
new file mode 100644
index 0000000..ea5f91e
--- /dev/null
+++ b/test-runner/android/test/TestBrowserActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2007 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.android.internal.R;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import java.util.List;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public abstract class TestBrowserActivity extends ListActivity
+ implements android.test.TestBrowserView, AdapterView.OnItemClickListener,
+ TestSuiteProvider {
+
+ private TestBrowserController mTestBrowserController;
+ public static final String BUNDLE_EXTRA_PACKAGE = "package";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getListView().setOnItemClickListener(this);
+
+ mTestBrowserController = ServiceLocator.getTestBrowserController();
+ mTestBrowserController.setTargetPackageName(getPackageName());
+ mTestBrowserController.registerView(this);
+ mTestBrowserController.setTargetBrowserActivityClassName(this.getClass().getName());
+
+ // Apk paths used to search for test classes when using TestSuiteBuilders.
+ String[] apkPaths = {getPackageCodePath()};
+ ClassPathPackageInfoSource.setApkPaths(apkPaths);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ TestSuite testSuite = getTestSuiteToBrowse();
+ mTestBrowserController.setTestSuite(testSuite);
+
+ String name = testSuite.getName();
+ if (name != null) {
+ setTitle(name.substring(name.lastIndexOf(".") + 1));
+ }
+ }
+
+ /**
+ * Subclasses will override this method and return the TestSuite specific to their .apk.
+ * When this method is invoked due to an intent fired from
+ * {@link #onItemClick(android.widget.AdapterView, android.view.View, int, long)} then get the
+ * targeted TestSuite from the intent.
+ *
+ * @return testSuite to browse
+ */
+ @SuppressWarnings("unchecked")
+ private TestSuite getTestSuiteToBrowse() {
+ Intent intent = getIntent();
+ if (Intent.ACTION_RUN.equals(intent.getAction())) {
+ String testClassName = intent.getData().toString();
+
+ try {
+ Class<Test> testClass = (Class<Test>) getClassLoader().loadClass(testClassName);
+ return TestCaseUtil.createTestSuite(testClass);
+ } catch (ClassNotFoundException e) {
+ Log.e("TestBrowserActivity", "ClassNotFoundException for " + testClassName, e);
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ Log.e("TestBrowserActivity", "IllegalAccessException for " + testClassName, e);
+ throw new RuntimeException(e);
+ } catch (InstantiationException e) {
+ Log.e("TestBrowserActivity", "InstantiationException for " + testClassName, e);
+ throw new RuntimeException(e);
+ }
+ } else {
+ // get test classes to browwes from subclass
+ return getTopTestSuite();
+ }
+
+ }
+
+ public TestSuite getTestSuite() {
+ return getTopTestSuite();
+ }
+
+ /**
+ * @return A TestSuite that should be run for a given application.
+ */
+ public abstract TestSuite getTopTestSuite();
+
+ public void onItemClick(AdapterView parent, View v, int position, long id) {
+ Intent intent = mTestBrowserController.getIntentForTestAt(position);
+ intent.putExtra(BUNDLE_EXTRA_PACKAGE, getPackageName());
+ startActivity(intent);
+ }
+
+ public void setTestNames(List<String> testNames) {
+ ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,
+ R.layout.test_list_item, testNames);
+ setListAdapter(arrayAdapter);
+ }
+}
+
diff --git a/test-runner/android/test/TestBrowserController.java b/test-runner/android/test/TestBrowserController.java
new file mode 100644
index 0000000..044e39f
--- /dev/null
+++ b/test-runner/android/test/TestBrowserController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 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.content.Intent;
+import junit.framework.TestSuite;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public interface TestBrowserController {
+ String BUNDLE_EXTRA_TEST_METHOD_NAME = "testMethodName";
+
+ Intent getIntentForTestAt(int position);
+
+ void setTestSuite(TestSuite testSuite);
+
+ void registerView(TestBrowserView testBrowserView);
+
+ void setTargetBrowserActivityClassName(String targetBrowserActivityClassName);
+
+ void setTargetPackageName(String targetPackageName);
+}
diff --git a/test-runner/android/test/TestBrowserControllerImpl.java b/test-runner/android/test/TestBrowserControllerImpl.java
new file mode 100644
index 0000000..b8f8975
--- /dev/null
+++ b/test-runner/android/test/TestBrowserControllerImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 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.content.Intent;
+import android.net.Uri;
+import com.google.android.collect.Lists;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.util.List;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public class TestBrowserControllerImpl implements TestBrowserController {
+
+ static final String TEST_RUNNER_ACTIVITY_CLASS_NAME =
+ "com.android.testharness.TestRunnerActivity";
+
+ private TestSuite mTestSuite;
+ private TestBrowserView mTestBrowserView;
+ private static final int RUN_ALL_INDEX = 0;
+ private String mTargetBrowserActivityClassName;
+ private String mTargetPackageName;
+
+ public void setTargetPackageName(String targetPackageName) {
+ mTargetPackageName = targetPackageName;
+ }
+
+ public Intent getIntentForTestAt(int position) {
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_RUN);
+ // We must add the following two flags to make sure that we create a new activity when
+ // we browse nested test suites.
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ String packageName = getDefaultPackageNameForTestRunner();
+ String className = "";
+ String testName = "";
+ if (shouldAllTestsBeRun(position)) {
+ testName = mTestSuite.getName();
+ className = TEST_RUNNER_ACTIVITY_CLASS_NAME;
+ } else {
+ Test test = TestCaseUtil.getTestAtIndex(mTestSuite, position - 1);
+ if (TestSuite.class.isAssignableFrom(test.getClass())) {
+ TestSuite testSuite = (TestSuite) test;
+ testName = testSuite.getName();
+ className = mTargetBrowserActivityClassName;
+ packageName = mTargetPackageName;
+ } else if (TestCase.class.isAssignableFrom(test.getClass())) {
+ TestCase testCase = (TestCase) test;
+ testName = testCase.getClass().getName();
+ className = TEST_RUNNER_ACTIVITY_CLASS_NAME;
+ String testMethodName = testCase.getName();
+ intent.putExtra(BUNDLE_EXTRA_TEST_METHOD_NAME, testMethodName);
+ }
+ }
+
+ intent.setClassName(packageName, className);
+ intent.setData(Uri.parse(testName));
+
+ return intent;
+ }
+
+ private String getDefaultPackageNameForTestRunner() {
+ return TEST_RUNNER_ACTIVITY_CLASS_NAME.substring(0,
+ TEST_RUNNER_ACTIVITY_CLASS_NAME.lastIndexOf("."));
+ }
+
+ private boolean shouldAllTestsBeRun(int position) {
+ return position == RUN_ALL_INDEX;
+ }
+
+ public void setTestSuite(TestSuite testSuite) {
+ mTestSuite = testSuite;
+
+ List<String> testCaseNames = Lists.newArrayList();
+ testCaseNames.add("Run All");
+ testCaseNames.addAll(TestCaseUtil.getTestCaseNames(testSuite, false));
+
+ mTestBrowserView.setTestNames(testCaseNames);
+ }
+
+ public void registerView(TestBrowserView testBrowserView) {
+ mTestBrowserView = testBrowserView;
+ }
+
+
+ public void setTargetBrowserActivityClassName(String targetBrowserActivityClassName) {
+ mTargetBrowserActivityClassName = targetBrowserActivityClassName;
+ }
+}
diff --git a/test-runner/android/test/TestBrowserView.java b/test-runner/android/test/TestBrowserView.java
new file mode 100644
index 0000000..4799f19
--- /dev/null
+++ b/test-runner/android/test/TestBrowserView.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 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.util.List;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public interface TestBrowserView {
+ void setTestNames(List<String> testNames);
+}
diff --git a/test-runner/android/test/TestCase.java b/test-runner/android/test/TestCase.java
new file mode 100644
index 0000000..5432ce8
--- /dev/null
+++ b/test-runner/android/test/TestCase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2006 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.content.Context;
+
+/**
+ * {@hide}
+ * More complex interface for test cases.
+ *
+ * <p>Just implementing Runnable is enough for many test cases. If you
+ * have additional setup or teardown, this interface might be for you,
+ * especially if you need to share it between different test cases, or your
+ * teardown code must execute regardless of whether your test passed.
+ *
+ * <p>See the android.test package documentation (click the more... link)
+ * for a full description
+ */
+
+@Deprecated
+public interface TestCase extends Runnable
+{
+ /**
+ * Called before run() is called.
+ */
+ public void setUp(Context context);
+
+ /**
+ * Called after run() is called, even if run() threw an exception, but
+ * not if setUp() threw an execption.
+ */
+ public void tearDown();
+}
+
diff --git a/test-runner/android/test/TestCaseUtil.java b/test-runner/android/test/TestCaseUtil.java
new file mode 100644
index 0000000..3ba9711
--- /dev/null
+++ b/test-runner/android/test/TestCaseUtil.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2007 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.Lists;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import junit.runner.BaseTestRunner;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public class TestCaseUtil {
+
+ private TestCaseUtil() {
+ }
+
+ @SuppressWarnings("unchecked")
+ public static List<String> getTestCaseNames(Test test, boolean flatten) {
+ List<Test> tests = (List<Test>) getTests(test, flatten);
+ List<String> testCaseNames = Lists.newArrayList();
+ for (Test aTest : tests) {
+ testCaseNames.add(getTestName(aTest));
+ }
+ return testCaseNames;
+ }
+
+ public static List<? extends Test> getTests(Test test, boolean flatten) {
+ return getTests(test, flatten, new HashSet<Class<?>>());
+ }
+
+ private static List<? extends Test> getTests(Test test, boolean flatten,
+ Set<Class<?>> seen) {
+ List<Test> testCases = Lists.newArrayList();
+ if (test != null) {
+
+ Test workingTest = null;
+ /*
+ * If we want to run a single TestCase method only, we must not
+ * invoke the suite() method, because we will run all test methods
+ * of the class then.
+ */
+ if (test instanceof TestCase &&
+ ((TestCase)test).getName() == null) {
+ workingTest = invokeSuiteMethodIfPossible(test.getClass(),
+ seen);
+ }
+ if (workingTest == null) {
+ workingTest = test;
+ }
+
+ if (workingTest instanceof TestSuite) {
+ TestSuite testSuite = (TestSuite) workingTest;
+ Enumeration enumeration = testSuite.tests();
+ while (enumeration.hasMoreElements()) {
+ Test childTest = (Test) enumeration.nextElement();
+ if (flatten) {
+ testCases.addAll(getTests(childTest, flatten, seen));
+ } else {
+ testCases.add(childTest);
+ }
+ }
+ } else {
+ testCases.add(workingTest);
+ }
+ }
+ return testCases;
+ }
+
+ private static Test invokeSuiteMethodIfPossible(Class testClass,
+ Set<Class<?>> seen) {
+ try {
+ Method suiteMethod = testClass.getMethod(
+ BaseTestRunner.SUITE_METHODNAME, new Class[0]);
+ /*
+ * Additional check necessary: If a TestCase contains a suite()
+ * method that returns a TestSuite including the TestCase itself,
+ * we need to stop the recursion. We use a set of classes to
+ * remember which classes' suite() methods were already invoked.
+ */
+ if (Modifier.isStatic(suiteMethod.getModifiers())
+ && !seen.contains(testClass)) {
+ seen.add(testClass);
+ try {
+ return (Test) suiteMethod.invoke(null, (Object[]) null);
+ } catch (InvocationTargetException e) {
+ // do nothing
+ } catch (IllegalAccessException e) {
+ // do nothing
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ return null;
+ }
+
+ public static String getTestName(Test test) {
+ if (test instanceof TestCase) {
+ TestCase testCase = (TestCase) test;
+ return testCase.getName();
+ } else if (test instanceof TestSuite) {
+ TestSuite testSuite = (TestSuite) test;
+ String name = testSuite.getName();
+ if (name != null) {
+ int index = name.lastIndexOf(".");
+ if (index > -1) {
+ return name.substring(index + 1);
+ } else {
+ return name;
+ }
+ }
+ }
+ return "";
+ }
+
+ public static Test getTestAtIndex(TestSuite testSuite, int position) {
+ int index = 0;
+ Enumeration enumeration = testSuite.tests();
+ while (enumeration.hasMoreElements()) {
+ Test test = (Test) enumeration.nextElement();
+ if (index == position) {
+ return test;
+ }
+ index++;
+ }
+ return null;
+ }
+
+ public static TestSuite createTestSuite(Class<? extends Test> testClass)
+ throws InstantiationException, IllegalAccessException {
+
+ Test test = invokeSuiteMethodIfPossible(testClass,
+ new HashSet<Class<?>>());
+ if (test == null) {
+ return new TestSuite(testClass);
+
+ } else if (TestCase.class.isAssignableFrom(test.getClass())) {
+ TestSuite testSuite = new TestSuite(test.getClass().getName());
+ testSuite.addTest(test);
+ return testSuite;
+ }
+
+ return (TestSuite) test;
+ }
+}
diff --git a/test-runner/android/test/TestListActivity.java b/test-runner/android/test/TestListActivity.java
new file mode 100644
index 0000000..a076a70
--- /dev/null
+++ b/test-runner/android/test/TestListActivity.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2007 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.ListActivity;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Activity base class to use to implement your application's tests.
+ *
+ * <p>Implement the getTestSuite() method to return the name of your
+ * test suite class.
+ *
+ * <p>See the android.test package documentation (click the more... link)
+ * for a full description
+ *
+ * {@hide} Not needed for SDK
+ */
+public abstract class TestListActivity extends ListActivity {
+ /** Supplied in the intent extras if we are running performance tests. */
+ public static final String PERFORMANCE_TESTS = "android.test.performance";
+
+ /** "Mode" group in the menu. */
+ static final int MODE_GROUP = Menu.FIRST;
+
+ /** Our suite */
+ String mSuite;
+
+ /** Our children tests */
+ String[] mTests;
+
+ /** which mode, REGRESSION, PERFORMANCE or PROFILING */
+ private int mMode = TestRunner.REGRESSION;
+
+ /** "Regression" menu item */
+ private MenuItem mRegressionItem;
+
+ /** "Performance" menu item */
+ private MenuItem mPerformanceItem;
+
+ /** "Profiling" menu item */
+ private MenuItem mProfilingItem;
+
+ private final Comparator<String> sComparator = new Comparator<String>() {
+ public final int compare(String a, String b) {
+ String s1 = makeCompareName(a);
+ String s2 = makeCompareName(b);
+
+ return s1.compareToIgnoreCase(s2);
+ }
+ };
+
+ /**
+ * Constructor that doesn't do much.
+ */
+ public TestListActivity() {
+ super();
+ }
+
+ /**
+ * Subclasses should implement this to return the names of the classes
+ * of their tests.
+ *
+ * @return test suite class name
+ */
+ public abstract String getTestSuite();
+
+ /**
+ * Typical onCreate(Bundle icicle) implementation.
+ */
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Intent intent = getIntent();
+
+ mMode = intent.getIntExtra(TestListActivity.PERFORMANCE_TESTS, mMode);
+
+
+ if (intent.getAction().equals(Intent.ACTION_MAIN)) {
+ // if we were called as MAIN, get the test suites,
+ mSuite = getTestSuite();
+ } else if (intent.getAction().equals(Intent.ACTION_RUN)) {
+ // We should have been provided a status channel. Bail out and
+ // run the test instead. This is how the TestHarness gets us
+ // loaded in our process for "Run All Tests."
+ Intent ntent = new Intent(Intent.ACTION_RUN,
+ intent.getData() != null
+ ? intent.getData()
+ : Uri.parse(getTestSuite()));
+ ntent.setClassName("com.android.testharness",
+ "com.android.testharness.RunTest");
+ ntent.putExtras(intent);
+ ntent.putExtra("package", getPackageName());
+ startActivity(ntent);
+ finish();
+ return;
+ } else if (intent.getAction().equals(Intent.ACTION_VIEW)) {
+ // otherwise use the one in the intent
+ mSuite = intent.getData() != null ? intent.getData().toString()
+ : null;
+ }
+
+ String[] children = TestRunner.getChildren(this, mSuite);
+
+ Arrays.sort(children, sComparator);
+
+ int len = children.length;
+ mTests = new String[len];
+ System.arraycopy(children, 0, mTests, 0, len);
+
+ setTitle(TestRunner.getTitle(mSuite));
+
+ MatrixCursor cursor = new MatrixCursor(new String[] { "name", "_id" });
+ addTestRows(cursor);
+
+ CursorAdapter adapter = new SimpleCursorAdapter(
+ this,
+ com.android.internal.R.layout.simple_list_item_1,
+ cursor,
+ new String[] {"name"},
+ new int[] {com.android.internal.R.id.text1});
+
+ setListAdapter(adapter);
+ }
+
+ private void addTestRows(MatrixCursor cursor) {
+ int id = 0;
+ cursor.newRow().add("Run All").add(id++);
+ for (String test : mTests) {
+ String title = TestRunner.getTitle(test);
+ String prefix = TestRunner.isTestSuite(this, test)
+ ? "Browse " : "Run ";
+
+ // I'd rather do this with an icon column, but I don't know how
+ cursor.newRow().add(prefix + title).add(id++);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ mRegressionItem = menu.add(MODE_GROUP, -1, 0, "Regression Mode");
+ mPerformanceItem = menu.add(MODE_GROUP, -1, 0, "Performance Mode");
+ mProfilingItem = menu.add(MODE_GROUP, -1, 0, "Profiling Mode");
+ menu.setGroupCheckable(MODE_GROUP, true, true);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item == mRegressionItem) {
+ mMode = TestRunner.REGRESSION;
+ } else if (item == mPerformanceItem) {
+ mMode = TestRunner.PERFORMANCE;
+ } else if (item == mProfilingItem) {
+ mMode = TestRunner.PROFILING;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ switch (mMode) {
+ case TestRunner.REGRESSION:
+ mRegressionItem.setChecked(true);
+ break;
+
+ case TestRunner.PERFORMANCE:
+ mPerformanceItem.setChecked(true);
+ break;
+
+ case TestRunner.PROFILING:
+ mProfilingItem.setChecked(true);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Intent intent = new Intent();
+
+ if (position == 0) {
+ if (false) {
+ intent.setClassName("com.android.testharness",
+ "com.android.testharness.RunAll");
+ intent.putExtra("tests", new String[]{mSuite});
+ } else {
+ intent.setClassName("com.android.testharness",
+ "com.android.testharness.RunTest");
+ intent.setAction(Intent.ACTION_RUN);
+ intent.setData(Uri.parse(mSuite));
+ }
+ } else {
+ String test = mTests[position - 1];
+ if (TestRunner.isTestSuite(this, test)) {
+ intent.setClassName(getPackageName(), this.getClass().getName());
+ intent.setAction(Intent.ACTION_VIEW);
+ } else {
+ intent.setClassName("com.android.testharness",
+ "com.android.testharness.RunTest");
+ }
+ intent.setData(Uri.parse(test));
+ }
+
+ intent.putExtra(PERFORMANCE_TESTS, mMode);
+ intent.putExtra("package", getPackageName());
+ startActivity(intent);
+ }
+
+ private String makeCompareName(String s) {
+ int index = s.lastIndexOf('.');
+
+ if (index == -1) {
+ return s;
+ }
+
+ return s.substring(index + 1);
+ }
+}
diff --git a/test-runner/android/test/TestLocationProvider.java b/test-runner/android/test/TestLocationProvider.java
new file mode 100644
index 0000000..00c1ce8
--- /dev/null
+++ b/test-runner/android/test/TestLocationProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2007 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.location.Criteria;
+import android.location.Location;
+import android.location.LocationProviderImpl;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public class TestLocationProvider extends LocationProviderImpl {
+
+ public static final String PROVIDER_NAME = "test";
+ public static final double LAT = 0;
+ public static final double LON = 1;
+ public static final double ALTITUDE = 10000;
+ public static final float SPEED = 10;
+ public static final float BEARING = 1;
+ public static final int STATUS = AVAILABLE;
+
+ private Location mLocation;
+ private boolean mEnabled;
+
+ public TestLocationProvider() {
+ super(PROVIDER_NAME);
+ mLocation = new Location(PROVIDER_NAME);
+ updateLocation();
+ }
+
+ //LocationProvider methods
+
+ @Override
+ public int getAccuracy() {
+ return Criteria.ACCURACY_COARSE;
+ }
+
+ @Override
+ public int getPowerRequirement() {
+ return Criteria.NO_REQUIREMENT;
+ }
+
+ @Override
+ public boolean hasMonetaryCost() {
+ return false;
+ }
+
+ @Override
+ public boolean requiresCell() {
+ return false;
+ }
+
+ @Override
+ public boolean requiresNetwork() {
+ return false;
+ }
+
+ @Override
+ public boolean requiresSatellite() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsAltitude() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsBearing() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsSpeed() {
+ return true;
+ }
+
+ //LocationProviderImpl methods
+ @Override
+ public void disable() {
+ mEnabled = false;
+ }
+
+ @Override
+ public void enable() {
+ mEnabled = true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean getLocation(Location l) {
+ updateLocation();
+ l.set(mLocation);
+ return true;
+ }
+
+ @Override
+ public int getStatus(Bundle extras) {
+ return STATUS;
+ }
+
+ private void updateLocation() {
+ long time = SystemClock.uptimeMillis();
+ long multiplier = (time/5000)%500000;
+ mLocation.setLatitude(LAT*multiplier);
+ mLocation.setLongitude(LON*multiplier);
+ mLocation.setAltitude(ALTITUDE);
+ mLocation.setSpeed(SPEED);
+ mLocation.setBearing(BEARING*multiplier);
+
+ Bundle extras = new Bundle();
+ extras.putInt("extraTest", 24);
+ mLocation.setExtras(extras);
+ mLocation.setTime(time);
+ }
+
+}
diff --git a/test-runner/android/test/TestPrinter.java b/test-runner/android/test/TestPrinter.java
new file mode 100644
index 0000000..37bd721
--- /dev/null
+++ b/test-runner/android/test/TestPrinter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2006 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.util.Log;
+import junit.framework.Test;
+import junit.framework.TestListener;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Prints the test progress to stdout. Android includes a default
+ * implementation and calls these methods to print out test progress; you
+ * probably will not need to create or extend this class or call its methods manually.
+ * See the full {@link android.test} package description for information about
+ * getting test results.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class TestPrinter implements TestRunner.Listener, TestListener {
+
+ private String mTag;
+ private boolean mOnlyFailures;
+ private Set<String> mFailedTests = new HashSet<String>();
+
+
+ public TestPrinter(String tag, boolean onlyFailures) {
+ mTag = tag;
+ mOnlyFailures = onlyFailures;
+ }
+
+ public void started(String className) {
+ if (!mOnlyFailures) {
+ Log.i(mTag, "started: " + className);
+ }
+ }
+
+ public void finished(String className) {
+ if (!mOnlyFailures) {
+ Log.i(mTag, "finished: " + className);
+ }
+ }
+
+ public void performance(String className,
+ long itemTimeNS, int iterations,
+ List<TestRunner.IntermediateTime> intermediates) {
+ Log.i(mTag, "perf: " + className + " = " + itemTimeNS + "ns/op (done "
+ + iterations + " times)");
+ if (intermediates != null && intermediates.size() > 0) {
+ int N = intermediates.size();
+ for (int i = 0; i < N; i++) {
+ TestRunner.IntermediateTime time = intermediates.get(i);
+ Log.i(mTag, " intermediate: " + time.name + " = "
+ + time.timeInNS + "ns");
+ }
+ }
+ }
+
+ public void passed(String className) {
+ if (!mOnlyFailures) {
+ Log.i(mTag, "passed: " + className);
+ }
+ }
+
+ public void failed(String className, Throwable exception) {
+ Log.i(mTag, "failed: " + className);
+ Log.i(mTag, "----- begin exception -----");
+ Log.i(mTag, "", exception);
+ Log.i(mTag, "----- end exception -----");
+ }
+
+ private void failed(Test test, Throwable t) {
+ mFailedTests.add(test.toString());
+ failed(test.toString(), t);
+ }
+
+ public void addError(Test test, Throwable t) {
+ failed(test, t);
+ }
+
+ public void addFailure(Test test, junit.framework.AssertionFailedError t) {
+ failed(test, t);
+ }
+
+ public void endTest(Test test) {
+ finished(test.toString());
+ if (!mFailedTests.contains(test.toString())) {
+ passed(test.toString());
+ }
+ mFailedTests.remove(test.toString());
+ }
+
+ public void startTest(Test test) {
+ started(test.toString());
+ }
+}
diff --git a/test-runner/android/test/TestRecorder.java b/test-runner/android/test/TestRecorder.java
new file mode 100644
index 0000000..7c368a0
--- /dev/null
+++ b/test-runner/android/test/TestRecorder.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2006 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.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.test.TestRunner.IntermediateTime;
+import android.util.Log;
+import junit.framework.Test;
+import junit.framework.TestListener;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class TestRecorder implements TestRunner.Listener, TestListener {
+ private static final int DATABASE_VERSION = 1;
+ private static SQLiteDatabase sDb;
+ private Set<String> mFailedTests = new HashSet<String>();
+
+ private static SQLiteDatabase getDatabase() {
+ if (sDb == null) {
+ File dir = new File(Environment.getDataDirectory(), "test_results");
+
+ /* TODO: add a DB version number and bootstrap/upgrade methods
+ * if the format of the table changes.
+ */
+ String dbName = "TestHarness.db";
+ File file = new File(dir, dbName);
+ sDb = SQLiteDatabase.openOrCreateDatabase(file.getPath(), null);
+
+ if (sDb.getVersion() == 0) {
+ int code = FileUtils.setPermissions(file.getPath(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR |
+ FileUtils.S_IRGRP | FileUtils.S_IWGRP |
+ FileUtils.S_IROTH | FileUtils.S_IWOTH, -1, -1);
+
+ if (code != 0) {
+ Log.w("TestRecorder",
+ "Set permissions for " + file.getPath() + " returned = " + code);
+ }
+
+ try {
+ sDb.execSQL("CREATE TABLE IF NOT EXISTS tests (_id INT PRIMARY KEY," +
+ "name TEXT," +
+ "result TEXT," +
+ "exception TEXT," +
+ "started INTEGER," +
+ "finished INTEGER," +
+ "time INTEGER," +
+ "iterations INTEGER," +
+ "allocations INTEGER," +
+ "parent INTEGER);");
+ sDb.setVersion(DATABASE_VERSION);
+ } catch (Exception e) {
+ Log.e("TestRecorder", "failed to create table 'tests'", e);
+ sDb = null;
+ }
+ }
+ }
+
+ return sDb;
+ }
+
+ public TestRecorder() {
+ }
+
+ public void started(String className) {
+ ContentValues map = new ContentValues(2);
+ map.put("name", className);
+ map.put("started", System.currentTimeMillis());
+
+ // try to update the row first in case we've ran this test before.
+ int rowsAffected = getDatabase().update("tests", map, "name = '" + className + "'", null);
+
+ if (rowsAffected == 0) {
+ getDatabase().insert("tests", null, map);
+ }
+ }
+
+ public void finished(String className) {
+ ContentValues map = new ContentValues(1);
+ map.put("finished", System.currentTimeMillis());
+
+ getDatabase().update("tests", map, "name = '" + className + "'", null);
+ }
+
+ public void performance(String className, long itemTimeNS, int iterations, List<IntermediateTime> intermediates) {
+ ContentValues map = new ContentValues();
+ map.put("time", itemTimeNS);
+ map.put("iterations", iterations);
+
+ getDatabase().update("tests", map, "name = '" + className + "'", null);
+
+ if (intermediates != null && intermediates.size() > 0) {
+ int n = intermediates.size();
+ for (int i = 0; i < n; i++) {
+ TestRunner.IntermediateTime time = intermediates.get(i);
+
+ getDatabase().execSQL("INSERT INTO tests (name, time, parent) VALUES ('" +
+ time.name + "', " + time.timeInNS + ", " +
+ "(SELECT _id FROM tests WHERE name = '" + className + "'));");
+ }
+ }
+ }
+
+ public void passed(String className) {
+ ContentValues map = new ContentValues();
+ map.put("result", "passed");
+
+ getDatabase().update("tests", map, "name = '" + className + "'", null);
+ }
+
+ public void failed(String className, Throwable exception) {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(stringWriter);
+ try {
+ exception.printStackTrace(printWriter);
+ } finally {
+ printWriter.close();
+ }
+ ContentValues map = new ContentValues();
+ map.put("result", "failed");
+ map.put("exception", stringWriter.toString());
+
+ getDatabase().update("tests", map, "name = '" + className + "'", null);
+ }
+
+ /**
+ * Reports a test case failure.
+ *
+ * @param className Name of the class/test.
+ * @param reason Reason for failure.
+ */
+ public void failed(String className, String reason) {
+ ContentValues map = new ContentValues();
+ map.put("result", "failed");
+ // The reason is put as the exception.
+ map.put("exception", reason);
+ getDatabase().update("tests", map, "name = '" + className + "'", null);
+ }
+
+ public void addError(Test test, Throwable t) {
+ mFailedTests.add(test.toString());
+ failed(test.toString(), t);
+ }
+
+ public void addFailure(Test test, junit.framework.AssertionFailedError t) {
+ mFailedTests.add(test.toString());
+ failed(test.toString(), t.getMessage());
+ }
+
+ public void endTest(Test test) {
+ finished(test.toString());
+ if (!mFailedTests.contains(test.toString())) {
+ passed(test.toString());
+ }
+ mFailedTests.remove(test.toString());
+ }
+
+ public void startTest(Test test) {
+ started(test.toString());
+ }
+}
diff --git a/test-runner/android/test/TestRunner.java b/test-runner/android/test/TestRunner.java
new file mode 100644
index 0000000..efa2480
--- /dev/null
+++ b/test-runner/android/test/TestRunner.java
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2006 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.content.Context;
+import android.util.Log;
+import android.os.Debug;
+import android.os.SystemClock;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestSuite;
+import junit.framework.TestListener;
+import junit.framework.Test;
+import junit.framework.TestResult;
+import com.google.android.collect.Lists;
+
+/**
+ * Support class that actually runs a test. Android uses this class,
+ * and you probably will not need to instantiate, extend, or call this
+ * class yourself. See the full {@link android.test} package description
+ * to learn more about testing Android applications.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class TestRunner implements PerformanceTestCase.Intermediates {
+ public static final int REGRESSION = 0;
+ public static final int PERFORMANCE = 1;
+ public static final int PROFILING = 2;
+
+ public static final int CLEARSCREEN = 0;
+ private static final String TAG = "TestHarness";
+ private Context mContext;
+
+ private int mMode = REGRESSION;
+
+ private List<Listener> mListeners = Lists.newArrayList();
+ private int mPassed;
+ private int mFailed;
+
+ private int mInternalIterations;
+ private long mStartTime;
+ private long mEndTime;
+
+ private String mClassName;
+
+ List<IntermediateTime> mIntermediates = null;
+
+ private static Class mRunnableClass;
+ private static Class mJUnitClass;
+
+ static {
+ try {
+ mRunnableClass = Class.forName("java.lang.Runnable", false, null);
+ mJUnitClass = Class.forName("junit.framework.TestCase", false, null);
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException("shouldn't happen", ex);
+ }
+ }
+
+ public class JunitTestSuite extends TestSuite implements TestListener {
+ boolean mError = false;
+
+ public JunitTestSuite() {
+ super();
+ }
+
+ public void run(TestResult result) {
+ result.addListener(this);
+ super.run(result);
+ result.removeListener(this);
+ }
+
+ /**
+ * Implemented method of the interface TestListener which will listen for the
+ * start of a test.
+ *
+ * @param test
+ */
+ public void startTest(Test test) {
+ started(test.toString());
+ }
+
+ /**
+ * Implemented method of the interface TestListener which will listen for the
+ * end of the test.
+ *
+ * @param test
+ */
+ public void endTest(Test test) {
+ finished(test.toString());
+ if (!mError) {
+ passed(test.toString());
+ }
+ }
+
+ /**
+ * Implemented method of the interface TestListener which will listen for an
+ * mError while running the test.
+ *
+ * @param test
+ */
+ public void addError(Test test, Throwable t) {
+ mError = true;
+ failed(test.toString(), t);
+ }
+
+ public void addFailure(Test test, junit.framework.AssertionFailedError t) {
+ mError = true;
+ failed(test.toString(), t);
+ }
+ }
+
+ /**
+ * Listener.performance() 'intermediates' argument is a list of these.
+ */
+ public static class IntermediateTime {
+ public IntermediateTime(String name, long timeInNS) {
+ this.name = name;
+ this.timeInNS = timeInNS;
+ }
+
+ public String name;
+ public long timeInNS;
+ }
+
+ /**
+ * Support class that receives status on test progress. You should not need to
+ * extend this interface yourself.
+ */
+ public interface Listener {
+ void started(String className);
+ void finished(String className);
+ void performance(String className,
+ long itemTimeNS, int iterations,
+ List<IntermediateTime> itermediates);
+ void passed(String className);
+ void failed(String className, Throwable execption);
+ }
+
+ public TestRunner(Context context) {
+ mContext = context;
+ }
+
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ public void startProfiling() {
+ File file = new File("/tmp/trace");
+ file.mkdir();
+ String base = "/tmp/trace/" + mClassName + ".dmtrace";
+ Debug.startMethodTracing(base, 8 * 1024 * 1024);
+ }
+
+ public void finishProfiling() {
+ Debug.stopMethodTracing();
+ }
+
+ private void started(String className) {
+
+ int count = mListeners.size();
+ for (int i = 0; i < count; i++) {
+ mListeners.get(i).started(className);
+ }
+ }
+
+ private void finished(String className) {
+ int count = mListeners.size();
+ for (int i = 0; i < count; i++) {
+ mListeners.get(i).finished(className);
+ }
+ }
+
+ private void performance(String className,
+ long itemTimeNS,
+ int iterations,
+ List<IntermediateTime> intermediates) {
+ int count = mListeners.size();
+ for (int i = 0; i < count; i++) {
+ mListeners.get(i).performance(className,
+ itemTimeNS,
+ iterations,
+ intermediates);
+ }
+ }
+
+ public void passed(String className) {
+ mPassed++;
+ int count = mListeners.size();
+ for (int i = 0; i < count; i++) {
+ mListeners.get(i).passed(className);
+ }
+ }
+
+ public void failed(String className, Throwable exception) {
+ mFailed++;
+ int count = mListeners.size();
+ for (int i = 0; i < count; i++) {
+ mListeners.get(i).failed(className, exception);
+ }
+ }
+
+ public int passedCount() {
+ return mPassed;
+ }
+
+ public int failedCount() {
+ return mFailed;
+ }
+
+ public void run(String[] classes) {
+ for (String cl : classes) {
+ run(cl);
+ }
+ }
+
+ public void setInternalIterations(int count) {
+ mInternalIterations = count;
+ }
+
+ public void startTiming(boolean realTime) {
+ if (realTime) {
+ mStartTime = System.currentTimeMillis();
+ } else {
+ mStartTime = SystemClock.currentThreadTimeMillis();
+ }
+ }
+
+ public void addIntermediate(String name) {
+ addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000);
+ }
+
+ public void addIntermediate(String name, long timeInNS) {
+ mIntermediates.add(new IntermediateTime(name, timeInNS));
+ }
+
+ public void finishTiming(boolean realTime) {
+ if (realTime) {
+ mEndTime = System.currentTimeMillis();
+ } else {
+ mEndTime = SystemClock.currentThreadTimeMillis();
+ }
+ }
+
+ public void setPerformanceMode(int mode) {
+ mMode = mode;
+ }
+
+ private void missingTest(String className, Throwable e) {
+ started(className);
+ finished(className);
+ failed(className, e);
+ }
+
+ /*
+ This class determines if more suites are added to this class then adds all individual
+ test classes to a test suite for run
+ */
+ public void run(String className) {
+ try {
+ mClassName = className;
+ Class clazz = mContext.getClassLoader().loadClass(className);
+ Method method = getChildrenMethod(clazz);
+ if (method != null) {
+ String[] children = getChildren(method);
+ run(children);
+ } else if (mRunnableClass.isAssignableFrom(clazz)) {
+ Runnable test = (Runnable) clazz.newInstance();
+ TestCase testcase = null;
+ if (test instanceof TestCase) {
+ testcase = (TestCase) test;
+ }
+ Throwable e = null;
+ boolean didSetup = false;
+ started(className);
+ try {
+ if (testcase != null) {
+ testcase.setUp(mContext);
+ didSetup = true;
+ }
+ if (mMode == PERFORMANCE) {
+ runInPerformanceMode(test, className, false, className);
+ } else if (mMode == PROFILING) {
+ //Need a way to mark a test to be run in profiling mode or not.
+ startProfiling();
+ test.run();
+ finishProfiling();
+ } else {
+ test.run();
+ }
+ } catch (Throwable ex) {
+ e = ex;
+ }
+ if (testcase != null && didSetup) {
+ try {
+ testcase.tearDown();
+ } catch (Throwable ex) {
+ e = ex;
+ }
+ }
+ finished(className);
+ if (e == null) {
+ passed(className);
+ } else {
+ failed(className, e);
+ }
+ } else if (mJUnitClass.isAssignableFrom(clazz)) {
+ Throwable e = null;
+ //Create a Junit Suite.
+ JunitTestSuite suite = new JunitTestSuite();
+ Method[] methods = getAllTestMethods(clazz);
+ for (Method m : methods) {
+ junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
+ test.setName(m.getName());
+
+ if (test instanceof AndroidTestCase) {
+ AndroidTestCase testcase = (AndroidTestCase) test;
+ try {
+ testcase.setContext(mContext);
+ } catch (Exception ex) {
+ Log.i("TestHarness", ex.toString());
+ }
+ }
+ suite.addTest(test);
+ }
+ if (mMode == PERFORMANCE) {
+ final int testCount = suite.testCount();
+
+ for (int j = 0; j < testCount; j++) {
+ Test test = suite.testAt(j);
+ started(test.toString());
+ try {
+ runInPerformanceMode(test, className, true, test.toString());
+ } catch (Throwable ex) {
+ e = ex;
+ }
+ finished(test.toString());
+ if (e == null) {
+ passed(test.toString());
+ } else {
+ failed(test.toString(), e);
+ }
+ }
+ } else if (mMode == PROFILING) {
+ //Need a way to mark a test to be run in profiling mode or not.
+ startProfiling();
+ junit.textui.TestRunner.run(suite);
+ finishProfiling();
+ } else {
+ junit.textui.TestRunner.run(suite);
+ }
+ } else {
+ System.out.println("Test wasn't Runnable and didn't have a"
+ + " children method: " + className);
+ }
+ } catch (ClassNotFoundException e) {
+ Log.e("ClassNotFoundException for " + className, e.toString());
+ if (isJunitTest(className)) {
+ runSingleJunitTest(className);
+ } else {
+ missingTest(className, e);
+ }
+ } catch (InstantiationException e) {
+ System.out.println("InstantiationException for " + className);
+ missingTest(className, e);
+ } catch (IllegalAccessException e) {
+ System.out.println("IllegalAccessException for " + className);
+ missingTest(className, e);
+ }
+ }
+
+ public void runInPerformanceMode(Object testCase, String className, boolean junitTest,
+ String testNameInDb) throws Exception {
+ boolean increaseIterations = true;
+ int iterations = 1;
+ long duration = 0;
+ mIntermediates = null;
+
+ mInternalIterations = 1;
+ Class clazz = mContext.getClassLoader().loadClass(className);
+ Object perftest = clazz.newInstance();
+
+ PerformanceTestCase perftestcase = null;
+ if (perftest instanceof PerformanceTestCase) {
+ perftestcase = (PerformanceTestCase) perftest;
+ // only run the test if it is not marked as a performance only test
+ if (mMode == REGRESSION && perftestcase.isPerformanceOnly()) return;
+ }
+
+ // First force GCs, to avoid GCs happening during out
+ // test and skewing its time.
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+
+ if (perftestcase != null) {
+ mIntermediates = new ArrayList<IntermediateTime>();
+ iterations = perftestcase.startPerformance(this);
+ if (iterations > 0) {
+ increaseIterations = false;
+ } else {
+ iterations = 1;
+ }
+ }
+
+ // Pause briefly to let things settle down...
+ Thread.sleep(1000);
+ do {
+ mEndTime = 0;
+ if (increaseIterations) {
+ // Test case does not implement
+ // PerformanceTestCase or returned 0 iterations,
+ // so we take care of measure the whole test time.
+ mStartTime = SystemClock.currentThreadTimeMillis();
+ } else {
+ // Try to make it obvious if the test case
+ // doesn't call startTiming().
+ mStartTime = 0;
+ }
+
+ if (junitTest) {
+ for (int i = 0; i < iterations; i++) {
+ junit.textui.TestRunner.run((junit.framework.Test) testCase);
+ }
+ } else {
+ Runnable test = (Runnable) testCase;
+ for (int i = 0; i < iterations; i++) {
+ test.run();
+ }
+ }
+
+ long endTime = mEndTime;
+ if (endTime == 0) {
+ endTime = SystemClock.currentThreadTimeMillis();
+ }
+
+ duration = endTime - mStartTime;
+ if (!increaseIterations) {
+ break;
+ }
+ if (duration <= 1) {
+ iterations *= 1000;
+ } else if (duration <= 10) {
+ iterations *= 100;
+ } else if (duration < 100) {
+ iterations *= 10;
+ } else if (duration < 1000) {
+ iterations *= (int) ((1000 / duration) + 2);
+ } else {
+ break;
+ }
+ } while (true);
+
+ if (duration != 0) {
+ iterations *= mInternalIterations;
+ performance(testNameInDb, (duration * 1000000) / iterations,
+ iterations, mIntermediates);
+ }
+ }
+
+ public void runSingleJunitTest(String className) {
+ Throwable excep = null;
+ int index = className.lastIndexOf('$');
+ String testName = "";
+ String originalClassName = className;
+ if (index >= 0) {
+ className = className.substring(0, index);
+ testName = originalClassName.substring(index + 1);
+ }
+ try {
+ Class clazz = mContext.getClassLoader().loadClass(className);
+ if (mJUnitClass.isAssignableFrom(clazz)) {
+ junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
+ JunitTestSuite newSuite = new JunitTestSuite();
+ test.setName(testName);
+
+ if (test instanceof AndroidTestCase) {
+ AndroidTestCase testcase = (AndroidTestCase) test;
+ try {
+ testcase.setContext(mContext);
+ } catch (Exception ex) {
+ Log.w(TAG, "Exception encountered while trying to set the context.", ex);
+ }
+ }
+ newSuite.addTest(test);
+
+ if (mMode == PERFORMANCE) {
+ try {
+ started(test.toString());
+ runInPerformanceMode(test, className, true, test.toString());
+ finished(test.toString());
+ if (excep == null) {
+ passed(test.toString());
+ } else {
+ failed(test.toString(), excep);
+ }
+ } catch (Throwable ex) {
+ excep = ex;
+ }
+
+ } else if (mMode == PROFILING) {
+ startProfiling();
+ junit.textui.TestRunner.run(newSuite);
+ finishProfiling();
+ } else {
+ junit.textui.TestRunner.run(newSuite);
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ Log.e("TestHarness", "No test case to run", e);
+ } catch (IllegalAccessException e) {
+ Log.e("TestHarness", "Illegal Access Exception", e);
+ } catch (InstantiationException e) {
+ Log.e("TestHarness", "Instantiation Exception", e);
+ }
+ }
+
+ public static Method getChildrenMethod(Class clazz) {
+ try {
+ return clazz.getMethod("children", (Class[]) null);
+ } catch (NoSuchMethodException e) {
+ }
+
+ return null;
+ }
+
+ public static Method getChildrenMethod(Context c, String className) {
+ try {
+ return getChildrenMethod(c.getClassLoader().loadClass(className));
+ } catch (ClassNotFoundException e) {
+ }
+ return null;
+ }
+
+ public static String[] getChildren(Context c, String className) {
+ Method m = getChildrenMethod(c, className);
+ String[] testChildren = getTestChildren(c, className);
+ if (m == null & testChildren == null) {
+ throw new RuntimeException("couldn't get children method for "
+ + className);
+ }
+ if (m != null) {
+ String[] children = getChildren(m);
+ if (testChildren != null) {
+ String[] allChildren = new String[testChildren.length + children.length];
+ System.arraycopy(children, 0, allChildren, 0, children.length);
+ System.arraycopy(testChildren, 0, allChildren, children.length, testChildren.length);
+ return allChildren;
+ } else {
+ return children;
+ }
+ } else {
+ if (testChildren != null) {
+ return testChildren;
+ }
+ }
+ return null;
+ }
+
+ public static String[] getChildren(Method m) {
+ try {
+ if (!Modifier.isStatic(m.getModifiers())) {
+ throw new RuntimeException("children method is not static");
+ }
+ return (String[]) m.invoke(null, (Object[]) null);
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ return new String[0];
+ }
+
+ public static String[] getTestChildren(Context c, String className) {
+ try {
+ Class clazz = c.getClassLoader().loadClass(className);
+
+ if (mJUnitClass.isAssignableFrom(clazz)) {
+ return getTestChildren(clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ Log.e("TestHarness", "No class found", e);
+ }
+ return null;
+ }
+
+ public static String[] getTestChildren(Class clazz) {
+ Method[] methods = getAllTestMethods(clazz);
+
+ String[] onScreenTestNames = new String[methods.length];
+ int index = 0;
+ for (Method m : methods) {
+ onScreenTestNames[index] = clazz.getName() + "$" + m.getName();
+ index++;
+ }
+ return onScreenTestNames;
+ }
+
+ public static Method[] getAllTestMethods(Class clazz) {
+ Method[] allMethods = clazz.getDeclaredMethods();
+ int numOfMethods = 0;
+ for (Method m : allMethods) {
+ boolean mTrue = isTestMethod(m);
+ if (mTrue) {
+ numOfMethods++;
+ }
+ }
+ int index = 0;
+ Method[] testMethods = new Method[numOfMethods];
+ for (Method m : allMethods) {
+ boolean mTrue = isTestMethod(m);
+ if (mTrue) {
+ testMethods[index] = m;
+ index++;
+ }
+ }
+ return testMethods;
+ }
+
+ private static boolean isTestMethod(Method m) {
+ return m.getName().startsWith("test") &&
+ m.getReturnType() == void.class &&
+ m.getParameterTypes().length == 0;
+ }
+
+ public static int countJunitTests(Class clazz) {
+ Method[] allTestMethods = getAllTestMethods(clazz);
+ int numberofMethods = allTestMethods.length;
+
+ return numberofMethods;
+ }
+
+ public static boolean isTestSuite(Context c, String className) {
+ boolean childrenMethods = getChildrenMethod(c, className) != null;
+
+ try {
+ Class clazz = c.getClassLoader().loadClass(className);
+ if (mJUnitClass.isAssignableFrom(clazz)) {
+ int numTests = countJunitTests(clazz);
+ if (numTests > 0)
+ childrenMethods = true;
+ }
+ } catch (ClassNotFoundException e) {
+ }
+ return childrenMethods;
+ }
+
+
+ public boolean isJunitTest(String className) {
+ int index = className.lastIndexOf('$');
+ if (index >= 0) {
+ className = className.substring(0, index);
+ }
+ try {
+ Class clazz = mContext.getClassLoader().loadClass(className);
+ if (mJUnitClass.isAssignableFrom(clazz)) {
+ return true;
+ }
+ } catch (ClassNotFoundException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of tests that will be run if you try to do this.
+ */
+ public static int countTests(Context c, String className) {
+ try {
+ Class clazz = c.getClassLoader().loadClass(className);
+ Method method = getChildrenMethod(clazz);
+ if (method != null) {
+
+ String[] children = getChildren(method);
+ int rv = 0;
+ for (String child : children) {
+ rv += countTests(c, child);
+ }
+ return rv;
+ } else if (mRunnableClass.isAssignableFrom(clazz)) {
+ return 1;
+ } else if (mJUnitClass.isAssignableFrom(clazz)) {
+ return countJunitTests(clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ return 1; // this gets the count right, because either this test
+ // is missing, and it will fail when run or it is a single Junit test to be run.
+ }
+ return 0;
+ }
+
+ /**
+ * Returns a title to display given the className of a test.
+ * <p/>
+ * <p>Currently this function just returns the portion of the
+ * class name after the last '.'
+ */
+ public static String getTitle(String className) {
+ int indexDot = className.lastIndexOf('.');
+ int indexDollar = className.lastIndexOf('$');
+ int index = indexDot > indexDollar ? indexDot : indexDollar;
+ if (index >= 0) {
+ className = className.substring(index + 1);
+ }
+ return className;
+ }
+}
diff --git a/test-runner/android/test/TestRunnerView.java b/test-runner/android/test/TestRunnerView.java
new file mode 100644
index 0000000..be90951
--- /dev/null
+++ b/test-runner/android/test/TestRunnerView.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 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.util.List;
+
+/**
+ * @hide - This is part of a framework that is under development and should not be used for
+ * active development.
+ */
+public interface TestRunnerView {
+ void setTestNames(List<String> testNames);
+
+ void setItemColorAt(int position, int color);
+
+ void setFailureCount(int failureCount);
+
+ void setRunCount(int runCount);
+
+ void setErrorCount(int errorCount);
+
+ void setTotalCount(int totalCount);
+
+ void setProgressBarColor(int color);
+
+ void testsCompleted();
+}
diff --git a/test-runner/android/test/TestSuiteProvider.java b/test-runner/android/test/TestSuiteProvider.java
new file mode 100644
index 0000000..dc9ce6e
--- /dev/null
+++ b/test-runner/android/test/TestSuiteProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.TestSuite;
+
+/**
+ * Implementors will know how to get a test suite.
+ */
+public interface TestSuiteProvider {
+
+ TestSuite getTestSuite();
+}
diff --git a/test-runner/android/test/TouchUtils.java b/test-runner/android/test/TouchUtils.java
new file mode 100644
index 0000000..52d2ee8
--- /dev/null
+++ b/test-runner/android/test/TouchUtils.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2007 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.app.Instrumentation;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+
+/**
+ * Reusable methods for generating touch events. These methods can be used with
+ * InstrumentationTestCase or ActivityInstrumentationTestCase2 to simulate user interaction with
+ * the application through a touch screen.
+ */
+public class TouchUtils {
+
+ /**
+ * Simulate touching in the center of the screen and dragging one quarter of the way down
+ * @param test The test case that is being run
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void dragQuarterScreenDown(ActivityInstrumentationTestCase test) {
+ dragQuarterScreenDown(test, test.getActivity());
+ }
+
+ /**
+ * Simulate touching in the center of the screen and dragging one quarter of the way down
+ * @param test The test case that is being run
+ * @param activity The activity that is in the foreground of the test case
+ */
+ public static void dragQuarterScreenDown(InstrumentationTestCase test, Activity activity) {
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ int screenHeight = display.getHeight();
+ int screenWidth = display.getWidth();
+
+ final float x = screenWidth / 2.0f;
+ final float fromY = screenHeight * 0.5f;
+ final float toY = screenHeight * 0.75f;
+
+ drag(test, x, x, fromY, toY, 4);
+ }
+
+ /**
+ * Simulate touching in the center of the screen and dragging one quarter of the way up
+ * @param test The test case that is being run
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void dragQuarterScreenUp(ActivityInstrumentationTestCase test) {
+ dragQuarterScreenUp(test, test.getActivity());
+ }
+
+ /**
+ * Simulate touching in the center of the screen and dragging one quarter of the way up
+ * @param test The test case that is being run
+ * @param activity The activity that is in the foreground of the test case
+ */
+ public static void dragQuarterScreenUp(InstrumentationTestCase test, Activity activity) {
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ int screenHeight = display.getHeight();
+ int screenWidth = display.getWidth();
+
+ final float x = screenWidth / 2.0f;
+ final float fromY = screenHeight * 0.5f;
+ final float toY = screenHeight * 0.25f;
+
+ drag(test, x, x, fromY, toY, 4);
+ }
+
+ /**
+ * Scroll a ViewGroup to the bottom by repeatedly calling
+ * {@link #dragQuarterScreenUp(InstrumentationTestCase, Activity)}
+ *
+ * @param test The test case that is being run
+ * @param v The ViewGroup that should be dragged
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void scrollToBottom(ActivityInstrumentationTestCase test, ViewGroup v) {
+ scrollToBottom(test, test.getActivity(), v);
+ }
+
+ /**
+ * Scroll a ViewGroup to the bottom by repeatedly calling
+ * {@link #dragQuarterScreenUp(InstrumentationTestCase, Activity)}
+ *
+ * @param test The test case that is being run
+ * @param activity The activity that is in the foreground of the test case
+ * @param v The ViewGroup that should be dragged
+ */
+ public static void scrollToBottom(InstrumentationTestCase test, Activity activity,
+ ViewGroup v) {
+ View firstChild;
+ int firstId = Integer.MIN_VALUE;
+ int firstTop = Integer.MIN_VALUE;
+ int prevId;
+ int prevTop;
+ do {
+ prevId = firstId;
+ prevTop = firstTop;
+ TouchUtils.dragQuarterScreenUp(test, activity);
+ firstChild = v.getChildAt(0);
+ firstId = firstChild.getId();
+ firstTop = firstChild.getTop();
+ } while ((prevId != firstId) || (prevTop != firstTop));
+ }
+
+ /**
+ * Scroll a ViewGroup to the top by repeatedly calling
+ * {@link #dragQuarterScreenDown(InstrumentationTestCase, Activity)}
+ *
+ * @param test The test case that is being run
+ * @param v The ViewGroup that should be dragged
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void scrollToTop(ActivityInstrumentationTestCase test, ViewGroup v) {
+ scrollToTop(test, test.getActivity(), v);
+ }
+
+ /**
+ * Scroll a ViewGroup to the top by repeatedly calling
+ * {@link #dragQuarterScreenDown(InstrumentationTestCase, Activity)}
+ *
+ * @param test The test case that is being run
+ * @param activity The activity that is in the foreground of the test case
+ * @param v The ViewGroup that should be dragged
+ */
+ public static void scrollToTop(InstrumentationTestCase test, Activity activity, ViewGroup v) {
+ View firstChild;
+ int firstId = Integer.MIN_VALUE;
+ int firstTop = Integer.MIN_VALUE;
+ int prevId;
+ int prevTop;
+ do {
+ prevId = firstId;
+ prevTop = firstTop;
+ TouchUtils.dragQuarterScreenDown(test, activity);
+ firstChild = v.getChildAt(0);
+ firstId = firstChild.getId();
+ firstTop = firstChild.getTop();
+ } while ((prevId != firstId) || (prevTop != firstTop));
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the bottom of the screen.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v) {
+ dragViewToBottom(test, test.getActivity(), v, 4);
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the bottom of the screen.
+ *
+ * @param test The test case that is being run
+ * @param activity The activity that is in the foreground of the test case
+ * @param v The view that should be dragged
+ */
+ public static void dragViewToBottom(InstrumentationTestCase test, Activity activity, View v) {
+ dragViewToBottom(test, activity, v, 4);
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the bottom of the screen.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param stepCount How many move steps to include in the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v,
+ int stepCount) {
+ dragViewToBottom(test, test.getActivity(), v, stepCount);
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the bottom of the screen.
+ *
+ * @param test The test case that is being run
+ * @param activity The activity that is in the foreground of the test case
+ * @param v The view that should be dragged
+ * @param stepCount How many move steps to include in the drag
+ */
+ public static void dragViewToBottom(InstrumentationTestCase test, Activity activity, View v,
+ int stepCount) {
+ int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
+
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float fromY = xy[1] + (viewHeight / 2.0f);
+ float toY = screenHeight - 1;
+
+ drag(test, x, x, fromY, toY, stepCount);
+ }
+
+ /**
+ * Simulate touching the center of a view and releasing quickly (before the tap timeout).
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be clicked
+ */
+ public static void tapView(InstrumentationTestCase test, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ Instrumentation inst = test.getInstrumentation();
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+
+ /**
+ * Simulate touching the center of a view and cancelling (so no onClick should
+ * fire, etc).
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be clicked
+ */
+ public static void touchAndCancelView(InstrumentationTestCase test, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ Instrumentation inst = test.getInstrumentation();
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL,
+ x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ }
+
+ /**
+ * Simulate touching the center of a view and releasing.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be clicked
+ */
+ public static void clickView(InstrumentationTestCase test, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ Instrumentation inst = test.getInstrumentation();
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Simulate touching the center of a view, holding until it is a long press, and then releasing.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be clicked
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void longClickView(ActivityInstrumentationTestCase test, View v) {
+ longClickView((InstrumentationTestCase) test, v);
+ }
+
+ /**
+ * Simulate touching the center of a view, holding until it is a long press, and then releasing.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be clicked
+ */
+ public static void longClickView(InstrumentationTestCase test, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ Instrumentation inst = test.getInstrumentation();
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x + touchSlop / 2, y + touchSlop / 2, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ try {
+ Thread.sleep((long)(ViewConfiguration.getLongPressTimeout() * 1.5f));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the top of the screen.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void dragViewToTop(ActivityInstrumentationTestCase test, View v) {
+ dragViewToTop((InstrumentationTestCase) test, v, 4);
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the top of the screen.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param stepCount How many move steps to include in the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void dragViewToTop(ActivityInstrumentationTestCase test, View v, int stepCount) {
+ dragViewToTop((InstrumentationTestCase) test, v, stepCount);
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the top of the screen.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ */
+ public static void dragViewToTop(InstrumentationTestCase test, View v) {
+ dragViewToTop(test, v, 4);
+ }
+
+ /**
+ * Simulate touching the center of a view and dragging to the top of the screen.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param stepCount How many move steps to include in the drag
+ */
+ public static void dragViewToTop(InstrumentationTestCase test, View v, int stepCount) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float fromY = xy[1] + (viewHeight / 2.0f);
+ float toY = 0;
+
+ drag(test, x, x, fromY, toY, stepCount);
+ }
+
+ /**
+ * Get the location of a view. Use the gravity param to specify which part of the view to
+ * return.
+ *
+ * @param v View to find
+ * @param gravity A combination of (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL,
+ * RIGHT)
+ * @param xy Result
+ */
+ private static void getStartLocation(View v, int gravity, int[] xy) {
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ break;
+ case Gravity.CENTER_VERTICAL:
+ xy[1] += viewHeight / 2;
+ break;
+ case Gravity.BOTTOM:
+ xy[1] += viewHeight - 1;
+ break;
+ default:
+ // Same as top -- do nothing
+ }
+
+ switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ xy[0] += viewWidth / 2;
+ break;
+ case Gravity.RIGHT:
+ xy[0] += viewWidth - 1;
+ break;
+ default:
+ // Same as left -- do nothing
+ }
+ }
+
+ /**
+ * Simulate touching a view and dragging it by the specified amount.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param deltaX Amount to drag horizontally in pixels
+ * @param deltaY Amount to drag vertically in pixels
+ *
+ * @return distance in pixels covered by the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static int dragViewBy(ActivityInstrumentationTestCase test, View v, int gravity,
+ int deltaX, int deltaY) {
+ return dragViewBy((InstrumentationTestCase) test, v, gravity, deltaX, deltaY);
+ }
+
+ /**
+ * Simulate touching a view and dragging it by the specified amount.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param deltaX Amount to drag horizontally in pixels
+ * @param deltaY Amount to drag vertically in pixels
+ *
+ * @return distance in pixels covered by the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ public static int dragViewBy(InstrumentationTestCase test, View v, int gravity, int deltaX,
+ int deltaY) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int distance = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ drag(test, fromX, fromX + deltaX, fromY, fromY + deltaY, distance);
+
+ return distance;
+ }
+
+ /**
+ * Simulate touching a view and dragging it to a specified location.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param toX Final location of the view after dragging
+ * @param toY Final location of the view after dragging
+ *
+ * @return distance in pixels covered by the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static int dragViewTo(ActivityInstrumentationTestCase test, View v, int gravity, int toX,
+ int toY) {
+ return dragViewTo((InstrumentationTestCase) test, v, gravity, toX, toY);
+ }
+
+ /**
+ * Simulate touching a view and dragging it to a specified location.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param toX Final location of the view after dragging
+ * @param toY Final location of the view after dragging
+ *
+ * @return distance in pixels covered by the drag
+ */
+ public static int dragViewTo(InstrumentationTestCase test, View v, int gravity, int toX,
+ int toY) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int deltaX = fromX - toX;
+ int deltaY = fromY - toY;
+
+ int distance = (int)Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ drag(test, fromX, toX, fromY, toY, distance);
+
+ return distance;
+ }
+
+ /**
+ * Simulate touching a view and dragging it to a specified location. Only moves horizontally.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param toX Final location of the view after dragging
+ *
+ * @return distance in pixels covered by the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static int dragViewToX(ActivityInstrumentationTestCase test, View v, int gravity,
+ int toX) {
+ return dragViewToX((InstrumentationTestCase) test, v, gravity, toX);
+ }
+
+ /**
+ * Simulate touching a view and dragging it to a specified location. Only moves horizontally.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param toX Final location of the view after dragging
+ *
+ * @return distance in pixels covered by the drag
+ */
+ public static int dragViewToX(InstrumentationTestCase test, View v, int gravity, int toX) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int deltaX = fromX - toX;
+
+ drag(test, fromX, toX, fromY, fromY, deltaX);
+
+ return deltaX;
+ }
+
+ /**
+ * Simulate touching a view and dragging it to a specified location. Only moves vertically.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param toY Final location of the view after dragging
+ *
+ * @return distance in pixels covered by the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static int dragViewToY(ActivityInstrumentationTestCase test, View v, int gravity,
+ int toY) {
+ return dragViewToY((InstrumentationTestCase) test, v, gravity, toY);
+ }
+
+ /**
+ * Simulate touching a view and dragging it to a specified location. Only moves vertically.
+ *
+ * @param test The test case that is being run
+ * @param v The view that should be dragged
+ * @param gravity Which part of the view to use for the initial down event. A combination of
+ * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
+ * @param toY Final location of the view after dragging
+ *
+ * @return distance in pixels covered by the drag
+ */
+ public static int dragViewToY(InstrumentationTestCase test, View v, int gravity, int toY) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int deltaY = fromY - toY;
+
+ drag(test, fromX, fromX, fromY, toY, deltaY);
+
+ return deltaY;
+ }
+
+
+ /**
+ * Simulate touching a specific location and dragging to a new location.
+ *
+ * @param test The test case that is being run
+ * @param fromX X coordinate of the initial touch, in screen coordinates
+ * @param toX Xcoordinate of the drag destination, in screen coordinates
+ * @param fromY X coordinate of the initial touch, in screen coordinates
+ * @param toY Y coordinate of the drag destination, in screen coordinates
+ * @param stepCount How many move steps to include in the drag
+ *
+ * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
+ * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
+ * configuring the Activity under test
+ */
+ @Deprecated
+ public static void drag(ActivityInstrumentationTestCase test, float fromX, float toX,
+ float fromY, float toY, int stepCount) {
+ drag((InstrumentationTestCase) test, fromX, toX, fromY, toY, stepCount);
+ }
+
+ /**
+ * Simulate touching a specific location and dragging to a new location.
+ *
+ * @param test The test case that is being run
+ * @param fromX X coordinate of the initial touch, in screen coordinates
+ * @param toX Xcoordinate of the drag destination, in screen coordinates
+ * @param fromY X coordinate of the initial touch, in screen coordinates
+ * @param toY Y coordinate of the drag destination, in screen coordinates
+ * @param stepCount How many move steps to include in the drag
+ */
+ public static void drag(InstrumentationTestCase test, float fromX, float toX, float fromY,
+ float toY, int stepCount) {
+ Instrumentation inst = test.getInstrumentation();
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ float y = fromY;
+ float x = fromX;
+
+ float yStep = (toY - fromY) / stepCount;
+ float xStep = (toX - fromX) / stepCount;
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, fromX, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ for (int i = 0; i < stepCount; ++i) {
+ y += yStep;
+ x += xStep;
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, fromX, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+}
diff --git a/test-runner/android/test/ViewAsserts.java b/test-runner/android/test/ViewAsserts.java
new file mode 100644
index 0000000..c575fc5
--- /dev/null
+++ b/test-runner/android/test/ViewAsserts.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2007 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 static junit.framework.Assert.*;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Some useful assertions about views.
+ */
+public class ViewAsserts {
+
+ private ViewAsserts() {}
+
+ /**
+ * Assert that view is on the screen.
+ * @param origin The root view of the screen.
+ * @param view The view.
+ */
+ static public void assertOnScreen(View origin, View view) {
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+
+ int[] xyRoot = new int[2];
+ origin.getLocationOnScreen(xyRoot);
+
+ int y = xy[1] - xyRoot[1];
+
+ assertTrue("view should have positive y coordinate on screen",
+ y >= 0);
+
+ assertTrue("view should have y location on screen less than drawing "
+ + "height of root view",
+ y <= view.getRootView().getHeight());
+ }
+
+ /**
+ * Assert that view is below the visible screen.
+ * @param origin The root view of the screen.
+ * @param view The view
+ */
+ static public void assertOffScreenBelow(View origin, View view) {
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+
+ int[] xyRoot = new int[2];
+ origin.getLocationOnScreen(xyRoot);
+
+ int y = xy[1] - xyRoot[1];
+
+ assertTrue("view should have y location on screen greater than drawing "
+ + "height of origen view (" + y + " is not greater than "
+ + origin.getHeight() + ")",
+ y > origin.getHeight());
+ }
+
+ /**
+ * Assert that view is above the visible screen.
+ * @param origin Te root view of the screen.
+ * @param view The view
+ */
+ static public void assertOffScreenAbove(View origin, View view) {
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+
+ int[] xyRoot = new int[2];
+ origin.getLocationOnScreen(xyRoot);
+
+ int y = xy[1] - xyRoot[1];
+
+ assertTrue("view should have y location less than that of origin view",
+ y < 0);
+ }
+
+ /**
+ * Assert that a view has a particular x and y position on the visible screen.
+ * @param origin The root view of the screen.
+ * @param view The view.
+ * @param x The expected x coordinate.
+ * @param y The expected y coordinate.
+ */
+ static public void assertHasScreenCoordinates(View origin, View view, int x, int y) {
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+
+ int[] xyRoot = new int[2];
+ origin.getLocationOnScreen(xyRoot);
+
+ assertEquals("x coordinate", x, xy[0] - xyRoot[0]);
+ assertEquals("y coordinate", y, xy[1] - xyRoot[1]);
+ }
+
+ /**
+ * Assert that two views are aligned on their baseline, that is that their baselines
+ * are on the same y location.
+ *
+ * @param first The first view
+ * @param second The second view
+ */
+ static public void assertBaselineAligned(View first, View second) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstTop = xy[1] + first.getBaseline();
+
+ second.getLocationOnScreen(xy);
+ int secondTop = xy[1] + second.getBaseline();
+
+ assertEquals("views are not baseline aligned", firstTop, secondTop);
+ }
+
+ /**
+ * Assert that two views are right aligned, that is that their right edges
+ * are on the same x location.
+ *
+ * @param first The first view
+ * @param second The second view
+ */
+ static public void assertRightAligned(View first, View second) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstRight = xy[0] + first.getMeasuredWidth();
+
+ second.getLocationOnScreen(xy);
+ int secondRight = xy[0] + second.getMeasuredWidth();
+
+ assertEquals("views are not right aligned", firstRight, secondRight);
+ }
+
+ /**
+ * Assert that two views are right aligned, that is that their right edges
+ * are on the same x location, with respect to the specified margin.
+ *
+ * @param first The first view
+ * @param second The second view
+ * @param margin The margin between the first view and the second view
+ */
+ static public void assertRightAligned(View first, View second, int margin) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstRight = xy[0] + first.getMeasuredWidth();
+
+ second.getLocationOnScreen(xy);
+ int secondRight = xy[0] + second.getMeasuredWidth();
+
+ assertEquals("views are not right aligned", Math.abs(firstRight - secondRight), margin);
+ }
+
+ /**
+ * Assert that two views are left aligned, that is that their left edges
+ * are on the same x location.
+ *
+ * @param first The first view
+ * @param second The second view
+ */
+ static public void assertLeftAligned(View first, View second) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstLeft = xy[0];
+
+ second.getLocationOnScreen(xy);
+ int secondLeft = xy[0];
+
+ assertEquals("views are not left aligned", firstLeft, secondLeft);
+ }
+
+ /**
+ * Assert that two views are left aligned, that is that their left edges
+ * are on the same x location, with respect to the specified margin.
+ *
+ * @param first The first view
+ * @param second The second view
+ * @param margin The margin between the first view and the second view
+ */
+ static public void assertLeftAligned(View first, View second, int margin) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstLeft = xy[0];
+
+ second.getLocationOnScreen(xy);
+ int secondLeft = xy[0];
+
+ assertEquals("views are not left aligned", Math.abs(firstLeft - secondLeft), margin);
+ }
+
+ /**
+ * Assert that two views are bottom aligned, that is that their bottom edges
+ * are on the same y location.
+ *
+ * @param first The first view
+ * @param second The second view
+ */
+ static public void assertBottomAligned(View first, View second) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstBottom = xy[1] + first.getMeasuredHeight();
+
+ second.getLocationOnScreen(xy);
+ int secondBottom = xy[1] + second.getMeasuredHeight();
+
+ assertEquals("views are not bottom aligned", firstBottom, secondBottom);
+ }
+
+ /**
+ * Assert that two views are bottom aligned, that is that their bottom edges
+ * are on the same y location, with respect to the specified margin.
+ *
+ * @param first The first view
+ * @param second The second view
+ * @param margin The margin between the first view and the second view
+ */
+ static public void assertBottomAligned(View first, View second, int margin) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstBottom = xy[1] + first.getMeasuredHeight();
+
+ second.getLocationOnScreen(xy);
+ int secondBottom = xy[1] + second.getMeasuredHeight();
+
+ assertEquals("views are not bottom aligned", Math.abs(firstBottom - secondBottom), margin);
+ }
+
+ /**
+ * Assert that two views are top aligned, that is that their top edges
+ * are on the same y location.
+ *
+ * @param first The first view
+ * @param second The second view
+ */
+ static public void assertTopAligned(View first, View second) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstTop = xy[1];
+
+ second.getLocationOnScreen(xy);
+ int secondTop = xy[1];
+
+ assertEquals("views are not top aligned", firstTop, secondTop);
+ }
+
+ /**
+ * Assert that two views are top aligned, that is that their top edges
+ * are on the same y location, with respect to the specified margin.
+ *
+ * @param first The first view
+ * @param second The second view
+ * @param margin The margin between the first view and the second view
+ */
+ static public void assertTopAligned(View first, View second, int margin) {
+ int[] xy = new int[2];
+ first.getLocationOnScreen(xy);
+ int firstTop = xy[1];
+
+ second.getLocationOnScreen(xy);
+ int secondTop = xy[1];
+
+ assertEquals("views are not top aligned", Math.abs(firstTop - secondTop), margin);
+ }
+
+ /**
+ * Assert that the <code>test</code> view is horizontally center aligned
+ * with respect to the <code>reference</code> view.
+ *
+ * @param reference The reference view
+ * @param test The view that should be center aligned with the reference view
+ */
+ static public void assertHorizontalCenterAligned(View reference, View test) {
+ int[] xy = new int[2];
+ reference.getLocationOnScreen(xy);
+ int referenceLeft = xy[0];
+
+ test.getLocationOnScreen(xy);
+ int testLeft = xy[0];
+
+ int center = (reference.getMeasuredWidth() - test.getMeasuredWidth()) / 2;
+ int delta = testLeft - referenceLeft;
+
+ assertEquals("views are not horizontally center aligned", center, delta);
+ }
+
+ /**
+ * Assert that the <code>test</code> view is vertically center aligned
+ * with respect to the <code>reference</code> view.
+ *
+ * @param reference The reference view
+ * @param test The view that should be center aligned with the reference view
+ */
+ static public void assertVerticalCenterAligned(View reference, View test) {
+ int[] xy = new int[2];
+ reference.getLocationOnScreen(xy);
+ int referenceTop = xy[1];
+
+ test.getLocationOnScreen(xy);
+ int testTop = xy[1];
+
+ int center = (reference.getMeasuredHeight() - test.getMeasuredHeight()) / 2;
+ int delta = testTop - referenceTop;
+
+ assertEquals("views are not vertically center aligned", center, delta);
+ }
+
+ /**
+ * Assert the specified group's integrity. The children count should be >= 0 and each
+ * child should be non-null.
+ *
+ * @param parent The group whose integrity to check
+ */
+ static public void assertGroupIntegrity(ViewGroup parent) {
+ final int count = parent.getChildCount();
+ assertTrue("child count should be >= 0", count >= 0);
+
+ for (int i = 0; i < count; i++) {
+ assertNotNull("group should not contain null children", parent.getChildAt(i));
+ assertSame(parent, parent.getChildAt(i).getParent());
+ }
+ }
+
+ /**
+ * Assert that the specified group contains a specific child once and only once.
+ *
+ * @param parent The group
+ * @param child The child that should belong to group
+ */
+ static public void assertGroupContains(ViewGroup parent, View child) {
+ final int count = parent.getChildCount();
+ assertTrue("Child count should be >= 0", count >= 0);
+
+ boolean found = false;
+ for (int i = 0; i < count; i++) {
+ if (parent.getChildAt(i) == child) {
+ if (!found) {
+ found = true;
+ } else {
+ assertTrue("child " + child + " is duplicated in parent", false);
+ }
+ }
+ }
+
+ assertTrue("group does not contain " + child, found);
+ }
+
+ /**
+ * Assert that the specified group does not contain a specific child.
+ *
+ * @param parent The group
+ * @param child The child that should not belong to group
+ */
+ static public void assertGroupNotContains(ViewGroup parent, View child) {
+ final int count = parent.getChildCount();
+ assertTrue("Child count should be >= 0", count >= 0);
+
+ for (int i = 0; i < count; i++) {
+ if (parent.getChildAt(i) == child) {
+ assertTrue("child " + child + " is found in parent", false);
+ }
+ }
+ }
+}
diff --git a/test-runner/android/test/mock/MockApplication.java b/test-runner/android/test/mock/MockApplication.java
new file mode 100644
index 0000000..572dfbf
--- /dev/null
+++ b/test-runner/android/test/mock/MockApplication.java
@@ -0,0 +1,46 @@
+/*
+ * 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.mock;
+
+import android.app.Application;
+import android.content.res.Configuration;
+
+/**
+ * A mock {@link android.app.Application} class. All methods are non-functional and throw
+ * {@link java.lang.UnsupportedOperationException}. Override it as necessary to provide the
+ * operations that you need.
+ */
+public class MockApplication extends Application {
+
+ public MockApplication() {
+ }
+
+ @Override
+ public void onCreate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onTerminate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/test-runner/android/test/mock/MockContentProvider.java b/test-runner/android/test/mock/MockContentProvider.java
new file mode 100644
index 0000000..d04fc44
--- /dev/null
+++ b/test-runner/android/test/mock/MockContentProvider.java
@@ -0,0 +1,107 @@
+/*
+ * 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.mock;
+
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.content.ISyncAdapter;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Mock implementation of IContentProvider that does nothing. All methods are non-functional and
+ * throw {@link java.lang.UnsupportedOperationException}. Tests can extend this class to
+ * implement behavior needed for tests.
+ *
+ * @hide - Because IContentProvider hides bulkQuery(), this doesn't pass through JavaDoc
+ * without generating errors.
+ *
+ */
+public class MockContentProvider implements IContentProvider {
+
+ @SuppressWarnings("unused")
+ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @SuppressWarnings("unused")
+ public IBulkCursor bulkQuery(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder, IContentObserver observer,
+ CursorWindow window) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public int delete(Uri url, String selection, String[] selectionArgs)
+ throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public ISyncAdapter getSyncAdapter() throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public String getType(Uri url) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public Uri insert(Uri url, ContentValues initialValues) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException,
+ FileNotFoundException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs)
+ throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+}
diff --git a/test-runner/android/test/mock/MockContentResolver.java b/test-runner/android/test/mock/MockContentResolver.java
new file mode 100644
index 0000000..3a1dc36c
--- /dev/null
+++ b/test-runner/android/test/mock/MockContentResolver.java
@@ -0,0 +1,70 @@
+/*
+ * 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.mock;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import com.google.android.collect.Maps;
+
+import java.util.Map;
+
+/**
+ * A mock {@link android.content.ContentResolver} class that isolates the test code from the real
+ * content system. All methods are non-functional and throw
+ * {@link java.lang.UnsupportedOperationException}.
+ *
+ * <p>This only isolates the test code in ways that have proven useful so far. More should be
+ * added as they become a problem.
+ */
+public class MockContentResolver extends ContentResolver {
+ Map<String, ContentProvider> mProviders;
+
+ public MockContentResolver() {
+ super(null);
+ mProviders = Maps.newHashMap();
+ }
+
+ public void addProvider(String name, ContentProvider provider) {
+ mProviders.put(name, provider);
+ }
+
+ /** @hide */
+ @Override
+ protected IContentProvider acquireProvider(Context context, String name) {
+ final ContentProvider provider = mProviders.get(name);
+ if (provider != null) {
+ return provider.getIContentProvider();
+ } else {
+ return null;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean releaseProvider(IContentProvider provider) {
+ return true;
+ }
+
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ }
+}
diff --git a/test-runner/android/test/mock/MockContext.java b/test-runner/android/test/mock/MockContext.java
new file mode 100644
index 0000000..e733dd1
--- /dev/null
+++ b/test-runner/android/test/mock/MockContext.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2007 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.mock;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A mock {@link android.content.Context} class. All methods are non-functional and throw
+ * {@link java.lang.UnsupportedOperationException}. You can use this to inject other dependencies,
+ * mocks, or monitors into the classes you are testing.
+ */
+public class MockContext extends Context {
+
+ @Override
+ public AssetManager getAssets() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Resources getResources() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPackageName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPackageCodePath() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FileInputStream openFileInput(String name) throws FileNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean deleteFile(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getFileStreamPath(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] fileList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getFilesDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getCacheDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getDir(String name, int mode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String file, int mode,
+ SQLiteDatabase.CursorFactory factory) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getDatabasePath(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] databaseList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean deleteDatabase(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getWallpaper() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable peekWallpaper() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumWidth() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumHeight() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setWallpaper(Bitmap bitmap) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setWallpaper(InputStream data) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearWallpaper() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent,
+ String receiverPermission) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent intent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ComponentName startService(Intent service) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean stopService(Intent service) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unbindService(ServiceConnection conn) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean startInstrumentation(ComponentName className,
+ String profileFile, Bundle arguments) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkCallingPermission(String permission) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enforcePermission(
+ String permission, int pid, int uid, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void revokeUriPermission(Uri uri, int modeFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri uri, int modeFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, String readPermission,
+ String writePermission, int pid, int uid, int modeFlags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enforceUriPermission(
+ Uri uri, int pid, int uid, int modeFlags, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enforceCallingUriPermission(
+ Uri uri, int modeFlags, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enforceCallingOrSelfUriPermission(
+ Uri uri, int modeFlags, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void enforceUriPermission(
+ Uri uri, String readPermission, String writePermission,
+ int pid, int uid, int modeFlags, String message) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Context createPackageContext(String packageName, int flags)
+ throws PackageManager.NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/test-runner/android/test/mock/MockDialogInterface.java b/test-runner/android/test/mock/MockDialogInterface.java
new file mode 100644
index 0000000..e4dd0ba
--- /dev/null
+++ b/test-runner/android/test/mock/MockDialogInterface.java
@@ -0,0 +1,20 @@
+// Copyright 2008 The Android Open Source Project
+
+package android.test.mock;
+
+import android.content.DialogInterface;
+
+/**
+ * A mock {@link android.content.DialogInterface} class. All methods are non-functional and throw
+ * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you
+ * need.
+ */
+public class MockDialogInterface implements DialogInterface {
+ public void cancel() {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+ public void dismiss() {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+}
diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java
new file mode 100644
index 0000000..ea190e2
--- /dev/null
+++ b/test-runner/android/test/mock/MockPackageManager.java
@@ -0,0 +1,413 @@
+/*
+ * 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.mock;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * A mock {@link android.content.pm.PackageManager} class. All methods are non-functional and throw
+ * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you
+ * need.
+ */
+public class MockPackageManager extends PackageManager {
+
+ @Override
+ public PackageInfo getPackageInfo(String packageName, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int[] getPackageGids(String packageName) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PermissionInfo getPermissionInfo(String name, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<PermissionInfo> queryPermissionsByGroup(String group, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(String name,
+ int flags) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ActivityInfo getActivityInfo(ComponentName className, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ActivityInfo getReceiverInfo(ComponentName className, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServiceInfo getServiceInfo(ComponentName className, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<PackageInfo> getInstalledPackages(int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkPermission(String permName, String pkgName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addPermission(PermissionInfo info) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removePermission(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int checkSignatures(String pkg1, String pkg2) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getPackagesForUid(int uid) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ 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) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResolveInfo resolveActivity(Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
+ Intent[] specifics, Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResolveInfo resolveService(Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ProviderInfo resolveContentProvider(String name, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<InstrumentationInfo> queryInstrumentation(
+ String targetPackage, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getActivityIcon(ComponentName activityName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getDefaultActivityIcon() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getApplicationIcon(ApplicationInfo info) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public XmlResourceParser getXml(String packageName, int resid,
+ ApplicationInfo appInfo) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CharSequence getApplicationLabel(ApplicationInfo info) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Resources getResourcesForActivity(ComponentName activityName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Resources getResourcesForApplication(ApplicationInfo app) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Resources getResourcesForApplication(String appPackageName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void installPackage(Uri packageURI, IPackageInstallObserver observer,
+ int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void clearApplicationUserData(
+ String packageName, IPackageDataObserver observer) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void deleteApplicationCacheFiles(
+ String packageName, IPackageDataObserver observer) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void freeStorageAndNotify(
+ long idealStorageSize, IPackageDataObserver observer) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void freeStorage(
+ long idealStorageSize, PendingIntent onFinishedIntent) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void deletePackage(
+ String packageName, IPackageDeleteObserver observer, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addPackageToPreferred(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removePackageFromPreferred(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<PackageInfo> getPreferredPackages(int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setComponentEnabledSetting(ComponentName componentName,
+ int newState, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getComponentEnabledSetting(ComponentName componentName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getApplicationEnabledSetting(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addPreferredActivity(IntentFilter filter,
+ int match, ComponentName[] set, ComponentName activity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearPackagePreferredActivities(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void installPackage(Uri packageURI) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPreferredActivities(List<IntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getSystemSharedLibraryNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSafeMode() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/test-runner/android/test/mock/MockResources.java b/test-runner/android/test/mock/MockResources.java
new file mode 100644
index 0000000..18752ce
--- /dev/null
+++ b/test-runner/android/test/mock/MockResources.java
@@ -0,0 +1,222 @@
+/*
+ * 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.mock;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.content.res.ColorStateList;
+import android.content.res.XmlResourceParser;
+import android.content.res.AssetFileDescriptor;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.util.AttributeSet;
+import android.graphics.drawable.Drawable;
+import android.graphics.Movie;
+
+import java.io.InputStream;
+
+/**
+ * A mock {@link android.content.res.Resources} class. All methods are non-functional and throw
+ * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you
+ * need.
+ */
+public class MockResources extends Resources {
+
+ public MockResources() {
+ super(new AssetManager(), null, null);
+ }
+
+ @Override
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
+ // this method is called from the constructor, so we just do nothing
+ }
+
+ @Override
+ public CharSequence getText(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getString(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getQuantityString(int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getQuantityString(int id, int quantity) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public CharSequence getText(int id, CharSequence def) {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public CharSequence[] getTextArray(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String[] getStringArray(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public int[] getIntArray(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public float getDimension(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public int getDimensionPixelOffset(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public int getDimensionPixelSize(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public Movie getMovie(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public int getColor(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public int getInteger(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public XmlResourceParser getLayout(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public XmlResourceParser getXml(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public InputStream openRawResource(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public DisplayMetrics getDisplayMetrics() {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public int getIdentifier(String name, String defType, String defPackage) {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getResourcePackageName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getResourceTypeName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+
+ @Override
+ public String getResourceEntryName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException("mock object, not implemented");
+ }
+}
diff --git a/test-runner/android/test/mock/package.html b/test-runner/android/test/mock/package.html
new file mode 100644
index 0000000..0f1bc6f4
--- /dev/null
+++ b/test-runner/android/test/mock/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Utility classes providing stubs or mocks of various Android framework building blocks.
+</BODY>
+</HTML>
diff --git a/test-runner/android/test/suitebuilder/AssignableFrom.java b/test-runner/android/test/suitebuilder/AssignableFrom.java
new file mode 100644
index 0000000..38b4ee3
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/AssignableFrom.java
@@ -0,0 +1,32 @@
+/*
+ * 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.suitebuilder;
+
+import com.android.internal.util.Predicate;
+
+class AssignableFrom implements Predicate<TestMethod> {
+
+ private final Class root;
+
+ AssignableFrom(Class root) {
+ this.root = root;
+ }
+
+ public boolean apply(TestMethod testMethod) {
+ return root.isAssignableFrom(testMethod.getEnclosingClass());
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java
new file mode 100644
index 0000000..128396e
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java
@@ -0,0 +1,35 @@
+/*
+ * 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.suitebuilder;
+
+/**
+ * A suite builder that finds instrumentation tests.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class InstrumentationTestSuiteBuilder extends TestSuiteBuilder {
+
+ public InstrumentationTestSuiteBuilder(Class clazz) {
+ this(clazz.getName(), clazz.getClassLoader());
+ }
+
+
+ public InstrumentationTestSuiteBuilder(String name, ClassLoader classLoader) {
+ super(name, classLoader);
+ addRequirements(TestPredicates.SELECT_INSTRUMENTATION);
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java
new file mode 100644
index 0000000..01e7ec6
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java
@@ -0,0 +1,35 @@
+/*
+ * 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.suitebuilder;
+
+/**
+ * A suite builder that runs smoke tests.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class SmokeTestSuiteBuilder extends TestSuiteBuilder {
+
+ public SmokeTestSuiteBuilder(Class clazz) {
+ this(clazz.getName(), clazz.getClassLoader());
+ }
+
+
+ public SmokeTestSuiteBuilder(String name, ClassLoader classLoader) {
+ super(name, classLoader);
+ addRequirements(TestPredicates.SELECT_SMOKE);
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/TestGrouping.java b/test-runner/android/test/suitebuilder/TestGrouping.java
new file mode 100644
index 0000000..df6da70
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/TestGrouping.java
@@ -0,0 +1,249 @@
+/*
+ * 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.suitebuilder;
+
+import android.test.ClassPathPackageInfo;
+import android.test.ClassPathPackageInfoSource;
+import android.test.PackageInfoSources;
+import android.util.Log;
+import com.android.internal.util.Predicate;
+import junit.framework.TestCase;
+
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Represents a collection of test classes present on the classpath. You can add individual classes
+ * or entire packages. By default sub-packages are included recursively, but methods are
+ * provided to allow for arbitrary inclusion or exclusion of sub-packages. Typically a
+ * {@link TestGrouping} will have only one root package, but this is not a requirement.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class TestGrouping {
+
+ SortedSet<Class<? extends TestCase>> testCaseClasses;
+
+ public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
+ = new SortBySimpleName();
+
+ public static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME
+ = new SortByFullyQualifiedName();
+
+ protected String firstIncludedPackage = null;
+ private ClassLoader classLoader;
+
+ public TestGrouping(Comparator<Class<? extends TestCase>> comparator) {
+ testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
+ }
+
+ /**
+ * @return A list of all tests in the package, including small, medium, large,
+ * flaky, and suppressed tests. Includes sub-packages recursively.
+ */
+ public List<TestMethod> getTests() {
+ List<TestMethod> testMethods = new ArrayList<TestMethod>();
+ for (Class<? extends TestCase> testCase : testCaseClasses) {
+ for (Method testMethod : getTestMethods(testCase)) {
+ testMethods.add(new TestMethod(testMethod, testCase));
+ }
+ }
+ return testMethods;
+ }
+
+ protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) {
+ List<Method> methods = Arrays.asList(testCaseClass.getMethods());
+ return select(methods, new TestMethodPredicate());
+ }
+
+ SortedSet<Class<? extends TestCase>> getTestCaseClasses() {
+ return testCaseClasses;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TestGrouping other = (TestGrouping) o;
+ if (!this.testCaseClasses.equals(other.testCaseClasses)) {
+ return false;
+ }
+ return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator());
+ }
+
+ public int hashCode() {
+ return testCaseClasses.hashCode();
+ }
+
+ /**
+ * Include all tests in the given packages and all their sub-packages, unless otherwise
+ * specified. Each of the given packages must contain at least one test class, either directly
+ * or in a sub-package.
+ *
+ * @param packageNames Names of packages to add.
+ * @return The {@link TestGrouping} for method chaining.
+ */
+ public TestGrouping addPackagesRecursive(String... packageNames) {
+ for (String packageName : packageNames) {
+ List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
+ if (addedClasses.isEmpty()) {
+ Log.w("TestGrouping", "Invalid Package: '" + packageName
+ + "' could not be found or has no tests");
+ }
+ testCaseClasses.addAll(addedClasses);
+ if (firstIncludedPackage == null) {
+ firstIncludedPackage = packageName;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Exclude all tests in the given packages and all their sub-packages, unless otherwise
+ * specified.
+ *
+ * @param packageNames Names of packages to remove.
+ * @return The {@link TestGrouping} for method chaining.
+ */
+ public TestGrouping removePackagesRecursive(String... packageNames) {
+ for (String packageName : packageNames) {
+ testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
+ }
+ return this;
+ }
+
+ /**
+ * @return The first package name passed to {@link #addPackagesRecursive(String[])}, or null
+ * if that method was never called.
+ */
+ public String getFirstIncludedPackage() {
+ return firstIncludedPackage;
+ }
+
+ private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
+ ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
+ ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);
+
+ return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) {
+ List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>();
+ for (Class<?> testClass : select(allClasses,
+ new TestCasePredicate())) {
+ testClasses.add((Class<? extends TestCase>) testClass);
+ }
+ return testClasses;
+ }
+
+ private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
+ ArrayList<T> selectedItems = new ArrayList<T>();
+ for (T item : items) {
+ if (predicate.apply(item)) {
+ selectedItems.add(item);
+ }
+ }
+ return selectedItems;
+ }
+
+ public void setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Sort classes by their simple names (i.e. without the package prefix), using
+ * their packages to sort classes with the same name.
+ */
+ private static class SortBySimpleName
+ implements Comparator<Class<? extends TestCase>>, Serializable {
+
+ public int compare(Class<? extends TestCase> class1,
+ Class<? extends TestCase> class2) {
+ int result = class1.getSimpleName().compareTo(class2.getSimpleName());
+ if (result != 0) {
+ return result;
+ }
+ return class1.getName().compareTo(class2.getName());
+ }
+ }
+
+ /**
+ * Sort classes by their fully qualified names (i.e. with the package
+ * prefix).
+ */
+ private static class SortByFullyQualifiedName
+ implements Comparator<Class<? extends TestCase>>, Serializable {
+
+ public int compare(Class<? extends TestCase> class1,
+ Class<? extends TestCase> class2) {
+ return class1.getName().compareTo(class2.getName());
+ }
+ }
+
+ private static class TestCasePredicate implements Predicate<Class<?>> {
+
+ public boolean apply(Class aClass) {
+ int modifiers = ((Class<?>) aClass).getModifiers();
+ return TestCase.class.isAssignableFrom((Class<?>) aClass)
+ && Modifier.isPublic(modifiers)
+ && !Modifier.isAbstract(modifiers)
+ && hasValidConstructor((Class<?>) aClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean hasValidConstructor(java.lang.Class<?> aClass) {
+ // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler,
+ // where the return type of Class.getDeclaredConstructors() was changed
+ // from Constructor<T>[] to Constructor<?>[]
+ Constructor<? extends TestCase>[] constructors
+ = (Constructor<? extends TestCase>[]) aClass.getConstructors();
+ for (Constructor<? extends TestCase> constructor : constructors) {
+ if (Modifier.isPublic(constructor.getModifiers())) {
+ java.lang.Class[] parameterTypes = constructor.getParameterTypes();
+ if (parameterTypes.length == 0 ||
+ (parameterTypes.length == 1 && parameterTypes[0] == String.class)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private static class TestMethodPredicate implements Predicate<Method> {
+
+ public boolean apply(Method method) {
+ return ((method.getParameterTypes().length == 0) &&
+ (method.getName().startsWith("test")) &&
+ (method.getReturnType().getSimpleName().equals("void")));
+ }
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/TestMethod.java b/test-runner/android/test/suitebuilder/TestMethod.java
new file mode 100644
index 0000000..08568d5
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/TestMethod.java
@@ -0,0 +1,146 @@
+/*
+ * 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.suitebuilder;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Represents a test to be run. Can be constructed without instantiating the TestCase or even
+ * loading the class.
+ */
+public class TestMethod {
+
+ private final String enclosingClassname;
+ private final String testMethodName;
+ 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 = methodName;
+ }
+
+ public TestMethod(TestCase testCase) {
+ this(testCase.getName(), testCase.getClass());
+ }
+
+ public String getName() {
+ return testMethodName;
+ }
+
+ public String getEnclosingClassname() {
+ return enclosingClassname;
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ try {
+ return getEnclosingClass().getMethod(getName()).getAnnotation(annotationClass);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public Class<? extends TestCase> getEnclosingClass() {
+ return enclosingClass;
+ }
+
+ public TestCase createTest()
+ throws InvocationTargetException, IllegalAccessException, InstantiationException {
+ return instantiateTest(enclosingClass, testMethodName);
+ }
+
+ @SuppressWarnings("unchecked")
+ private TestCase instantiateTest(Class testCaseClass, String testName)
+ throws InvocationTargetException, IllegalAccessException, InstantiationException {
+ Constructor[] constructors = testCaseClass.getConstructors();
+
+ if (constructors.length == 0) {
+ return instantiateTest(testCaseClass.getSuperclass(), testName);
+ } else {
+ for (Constructor constructor : constructors) {
+ Class[] params = constructor.getParameterTypes();
+ if (noargsConstructor(params)) {
+ TestCase test = ((Constructor<? extends TestCase>) constructor).newInstance();
+ // JUnit will run just the one test if you call
+ // {@link TestCase#setName(String)}
+ test.setName(testName);
+ return test;
+ } else if (singleStringConstructor(params)) {
+ return ((Constructor<? extends TestCase>) constructor)
+ .newInstance(testName);
+ }
+ }
+ }
+ throw new RuntimeException("Unable to locate a constructor for "
+ + testCaseClass.getName());
+ }
+
+ private boolean singleStringConstructor(Class[] params) {
+ return (params.length == 1) && (params[0].equals(String.class));
+ }
+
+ private boolean noargsConstructor(Class[] params) {
+ return params.length == 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ TestMethod that = (TestMethod) o;
+
+ if (enclosingClassname != null
+ ? !enclosingClassname.equals(that.enclosingClassname)
+ : that.enclosingClassname != null) {
+ return false;
+ }
+ if (testMethodName != null
+ ? !testMethodName.equals(that.testMethodName)
+ : that.testMethodName != null) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = (enclosingClassname != null ? enclosingClassname.hashCode() : 0);
+ result = 31 * result + (testMethodName != null ? testMethodName.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return enclosingClassname + "." + testMethodName;
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/TestPredicates.java b/test-runner/android/test/suitebuilder/TestPredicates.java
new file mode 100644
index 0000000..d814e0b
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/TestPredicates.java
@@ -0,0 +1,49 @@
+/*
+ * 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.suitebuilder;
+
+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;
+
+/**
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class TestPredicates {
+
+ public static final Predicate<TestMethod> SELECT_INSTRUMENTATION =
+ new AssignableFrom(InstrumentationTestCase.class);
+ public static final Predicate<TestMethod> REJECT_INSTRUMENTATION =
+ 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 =
+ Predicates.not(new AssignableFrom(PerformanceTestBase.class));
+
+}
diff --git a/test-runner/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java
new file mode 100644
index 0000000..428905e
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java
@@ -0,0 +1,282 @@
+/*
+ * 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.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;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Build suites based on a combination of included packages, excluded packages,
+ * and predicates that must be satisfied.
+ */
+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;
+ private String suiteName;
+
+ /**
+ * The given name is automatically prefixed with the package containing the tests to be run.
+ * If more than one package is specified, the first is used.
+ *
+ * @param clazz Use the class from your .apk. Use the class name for the test suite name.
+ * Use the class' classloader in order to load classes for testing.
+ * This is needed when running in the emulator.
+ */
+ public TestSuiteBuilder(Class clazz) {
+ this(clazz.getName(), clazz.getClassLoader());
+ }
+
+ public TestSuiteBuilder(String name, ClassLoader classLoader) {
+ this.suiteName = name;
+ this.testGrouping.setClassLoader(classLoader);
+ this.testCases = Lists.newArrayList();
+ addRequirements(REJECT_SUPPRESSED);
+ }
+
+ /** @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;
+ }
+
+ /**
+ * Include all tests that satisfy the requirements in the given packages and all sub-packages,
+ * unless otherwise specified.
+ *
+ * @param packageNames Names of packages to add.
+ * @return The builder for method chaining.
+ */
+ public TestSuiteBuilder includePackages(String... packageNames) {
+ testGrouping.addPackagesRecursive(packageNames);
+ return this;
+ }
+
+ /**
+ * Exclude all tests in the given packages and all sub-packages, unless otherwise specified.
+ *
+ * @param packageNames Names of packages to remove.
+ * @return The builder for method chaining.
+ */
+ public TestSuiteBuilder excludePackages(String... packageNames) {
+ testGrouping.removePackagesRecursive(packageNames);
+ return this;
+ }
+
+ /**
+ * Exclude tests that fail to satisfy all of the given predicates.
+ *
+ * @param predicates Predicates to add to the list of requirements.
+ * @return The builder for method chaining.
+ */
+ public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) {
+ this.predicates.addAll(predicates);
+ return this;
+ }
+
+ /**
+ * Include all junit tests that satisfy the requirements in the calling class' package and all
+ * sub-packages.
+ *
+ * @return The builder for method chaining.
+ */
+ public final TestSuiteBuilder includeAllPackagesUnderHere() {
+ StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+
+ String callingClassName = null;
+ String thisClassName = TestSuiteBuilder.class.getName();
+
+ // We want to get the package of this method's calling class. This method's calling class
+ // should be one level below this class in the stack trace.
+ for (int i = 0; i < stackTraceElements.length; i++) {
+ StackTraceElement element = stackTraceElements[i];
+ if (thisClassName.equals(element.getClassName())
+ && "includeAllPackagesUnderHere".equals(element.getMethodName())) {
+ // We've found this class in the call stack. The calling class must be the
+ // next class in the stack.
+ callingClassName = stackTraceElements[i + 1].getClassName();
+ break;
+ }
+ }
+
+ String packageName = parsePackageNameFromClassName(callingClassName);
+ return includePackages(packageName);
+ }
+
+ /**
+ * Override the default name for the suite being built. This should generally be called if you
+ * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which
+ * tests will be included. The name you specify is automatically prefixed with the package
+ * containing the tests to be run. If more than one package is specified, the first is used.
+ *
+ * @param newSuiteName Prefix of name to give the suite being built.
+ * @return The builder for method chaining.
+ */
+ public TestSuiteBuilder named(String newSuiteName) {
+ suiteName = newSuiteName;
+ return this;
+ }
+
+ /**
+ * Call this method once you've configured your builder as desired.
+ *
+ * @return The suite containing the requested tests.
+ */
+ public final TestSuite build() {
+ rootSuite = new TestSuite(getSuiteName());
+
+ // Keep track of current class so we know when to create a new sub-suite.
+ currentClassname = null;
+ try {
+ for (TestMethod test : testGrouping.getTests()) {
+ if (satisfiesAllPredicates(test)) {
+ 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());
+ suite.addTest(new FailedToCreateTests(exception));
+ return suite;
+ }
+ return rootSuite;
+ }
+
+ /**
+ * Subclasses use this method to determine the name of the suite.
+ *
+ * @return The package and suite name combined.
+ */
+ protected String getSuiteName() {
+ return suiteName;
+ }
+
+ /**
+ * Exclude tests that fail to satisfy all of the given predicates. If you call this method, you
+ * probably also want to call {@link #named(String)} to override the default suite name.
+ *
+ * @param predicates Predicates to add to the list of requirements.
+ * @return The builder for method chaining.
+ */
+ public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) {
+ ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>();
+ Collections.addAll(list, predicates);
+ return addRequirements(list);
+ }
+
+ /**
+ * A special {@link junit.framework.TestCase} used to indicate a failure during the build()
+ * step.
+ */
+ public static class FailedToCreateTests extends TestCase {
+ private final Exception exception;
+
+ public FailedToCreateTests(Exception exception) {
+ super("testSuiteConstructionFailed");
+ this.exception = exception;
+ }
+
+ public void testSuiteConstructionFailed() {
+ throw new RuntimeException("Exception during suite construction", exception);
+ }
+ }
+
+ /**
+ * @return the test package that represents the packages that were included for our test suite.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+ protected TestGrouping getTestGrouping() {
+ return testGrouping;
+ }
+
+ private boolean satisfiesAllPredicates(TestMethod test) {
+ for (Predicate<TestMethod> predicate : predicates) {
+ if (!predicate.apply(test)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void addTest(TestMethod testMethod) throws Exception {
+ addSuiteIfNecessary(testMethod.getEnclosingClassname());
+ suiteForCurrentClass.addTest(testMethod.createTest());
+ }
+
+ private void addTest(Test test) {
+ addSuiteIfNecessary(test.getClass().getName());
+ suiteForCurrentClass.addTest(test);
+ }
+
+ private void addSuiteIfNecessary(String parentClassname) {
+ if (!parentClassname.equals(currentClassname)) {
+ currentClassname = parentClassname;
+ suiteForCurrentClass = new TestSuite(parentClassname);
+ rootSuite.addTest(suiteForCurrentClass);
+ }
+ }
+
+ private static String parsePackageNameFromClassName(String className) {
+ return className.substring(0, className.lastIndexOf('.'));
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java
new file mode 100644
index 0000000..8cf4c86
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java
@@ -0,0 +1,36 @@
+/*
+ * 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.suitebuilder;
+
+/**
+ * A suite builder that finds unit tests.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class UnitTestSuiteBuilder extends TestSuiteBuilder {
+
+ public UnitTestSuiteBuilder(Class clazz) {
+ this(clazz.getName(), clazz.getClassLoader());
+ }
+
+
+ public UnitTestSuiteBuilder(String name, ClassLoader classLoader) {
+ super(name, classLoader);
+ addRequirements(TestPredicates.REJECT_INSTRUMENTATION);
+ addRequirements(TestPredicates.REJECT_PERFORMANCE);
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java
new file mode 100644
index 0000000..a2868fc
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java
@@ -0,0 +1,44 @@
+/*
+ * 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.suitebuilder.annotation;
+
+import static com.android.internal.util.Predicates.or;
+import com.android.internal.util.Predicate;
+import android.test.suitebuilder.TestMethod;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * A predicate that checks to see if a {@link TestMethod} has a specific annotation, either on the
+ * method or on the containing class.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+public class HasAnnotation implements Predicate<TestMethod> {
+
+ private Predicate<TestMethod> hasMethodOrClassAnnotation;
+
+ public HasAnnotation(Class<? extends Annotation> annotationClass) {
+ this.hasMethodOrClassAnnotation = or(
+ new HasMethodAnnotation(annotationClass),
+ new HasClassAnnotation(annotationClass));
+ }
+
+ public boolean apply(TestMethod testMethod) {
+ return hasMethodOrClassAnnotation.apply(testMethod);
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java
new file mode 100644
index 0000000..ac76f4c
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java
@@ -0,0 +1,41 @@
+/*
+ * 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.suitebuilder.annotation;
+
+import java.lang.annotation.Annotation;
+
+import android.test.suitebuilder.TestMethod;
+import com.android.internal.util.Predicate;
+
+/**
+ * A predicate that checks to see if a {@link android.test.suitebuilder.TestMethod} has a specific annotation on the
+ * containing class. Consider using the public {@link HasAnnotation} class instead of this class.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+class HasClassAnnotation implements Predicate<TestMethod> {
+
+ private Class<? extends Annotation> annotationClass;
+
+ public HasClassAnnotation(Class<? extends Annotation> annotationClass) {
+ this.annotationClass = annotationClass;
+ }
+
+ public boolean apply(TestMethod testMethod) {
+ return testMethod.getEnclosingClass().getAnnotation(annotationClass) != null;
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java
new file mode 100644
index 0000000..96bd922
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java
@@ -0,0 +1,41 @@
+/*
+ * 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.suitebuilder.annotation;
+
+import com.android.internal.util.Predicate;
+import android.test.suitebuilder.TestMethod;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * A predicate that checks to see if a the method represented by {@link TestMethod} has a certain
+ * annotation on it. Consider using the public {@link HasAnnotation} class instead of this class.
+ *
+ * {@hide} Not needed for 1.0 SDK.
+ */
+class HasMethodAnnotation implements Predicate<TestMethod> {
+
+ private final Class<? extends Annotation> annotationClass;
+
+ public HasMethodAnnotation(Class<? extends Annotation> annotationClass) {
+ this.annotationClass = annotationClass;
+ }
+
+ public boolean apply(TestMethod testMethod) {
+ return testMethod.getAnnotation(annotationClass) != null;
+ }
+}
diff --git a/test-runner/android/test/suitebuilder/annotation/package.html b/test-runner/android/test/suitebuilder/annotation/package.html
new file mode 100644
index 0000000..ffba2e9
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/annotation/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Utility classes supporting the test runner classes.
+</BODY>
+</HTML>
diff --git a/test-runner/android/test/suitebuilder/package.html b/test-runner/android/test/suitebuilder/package.html
new file mode 100644
index 0000000..ffba2e9
--- /dev/null
+++ b/test-runner/android/test/suitebuilder/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Utility classes supporting the test runner classes.
+</BODY>
+</HTML>
diff --git a/test-runner/junit/MODULE_LICENSE_CPL b/test-runner/junit/MODULE_LICENSE_CPL
new file mode 100644
index 0000000..541dbb5
--- /dev/null
+++ b/test-runner/junit/MODULE_LICENSE_CPL
@@ -0,0 +1 @@
+http://www.opensource.org/licenses/cpl1.0.php
diff --git a/test-runner/junit/runner/BaseTestRunner.java b/test-runner/junit/runner/BaseTestRunner.java
new file mode 100644
index 0000000..e073ef7
--- /dev/null
+++ b/test-runner/junit/runner/BaseTestRunner.java
@@ -0,0 +1,326 @@
+package junit.runner;
+
+import junit.framework.*;
+import java.lang.reflect.*;
+import java.text.NumberFormat;
+import java.io.*;
+import java.util.*;
+
+/**
+ * Base class for all test runners.
+ * This class was born live on stage in Sardinia during XP2000.
+ */
+public abstract class BaseTestRunner implements TestListener {
+ public static final String SUITE_METHODNAME= "suite";
+
+ private static Properties fPreferences;
+ static int fgMaxMessageLength= 500;
+ static boolean fgFilterStack= true;
+ boolean fLoading= true;
+
+ /*
+ * Implementation of TestListener
+ */
+ public synchronized void startTest(Test test) {
+ testStarted(test.toString());
+ }
+
+ protected static void setPreferences(Properties preferences) {
+ fPreferences= preferences;
+ }
+
+ protected static Properties getPreferences() {
+ if (fPreferences == null) {
+ fPreferences= new Properties();
+ fPreferences.put("loading", "true");
+ fPreferences.put("filterstack", "true");
+ readPreferences();
+ }
+ return fPreferences;
+ }
+
+ public static void savePreferences() throws IOException {
+ FileOutputStream fos= new FileOutputStream(getPreferencesFile());
+ try {
+ getPreferences().store(fos, "");
+ } finally {
+ fos.close();
+ }
+ }
+
+ public void setPreference(String key, String value) {
+ getPreferences().setProperty(key, value);
+ }
+
+ public synchronized void endTest(Test test) {
+ testEnded(test.toString());
+ }
+
+ public synchronized void addError(final Test test, final Throwable t) {
+ testFailed(TestRunListener.STATUS_ERROR, test, t);
+ }
+
+ public synchronized void addFailure(final Test test, final AssertionFailedError t) {
+ testFailed(TestRunListener.STATUS_FAILURE, test, t);
+ }
+
+ // TestRunListener implementation
+
+ public abstract void testStarted(String testName);
+
+ public abstract void testEnded(String testName);
+
+ public abstract void testFailed(int status, Test test, Throwable t);
+
+ /**
+ * Returns the Test corresponding to the given suite. This is
+ * a template method, subclasses override runFailed(), clearStatus().
+ */
+ public Test getTest(String suiteClassName) {
+ if (suiteClassName.length() <= 0) {
+ clearStatus();
+ return null;
+ }
+ Class testClass= null;
+ try {
+ testClass= loadSuiteClass(suiteClassName);
+ } catch (ClassNotFoundException e) {
+ String clazz= e.getMessage();
+ if (clazz == null)
+ clazz= suiteClassName;
+ runFailed("Class not found \""+clazz+"\"");
+ return null;
+ } catch(Exception e) {
+ runFailed("Error: "+e.toString());
+ return null;
+ }
+ Method suiteMethod= null;
+ try {
+ suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
+ } catch(Exception e) {
+ // try to extract a test suite automatically
+ clearStatus();
+ return new TestSuite(testClass);
+ }
+ if (! Modifier.isStatic(suiteMethod.getModifiers())) {
+ runFailed("Suite() method must be static");
+ return null;
+ }
+ Test test= null;
+ try {
+ test= (Test)suiteMethod.invoke(null); // static method
+ if (test == null)
+ return test;
+ }
+ catch (InvocationTargetException e) {
+ runFailed("Failed to invoke suite():" + e.getTargetException().toString());
+ return null;
+ }
+ catch (IllegalAccessException e) {
+ runFailed("Failed to invoke suite():" + e.toString());
+ return null;
+ }
+
+ clearStatus();
+ return test;
+ }
+
+ /**
+ * Returns the formatted string of the elapsed time.
+ */
+ public String elapsedTimeAsString(long runTime) {
+ return NumberFormat.getInstance().format((double)runTime/1000);
+ }
+
+ /**
+ * Processes the command line arguments and
+ * returns the name of the suite class to run or null
+ */
+ protected String processArguments(String[] args) {
+ String suiteName= null;
+ for (int i= 0; i < args.length; i++) {
+ if (args[i].equals("-noloading")) {
+ setLoading(false);
+ } else if (args[i].equals("-nofilterstack")) {
+ fgFilterStack= false;
+ } else if (args[i].equals("-c")) {
+ if (args.length > i+1)
+ suiteName= extractClassName(args[i+1]);
+ else
+ System.out.println("Missing Test class name");
+ i++;
+ } else {
+ suiteName= args[i];
+ }
+ }
+ return suiteName;
+ }
+
+ /**
+ * Sets the loading behaviour of the test runner
+ */
+ public void setLoading(boolean enable) {
+ fLoading= enable;
+ }
+ /**
+ * Extract the class name from a String
+ */
+ public String extractClassName(String className) {
+ if(className.startsWith("Default package for"))
+ return className.substring(className.lastIndexOf(".")+1);
+ return className;
+ }
+
+ /**
+ * Truncates a String to the maximum length.
+ */
+ public static String truncate(String s) {
+ if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength)
+ s= s.substring(0, fgMaxMessageLength)+"...";
+ return s;
+ }
+
+ /**
+ * Override to define how to handle a failed loading of
+ * a test suite.
+ */
+ protected abstract void runFailed(String message);
+
+ /**
+ * Returns the loaded Class for a suite name.
+ */
+ protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
+ return getLoader().load(suiteClassName);
+ }
+
+ /**
+ * Clears the status message.
+ */
+ protected void clearStatus() { // Belongs in the GUI TestRunner class
+ }
+
+ /**
+ * Returns the loader to be used.
+ */
+ public TestSuiteLoader getLoader() {
+ if (useReloadingTestSuiteLoader())
+ return new ReloadingTestSuiteLoader();
+ return new StandardTestSuiteLoader();
+ }
+
+ protected boolean useReloadingTestSuiteLoader() {
+ return getPreference("loading").equals("true") && !inVAJava() && fLoading;
+ }
+
+ private static File getPreferencesFile() {
+ String home= System.getProperty("user.home");
+ return new File(home, "junit.properties");
+ }
+
+ private static void readPreferences() {
+ InputStream is= null;
+ try {
+ is= new FileInputStream(getPreferencesFile());
+ setPreferences(new Properties(getPreferences()));
+ getPreferences().load(is);
+ } catch (IOException e) {
+ try {
+ if (is != null)
+ is.close();
+ } catch (IOException e1) {
+ }
+ }
+ }
+
+ public static String getPreference(String key) {
+ return getPreferences().getProperty(key);
+ }
+
+ public static int getPreference(String key, int dflt) {
+ String value= getPreference(key);
+ int intValue= dflt;
+ if (value == null)
+ return intValue;
+ try {
+ intValue= Integer.parseInt(value);
+ } catch (NumberFormatException ne) {
+ }
+ return intValue;
+ }
+
+ public static boolean inVAJava() {
+ try {
+ Class.forName("com.ibm.uvm.tools.DebugSupport");
+ }
+ catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a filtered stack trace
+ */
+ public static String getFilteredTrace(Throwable t) {
+ StringWriter stringWriter= new StringWriter();
+ PrintWriter writer= new PrintWriter(stringWriter);
+ t.printStackTrace(writer);
+ StringBuffer buffer= stringWriter.getBuffer();
+ String trace= buffer.toString();
+ return BaseTestRunner.getFilteredTrace(trace);
+ }
+
+ /**
+ * Filters stack frames from internal JUnit classes
+ */
+ public static String getFilteredTrace(String stack) {
+ if (showStackRaw())
+ return stack;
+
+ StringWriter sw= new StringWriter();
+ PrintWriter pw= new PrintWriter(sw);
+ StringReader sr= new StringReader(stack);
+ // BEGIN android-changed
+ // Use a sensible default buffer size
+ BufferedReader br= new BufferedReader(sr, 1000);
+ // END android-changed
+
+ String line;
+ try {
+ while ((line= br.readLine()) != null) {
+ if (!filterLine(line))
+ pw.println(line);
+ }
+ } catch (Exception IOException) {
+ return stack; // return the stack unfiltered
+ }
+ return sw.toString();
+ }
+
+ protected static boolean showStackRaw() {
+ return !getPreference("filterstack").equals("true") || fgFilterStack == false;
+ }
+
+ static boolean filterLine(String line) {
+ String[] patterns= new String[] {
+ "junit.framework.TestCase",
+ "junit.framework.TestResult",
+ "junit.framework.TestSuite",
+ "junit.framework.Assert.", // don't filter AssertionFailure
+ "junit.swingui.TestRunner",
+ "junit.awtui.TestRunner",
+ "junit.textui.TestRunner",
+ "java.lang.reflect.Method.invoke("
+ };
+ for (int i= 0; i < patterns.length; i++) {
+ if (line.indexOf(patterns[i]) > 0)
+ return true;
+ }
+ return false;
+ }
+
+ static {
+ fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
+ }
+
+}
diff --git a/test-runner/junit/runner/ClassPathTestCollector.java b/test-runner/junit/runner/ClassPathTestCollector.java
new file mode 100644
index 0000000..8a3c702
--- /dev/null
+++ b/test-runner/junit/runner/ClassPathTestCollector.java
@@ -0,0 +1,81 @@
+package junit.runner;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * An implementation of a TestCollector that consults the
+ * class path. It considers all classes on the class path
+ * excluding classes in JARs. It leaves it up to subclasses
+ * to decide whether a class is a runnable Test.
+ *
+ * @see TestCollector
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public abstract class ClassPathTestCollector implements TestCollector {
+
+ static final int SUFFIX_LENGTH= ".class".length();
+
+ public ClassPathTestCollector() {
+ }
+
+ public Enumeration collectTests() {
+ String classPath= System.getProperty("java.class.path");
+ Hashtable result = collectFilesInPath(classPath);
+ return result.elements();
+ }
+
+ public Hashtable collectFilesInPath(String classPath) {
+ Hashtable result= collectFilesInRoots(splitClassPath(classPath));
+ return result;
+ }
+
+ Hashtable collectFilesInRoots(Vector roots) {
+ Hashtable result= new Hashtable(100);
+ Enumeration e= roots.elements();
+ while (e.hasMoreElements())
+ gatherFiles(new File((String)e.nextElement()), "", result);
+ return result;
+ }
+
+ void gatherFiles(File classRoot, String classFileName, Hashtable result) {
+ File thisRoot= new File(classRoot, classFileName);
+ if (thisRoot.isFile()) {
+ if (isTestClass(classFileName)) {
+ String className= classNameFromFile(classFileName);
+ result.put(className, className);
+ }
+ return;
+ }
+ String[] contents= thisRoot.list();
+ if (contents != null) {
+ for (int i= 0; i < contents.length; i++)
+ gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
+ }
+ }
+
+ Vector splitClassPath(String classPath) {
+ Vector result= new Vector();
+ String separator= System.getProperty("path.separator");
+ StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
+ while (tokenizer.hasMoreTokens())
+ result.addElement(tokenizer.nextToken());
+ return result;
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ return
+ classFileName.endsWith(".class") &&
+ classFileName.indexOf('$') < 0 &&
+ classFileName.indexOf("Test") > 0;
+ }
+
+ protected String classNameFromFile(String classFileName) {
+ // convert /a/b.class to a.b
+ String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
+ String s2= s.replace(File.separatorChar, '.');
+ if (s2.startsWith("."))
+ return s2.substring(1);
+ return s2;
+ }
+}
diff --git a/test-runner/junit/runner/FailureDetailView.java b/test-runner/junit/runner/FailureDetailView.java
new file mode 100644
index 0000000..7108cec
--- /dev/null
+++ b/test-runner/junit/runner/FailureDetailView.java
@@ -0,0 +1,28 @@
+package junit.runner;
+
+// The following line was removed for compatibility with Android libraries.
+//import java.awt.Component;
+
+import junit.framework.*;
+
+/**
+ * A view to show a details about a failure
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public interface FailureDetailView {
+ // The following definition was removed for compatibility with Android
+ // libraries.
+ // /**
+ // * Returns the component used to present the TraceView
+ // */
+ // public Component getComponent();
+
+ /**
+ * Shows details of a TestFailure
+ */
+ public void showFailure(TestFailure failure);
+ /**
+ * Clears the view
+ */
+ public void clear();
+}
diff --git a/test-runner/junit/runner/LoadingTestCollector.java b/test-runner/junit/runner/LoadingTestCollector.java
new file mode 100644
index 0000000..b1760b1
--- /dev/null
+++ b/test-runner/junit/runner/LoadingTestCollector.java
@@ -0,0 +1,70 @@
+package junit.runner;
+
+import java.lang.reflect.*;
+import junit.runner.*;
+import junit.framework.*;
+
+/**
+ * An implementation of a TestCollector that loads
+ * all classes on the class path and tests whether
+ * it is assignable from Test or provides a static suite method.
+ * @see TestCollector
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public class LoadingTestCollector extends ClassPathTestCollector {
+
+ TestCaseClassLoader fLoader;
+
+ public LoadingTestCollector() {
+ fLoader= new TestCaseClassLoader();
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ try {
+ if (classFileName.endsWith(".class")) {
+ Class testClass= classFromFile(classFileName);
+ return (testClass != null) && isTestClass(testClass);
+ }
+ }
+ catch (ClassNotFoundException expected) {
+ }
+ catch (NoClassDefFoundError notFatal) {
+ }
+ return false;
+ }
+
+ Class classFromFile(String classFileName) throws ClassNotFoundException {
+ String className= classNameFromFile(classFileName);
+ if (!fLoader.isExcluded(className))
+ return fLoader.loadClass(className, false);
+ return null;
+ }
+
+ boolean isTestClass(Class testClass) {
+ if (hasSuiteMethod(testClass))
+ return true;
+ if (Test.class.isAssignableFrom(testClass) &&
+ Modifier.isPublic(testClass.getModifiers()) &&
+ hasPublicConstructor(testClass))
+ return true;
+ return false;
+ }
+
+ boolean hasSuiteMethod(Class testClass) {
+ try {
+ testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
+ } catch(Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean hasPublicConstructor(Class testClass) {
+ try {
+ TestSuite.getTestConstructor(testClass);
+ } catch(NoSuchMethodException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/test-runner/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/junit/runner/ReloadingTestSuiteLoader.java
new file mode 100644
index 0000000..a6d84fe
--- /dev/null
+++ b/test-runner/junit/runner/ReloadingTestSuiteLoader.java
@@ -0,0 +1,20 @@
+package junit.runner;
+
+/**
+ * A TestSuite loader that can reload classes.
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public class ReloadingTestSuiteLoader implements TestSuiteLoader {
+
+ public Class load(String suiteClassName) throws ClassNotFoundException {
+ return createLoader().loadClass(suiteClassName, true);
+ }
+
+ public Class reload(Class aClass) throws ClassNotFoundException {
+ return createLoader().loadClass(aClass.getName(), true);
+ }
+
+ protected TestCaseClassLoader createLoader() {
+ return new TestCaseClassLoader();
+ }
+}
diff --git a/test-runner/junit/runner/SimpleTestCollector.java b/test-runner/junit/runner/SimpleTestCollector.java
new file mode 100644
index 0000000..543168f
--- /dev/null
+++ b/test-runner/junit/runner/SimpleTestCollector.java
@@ -0,0 +1,21 @@
+package junit.runner;
+
+/**
+ * An implementation of a TestCollector that considers
+ * a class to be a test class when it contains the
+ * pattern "Test" in its name
+ * @see TestCollector
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public class SimpleTestCollector extends ClassPathTestCollector {
+
+ public SimpleTestCollector() {
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ return
+ classFileName.endsWith(".class") &&
+ classFileName.indexOf('$') < 0 &&
+ classFileName.indexOf("Test") > 0;
+ }
+}
diff --git a/test-runner/junit/runner/Sorter.java b/test-runner/junit/runner/Sorter.java
new file mode 100644
index 0000000..66f551e
--- /dev/null
+++ b/test-runner/junit/runner/Sorter.java
@@ -0,0 +1,39 @@
+package junit.runner;
+
+import java.util.*;
+
+import junit.runner.*;
+
+/**
+ * A custom quick sort with support to customize the swap behaviour.
+ * NOTICE: We can't use the the sorting support from the JDK 1.2 collection
+ * classes because of the JDK 1.1.7 compatibility.
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public class Sorter {
+ public static interface Swapper {
+ public void swap(Vector values, int left, int right);
+ }
+
+ public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
+ int oleft= left;
+ int oright= right;
+ String mid= (String)values.elementAt((left + right) / 2);
+ do {
+ while (((String)(values.elementAt(left))).compareTo(mid) < 0)
+ left++;
+ while (mid.compareTo((String)(values.elementAt(right))) < 0)
+ right--;
+ if (left <= right) {
+ swapper.swap(values, left, right);
+ left++;
+ right--;
+ }
+ } while (left <= right);
+
+ if (oleft < right)
+ sortStrings(values, oleft, right, swapper);
+ if (left < oright)
+ sortStrings(values, left, oright, swapper);
+ }
+}
diff --git a/test-runner/junit/runner/StandardTestSuiteLoader.java b/test-runner/junit/runner/StandardTestSuiteLoader.java
new file mode 100644
index 0000000..bce7dec
--- /dev/null
+++ b/test-runner/junit/runner/StandardTestSuiteLoader.java
@@ -0,0 +1,20 @@
+package junit.runner;
+
+/**
+ * The standard test suite loader. It can only load the same class once.
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public class StandardTestSuiteLoader implements TestSuiteLoader {
+ /**
+ * Uses the system class loader to load the test class
+ */
+ public Class load(String suiteClassName) throws ClassNotFoundException {
+ return Class.forName(suiteClassName);
+ }
+ /**
+ * Uses the system class loader to load the test class
+ */
+ public Class reload(Class aClass) throws ClassNotFoundException {
+ return aClass;
+ }
+}
diff --git a/test-runner/junit/runner/TestCaseClassLoader.java b/test-runner/junit/runner/TestCaseClassLoader.java
new file mode 100644
index 0000000..3a510c6
--- /dev/null
+++ b/test-runner/junit/runner/TestCaseClassLoader.java
@@ -0,0 +1,225 @@
+package junit.runner;
+
+import java.util.*;
+import java.io.*;
+import java.net.URL;
+import java.util.zip.*;
+
+/**
+ * A custom class loader which enables the reloading
+ * of classes for each test run. The class loader
+ * can be configured with a list of package paths that
+ * should be excluded from loading. The loading
+ * of these packages is delegated to the system class
+ * loader. They will be shared across test runs.
+ * <p>
+ * The list of excluded package paths is specified in
+ * a properties file "excluded.properties" that is located in
+ * the same place as the TestCaseClassLoader class.
+ * <p>
+ * <b>Known limitation:</b> the TestCaseClassLoader cannot load classes
+ * from jar files.
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public class TestCaseClassLoader extends ClassLoader {
+ /** scanned class path */
+ private Vector fPathItems;
+ /** default excluded paths */
+ private String[] defaultExclusions= {
+ "junit.framework.",
+ "junit.extensions.",
+ "junit.runner."
+ };
+ /** name of excluded properties file */
+ static final String EXCLUDED_FILE= "excluded.properties";
+ /** excluded paths */
+ private Vector fExcluded;
+
+ /**
+ * Constructs a TestCaseLoader. It scans the class path
+ * and the excluded package paths
+ */
+ public TestCaseClassLoader() {
+ this(System.getProperty("java.class.path"));
+ }
+
+ /**
+ * Constructs a TestCaseLoader. It scans the class path
+ * and the excluded package paths
+ */
+ public TestCaseClassLoader(String classPath) {
+ scanPath(classPath);
+ readExcludedPackages();
+ }
+
+ private void scanPath(String classPath) {
+ String separator= System.getProperty("path.separator");
+ fPathItems= new Vector(10);
+ StringTokenizer st= new StringTokenizer(classPath, separator);
+ while (st.hasMoreTokens()) {
+ fPathItems.addElement(st.nextToken());
+ }
+ }
+
+ public URL getResource(String name) {
+ return ClassLoader.getSystemResource(name);
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return ClassLoader.getSystemResourceAsStream(name);
+ }
+
+ public boolean isExcluded(String name) {
+ for (int i= 0; i < fExcluded.size(); i++) {
+ if (name.startsWith((String) fExcluded.elementAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public synchronized Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+
+ Class c= findLoadedClass(name);
+ if (c != null)
+ return c;
+ //
+ // Delegate the loading of excluded classes to the
+ // standard class loader.
+ //
+ if (isExcluded(name)) {
+ try {
+ c= findSystemClass(name);
+ return c;
+ } catch (ClassNotFoundException e) {
+ // keep searching
+ }
+ }
+ if (c == null) {
+ byte[] data= lookupClassData(name);
+ if (data == null)
+ throw new ClassNotFoundException();
+ c= defineClass(name, data, 0, data.length);
+ }
+ if (resolve)
+ resolveClass(c);
+ return c;
+ }
+
+ private byte[] lookupClassData(String className) throws ClassNotFoundException {
+ byte[] data= null;
+ for (int i= 0; i < fPathItems.size(); i++) {
+ String path= (String) fPathItems.elementAt(i);
+ String fileName= className.replace('.', '/')+".class";
+ if (isJar(path)) {
+ data= loadJarData(path, fileName);
+ } else {
+ data= loadFileData(path, fileName);
+ }
+ if (data != null)
+ return data;
+ }
+ throw new ClassNotFoundException(className);
+ }
+
+ boolean isJar(String pathEntry) {
+ return pathEntry.endsWith(".jar") ||
+ pathEntry.endsWith(".apk") ||
+ pathEntry.endsWith(".zip");
+ }
+
+ private byte[] loadFileData(String path, String fileName) {
+ File file= new File(path, fileName);
+ if (file.exists()) {
+ return getClassData(file);
+ }
+ return null;
+ }
+
+ private byte[] getClassData(File f) {
+ try {
+ FileInputStream stream= new FileInputStream(f);
+ ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
+ byte[] b= new byte[1000];
+ int n;
+ while ((n= stream.read(b)) != -1)
+ out.write(b, 0, n);
+ stream.close();
+ out.close();
+ return out.toByteArray();
+
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ private byte[] loadJarData(String path, String fileName) {
+ ZipFile zipFile= null;
+ InputStream stream= null;
+ File archive= new File(path);
+ if (!archive.exists())
+ return null;
+ try {
+ zipFile= new ZipFile(archive);
+ } catch(IOException io) {
+ return null;
+ }
+ ZipEntry entry= zipFile.getEntry(fileName);
+ if (entry == null)
+ return null;
+ int size= (int) entry.getSize();
+ try {
+ stream= zipFile.getInputStream(entry);
+ byte[] data= new byte[size];
+ int pos= 0;
+ while (pos < size) {
+ int n= stream.read(data, pos, data.length - pos);
+ pos += n;
+ }
+ zipFile.close();
+ return data;
+ } catch (IOException e) {
+ } finally {
+ try {
+ if (stream != null)
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ return null;
+ }
+
+ private void readExcludedPackages() {
+ fExcluded= new Vector(10);
+ for (int i= 0; i < defaultExclusions.length; i++)
+ fExcluded.addElement(defaultExclusions[i]);
+
+ InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
+ if (is == null)
+ return;
+ Properties p= new Properties();
+ try {
+ p.load(is);
+ }
+ catch (IOException e) {
+ return;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
+ String key= (String)e.nextElement();
+ if (key.startsWith("excluded.")) {
+ String path= p.getProperty(key);
+ path= path.trim();
+ if (path.endsWith("*"))
+ path= path.substring(0, path.length()-1);
+ if (path.length() > 0)
+ fExcluded.addElement(path);
+ }
+ }
+ }
+}
diff --git a/test-runner/junit/runner/TestCollector.java b/test-runner/junit/runner/TestCollector.java
new file mode 100644
index 0000000..208dccd
--- /dev/null
+++ b/test-runner/junit/runner/TestCollector.java
@@ -0,0 +1,17 @@
+package junit.runner;
+
+import java.util.*;
+
+
+/**
+ * Collects Test class names to be presented
+ * by the TestSelector.
+ * @see TestSelector
+ * {@hide} - Not needed for 1.0 SDK
+ */
+public interface TestCollector {
+ /**
+ * Returns an enumeration of Strings with qualified class names
+ */
+ public Enumeration collectTests();
+}
diff --git a/test-runner/junit/runner/TestRunListener.java b/test-runner/junit/runner/TestRunListener.java
new file mode 100644
index 0000000..0e95819
--- /dev/null
+++ b/test-runner/junit/runner/TestRunListener.java
@@ -0,0 +1,20 @@
+package junit.runner;
+/**
+ * A listener interface for observing the
+ * execution of a test run. Unlike TestListener,
+ * this interface using only primitive objects,
+ * making it suitable for remote test execution.
+ * {@hide} - Not needed for 1.0 SDK
+ */
+ public interface TestRunListener {
+ /* test status constants*/
+ public static final int STATUS_ERROR= 1;
+ public static final int STATUS_FAILURE= 2;
+
+ public void testRunStarted(String testSuiteName, int testCount);
+ public void testRunEnded(long elapsedTime);
+ public void testRunStopped(long elapsedTime);
+ public void testStarted(String testName);
+ public void testEnded(String testName);
+ public void testFailed(int status, String testName, String trace);
+}
diff --git a/test-runner/junit/runner/TestSuiteLoader.java b/test-runner/junit/runner/TestSuiteLoader.java
new file mode 100644
index 0000000..39a4cf7
--- /dev/null
+++ b/test-runner/junit/runner/TestSuiteLoader.java
@@ -0,0 +1,9 @@
+package junit.runner;
+
+/**
+ * An interface to define how a test suite should be loaded.
+ */
+public interface TestSuiteLoader {
+ abstract public Class load(String suiteClassName) throws ClassNotFoundException;
+ abstract public Class reload(Class aClass) throws ClassNotFoundException;
+}
diff --git a/test-runner/junit/runner/Version.java b/test-runner/junit/runner/Version.java
new file mode 100644
index 0000000..b4541ab
--- /dev/null
+++ b/test-runner/junit/runner/Version.java
@@ -0,0 +1,14 @@
+package junit.runner;
+
+/**
+ * This class defines the current version of JUnit
+ */
+public class Version {
+ private Version() {
+ // don't instantiate
+ }
+
+ public static String id() {
+ return "3.8.1";
+ }
+}
diff --git a/test-runner/junit/runner/excluded.properties b/test-runner/junit/runner/excluded.properties
new file mode 100644
index 0000000..3284628
--- /dev/null
+++ b/test-runner/junit/runner/excluded.properties
@@ -0,0 +1,12 @@
+#
+# The list of excluded package paths for the TestCaseClassLoader
+#
+excluded.0=sun.*
+excluded.1=com.sun.*
+excluded.2=org.omg.*
+excluded.3=javax.*
+excluded.4=sunw.*
+excluded.5=java.*
+excluded.6=org.w3c.dom.*
+excluded.7=org.xml.sax.*
+excluded.8=net.jini.*
diff --git a/test-runner/junit/runner/logo.gif b/test-runner/junit/runner/logo.gif
new file mode 100644
index 0000000..d0e1547
--- /dev/null
+++ b/test-runner/junit/runner/logo.gif
Binary files differ
diff --git a/test-runner/junit/runner/package.html b/test-runner/junit/runner/package.html
new file mode 100644
index 0000000..f08fa70
--- /dev/null
+++ b/test-runner/junit/runner/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Utility classes supporting the junit test framework.
+</BODY>
+</HTML>
diff --git a/test-runner/junit/runner/smalllogo.gif b/test-runner/junit/runner/smalllogo.gif
new file mode 100644
index 0000000..7b25eaf6
--- /dev/null
+++ b/test-runner/junit/runner/smalllogo.gif
Binary files differ
diff --git a/test-runner/junit/textui/ResultPrinter.java b/test-runner/junit/textui/ResultPrinter.java
new file mode 100644
index 0000000..5c97112
--- /dev/null
+++ b/test-runner/junit/textui/ResultPrinter.java
@@ -0,0 +1,142 @@
+
+package junit.textui;
+
+import java.io.PrintStream;
+// The following line was removed for compatibility with Android libraries.
+//import java.text.NumberFormat;
+import java.util.Enumeration;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestFailure;
+import junit.framework.TestListener;
+import junit.framework.TestResult;
+import junit.runner.BaseTestRunner;
+
+public class ResultPrinter implements TestListener {
+ PrintStream fWriter;
+ int fColumn= 0;
+
+ public ResultPrinter(PrintStream writer) {
+ fWriter= writer;
+ }
+
+ /* API for use by textui.TestRunner
+ */
+
+ synchronized void print(TestResult result, long runTime) {
+ printHeader(runTime);
+ printErrors(result);
+ printFailures(result);
+ printFooter(result);
+ }
+
+ void printWaitPrompt() {
+ getWriter().println();
+ getWriter().println("<RETURN> to continue");
+ }
+
+ /* Internal methods
+ */
+
+ protected void printHeader(long runTime) {
+ getWriter().println();
+ getWriter().println("Time: "+elapsedTimeAsString(runTime));
+ }
+
+ protected void printErrors(TestResult result) {
+ printDefects(result.errors(), result.errorCount(), "error");
+ }
+
+ protected void printFailures(TestResult result) {
+ printDefects(result.failures(), result.failureCount(), "failure");
+ }
+
+ protected void printDefects(Enumeration booBoos, int count, String type) {
+ if (count == 0) return;
+ if (count == 1)
+ getWriter().println("There was " + count + " " + type + ":");
+ else
+ getWriter().println("There were " + count + " " + type + "s:");
+ for (int i= 1; booBoos.hasMoreElements(); i++) {
+ printDefect((TestFailure) booBoos.nextElement(), i);
+ }
+ }
+
+ public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
+ printDefectHeader(booBoo, count);
+ printDefectTrace(booBoo);
+ }
+
+ protected void printDefectHeader(TestFailure booBoo, int count) {
+ // I feel like making this a println, then adding a line giving the throwable a chance to print something
+ // before we get to the stack trace.
+ getWriter().print(count + ") " + booBoo.failedTest());
+ }
+
+ protected void printDefectTrace(TestFailure booBoo) {
+ getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
+ }
+
+ protected void printFooter(TestResult result) {
+ if (result.wasSuccessful()) {
+ getWriter().println();
+ getWriter().print("OK");
+ getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+
+ } else {
+ getWriter().println();
+ getWriter().println("FAILURES!!!");
+ getWriter().println("Tests run: "+result.runCount()+
+ ", Failures: "+result.failureCount()+
+ ", Errors: "+result.errorCount());
+ }
+ getWriter().println();
+ }
+
+
+ /**
+ * Returns the formatted string of the elapsed time.
+ * Duplicated from BaseTestRunner. Fix it.
+ */
+ protected String elapsedTimeAsString(long runTime) {
+ // The following line was altered for compatibility with
+ // Android libraries.
+ return Double.toString((double)runTime/1000);
+ }
+
+ public PrintStream getWriter() {
+ return fWriter;
+ }
+ /**
+ * @see junit.framework.TestListener#addError(Test, Throwable)
+ */
+ public void addError(Test test, Throwable t) {
+ getWriter().print("E");
+ }
+
+ /**
+ * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
+ */
+ public void addFailure(Test test, AssertionFailedError t) {
+ getWriter().print("F");
+ }
+
+ /**
+ * @see junit.framework.TestListener#endTest(Test)
+ */
+ public void endTest(Test test) {
+ }
+
+ /**
+ * @see junit.framework.TestListener#startTest(Test)
+ */
+ public void startTest(Test test) {
+ getWriter().print(".");
+ if (fColumn++ >= 40) {
+ getWriter().println();
+ fColumn= 0;
+ }
+ }
+
+}
diff --git a/test-runner/junit/textui/TestRunner.java b/test-runner/junit/textui/TestRunner.java
new file mode 100644
index 0000000..8bdc325
--- /dev/null
+++ b/test-runner/junit/textui/TestRunner.java
@@ -0,0 +1,189 @@
+package junit.textui;
+
+
+import java.io.PrintStream;
+
+import junit.framework.*;
+import junit.runner.*;
+
+/**
+ * A command line based tool to run tests.
+ * <pre>
+ * java junit.textui.TestRunner [-wait] TestCaseClass
+ * </pre>
+ * TestRunner expects the name of a TestCase class as argument.
+ * If this class defines a static <code>suite</code> method it
+ * will be invoked and the returned test is run. Otherwise all
+ * the methods starting with "test" having no arguments are run.
+ * <p>
+ * When the wait command line argument is given TestRunner
+ * waits until the users types RETURN.
+ * <p>
+ * TestRunner prints a trace as the tests are executed followed by a
+ * summary at the end.
+ */
+public class TestRunner extends BaseTestRunner {
+ private ResultPrinter fPrinter;
+
+ public static final int SUCCESS_EXIT= 0;
+ public static final int FAILURE_EXIT= 1;
+ public static final int EXCEPTION_EXIT= 2;
+
+ /**
+ * Constructs a TestRunner.
+ */
+ public TestRunner() {
+ this(System.out);
+ }
+
+ /**
+ * Constructs a TestRunner using the given stream for all the output
+ */
+ public TestRunner(PrintStream writer) {
+ this(new ResultPrinter(writer));
+ }
+
+ /**
+ * Constructs a TestRunner using the given ResultPrinter all the output
+ */
+ public TestRunner(ResultPrinter printer) {
+ fPrinter= printer;
+ }
+
+ /**
+ * Runs a suite extracted from a TestCase subclass.
+ */
+ static public void run(Class testClass) {
+ run(new TestSuite(testClass));
+ }
+
+ /**
+ * Runs a single test and collects its results.
+ * This method can be used to start a test run
+ * from your program.
+ * <pre>
+ * public static void main (String[] args) {
+ * test.textui.TestRunner.run(suite());
+ * }
+ * </pre>
+ */
+ static public TestResult run(Test test) {
+ TestRunner runner= new TestRunner();
+ return runner.doRun(test);
+ }
+
+ /**
+ * Runs a single test and waits until the user
+ * types RETURN.
+ */
+ static public void runAndWait(Test suite) {
+ TestRunner aTestRunner= new TestRunner();
+ aTestRunner.doRun(suite, true);
+ }
+
+ /**
+ * Always use the StandardTestSuiteLoader. Overridden from
+ * BaseTestRunner.
+ */
+ public TestSuiteLoader getLoader() {
+ return new StandardTestSuiteLoader();
+ }
+
+ public void testFailed(int status, Test test, Throwable t) {
+ }
+
+ public void testStarted(String testName) {
+ }
+
+ public void testEnded(String testName) {
+ }
+
+ /**
+ * Creates the TestResult to be used for the test run.
+ */
+ protected TestResult createTestResult() {
+ return new TestResult();
+ }
+
+ public TestResult doRun(Test test) {
+ return doRun(test, false);
+ }
+
+ public TestResult doRun(Test suite, boolean wait) {
+ TestResult result= createTestResult();
+ result.addListener(fPrinter);
+ long startTime= System.currentTimeMillis();
+ suite.run(result);
+ long endTime= System.currentTimeMillis();
+ long runTime= endTime-startTime;
+ fPrinter.print(result, runTime);
+
+ pause(wait);
+ return result;
+ }
+
+ protected void pause(boolean wait) {
+ if (!wait) return;
+ fPrinter.printWaitPrompt();
+ try {
+ System.in.read();
+ }
+ catch(Exception e) {
+ }
+ }
+
+ public static void main(String args[]) {
+ TestRunner aTestRunner= new TestRunner();
+ try {
+ TestResult r= aTestRunner.start(args);
+ if (!r.wasSuccessful())
+ System.exit(FAILURE_EXIT);
+ System.exit(SUCCESS_EXIT);
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ System.exit(EXCEPTION_EXIT);
+ }
+ }
+
+ /**
+ * Starts a test run. Analyzes the command line arguments
+ * and runs the given test suite.
+ */
+ protected TestResult start(String args[]) throws Exception {
+ String testCase= "";
+ boolean wait= false;
+
+ for (int i= 0; i < args.length; i++) {
+ if (args[i].equals("-wait"))
+ wait= true;
+ else if (args[i].equals("-c"))
+ testCase= extractClassName(args[++i]);
+ else if (args[i].equals("-v"))
+ System.err.println("JUnit "+Version.id()+" by Kent Beck and Erich Gamma");
+ else
+ testCase= args[i];
+ }
+
+ if (testCase.equals(""))
+ throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+
+ try {
+ Test suite= getTest(testCase);
+ return doRun(suite, wait);
+ }
+ catch(Exception e) {
+ throw new Exception("Could not create and run test suite: "+e);
+ }
+ }
+
+ protected void runFailed(String message) {
+ System.err.println(message);
+ System.exit(FAILURE_EXIT);
+ }
+
+ public void setPrinter(ResultPrinter printer) {
+ fPrinter= printer;
+ }
+
+
+}
diff --git a/test-runner/junit/textui/package.html b/test-runner/junit/textui/package.html
new file mode 100644
index 0000000..723f2ae
--- /dev/null
+++ b/test-runner/junit/textui/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Utility classes supporting the junit test framework.
+{@hide} - Not needed for 1.0 SDK
+</BODY>
+</HTML>