diff options
Diffstat (limited to 'test-runner/src')
90 files changed, 11770 insertions, 0 deletions
diff --git a/test-runner/src/android/test/ActivityInstrumentationTestCase.java b/test-runner/src/android/test/ActivityInstrumentationTestCase.java new file mode 100644 index 0000000..d12ff6f --- /dev/null +++ b/test-runner/src/android/test/ActivityInstrumentationTestCase.java @@ -0,0 +1,97 @@ +/* + * 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; + + /** + * Creates an {@link ActivityInstrumentationTestCase} in non-touch mode. + * + * @param pkg ignored - no longer in use. + * @param activityClass The activity to test. This must be a class in the instrumentation + * targetPackage specified in the AndroidManifest.xml + */ + public ActivityInstrumentationTestCase(String pkg, Class<T> activityClass) { + this(pkg, activityClass, false); + } + + /** + * Creates an {@link ActivityInstrumentationTestCase}. + * + * @param pkg ignored - no longer in use. + * @param activityClass The activity to test. This must be a class in the instrumentation + * targetPackage specified in the AndroidManifest.xml + * @param initialTouchMode true = in touch mode + */ + public ActivityInstrumentationTestCase(String pkg, Class<T> activityClass, + boolean initialTouchMode) { + 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); + final String targetPackageName = getInstrumentation().getTargetContext().getPackageName(); + setActivity(launchActivity(targetPackageName, 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/src/android/test/ActivityInstrumentationTestCase2.java b/test-runner/src/android/test/ActivityInstrumentationTestCase2.java new file mode 100644 index 0000000..e8570bd --- /dev/null +++ b/test-runner/src/android/test/ActivityInstrumentationTestCase2.java @@ -0,0 +1,189 @@ +/* + * 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 { + Class<T> mActivityClass; + boolean mInitialTouchMode = false; + Intent mActivityIntent = null; + + /** + * Creates an {@link ActivityInstrumentationTestCase2}. + * + * @param pkg ignored - no longer in use. + * @param activityClass The activity to test. This must be a class in the instrumentation + * targetPackage specified in the AndroidManifest.xml + * + * @deprecated use {@link #ActivityInstrumentationTestCase2(Class)} instead + */ + @Deprecated + public ActivityInstrumentationTestCase2(String pkg, Class<T> activityClass) { + this(activityClass); + } + + /** + * Creates an {@link ActivityInstrumentationTestCase2}. + * + * @param activityClass The activity to test. This must be a class in the instrumentation + * targetPackage specified in the AndroidManifest.xml + */ + public ActivityInstrumentationTestCase2(Class<T> activityClass) { + 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); + final String targetPackage = getInstrumentation().getTargetContext().getPackageName(); + // inject custom intent, if provided + if (mActivityIntent == null) { + a = launchActivity(targetPackage, mActivityClass, null); + } else { + a = launchActivityWithIntent(targetPackage, 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/src/android/test/ActivityTestCase.java b/test-runner/src/android/test/ActivityTestCase.java new file mode 100644 index 0000000..18bfccc --- /dev/null +++ b/test-runner/src/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/src/android/test/ActivityUnitTestCase.java b/test-runner/src/android/test/ActivityUnitTestCase.java new file mode 100644 index 0000000..6bd19a6 --- /dev/null +++ b/test-runner/src/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 = new ActivityInfo(); + 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/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java new file mode 100644 index 0000000..fc9832c --- /dev/null +++ b/test-runner/src/android/test/AndroidTestRunner.java @@ -0,0 +1,235 @@ +/* + * 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 android.os.PerformanceCollector.PerformanceResultsWriter; + +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; + private PerformanceResultsWriter mPerfWriter; + + @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); + } + + Context testContext = mInstrumentation == null ? mContext : mInstrumentation.getContext(); + for (TestCase testCase : mTestCases) { + setContextIfAndroidTestCase(testCase, mContext, testContext); + setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation); + setPerformanceWriterIfPerformanceCollectorTestCase(testCase, mPerfWriter); + testCase.run(mTestResult); + } + } + + private void setContextIfAndroidTestCase(Test test, Context context, Context testContext) { + if (AndroidTestCase.class.isAssignableFrom(test.getClass())) { + ((AndroidTestCase) test).setContext(context); + ((AndroidTestCase) test).setTestContext(testContext); + } + } + + public void setContext(Context context) { + mContext = context; + } + + private void setInstrumentationIfInstrumentationTestCase( + Test test, Instrumentation instrumentation) { + if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) { + ((InstrumentationTestCase) test).injectInstrumentation(instrumentation); + } + } + + private void setPerformanceWriterIfPerformanceCollectorTestCase( + Test test, PerformanceResultsWriter writer) { + if (PerformanceCollectorTestCase.class.isAssignableFrom(test.getClass())) { + ((PerformanceCollectorTestCase) test).setPerformanceResultsWriter(writer); + } + } + + public void setInstrumentation(Instrumentation instrumentation) { + mInstrumentation = instrumentation; + } + + /** + * @deprecated Incorrect spelling, + * use {@link #setInstrumentation(android.app.Instrumentation)} instead. + */ + @Deprecated + public void setInstrumentaiton(Instrumentation instrumentation) { + setInstrumentation(instrumentation); + } + + /** + * {@hide} Pending approval for public API. + */ + public void setPerformanceResultsWriter(PerformanceResultsWriter writer) { + mPerfWriter = writer; + } + + @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/src/android/test/ApplicationTestCase.java b/test-runner/src/android/test/ApplicationTestCase.java new file mode 100644 index 0000000..ae5fa4d --- /dev/null +++ b/test-runner/src/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 + * {@link #createApplication()}. 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/src/android/test/AssertionFailedError.java b/test-runner/src/android/test/AssertionFailedError.java new file mode 100644 index 0000000..7af5806 --- /dev/null +++ b/test-runner/src/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/src/android/test/BundlePrinter.java b/test-runner/src/android/test/BundlePrinter.java new file mode 100644 index 0000000..96213e7 --- /dev/null +++ b/test-runner/src/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/src/android/test/BundleTestListener.java b/test-runner/src/android/test/BundleTestListener.java new file mode 100644 index 0000000..772713f --- /dev/null +++ b/test-runner/src/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/src/android/test/ClassPathPackageInfo.java b/test-runner/src/android/test/ClassPathPackageInfo.java new file mode 100644 index 0000000..1f6e647 --- /dev/null +++ b/test-runner/src/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/src/android/test/ClassPathPackageInfoSource.java b/test-runner/src/android/test/ClassPathPackageInfoSource.java new file mode 100644 index 0000000..877075f --- /dev/null +++ b/test-runner/src/android/test/ClassPathPackageInfoSource.java @@ -0,0 +1,323 @@ +/* + * 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)) { + String subPackageName = packageName; + int lastPackageSeparator = className.lastIndexOf('.'); + if (lastPackageSeparator > 0) { + 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/src/android/test/ComparisonFailure.java b/test-runner/src/android/test/ComparisonFailure.java new file mode 100644 index 0000000..e7e9698 --- /dev/null +++ b/test-runner/src/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/src/android/test/DatabaseTestUtils.java b/test-runner/src/android/test/DatabaseTestUtils.java new file mode 100644 index 0000000..23e0aba --- /dev/null +++ b/test-runner/src/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/src/android/test/InstrumentationCoreTestRunner.java b/test-runner/src/android/test/InstrumentationCoreTestRunner.java new file mode 100644 index 0000000..ff99a74 --- /dev/null +++ b/test-runner/src/android/test/InstrumentationCoreTestRunner.java @@ -0,0 +1,212 @@ +/* + * 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 java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; + +import com.android.internal.util.Predicate; +import com.android.internal.util.Predicates; + +import dalvik.annotation.BrokenTest; +import dalvik.annotation.SideEffect; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestListener; +import android.os.Bundle; +import android.test.suitebuilder.TestMethod; +import android.test.suitebuilder.annotation.HasAnnotation; +import android.util.Log; + +/** + * 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 file system. We also need to set the harness + * Thread's context ClassLoader. Otherwise some classes and resources will not + * be found. Finally, we add a means to free memory allocated by a TestCase + * after its execution. + * + * @hide + */ +public class InstrumentationCoreTestRunner extends InstrumentationTestRunner { + + /** + * Convenience definition of our log tag. + */ + private static final String TAG = "InstrumentationCoreTestRunner"; + + /** + * True if (and only if) we are running in single-test mode (as opposed to + * batch mode). + */ + private boolean singleTest = false; + + @Override + public void onCreate(Bundle arguments) { + // We might want to move this to /sdcard, if is is mounted/writable. + File cacheDir = getTargetContext().getCacheDir(); + + // Set some properties that the core tests absolutely need. + System.setProperty("user.language", "en"); + System.setProperty("user.region", "US"); + + System.setProperty("java.home", cacheDir.getAbsolutePath()); + System.setProperty("user.home", cacheDir.getAbsolutePath()); + System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath()); + System.setProperty("javax.net.ssl.trustStore", + "/etc/security/cacerts.bks"); + + if (arguments != null) { + String classArg = arguments.getString(ARGUMENT_TEST_CLASS); + singleTest = classArg != null && classArg.contains("#"); + } + + super.onCreate(arguments); + } + + @Override + protected AndroidTestRunner getAndroidTestRunner() { + AndroidTestRunner runner = super.getAndroidTestRunner(); + + runner.addTestListener(new TestListener() { + /** + * The last test class we executed code from. + */ + private Class<?> lastClass; + + /** + * The minimum time we expect a test to take. + */ + private static final int MINIMUM_TIME = 100; + + /** + * The start time of our current test in System.currentTimeMillis(). + */ + private long startTime; + + public void startTest(Test test) { + if (test.getClass() != lastClass) { + lastClass = test.getClass(); + printMemory(test.getClass()); + } + + Thread.currentThread().setContextClassLoader( + test.getClass().getClassLoader()); + + startTime = System.currentTimeMillis(); + } + + public void endTest(Test test) { + if (test instanceof TestCase) { + cleanup((TestCase)test); + + /* + * Make sure all tests take at least MINIMUM_TIME to + * complete. If they don't, we wait a bit. The Cupcake + * Binder can't handle too many operations in a very + * short time, which causes headache for the CTS. + */ + long timeTaken = System.currentTimeMillis() - startTime; + + if (timeTaken < MINIMUM_TIME) { + try { + Thread.sleep(MINIMUM_TIME - timeTaken); + } catch (InterruptedException ignored) { + // We don't care. + } + } + } + } + + public void addError(Test test, Throwable t) { + // This space intentionally left blank. + } + + public void addFailure(Test test, AssertionFailedError t) { + // This space intentionally left blank. + } + + /** + * Dumps some memory info. + */ + private void printMemory(Class<? extends Test> testClass) { + Runtime runtime = Runtime.getRuntime(); + + long total = runtime.totalMemory(); + long free = runtime.freeMemory(); + long used = total - free; + + Log.d(TAG, "Total memory : " + total); + Log.d(TAG, "Used memory : " + used); + Log.d(TAG, "Free memory : " + free); + Log.d(TAG, "Now executing : " + testClass.getName()); + } + + /** + * Nulls all non-static reference fields in the given test class. + * This method helps us with those test classes that don't have an + * explicit tearDown() method. Normally the garbage collector should + * take care of everything, but since JUnit keeps references to all + * test cases, a little help might be a good idea. + */ + private void cleanup(TestCase test) { + Class<?> clazz = test.getClass(); + + while (clazz != TestCase.class) { + Field[] fields = clazz.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field f = fields[i]; + if (!f.getType().isPrimitive() && + !Modifier.isStatic(f.getModifiers())) { + try { + f.setAccessible(true); + f.set(test, null); + } catch (Exception ignored) { + // Nothing we can do about it. + } + } + } + + clazz = clazz.getSuperclass(); + } + } + + }); + + return runner; + } + + @Override + List<Predicate<TestMethod>> getBuilderRequirements() { + List<Predicate<TestMethod>> builderRequirements = + super.getBuilderRequirements(); + Predicate<TestMethod> brokenTestPredicate = + Predicates.not(new HasAnnotation(BrokenTest.class)); + builderRequirements.add(brokenTestPredicate); + if (!singleTest) { + Predicate<TestMethod> sideEffectPredicate = + Predicates.not(new HasAnnotation(SideEffect.class)); + builderRequirements.add(sideEffectPredicate); + } + return builderRequirements; + } +} diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java new file mode 100644 index 0000000..3e9cd9f --- /dev/null +++ b/test-runner/src/android/test/InstrumentationTestRunner.java @@ -0,0 +1,776 @@ +/* + * 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 com.android.internal.util.Predicate; + +import android.app.Activity; +import android.app.Instrumentation; +import android.os.Bundle; +import android.os.Debug; +import android.os.Looper; +import android.os.Parcelable; +import android.os.PerformanceCollector; +import android.os.PerformanceCollector.PerformanceResultsWriter; +import android.test.suitebuilder.TestMethod; +import android.test.suitebuilder.TestPredicates; +import android.test.suitebuilder.TestSuiteBuilder; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +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; + +/** + * 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.ActivityInstrumentationTestCase2}</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 in a /data/<app>/coverage.ec file, 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"; + /** + * If included in the status or final bundle sent to an IInstrumentationWatcher, this key + * identifies the path to the generated code coverage file. + */ + private static final String REPORT_KEY_COVERAGE_PATH = "coverageFilePath"; + + /** + * 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"; + + // Default file name for code coverage + private static final String DEFAULT_COVERAGE_FILE_NAME = "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) { + if (mPackageOfTests != null) { + testSuiteBuilder.includePackages(mPackageOfTests); + } else { + TestSuite testSuite = getTestSuite(); + if (testSuite != null) { + testSuiteBuilder.addTestSuite(testSuite); + } else { + // no package or class bundle arguments were supplied, and no test suite + // provided so add all tests in application + testSuiteBuilder.includePackages(""); + } + } + } else { + parseTestClasses(testClassesArg, testSuiteBuilder); + } + + testSuiteBuilder.addRequirements(getBuilderRequirements()); + + mTestRunner = getAndroidTestRunner(); + mTestRunner.setContext(getTargetContext()); + mTestRunner.setInstrumentation(this); + mTestRunner.setSkipExecution(logOnly); + mTestRunner.setTest(testSuiteBuilder.build()); + mTestCount = mTestRunner.getTestCases().size(); + if (mSuiteAssignmentMode) { + mTestRunner.addTestListener(new SuiteAssignmentPrinter()); + } else { + WatcherResultPrinter resultPrinter = new WatcherResultPrinter(mTestCount); + mTestRunner.addTestListener(new TestPrinter("TestRunner", false)); + mTestRunner.addTestListener(resultPrinter); + mTestRunner.setPerformanceResultsWriter(resultPrinter); + } + start(); + } + + List<Predicate<TestMethod>> getBuilderRequirements() { + return new ArrayList<Predicate<TestMethod>>(); + } + + /** + * 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 + String coverageFilePath = getCoverageFilePath(); + java.io.File coverageFile = new java.io.File(coverageFilePath); + 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); + // output path to generated coverage file so it can be parsed by a test harness if + // needed + mResults.putString(REPORT_KEY_COVERAGE_PATH, coverageFilePath); + // also output a more user friendly msg + mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("Generated code coverage data to %s", coverageFilePath)); + } 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 getTargetContext().getFilesDir().getAbsolutePath() + File.separator + + DEFAULT_COVERAGE_FILE_NAME; + } 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, PerformanceResultsWriter { + private final Bundle mResultTemplate; + Bundle mTestResult; + int mTestNum = 0; + int mTestResultCode = 0; + String mTestClass = null; + PerformanceCollector mPerfCollector = new PerformanceCollector(); + boolean mIsTimedTest = false; + boolean mIncludeDetailedStats = false; + + 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(); + String testName = ((TestCase)test).getName(); + mTestResult = new Bundle(mResultTemplate); + mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass); + mTestResult.putString(REPORT_KEY_NAME_TEST, testName); + 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; + + mIsTimedTest = false; + mIncludeDetailedStats = false; + try { + // Look for TimedTest annotation on both test class and test method + if (test.getClass().getMethod(testName).isAnnotationPresent(TimedTest.class)) { + mIsTimedTest = true; + mIncludeDetailedStats = test.getClass().getMethod(testName).getAnnotation( + TimedTest.class).includeDetailedStats(); + } else if (test.getClass().isAnnotationPresent(TimedTest.class)) { + mIsTimedTest = true; + mIncludeDetailedStats = test.getClass().getAnnotation( + TimedTest.class).includeDetailedStats(); + } + } catch (SecurityException e) { + throw new IllegalStateException(e); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e); + } + + if (mIsTimedTest && mIncludeDetailedStats) { + mPerfCollector.beginSnapshot(""); + } else if (mIsTimedTest) { + mPerfCollector.startTiming(""); + } + } + + /** + * @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 (mIsTimedTest && mIncludeDetailedStats) { + mTestResult.putAll(mPerfCollector.endSnapshot()); + } else if (mIsTimedTest) { + writeStopTiming(mPerfCollector.stopTiming("")); + } + + 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); + } + } + + public void writeBeginSnapshot(String label) { + // Do nothing + } + + public void writeEndSnapshot(Bundle results) { + // Copy all snapshot data fields into mResults, which is outputted + // via Instrumentation.finish + mResults.putAll(results); + } + + public void writeStartTiming(String label) { + // Do nothing + } + + public void writeStopTiming(Bundle results) { + // Copy results into mTestResult by flattening list of iterations, + // which is outputted via WatcherResultPrinter.endTest + int i = 0; + for (Parcelable p : + results.getParcelableArrayList(PerformanceCollector.METRIC_KEY_ITERATIONS)) { + Bundle iteration = (Bundle)p; + String index = "iteration" + i + "."; + mTestResult.putString(index + PerformanceCollector.METRIC_KEY_LABEL, + iteration.getString(PerformanceCollector.METRIC_KEY_LABEL)); + mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_CPU_TIME, + iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME)); + mTestResult.putLong(index + PerformanceCollector.METRIC_KEY_EXECUTION_TIME, + iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME)); + i++; + } + } + + public void writeMeasurement(String label, long value) { + mTestResult.putLong(label, value); + } + + public void writeMeasurement(String label, float value) { + mTestResult.putFloat(label, value); + } + + public void writeMeasurement(String label, String value) { + mTestResult.putString(label, value); + } + + // TODO report the end of the cycle + } +} diff --git a/test-runner/src/android/test/InstrumentationUtils.java b/test-runner/src/android/test/InstrumentationUtils.java new file mode 100644 index 0000000..4c59097 --- /dev/null +++ b/test-runner/src/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/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java new file mode 100644 index 0000000..485e45c --- /dev/null +++ b/test-runner/src/android/test/IsolatedContext.java @@ -0,0 +1,113 @@ +package android.test; + +import com.google.android.collect.Lists; + +import android.accounts.AccountManager; +import android.accounts.OnAccountsUpdateListener; +import android.accounts.Account; +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 android.os.Handler; + +import java.util.List; +import java.io.File; + +/** + * 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 final MockAccountManager mMockAccountManager; + + private List<Intent> mBroadcastIntents = Lists.newArrayList(); + + public IsolatedContext( + ContentResolver resolver, Context targetContext) { + super(targetContext); + mResolver = resolver; + mMockAccountManager = new MockAccountManager(); + } + + /** 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) { + if (Context.ACCOUNT_SERVICE.equals(name)) { + return mMockAccountManager; + } + // No other services exist in this context. + return null; + } + + private class MockAccountManager extends AccountManager { + public MockAccountManager() { + super(IsolatedContext.this, null /* IAccountManager */, null /* handler */); + } + + public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener, + Handler handler, boolean updateImmediately) { + // do nothing + } + + public Account[] getAccounts() { + return new Account[]{}; + } + } + @Override + public File getFilesDir() { + return new File("/dev/null"); + } +} diff --git a/test-runner/src/android/test/LaunchPerformanceBase.java b/test-runner/src/android/test/LaunchPerformanceBase.java new file mode 100644 index 0000000..c324446 --- /dev/null +++ b/test-runner/src/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/src/android/test/MoreAsserts.java b/test-runner/src/android/test/MoreAsserts.java new file mode 100644 index 0000000..9e0d018 --- /dev/null +++ b/test-runner/src/android/test/MoreAsserts.java @@ -0,0 +1,556 @@ +/* + * 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.ArrayList; +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) { + ArrayList actualList = new ArrayList(); + for (Object o : actual) { + actualList.add(o); + } + Assert.assertEquals(message, Arrays.asList(expected), actualList); + } + + /** + * 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/src/android/test/NoExecTestResult.java b/test-runner/src/android/test/NoExecTestResult.java new file mode 100755 index 0000000..1ee62c1 --- /dev/null +++ b/test-runner/src/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/src/android/test/PackageInfoSources.java b/test-runner/src/android/test/PackageInfoSources.java new file mode 100644 index 0000000..ef37449 --- /dev/null +++ b/test-runner/src/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/src/android/test/PerformanceCollectorTestCase.java b/test-runner/src/android/test/PerformanceCollectorTestCase.java new file mode 100644 index 0000000..4309ff7 --- /dev/null +++ b/test-runner/src/android/test/PerformanceCollectorTestCase.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 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.os.PerformanceCollector; +import android.os.PerformanceCollector.PerformanceResultsWriter; + +/** + * A simple interface for passing in a PerformanceResultsWriter instance to be used with + * PerformanceCollector. + * <p/> + * A one line implementation of {@link #setPerformanceResultsWriter(PerformanceResultsWriter)} + * is sufficient in most cases: + * <p/> + * <code>mPerfCollector.setPerformanceResultsWriter(writer);</code> + * + * {@hide} Not needed for SDK. + */ +public interface PerformanceCollectorTestCase { + public PerformanceCollector mPerfCollector = new PerformanceCollector(); + + public void setPerformanceResultsWriter(PerformanceResultsWriter writer); +} diff --git a/test-runner/src/android/test/PerformanceTestBase.java b/test-runner/src/android/test/PerformanceTestBase.java new file mode 100644 index 0000000..4a0a589 --- /dev/null +++ b/test-runner/src/android/test/PerformanceTestBase.java @@ -0,0 +1,33 @@ +/* + * 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 junit.framework.TestCase; + +/** + * {@hide} Not needed for SDK. + */ +public class PerformanceTestBase extends TestCase implements PerformanceTestCase { + + public int startPerformance(PerformanceTestCase.Intermediates intermediates) { + return 0; + } + + public boolean isPerformanceOnly() { + return true; + } +} diff --git a/test-runner/src/android/test/ProviderTestCase.java b/test-runner/src/android/test/ProviderTestCase.java new file mode 100644 index 0000000..668e9f7 --- /dev/null +++ b/test-runner/src/android/test/ProviderTestCase.java @@ -0,0 +1,88 @@ +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. + */ +@Deprecated +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/src/android/test/ProviderTestCase2.java b/test-runner/src/android/test/ProviderTestCase2.java new file mode 100644 index 0000000..f3655fc --- /dev/null +++ b/test-runner/src/android/test/ProviderTestCase2.java @@ -0,0 +1,105 @@ +package android.test; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.test.mock.MockContext; +import android.test.mock.MockContentResolver; +import android.database.DatabaseUtils; + +import java.io.File; + +/** + * This TestCase class provides a framework for isolated testing of a single + * ContentProvider. It uses a {@link android.test.mock.MockContentResolver} to + * access the provider, restricts the provider to an isolated area of the + * filesystem (for safely creating & modifying databases & files), and injects + * {@link android.test.IsolatedContext} to isolate the ContentProvider from the + * rest of the running system. + * + * <p>This environment is created automatically by {@link #setUp} and {@link + * #tearDown}. + */ +public abstract class ProviderTestCase2<T extends ContentProvider> extends AndroidTestCase { + + Class<T> mProviderClass; + String mProviderAuthority; + + private IsolatedContext mProviderContext; + private MockContentResolver mResolver; + + private class MockContext2 extends MockContext { + + @Override + public Resources getResources() { + return getContext().getResources(); + } + + @Override + public File getDir(String name, int mode) { + // name the directory so the directory will be seperated from + // one created through the regular Context + return getContext().getDir("mockcontext2_" + name, mode); + } + } + + 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 MockContext2(), // 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; + } +} diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java new file mode 100644 index 0000000..0ea43ab --- /dev/null +++ b/test-runner/src/android/test/RenamingDelegatingContext.java @@ -0,0 +1,233 @@ +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 android.os.FileUtils; +import android.util.Log; + +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 File mCacheDir; + private final Object mSync = new Object(); + + 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 File getDatabasePath(String name) { + return mFileContext.getDatabasePath(renamedFileName(name)); + } + + @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[]{}); + } + + /** + * In order to support calls to getCacheDir(), we create a temp cache dir (inside the real + * one) and return it instead. This code is basically getCacheDir(), except it uses the real + * cache dir as the parent directory and creates a test cache dir inside that. + */ + @Override + public File getCacheDir() { + synchronized (mSync) { + if (mCacheDir == null) { + mCacheDir = new File(mFileContext.getCacheDir(), renamedFileName("cache")); + } + if (!mCacheDir.exists()) { + if(!mCacheDir.mkdirs()) { + Log.w("RenamingDelegatingContext", "Unable to create cache directory"); + return null; + } + FileUtils.setPermissions( + mCacheDir.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + } + return mCacheDir; + } + + +// /** +// * 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/src/android/test/ServiceLocator.java b/test-runner/src/android/test/ServiceLocator.java new file mode 100644 index 0000000..3324008 --- /dev/null +++ b/test-runner/src/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/src/android/test/ServiceTestCase.java b/test-runner/src/android/test/ServiceTestCase.java new file mode 100644 index 0000000..fcb9d55 --- /dev/null +++ b/test-runner/src/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/src/android/test/SimpleCache.java b/test-runner/src/android/test/SimpleCache.java new file mode 100644 index 0000000..44424ec --- /dev/null +++ b/test-runner/src/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/src/android/test/SingleLaunchActivityTestCase.java b/test-runner/src/android/test/SingleLaunchActivityTestCase.java new file mode 100644 index 0000000..b63b3ce --- /dev/null +++ b/test-runner/src/android/test/SingleLaunchActivityTestCase.java @@ -0,0 +1,87 @@ +/* + * 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; + + /** + * <b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the + * package hosting the activity to be launched, which is specified in the AndroidManifest.xml + * file. This is not necessarily the same as the java package name. + * + * @param pkg The package hosting the activity to be launched. + * @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/src/android/test/SyncBaseInstrumentation.java b/test-runner/src/android/test/SyncBaseInstrumentation.java new file mode 100644 index 0000000..7d418f0 --- /dev/null +++ b/test-runner/src/android/test/SyncBaseInstrumentation.java @@ -0,0 +1,79 @@ +/* + * 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.os.Bundle; +import android.os.SystemClock; +import android.net.Uri; +import android.accounts.Account; + +/** + * 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 accountName, String authority) throws Exception { + Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); + Account account = new Account(accountName, "com.google"); + + ContentResolver.requestSync(account, authority, 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 (ContentResolver.isSyncActive(account, authority)) { + counter = 0; + continue; + } + counter++; + } + } + + protected void cancelSyncsandDisableAutoSync() { + ContentResolver.setMasterSyncAutomatically(false); + ContentResolver.cancelSync(null /* all accounts */, null /* all authorities */); + } +} diff --git a/test-runner/src/android/test/TestBrowserActivity.java b/test-runner/src/android/test/TestBrowserActivity.java new file mode 100644 index 0000000..ea5f91e --- /dev/null +++ b/test-runner/src/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/src/android/test/TestBrowserController.java b/test-runner/src/android/test/TestBrowserController.java new file mode 100644 index 0000000..044e39f --- /dev/null +++ b/test-runner/src/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/src/android/test/TestBrowserControllerImpl.java b/test-runner/src/android/test/TestBrowserControllerImpl.java new file mode 100644 index 0000000..b8f8975 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestBrowserView.java b/test-runner/src/android/test/TestBrowserView.java new file mode 100644 index 0000000..4799f19 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestCase.java b/test-runner/src/android/test/TestCase.java new file mode 100644 index 0000000..5432ce8 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestCaseUtil.java b/test-runner/src/android/test/TestCaseUtil.java new file mode 100644 index 0000000..3ba9711 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestListActivity.java b/test-runner/src/android/test/TestListActivity.java new file mode 100644 index 0000000..a076a70 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestPrinter.java b/test-runner/src/android/test/TestPrinter.java new file mode 100644 index 0000000..37bd721 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestRecorder.java b/test-runner/src/android/test/TestRecorder.java new file mode 100644 index 0000000..7c368a0 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestRunner.java b/test-runner/src/android/test/TestRunner.java new file mode 100644 index 0000000..012df35 --- /dev/null +++ b/test-runner/src/android/test/TestRunner.java @@ -0,0 +1,725 @@ +/* + * 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(); + } + + @Override + 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); + testcase.setTestContext(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/src/android/test/TestRunnerView.java b/test-runner/src/android/test/TestRunnerView.java new file mode 100644 index 0000000..be90951 --- /dev/null +++ b/test-runner/src/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/src/android/test/TestSuiteProvider.java b/test-runner/src/android/test/TestSuiteProvider.java new file mode 100644 index 0000000..dc9ce6e --- /dev/null +++ b/test-runner/src/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/src/android/test/TimedTest.java b/test-runner/src/android/test/TimedTest.java new file mode 100644 index 0000000..95cc9bf --- /dev/null +++ b/test-runner/src/android/test/TimedTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 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.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This annotation can be used on an {@link junit.framework.TestCase}'s test methods. When the + * annotation is present, the test method is timed and the results written through instrumentation + * output. It can also be used on the class itself, which is equivalent to tagging all test methods + * with this annotation. + * <p/> + * Setting {@link #includeDetailedStats()} to true causes additional metrics such as memory usage + * and binder transactions to be gathered and written through instrumentation output. + * + * {@hide} Pending approval for public API. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface TimedTest { + boolean includeDetailedStats() default false; +} diff --git a/test-runner/src/android/test/TouchUtils.java b/test-runner/src/android/test/TouchUtils.java new file mode 100644 index 0000000..69c6d2d --- /dev/null +++ b/test-runner/src/android/test/TouchUtils.java @@ -0,0 +1,794 @@ +/* + * 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 + */ + @Deprecated + 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, x, 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, x, y, 0); + inst.sendPointerSync(event); + inst.waitForIdleSync(); + } +} diff --git a/test-runner/src/android/test/ViewAsserts.java b/test-runner/src/android/test/ViewAsserts.java new file mode 100644 index 0000000..c575fc5 --- /dev/null +++ b/test-runner/src/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/src/android/test/mock/MockApplication.java b/test-runner/src/android/test/mock/MockApplication.java new file mode 100644 index 0000000..572dfbf --- /dev/null +++ b/test-runner/src/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/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java new file mode 100644 index 0000000..4078622 --- /dev/null +++ b/test-runner/src/android/test/mock/MockContentProvider.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2009 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.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.content.Context; +import android.content.EntityIterator; +import android.content.IContentProvider; +import android.content.OperationApplicationException; +import android.content.pm.PathPermission; +import android.content.pm.ProviderInfo; +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.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +/** + * Mock implementation of ContentProvider. All methods are non-functional and throw + * {@link java.lang.UnsupportedOperationException}. Tests can extend this class to + * implement behavior needed for tests. + */ +public class MockContentProvider extends ContentProvider { + /* + * Note: if you add methods to ContentProvider, you must add similar methods to + * MockContentProvider. + */ + + /** + * IContentProvider that directs all calls to this MockContentProvider. + */ + private class InversionIContentProvider implements IContentProvider { + @SuppressWarnings("unused") + public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) + throws RemoteException, OperationApplicationException { + return MockContentProvider.this.applyBatch(operations); + } + + @SuppressWarnings("unused") + public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { + return MockContentProvider.this.bulkInsert(url, initialValues); + } + + @SuppressWarnings("unused") + public IBulkCursor bulkQuery(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder, IContentObserver observer, + CursorWindow window) throws RemoteException { + throw new UnsupportedOperationException("Must not come here"); + } + + @SuppressWarnings("unused") + public int delete(Uri url, String selection, String[] selectionArgs) + throws RemoteException { + return MockContentProvider.this.delete(url, selection, selectionArgs); + } + + @SuppressWarnings("unused") + public String getType(Uri url) throws RemoteException { + return MockContentProvider.this.getType(url); + } + + @SuppressWarnings("unused") + public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { + return MockContentProvider.this.insert(url, initialValues); + } + + @SuppressWarnings("unused") + public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, + FileNotFoundException { + return MockContentProvider.this.openAssetFile(url, mode); + } + + @SuppressWarnings("unused") + public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, + FileNotFoundException { + return MockContentProvider.this.openFile(url, mode); + } + + @SuppressWarnings("unused") + public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, + String sortOrder) throws RemoteException { + return MockContentProvider.this.query(url, projection, selection, + selectionArgs, sortOrder); + } + + @SuppressWarnings("unused") + public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) + throws RemoteException { + return MockContentProvider.this.update(url, values, selection, selectionArgs); + } + + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + } + private final InversionIContentProvider mIContentProvider = new InversionIContentProvider(); + + /** + * A constructor using {@link MockContext} instance as a Context in it. + */ + protected MockContentProvider() { + super(new MockContext(), "", "", null); + } + + /** + * A constructor accepting a Context instance, which is supposed to be the subclasss of + * {@link MockContext}. + */ + public MockContentProvider(Context context) { + super(context, "", "", null); + } + + /** + * A constructor which initialize four member variables which + * {@link android.content.ContentProvider} have internally. + * + * @param context A Context object which should be some mock instance (like the + * instance of {@link android.test.mock.MockContext}). + * @param readPermission The read permision you want this instance should have in the + * test, which is available via {@link #getReadPermission()}. + * @param writePermission The write permission you want this instance should have + * in the test, which is available via {@link #getWritePermission()}. + * @param pathPermissions The PathPermissions you want this instance should have + * in the test, which is available via {@link #getPathPermissions()}. + */ + public MockContentProvider(Context context, + String readPermission, + String writePermission, + PathPermission[] pathPermissions) { + super(context, readPermission, writePermission, pathPermissions); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public String getType(Uri uri) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public boolean onCreate() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + /** + * If you're reluctant to implement this manually, please just call super.bulkInsert(). + */ + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public void attachInfo(Context context, ProviderInfo info) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override + public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + /** + * Returns IContentProvider which calls back same methods in this class. + * By overriding this class, we avoid the mechanism hidden behind ContentProvider + * (IPC, etc.) + * + * @hide + */ + @Override + public final IContentProvider getIContentProvider() { + return mIContentProvider; + } +} diff --git a/test-runner/src/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java new file mode 100644 index 0000000..3a1dc36c --- /dev/null +++ b/test-runner/src/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/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java new file mode 100644 index 0000000..ffd757c --- /dev/null +++ b/test-runner/src/android/test/mock/MockContext.java @@ -0,0 +1,431 @@ +/* + * 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.IntentSender; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +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 ApplicationInfo getApplicationInfo() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPackageResourcePath() { + throw new UnsupportedOperationException(); + } + + /** @hide */ + @Override + public File getSharedPrefsFile(String name) { + 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 getExternalFilesDir(String type) { + throw new UnsupportedOperationException(); + } + + @Override + public File getCacheDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getExternalCacheDir() { + 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 startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + 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 sendStickyOrderedBroadcast(Intent intent, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + 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(); + } + + @Override + public boolean isRestricted() { + throw new UnsupportedOperationException(); + } +} diff --git a/test-runner/src/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java new file mode 100644 index 0000000..9b1c0ef --- /dev/null +++ b/test-runner/src/android/test/mock/MockCursor.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2009 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.ContentResolver; +import android.database.CharArrayBuffer; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.net.Uri; +import android.os.Bundle; + +import java.util.Map; + +/** + * <P> + * A mock {@link android.database.Cursor} class that isolates the test code from real + * Cursor implementation. + * </P> + * <P> + * All methods including ones related to querying the state of the cursor are + * are non-functional and throw {@link java.lang.UnsupportedOperationException}. + * </P> + */ +public class MockCursor implements Cursor { + public int getColumnCount() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public int getColumnIndex(String columnName) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public int getColumnIndexOrThrow(String columnName) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public String getColumnName(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public String[] getColumnNames() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public int getCount() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean isNull(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public int getInt(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public long getLong(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public short getShort(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public float getFloat(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public double getDouble(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public byte[] getBlob(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public String getString(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public Bundle getExtras() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public int getPosition() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean isAfterLast() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean isBeforeFirst() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean isFirst() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean isLast() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean move(int offset) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean moveToFirst() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean moveToLast() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean moveToNext() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean moveToPrevious() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean moveToPosition(int position) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public void deactivate() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public void close() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean isClosed() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean requery() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public void registerContentObserver(ContentObserver observer) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public void registerDataSetObserver(DataSetObserver observer) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public Bundle respond(Bundle extras) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public boolean getWantsAllOnMoveCalls() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean commitUpdates() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean hasUpdates() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public void setNotificationUri(ContentResolver cr, Uri uri) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean supportsUpdates() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean deleteRow() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public void unregisterContentObserver(ContentObserver observer) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public void unregisterDataSetObserver(DataSetObserver observer) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateBlob(int columnIndex, byte[] value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateDouble(int columnIndex, double value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateFloat(int columnIndex, float value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateInt(int columnIndex, int value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateLong(int columnIndex, long value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateShort(int columnIndex, short value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateString(int columnIndex, String value) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public boolean updateToNull(int columnIndex) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("deprecation") + public void abortUpdates() { + throw new UnsupportedOperationException("unimplemented mock method"); + } +}
\ No newline at end of file diff --git a/test-runner/src/android/test/mock/MockDialogInterface.java b/test-runner/src/android/test/mock/MockDialogInterface.java new file mode 100644 index 0000000..e4dd0ba --- /dev/null +++ b/test-runner/src/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/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java new file mode 100644 index 0000000..7c0a1e2 --- /dev/null +++ b/test-runner/src/android/test/mock/MockIContentProvider.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2009 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.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.content.EntityIterator; +import android.content.IContentProvider; +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.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.util.ArrayList; + +/** + * Mock implementation of IContentProvider. All methods are non-functional and throw + * {@link java.lang.UnsupportedOperationException}. Tests can extend this class to + * implement behavior needed for tests. + * + * @hide - @hide because this exposes bulkQuery(), which must also be hidden. + */ +public class MockIContentProvider implements IContentProvider { + public int bulkInsert(Uri url, ContentValues[] initialValues) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public IBulkCursor bulkQuery(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder, IContentObserver observer, + CursorWindow window) { + 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"); + } + + public String getType(Uri url) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public ParcelFileDescriptor openFile(Uri url, String mode) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public AssetFileDescriptor openAssetFile(Uri uri, String mode) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public EntityIterator queryEntities(Uri url, String selection, String[] selectionArgs, + String sortOrder) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + 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/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java new file mode 100644 index 0000000..f1ba44a --- /dev/null +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -0,0 +1,452 @@ +/* + * 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.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.FeatureInfo; +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.PackageParser; +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 String[] currentToCanonicalPackageNames(String[] names) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] canonicalToCurrentPackageNames(String[] names) { + throw new UnsupportedOperationException(); + } + + @Override + public Intent getLaunchIntentForPackage(String packageName) { + 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 int checkSignatures(int uid1, int uid2) { + 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(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void installPackage(Uri packageURI, IPackageInstallObserver observer, + int flags, String installerPackageName) { + throw new UnsupportedOperationException(); + } + + @Override + public String getInstallerPackageName(String packageName) { + 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, IntentSender pi) { + 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(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void replacePreferredActivity(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 int getPreferredActivities(List<IntentFilter> outFilters, + List<ComponentName> outActivities, String packageName) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getSystemSharedLibraryNames() { + throw new UnsupportedOperationException(); + } + + @Override + public FeatureInfo[] getSystemAvailableFeatures() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasSystemFeature(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSafeMode() { + throw new UnsupportedOperationException(); + } +} diff --git a/test-runner/src/android/test/mock/MockResources.java b/test-runner/src/android/test/mock/MockResources.java new file mode 100644 index 0000000..18752ce --- /dev/null +++ b/test-runner/src/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/src/android/test/mock/package.html b/test-runner/src/android/test/mock/package.html new file mode 100644 index 0000000..0f1bc6f4 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/AssignableFrom.java b/test-runner/src/android/test/suitebuilder/AssignableFrom.java new file mode 100644 index 0000000..38b4ee3 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java new file mode 100644 index 0000000..128396e --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/SmokeTestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/SmokeTestSuiteBuilder.java new file mode 100644 index 0000000..01e7ec6 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/TestGrouping.java b/test-runner/src/android/test/suitebuilder/TestGrouping.java new file mode 100644 index 0000000..df6da70 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/TestMethod.java b/test-runner/src/android/test/suitebuilder/TestMethod.java new file mode 100644 index 0000000..08568d5 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/TestPredicates.java b/test-runner/src/android/test/suitebuilder/TestPredicates.java new file mode 100644 index 0000000..d814e0b --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java new file mode 100644 index 0000000..428905e --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/UnitTestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/UnitTestSuiteBuilder.java new file mode 100644 index 0000000..8cf4c86 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/annotation/HasAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasAnnotation.java new file mode 100644 index 0000000..a2868fc --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/annotation/HasClassAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasClassAnnotation.java new file mode 100644 index 0000000..ac76f4c --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java new file mode 100644 index 0000000..96bd922 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/annotation/package.html b/test-runner/src/android/test/suitebuilder/annotation/package.html new file mode 100644 index 0000000..ffba2e9 --- /dev/null +++ b/test-runner/src/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/src/android/test/suitebuilder/package.html b/test-runner/src/android/test/suitebuilder/package.html new file mode 100644 index 0000000..ffba2e9 --- /dev/null +++ b/test-runner/src/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/src/junit/MODULE_LICENSE_CPL b/test-runner/src/junit/MODULE_LICENSE_CPL new file mode 100644 index 0000000..541dbb5 --- /dev/null +++ b/test-runner/src/junit/MODULE_LICENSE_CPL @@ -0,0 +1 @@ +http://www.opensource.org/licenses/cpl1.0.php diff --git a/test-runner/src/junit/runner/BaseTestRunner.java b/test-runner/src/junit/runner/BaseTestRunner.java new file mode 100644 index 0000000..e073ef7 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/ClassPathTestCollector.java b/test-runner/src/junit/runner/ClassPathTestCollector.java new file mode 100644 index 0000000..8a3c702 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/FailureDetailView.java b/test-runner/src/junit/runner/FailureDetailView.java new file mode 100644 index 0000000..7108cec --- /dev/null +++ b/test-runner/src/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/src/junit/runner/LoadingTestCollector.java b/test-runner/src/junit/runner/LoadingTestCollector.java new file mode 100644 index 0000000..b1760b1 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java new file mode 100644 index 0000000..a6d84fe --- /dev/null +++ b/test-runner/src/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/src/junit/runner/SimpleTestCollector.java b/test-runner/src/junit/runner/SimpleTestCollector.java new file mode 100644 index 0000000..543168f --- /dev/null +++ b/test-runner/src/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/src/junit/runner/Sorter.java b/test-runner/src/junit/runner/Sorter.java new file mode 100644 index 0000000..66f551e --- /dev/null +++ b/test-runner/src/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/src/junit/runner/StandardTestSuiteLoader.java b/test-runner/src/junit/runner/StandardTestSuiteLoader.java new file mode 100644 index 0000000..bce7dec --- /dev/null +++ b/test-runner/src/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/src/junit/runner/TestCaseClassLoader.java b/test-runner/src/junit/runner/TestCaseClassLoader.java new file mode 100644 index 0000000..3a510c6 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/TestCollector.java b/test-runner/src/junit/runner/TestCollector.java new file mode 100644 index 0000000..208dccd --- /dev/null +++ b/test-runner/src/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/src/junit/runner/TestRunListener.java b/test-runner/src/junit/runner/TestRunListener.java new file mode 100644 index 0000000..0e95819 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/TestSuiteLoader.java b/test-runner/src/junit/runner/TestSuiteLoader.java new file mode 100644 index 0000000..39a4cf7 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/Version.java b/test-runner/src/junit/runner/Version.java new file mode 100644 index 0000000..b4541ab --- /dev/null +++ b/test-runner/src/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/src/junit/runner/excluded.properties b/test-runner/src/junit/runner/excluded.properties new file mode 100644 index 0000000..3284628 --- /dev/null +++ b/test-runner/src/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/src/junit/runner/logo.gif b/test-runner/src/junit/runner/logo.gif Binary files differnew file mode 100644 index 0000000..d0e1547 --- /dev/null +++ b/test-runner/src/junit/runner/logo.gif diff --git a/test-runner/src/junit/runner/package.html b/test-runner/src/junit/runner/package.html new file mode 100644 index 0000000..f08fa70 --- /dev/null +++ b/test-runner/src/junit/runner/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes supporting the junit test framework. +</BODY> +</HTML> diff --git a/test-runner/src/junit/runner/smalllogo.gif b/test-runner/src/junit/runner/smalllogo.gif Binary files differnew file mode 100644 index 0000000..7b25eaf6 --- /dev/null +++ b/test-runner/src/junit/runner/smalllogo.gif diff --git a/test-runner/src/junit/textui/ResultPrinter.java b/test-runner/src/junit/textui/ResultPrinter.java new file mode 100644 index 0000000..5c97112 --- /dev/null +++ b/test-runner/src/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/src/junit/textui/TestRunner.java b/test-runner/src/junit/textui/TestRunner.java new file mode 100644 index 0000000..8bdc325 --- /dev/null +++ b/test-runner/src/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/src/junit/textui/package.html b/test-runner/src/junit/textui/package.html new file mode 100644 index 0000000..723f2ae --- /dev/null +++ b/test-runner/src/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> |