diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /test-runner/android | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'test-runner/android')
61 files changed, 8638 insertions, 0 deletions
diff --git a/test-runner/android/test/ActivityInstrumentationTestCase.java b/test-runner/android/test/ActivityInstrumentationTestCase.java new file mode 100644 index 0000000..b198a7a --- /dev/null +++ b/test-runner/android/test/ActivityInstrumentationTestCase.java @@ -0,0 +1,86 @@ +/* + * 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}. + */ +public abstract class ActivityInstrumentationTestCase<T extends Activity> + extends ActivityTestCase { + String mPackage; + Class<T> mActivityClass; + boolean mInitialTouchMode = false; + + /** + * @param pkg The package of the instrumentation. + * @param activityClass The activity to test. + */ + public ActivityInstrumentationTestCase(String pkg, Class<T> activityClass) { + this(pkg, activityClass, false); + } + + /** + * @param pkg The package of the instrumentation. + * @param activityClass The activity to test. + * @param initialTouchMode true = in touch mode + */ + public ActivityInstrumentationTestCase(String pkg, Class<T> activityClass, + boolean initialTouchMode) { + mPackage = pkg; + mActivityClass = activityClass; + mInitialTouchMode = initialTouchMode; + } + + @Override + public T getActivity() { + return (T) super.getActivity(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + // set initial touch mode + getInstrumentation().setInTouchMode(mInitialTouchMode); + setActivity(launchActivity(mPackage, mActivityClass, null)); + } + + @Override + protected void tearDown() throws Exception { + getActivity().finish(); + setActivity(null); + + // Scrub out members - protects against memory leaks in the case where someone + // creates a non-static inner class (thus referencing the test case) and gives it to + // someone else to hold onto + scrubClass(ActivityInstrumentationTestCase.class); + + super.tearDown(); + } + + public void testActivityTestCaseSetUpProperly() throws Exception { + assertNotNull("activity should be launched successfully", getActivity()); + } +} diff --git a/test-runner/android/test/ActivityTestCase.java b/test-runner/android/test/ActivityTestCase.java new file mode 100644 index 0000000..18bfccc --- /dev/null +++ b/test-runner/android/test/ActivityTestCase.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Activity; + +import java.lang.reflect.Field; + +/** + * This is common code used to support Activity test cases. For more useful classes, please see + * {@link android.test.ActivityUnitTestCase} and + * {@link android.test.ActivityInstrumentationTestCase}. + */ +public abstract class ActivityTestCase extends InstrumentationTestCase { + + /** + * The activity that will be set up for use in each test method. + */ + private Activity mActivity; + + /** + * @return Returns the activity under test. + */ + protected Activity getActivity() { + return mActivity; + } + + /** + * Set the activity under test. + * @param testActivity The activity under test + */ + protected void setActivity(Activity testActivity) { + mActivity = testActivity; + } + + /** + * This function is called by various TestCase implementations, at tearDown() time, in order + * to scrub out any class variables. This protects against memory leaks in the case where a + * test case creates a non-static inner class (thus referencing the test case) and gives it to + * someone else to hold onto. + * + * @param testCaseClass The class of the derived TestCase implementation. + * + * @throws IllegalAccessException + */ + protected void scrubClass(final Class<?> testCaseClass) + throws IllegalAccessException { + final Field[] fields = getClass().getDeclaredFields(); + for (Field field : fields) { + final Class<?> fieldClass = field.getDeclaringClass(); + if (testCaseClass.isAssignableFrom(fieldClass) && !field.getType().isPrimitive()) { + try { + field.setAccessible(true); + field.set(this, null); + } catch (Exception e) { + android.util.Log.d("TestCase", "Error: Could not nullify field!"); + } + + if (field.get(this) != null) { + android.util.Log.d("TestCase", "Error: Could not nullify field!"); + } + } + } + } + + + +} diff --git a/test-runner/android/test/ActivityUnitTestCase.java b/test-runner/android/test/ActivityUnitTestCase.java new file mode 100644 index 0000000..dfd8fc2 --- /dev/null +++ b/test-runner/android/test/ActivityUnitTestCase.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Activity; +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.test.mock.MockApplication; +import android.view.Window; + +/** + * This class provides isolated testing of a single activity. The activity under test will + * be created with minimal connection to the system infrastructure, and you can inject mocked or + * wrappered versions of many of Activity's dependencies. Most of the work is handled + * automatically here by {@link #setUp} and {@link #tearDown}. + * + * <p>If you prefer a functional test, see {@link android.test.ActivityInstrumentationTestCase}. + * + * <p>It must be noted that, as a true unit test, your Activity will not be running in the + * normal system and will not participate in the normal interactions with other Activities. + * The following methods should not be called in this configuration - most of them will throw + * exceptions: + * <ul> + * <li>{@link android.app.Activity#createPendingResult(int, Intent, int)}</li> + * <li>{@link android.app.Activity#startActivityIfNeeded(Intent, int)}</li> + * <li>{@link android.app.Activity#startActivityFromChild(Activity, Intent, int)}</li> + * <li>{@link android.app.Activity#startNextMatchingActivity(Intent)}</li> + * <li>{@link android.app.Activity#getCallingActivity()}</li> + * <li>{@link android.app.Activity#getCallingPackage()}</li> + * <li>{@link android.app.Activity#createPendingResult(int, Intent, int)}</li> + * <li>{@link android.app.Activity#getTaskId()}</li> + * <li>{@link android.app.Activity#isTaskRoot()}</li> + * <li>{@link android.app.Activity#moveTaskToBack(boolean)}</li> + * <li>{@link android.app.Activity#setPersistent(boolean)}</li> + * </ul> + * + * <p>The following methods may be called but will not do anything. For test purposes, you can use + * the methods {@link #getStartedActivityIntent()} and {@link #getStartedActivityRequest()} to + * inspect the parameters that they were called with. + * <ul> + * <li>{@link android.app.Activity#startActivity(Intent)}</li> + * <li>{@link android.app.Activity#startActivityForResult(Intent, int)}</li> + * </ul> + * + * <p>The following methods may be called but will not do anything. For test purposes, you can use + * the methods {@link #isFinishCalled()} and {@link #getFinishedActivityRequest()} to inspect the + * parameters that they were called with. + * <ul> + * <li>{@link android.app.Activity#finish()}</li> + * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li> + * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li> + * </ul> + * + */ +public abstract class ActivityUnitTestCase<T extends Activity> + extends ActivityTestCase { + + private Class<T> mActivityClass; + + private Context mActivityContext; + private Application mApplication; + private MockParent mMockParent; + + private boolean mAttached = false; + private boolean mCreated = false; + + public ActivityUnitTestCase(Class<T> activityClass) { + mActivityClass = activityClass; + } + + @Override + public T getActivity() { + return (T) super.getActivity(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // default value for target context, as a default + mActivityContext = getInstrumentation().getTargetContext(); + } + + /** + * Start the activity under test, in the same way as if it was started by + * {@link android.content.Context#startActivity Context.startActivity()}, providing the + * arguments it supplied. When you use this method to start the activity, it will automatically + * be stopped by {@link #tearDown}. + * + * <p>This method will call onCreate(), but if you wish to further exercise Activity life + * cycle methods, you must call them yourself from your test case. + * + * <p><i>Do not call from your setUp() method. You must call this method from each of your + * test methods.</i> + * + * @param intent The Intent as if supplied to {@link android.content.Context#startActivity}. + * @param savedInstanceState The instance state, if you are simulating this part of the life + * cycle. Typically null. + * @param lastNonConfigurationInstance This Object will be available to the + * Activity if it calls {@link android.app.Activity#getLastNonConfigurationInstance()}. + * Typically null. + * @return Returns the Activity that was created + */ + protected T startActivity(Intent intent, Bundle savedInstanceState, + Object lastNonConfigurationInstance) { + assertFalse("Activity already created", mCreated); + + if (!mAttached) { + assertNotNull(mActivityClass); + setActivity(null); + T newActivity = null; + try { + IBinder token = null; + if (mApplication == null) { + setApplication(new MockApplication()); + } + ComponentName cn = new ComponentName(mActivityClass.getPackage().getName(), + mActivityClass.getName()); + intent.setComponent(cn); + ActivityInfo info = null; + CharSequence title = mActivityClass.getName(); + mMockParent = new MockParent(); + String id = null; + + newActivity = (T) getInstrumentation().newActivity(mActivityClass, mActivityContext, + token, mApplication, intent, info, title, mMockParent, id, + lastNonConfigurationInstance); + } catch (Exception e) { + assertNotNull(newActivity); + } + + assertNotNull(newActivity); + setActivity(newActivity); + + mAttached = true; + } + + T result = getActivity(); + if (result != null) { + getInstrumentation().callActivityOnCreate(getActivity(), savedInstanceState); + mCreated = true; + } + return result; + } + + @Override + protected void tearDown() throws Exception { + + setActivity(null); + + // Scrub out members - protects against memory leaks in the case where someone + // creates a non-static inner class (thus referencing the test case) and gives it to + // someone else to hold onto + scrubClass(ActivityInstrumentationTestCase.class); + + super.tearDown(); + } + + /** + * Set the application for use during the test. You must call this function before calling + * {@link #startActivity}. If your test does not call this method, + * @param application The Application object that will be injected into the Activity under test. + */ + public void setApplication(Application application) { + mApplication = application; + } + + /** + * If you wish to inject a Mock, Isolated, or otherwise altered context, you can do so + * here. You must call this function before calling {@link #startActivity}. If you wish to + * obtain a real Context, as a building block, use getInstrumentation().getTargetContext(). + */ + public void setActivityContext(Context activityContext) { + mActivityContext = activityContext; + } + + /** + * This method will return the value if your Activity under test calls + * {@link android.app.Activity#setRequestedOrientation}. + */ + public int getRequestedOrientation() { + if (mMockParent != null) { + return mMockParent.mRequestedOrientation; + } + return 0; + } + + /** + * This method will return the launch intent if your Activity under test calls + * {@link android.app.Activity#startActivity(Intent)} or + * {@link android.app.Activity#startActivityForResult(Intent, int)}. + * @return The Intent provided in the start call, or null if no start call was made. + */ + public Intent getStartedActivityIntent() { + if (mMockParent != null) { + return mMockParent.mStartedActivityIntent; + } + return null; + } + + /** + * This method will return the launch request code if your Activity under test calls + * {@link android.app.Activity#startActivityForResult(Intent, int)}. + * @return The request code provided in the start call, or -1 if no start call was made. + */ + public int getStartedActivityRequest() { + if (mMockParent != null) { + return mMockParent.mStartedActivityRequest; + } + return 0; + } + + /** + * This method will notify you if the Activity under test called + * {@link android.app.Activity#finish()}, + * {@link android.app.Activity#finishFromChild(Activity)}, or + * {@link android.app.Activity#finishActivity(int)}. + * @return Returns true if one of the listed finish methods was called. + */ + public boolean isFinishCalled() { + if (mMockParent != null) { + return mMockParent.mFinished; + } + return false; + } + + /** + * This method will return the request code if the Activity under test called + * {@link android.app.Activity#finishActivity(int)}. + * @return The request code provided in the start call, or -1 if no finish call was made. + */ + public int getFinishedActivityRequest() { + if (mMockParent != null) { + return mMockParent.mFinishedActivityRequest; + } + return 0; + } + + /** + * This mock Activity represents the "parent" activity. By injecting this, we allow the user + * to call a few more Activity methods, including: + * <ul> + * <li>{@link android.app.Activity#getRequestedOrientation()}</li> + * <li>{@link android.app.Activity#setRequestedOrientation(int)}</li> + * <li>{@link android.app.Activity#finish()}</li> + * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li> + * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li> + * </ul> + * + * TODO: Make this overrideable, and the unit test can look for calls to other methods + */ + private static class MockParent extends Activity { + + public int mRequestedOrientation = 0; + public Intent mStartedActivityIntent = null; + public int mStartedActivityRequest = -1; + public boolean mFinished = false; + public int mFinishedActivityRequest = -1; + + /** + * Implementing in the parent allows the user to call this function on the tested activity. + */ + @Override + public void setRequestedOrientation(int requestedOrientation) { + mRequestedOrientation = requestedOrientation; + } + + /** + * Implementing in the parent allows the user to call this function on the tested activity. + */ + @Override + public int getRequestedOrientation() { + return mRequestedOrientation; + } + + /** + * By returning null here, we inhibit the creation of any "container" for the window. + */ + @Override + public Window getWindow() { + return null; + } + + /** + * By defining this in the parent, we allow the tested activity to call + * <ul> + * <li>{@link android.app.Activity#startActivity(Intent)}</li> + * <li>{@link android.app.Activity#startActivityForResult(Intent, int)}</li> + * </ul> + */ + @Override + public void startActivityFromChild(Activity child, Intent intent, int requestCode) { + mStartedActivityIntent = intent; + mStartedActivityRequest = requestCode; + } + + /** + * By defining this in the parent, we allow the tested activity to call + * <ul> + * <li>{@link android.app.Activity#finish()}</li> + * <li>{@link android.app.Activity#finishFromChild(Activity child)}</li> + * </ul> + */ + @Override + public void finishFromChild(Activity child) { + mFinished = true; + } + + /** + * By defining this in the parent, we allow the tested activity to call + * <ul> + * <li>{@link android.app.Activity#finishActivity(int requestCode)}</li> + * </ul> + */ + @Override + public void finishActivityFromChild(Activity child, int requestCode) { + mFinished = true; + mFinishedActivityRequest = requestCode; + } + } +} diff --git a/test-runner/android/test/AndroidTestRunner.java b/test-runner/android/test/AndroidTestRunner.java new file mode 100644 index 0000000..353255e --- /dev/null +++ b/test-runner/android/test/AndroidTestRunner.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Instrumentation; +import android.content.Context; +import com.google.android.collect.Lists; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.runner.BaseTestRunner; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +public class AndroidTestRunner extends BaseTestRunner { + + private TestResult mTestResult; + private String mTestClassName; + private List<TestCase> mTestCases; + private Context mContext; + + private List<TestListener> mTestListeners = Lists.newArrayList(); + private Instrumentation mInstrumentation; + + @SuppressWarnings("unchecked") + public void setTestClassName(String testClassName, String testMethodName) { + Class testClass = loadTestClass(testClassName); + + if (shouldRunSingleTestMethod(testMethodName, testClass)) { + TestCase testCase = buildSingleTestMethod(testClass, testMethodName); + mTestCases = Lists.newArrayList(testCase); + mTestClassName = testClass.getSimpleName(); + } else { + setTest(getTest(testClass), testClass); + } + } + + public void setTest(Test test) { + setTest(test, test.getClass()); + } + + private void setTest(Test test, Class<? extends Test> testClass) { + mTestCases = (List<TestCase>) TestCaseUtil.getTests(test, true); + if (TestSuite.class.isAssignableFrom(testClass)) { + mTestClassName = TestCaseUtil.getTestName(test); + } else { + mTestClassName = testClass.getSimpleName(); + } + } + + public void clearTestListeners() { + mTestListeners.clear(); + } + + public void addTestListener(TestListener testListener) { + if (testListener != null) { + mTestListeners.add(testListener); + } + } + + @SuppressWarnings("unchecked") + private Class<? extends Test> loadTestClass(String testClassName) { + try { + return (Class<? extends Test>) mContext.getClassLoader().loadClass(testClassName); + } catch (ClassNotFoundException e) { + runFailed("Could not find test class. Class: " + testClassName); + } + return null; + } + + private TestCase buildSingleTestMethod(Class testClass, String testMethodName) { + try { + TestCase testCase = (TestCase) testClass.newInstance(); + testCase.setName(testMethodName); + return testCase; + } catch (IllegalAccessException e) { + runFailed("Could not access test class. Class: " + testClass.getName()); + } catch (InstantiationException e) { + runFailed("Could not instantiate test class. Class: " + testClass.getName()); + } + + return null; + } + + private boolean shouldRunSingleTestMethod(String testMethodName, + Class<? extends Test> testClass) { + return testMethodName != null && TestCase.class.isAssignableFrom(testClass); + } + + private Test getTest(Class clazz) { + if (TestSuiteProvider.class.isAssignableFrom(clazz)) { + try { + TestSuiteProvider testSuiteProvider = + (TestSuiteProvider) clazz.getConstructor().newInstance(); + return testSuiteProvider.getTestSuite(); + } catch (InstantiationException e) { + runFailed("Could not instantiate test suite provider. Class: " + clazz.getName()); + } catch (IllegalAccessException e) { + runFailed("Illegal access of test suite provider. Class: " + clazz.getName()); + } catch (InvocationTargetException e) { + runFailed("Invocation exception test suite provider. Class: " + clazz.getName()); + } catch (NoSuchMethodException e) { + runFailed("No such method on test suite provider. Class: " + clazz.getName()); + } + } + return getTest(clazz.getName()); + } + + protected TestResult createTestResult() { + return new TestResult(); + } + + public List<TestCase> getTestCases() { + return mTestCases; + } + + public String getTestClassName() { + return mTestClassName; + } + + public TestResult getTestResult() { + return mTestResult; + } + + public void runTest() { + runTest(createTestResult()); + } + + public void runTest(TestResult testResult) { + mTestResult = testResult; + + for (TestListener testListener : mTestListeners) { + mTestResult.addListener(testListener); + } + + for (TestCase testCase : mTestCases) { + setContextIfAndroidTestCase(testCase, mContext); + setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation); + testCase.run(mTestResult); + } + } + + private void setContextIfAndroidTestCase(Test test, Context context) { + if (AndroidTestCase.class.isAssignableFrom(test.getClass())) { + ((AndroidTestCase) test).setContext(context); + } + } + + public void setContext(Context context) { + mContext = context; + } + + private void setInstrumentationIfInstrumentationTestCase( + Test test, Instrumentation instrumentation) { + if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) { + ((InstrumentationTestCase) test).injectInsrumentation(instrumentation); + } + } + + public void setInstrumentaiton(Instrumentation instrumentation) { + mInstrumentation = instrumentation; + } + + @Override + protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException { + return mContext.getClassLoader().loadClass(suiteClassName); + } + + public void testStarted(String testName) { + } + + public void testEnded(String testName) { + } + + public void testFailed(int status, Test test, Throwable t) { + } + + protected void runFailed(String message) { + throw new RuntimeException(message); + } +} diff --git a/test-runner/android/test/ApplicationTestCase.java b/test-runner/android/test/ApplicationTestCase.java new file mode 100644 index 0000000..31b226a --- /dev/null +++ b/test-runner/android/test/ApplicationTestCase.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import java.lang.reflect.Field; + +import android.app.Application; +import android.app.Instrumentation; +import android.content.Context; + +/** + * This test case provides a framework in which you can test Application classes in + * a controlled environment. It provides basic support for the lifecycle of a + * Application, and hooks by which you can inject various dependencies and control + * the environment in which your Application is tested. + * + * <p><b>Lifecycle Support.</b> + * Every Application is designed to be accessed within a specific sequence of + * method calls (see {@link android.app.Application} for more details). + * In order to support the lifecycle of a Application, this test case will make the + * following calls at the following times. + * + * <ul><li>The test case will not call onCreate() until your test calls + * {@link #createApplication()}. This gives you a chance + * to set up or adjust any additional framework or test logic before + * onCreate().</li> + * <li>After your test completes, the test case {@link #tearDown} method is + * automatically called, and it will stop & destroy your application by calling its + * onDestroy() method.</li> + * </ul> + * + * <p><b>Dependency Injection.</b> + * Every Application has one inherent dependency, the {@link android.content.Context Context} in + * which it runs. + * This framework allows you to inject a modified, mock, or isolated replacement for this + * dependencies, and thus perform a true unit test. + * + * <p>If simply run your tests as-is, your Application will be injected with a fully-functional + * Context. + * You can create and inject alternative types of Contexts by calling + * {@link AndroidTestCase#setContext(Context) setContext()}. You must do this <i>before</i> calling + * startApplication(). The test framework provides a + * number of alternatives for Context, including {@link android.test.mock.MockContext MockContext}, + * {@link android.test.RenamingDelegatingContext RenamingDelegatingContext}, and + * {@link android.content.ContextWrapper ContextWrapper}. + */ +public abstract class ApplicationTestCase<T extends Application> extends AndroidTestCase { + + Class<T> mApplicationClass; + + private Context mSystemContext; + + public ApplicationTestCase(Class<T> applicationClass) { + mApplicationClass = applicationClass; + } + + private T mApplication; + private boolean mAttached = false; + private boolean mCreated = false; + + /** + * @return Returns the actual Application under test. + */ + public T getApplication() { + return mApplication; + } + + /** + * This will do the work to instantiate the Application under test. After this, your test + * code must also start and stop the Application. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + // get the real context, before the individual tests have a chance to muck with it + mSystemContext = getContext(); + } + + /** + * Load and attach the application under test. + */ + private void setupApplication() { + mApplication = null; + try { + mApplication = (T) Instrumentation.newApplication(mApplicationClass, getContext()); + } catch (Exception e) { + assertNotNull(mApplication); + } + mAttached = true; + } + + /** + * Start the Application under test, in the same way as if it was started by the system. + * If you use this method to start the Application, it will automatically + * be stopped by {@link #tearDown}. If you wish to inject a specialized Context for your + * test, by calling {@link AndroidTestCase#setContext(Context) setContext()}, + * you must do so before calling this method. + */ + final protected void createApplication() { + assertFalse(mCreated); + + if (!mAttached) { + setupApplication(); + } + assertNotNull(mApplication); + + mApplication.onCreate(); + mCreated = true; + } + + /** + * This will make the necessary calls to terminate the Application under test (it will + * call onTerminate(). Ordinarily this will be called automatically (by {@link #tearDown}, but + * you can call it directly from your test in order to check for proper shutdown behaviors. + */ + final protected void terminateApplication() { + if (mCreated) { + mApplication.onTerminate(); + } + } + + /** + * Shuts down the Application under test. Also makes sure all resources are cleaned up and + * garbage collected before moving on to the next + * test. Subclasses that override this method should make sure they call super.tearDown() + * at the end of the overriding method. + * + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + terminateApplication(); + mApplication = null; + + // Scrub out members - protects against memory leaks in the case where someone + // creates a non-static inner class (thus referencing the test case) and gives it to + // someone else to hold onto + scrubClass(ApplicationTestCase.class); + + super.tearDown(); + } + + /** + * Return a real (not mocked or instrumented) system Context that can be used when generating + * Mock or other Context objects for your Application under test. + * + * @return Returns a reference to a normal Context. + */ + public Context getSystemContext() { + return mSystemContext; + } + + /** + * This test simply confirms that the Application class can be instantiated properly. + * + * @throws Exception + */ + final public void testApplicationTestCaseSetUpProperly() throws Exception { + setupApplication(); + assertNotNull("Application class could not be instantiated successfully", mApplication); + } +} diff --git a/test-runner/android/test/AssertionFailedError.java b/test-runner/android/test/AssertionFailedError.java new file mode 100644 index 0000000..7af5806 --- /dev/null +++ b/test-runner/android/test/AssertionFailedError.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +/** + * Thrown when an assertion failed. + * + * Note: Most users of this class should simply use junit.framework.AssertionFailedError, + * which provides the same functionality. + */ +public class AssertionFailedError extends Error { + + /** + * It is more typical to call {@link #AssertionFailedError(String)}. + */ + public AssertionFailedError() { + } + + public AssertionFailedError(String errorMessage) { + super(errorMessage); + } +} diff --git a/test-runner/android/test/BundlePrinter.java b/test-runner/android/test/BundlePrinter.java new file mode 100644 index 0000000..96213e7 --- /dev/null +++ b/test-runner/android/test/BundlePrinter.java @@ -0,0 +1,63 @@ +package android.test; + +import java.io.PrintStream; + +import android.os.Bundle; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.runner.BaseTestRunner; +import junit.textui.ResultPrinter; + + +/** + * Subclass of ResultPrinter that adds test case results to a bundle. + * + * {@hide} - This class is deprecated, and will be going away. Please don't use it. + */ +public class BundlePrinter extends ResultPrinter { + + private Bundle mResults; + private boolean mFailure; + private boolean mError; + + public BundlePrinter(PrintStream writer, Bundle result) { + super(writer); + mResults = result; + } + + @Override + public void addError(Test test, Throwable t) { + mResults.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t)); + mFailure = true; + super.addError(test, t); + } + + @Override + public void addFailure(Test test, AssertionFailedError t) { + mResults.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t)); + mError = true; + super.addFailure(test, t); + } + + @Override + public void endTest(Test test) { + if (!mFailure && !mError) { + mResults.putString(getComboName(test), "passed"); + } + super.endTest(test); + } + + @Override + public void startTest(Test test) { + mFailure = false; + mError = false; + super.startTest(test); + } + + private String getComboName(Test test) { + return test.getClass().getName() + ":" + ((TestCase) test).getName(); + } + +} diff --git a/test-runner/android/test/BundleTestListener.java b/test-runner/android/test/BundleTestListener.java new file mode 100644 index 0000000..772713f --- /dev/null +++ b/test-runner/android/test/BundleTestListener.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import junit.framework.*; +import junit.framework.TestCase; +import junit.runner.BaseTestRunner; +import android.os.Bundle; + +/** + * A {@link TestListener} that adds test case results to a bundle. + * + * {@hide} - This class is deprecated, and will be going away. Please don't use it. + */ +public class BundleTestListener implements TestListener { + + private Bundle mBundle; + private boolean mFailed; + + public BundleTestListener(Bundle bundle) { + mBundle = bundle; + } + + + public void addError(Test test, Throwable t) { + mBundle.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t)); + mFailed = true; + } + + public void addFailure(Test test, junit.framework.AssertionFailedError t) { + mBundle.putString(getComboName(test), BaseTestRunner.getFilteredTrace(t)); + mFailed = true; + } + + public void endTest(Test test) { + if (!mFailed) { + mBundle.putString(getComboName(test), "passed"); + } + } + + public void startTest(Test test) { + mFailed = false; + } + + private String getComboName(Test test) { + return test.getClass().getName() + ":" + ((TestCase) test).getName(); + } + +} diff --git a/test-runner/android/test/ClassPathPackageInfo.java b/test-runner/android/test/ClassPathPackageInfo.java new file mode 100644 index 0000000..1f6e647 --- /dev/null +++ b/test-runner/android/test/ClassPathPackageInfo.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import com.google.android.collect.Sets; + +import java.util.Collections; +import java.util.Set; + +/** + * The Package object doesn't allow you to iterate over the contained + * classes and subpackages of that package. This is a version that does. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class ClassPathPackageInfo { + + private final ClassPathPackageInfoSource source; + private final String packageName; + private final Set<String> subpackageNames; + private final Set<Class<?>> topLevelClasses; + + ClassPathPackageInfo(ClassPathPackageInfoSource source, String packageName, + Set<String> subpackageNames, Set<Class<?>> topLevelClasses) { + this.source = source; + this.packageName = packageName; + this.subpackageNames = Collections.unmodifiableSet(subpackageNames); + this.topLevelClasses = Collections.unmodifiableSet(topLevelClasses); + } + + public Set<ClassPathPackageInfo> getSubpackages() { + Set<ClassPathPackageInfo> info = Sets.newHashSet(); + for (String name : subpackageNames) { + info.add(source.getPackageInfo(name)); + } + return info; + } + + public Set<Class<?>> getTopLevelClassesRecursive() { + Set<Class<?>> set = Sets.newHashSet(); + addTopLevelClassesTo(set); + return set; + } + + private void addTopLevelClassesTo(Set<Class<?>> set) { + set.addAll(topLevelClasses); + for (ClassPathPackageInfo info : getSubpackages()) { + info.addTopLevelClassesTo(set); + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ClassPathPackageInfo) { + ClassPathPackageInfo that = (ClassPathPackageInfo) obj; + return (this.packageName).equals(that.packageName); + } + return false; + } + + @Override + public int hashCode() { + return packageName.hashCode(); + } +} diff --git a/test-runner/android/test/ClassPathPackageInfoSource.java b/test-runner/android/test/ClassPathPackageInfoSource.java new file mode 100644 index 0000000..12bc7f3 --- /dev/null +++ b/test-runner/android/test/ClassPathPackageInfoSource.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.util.Config; +import android.util.Log; +import com.google.android.collect.Maps; +import com.google.android.collect.Sets; +import dalvik.system.DexFile; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Generate {@link ClassPathPackageInfo}s by scanning apk paths. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class ClassPathPackageInfoSource { + + private static final String CLASS_EXTENSION = ".class"; + + private static final ClassLoader CLASS_LOADER + = ClassPathPackageInfoSource.class.getClassLoader(); + + private final SimpleCache<String, ClassPathPackageInfo> cache = + new SimpleCache<String, ClassPathPackageInfo>() { + @Override + protected ClassPathPackageInfo load(String pkgName) { + return createPackageInfo(pkgName); + } + }; + + // The class path of the running application + private final String[] classPath; + private static String[] apkPaths; + + // A cache of jar file contents + private final Map<File, Set<String>> jarFiles = Maps.newHashMap(); + private ClassLoader classLoader; + + ClassPathPackageInfoSource() { + classPath = getClassPath(); + } + + + public static void setApkPaths(String[] apkPaths) { + ClassPathPackageInfoSource.apkPaths = apkPaths; + } + + public ClassPathPackageInfo getPackageInfo(String pkgName) { + return cache.get(pkgName); + } + + private ClassPathPackageInfo createPackageInfo(String packageName) { + Set<String> subpackageNames = new TreeSet<String>(); + Set<String> classNames = new TreeSet<String>(); + Set<Class<?>> topLevelClasses = Sets.newHashSet(); + findClasses(packageName, classNames, subpackageNames); + for (String className : classNames) { + if (className.endsWith(".R") || className.endsWith(".Manifest")) { + // Don't try to load classes that are generated. They usually aren't in test apks. + continue; + } + + try { + // We get errors in the emulator if we don't use the caller's class loader. + topLevelClasses.add(Class.forName(className, false, + (classLoader != null) ? classLoader : CLASS_LOADER)); + } catch (ClassNotFoundException e) { + // Should not happen unless there is a generated class that is not included in + // the .apk. + Log.w("ClassPathPackageInfoSource", "Cannot load class. " + + "Make sure it is in your apk. Class name: '" + className + + "'. Message: " + e.getMessage(), e); + } + } + return new ClassPathPackageInfo(this, packageName, subpackageNames, + topLevelClasses); + } + + /** + * Finds all classes and sub packages that are below the packageName and + * add them to the respective sets. Searches the package on the whole class + * path. + */ + private void findClasses(String packageName, Set<String> classNames, + Set<String> subpackageNames) { + String packagePrefix = packageName + '.'; + String pathPrefix = packagePrefix.replace('.', '/'); + + for (String entryName : classPath) { + File classPathEntry = new File(entryName); + + // Forge may not have brought over every item in the classpath. Be + // polite and ignore missing entries. + if (classPathEntry.exists()) { + try { + if (entryName.endsWith(".apk")) { + findClassesInApk(entryName, packageName, classNames, subpackageNames); + } else if ("true".equals(System.getProperty("android.vm.dexfile", "false"))) { + // If the vm supports dex files then scan the directories that contain + // apk files. + for (String apkPath : apkPaths) { + File file = new File(apkPath); + scanForApkFiles(file, packageName, classNames, subpackageNames); + } + } else if (entryName.endsWith(".jar")) { + findClassesInJar(classPathEntry, pathPrefix, + classNames, subpackageNames); + } else if (classPathEntry.isDirectory()) { + findClassesInDirectory(classPathEntry, packagePrefix, pathPrefix, + classNames, subpackageNames); + } else { + throw new AssertionError("Don't understand classpath entry " + + classPathEntry); + } + } catch (IOException e) { + throw new AssertionError("Can't read classpath entry " + + entryName + ": " + e.getMessage()); + } + } + } + } + + private void scanForApkFiles(File source, String packageName, + Set<String> classNames, Set<String> subpackageNames) throws IOException { + if (source.getPath().endsWith(".apk")) { + findClassesInApk(source.getPath(), packageName, classNames, subpackageNames); + } else { + File[] files = source.listFiles(); + if (files != null) { + for (File file : files) { + scanForApkFiles(file, packageName, classNames, subpackageNames); + } + } + } + } + + /** + * Finds all classes and sub packages that are below the packageName and + * add them to the respective sets. Searches the package in a class directory. + */ + private void findClassesInDirectory(File classDir, + String packagePrefix, String pathPrefix, Set<String> classNames, + Set<String> subpackageNames) + throws IOException { + File directory = new File(classDir, pathPrefix); + + if (directory.exists()) { + for (File f : directory.listFiles()) { + String name = f.getName(); + if (name.endsWith(CLASS_EXTENSION) && isToplevelClass(name)) { + classNames.add(packagePrefix + getClassName(name)); + } else if (f.isDirectory()) { + subpackageNames.add(packagePrefix + name); + } + } + } + } + + /** + * Finds all classes and sub packages that are below the packageName and + * add them to the respective sets. Searches the package in a single jar file. + */ + private void findClassesInJar(File jarFile, String pathPrefix, + Set<String> classNames, Set<String> subpackageNames) + throws IOException { + Set<String> entryNames = getJarEntries(jarFile); + // check if the Jar contains the package. + if (!entryNames.contains(pathPrefix)) { + return; + } + int prefixLength = pathPrefix.length(); + for (String entryName : entryNames) { + if (entryName.startsWith(pathPrefix)) { + if (entryName.endsWith(CLASS_EXTENSION)) { + // check if the class is in the package itself or in one of its + // subpackages. + int index = entryName.indexOf('/', prefixLength); + if (index >= 0) { + String p = entryName.substring(0, index).replace('/', '.'); + subpackageNames.add(p); + } else if (isToplevelClass(entryName)) { + classNames.add(getClassName(entryName).replace('/', '.')); + } + } + } + } + } + + /** + * Finds all classes and sub packages that are below the packageName and + * add them to the respective sets. Searches the package in a single apk file. + */ + private void findClassesInApk(String apkPath, String packageName, + Set<String> classNames, Set<String> subpackageNames) + throws IOException { + + DexFile dexFile = null; + try { + dexFile = new DexFile(apkPath); + Enumeration<String> apkClassNames = dexFile.entries(); + while (apkClassNames.hasMoreElements()) { + String className = apkClassNames.nextElement(); + + if (className.startsWith(packageName)) { + int lastPackageSeparator = className.lastIndexOf('.'); + String subPackageName = className.substring(0, lastPackageSeparator); + if (subPackageName.length() > packageName.length()) { + subpackageNames.add(subPackageName); + } else if (isToplevelClass(className)) { + classNames.add(className); + } + } + } + } catch (IOException e) { + if (Config.LOGV) { + Log.w("ClassPathPackageInfoSource", + "Error finding classes at apk path: " + apkPath, e); + } + } finally { + if (dexFile != null) { + // Todo: figure out why closing causes a dalvik error resulting in vm shutdown. +// dexFile.close(); + } + } + } + + /** + * Gets the class and package entries from a Jar. + */ + private Set<String> getJarEntries(File jarFile) + throws IOException { + Set<String> entryNames = jarFiles.get(jarFile); + if (entryNames == null) { + entryNames = Sets.newHashSet(); + ZipFile zipFile = new ZipFile(jarFile); + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + String entryName = entries.nextElement().getName(); + if (entryName.endsWith(CLASS_EXTENSION)) { + // add the entry name of the class + entryNames.add(entryName); + + // add the entry name of the classes package, i.e. the entry name of + // the directory that the class is in. Used to quickly skip jar files + // if they do not contain a certain package. + // + // Also add parent packages so that a JAR that contains + // pkg1/pkg2/Foo.class will be marked as containing pkg1/ in addition + // to pkg1/pkg2/ and pkg1/pkg2/Foo.class. We're still interested in + // JAR files that contains subpackages of a given package, even if + // an intermediate package contains no direct classes. + // + // Classes in the default package will cause a single package named + // "" to be added instead. + int lastIndex = entryName.lastIndexOf('/'); + do { + String packageName = entryName.substring(0, lastIndex + 1); + entryNames.add(packageName); + lastIndex = entryName.lastIndexOf('/', lastIndex - 1); + } while (lastIndex > 0); + } + } + jarFiles.put(jarFile, entryNames); + } + return entryNames; + } + + /** + * Checks if a given file name represents a toplevel class. + */ + private static boolean isToplevelClass(String fileName) { + return fileName.indexOf('$') < 0; + } + + /** + * Given the absolute path of a class file, return the class name. + */ + private static String getClassName(String className) { + int classNameEnd = className.length() - CLASS_EXTENSION.length(); + return className.substring(0, classNameEnd); + } + + /** + * Gets the class path from the System Property "java.class.path" and splits + * it up into the individual elements. + */ + private static String[] getClassPath() { + String classPath = System.getProperty("java.class.path"); + String separator = System.getProperty("path.separator", ":"); + return classPath.split(Pattern.quote(separator)); + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } +} diff --git a/test-runner/android/test/ComparisonFailure.java b/test-runner/android/test/ComparisonFailure.java new file mode 100644 index 0000000..e7e9698 --- /dev/null +++ b/test-runner/android/test/ComparisonFailure.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +/** + * Thrown when an assert equals for Strings failed. + * + * Note: Most users of this class should simply use junit.framework.ComparisonFailure, + * which provides the same functionality at a lighter weight. + */ +public class ComparisonFailure extends AssertionFailedError { + private junit.framework.ComparisonFailure mComparison; + + public ComparisonFailure(String message, String expected, String actual) { + mComparison = new junit.framework.ComparisonFailure(message, expected, actual); + } + + public String getMessage() { + return mComparison.getMessage(); + } +} diff --git a/test-runner/android/test/InstrumentationTestRunner.java b/test-runner/android/test/InstrumentationTestRunner.java new file mode 100644 index 0000000..930da12 --- /dev/null +++ b/test-runner/android/test/InstrumentationTestRunner.java @@ -0,0 +1,442 @@ +/* + * 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.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.runner.BaseTestRunner; +import junit.textui.ResultPrinter; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Context; +import android.os.Bundle; +import android.os.Debug; +import android.os.Looper; +import android.test.suitebuilder.InstrumentationTestSuiteBuilder; +import android.test.suitebuilder.TestSuiteBuilder; +import android.test.suitebuilder.UnitTestSuiteBuilder; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +/** + * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against + * an Android package (application). Typical usage: + * <ol> + * <li>Write {@link junit.framework.TestCase}s that perform unit, functional, or performance tests + * against the classes in your package. Typically these are subclassed from: + * <ul><li>{@link android.test.ActivityInstrumentationTestCase}</li> + * <li>{@link android.test.ActivityUnitTestCase}</li> + * <li>{@link android.test.AndroidTestCase}</li> + * <li>{@link android.test.ApplicationTestCase}</li> + * <li>{@link android.test.InstrumentationTestCase}</li> + * <li>{@link android.test.ProviderTestCase}</li> + * <li>{@link android.test.ServiceTestCase}</li> + * <li>{@link android.test.SingleLaunchActivityTestCase}</li></ul> + * <li>In an appropriate AndroidManifest.xml, define the this instrumentation with + * the appropriate android:targetPackage set. + * <li>Run the instrumentation using "adb shell am instrument -w", + * with no optional arguments, to run all tests (except performance tests). + * <li>Run the instrumentation using "adb shell am instrument -w", + * with the argument '-e func true' to run all functional tests. These are tests that derive from + * {@link android.test.InstrumentationTestCase}. + * <li>Run the instrumentation using "adb shell am instrument -w", + * with the argument '-e unit true' to run all unit tests. These are tests that <i>do not</i>derive + * from {@link android.test.InstrumentationTestCase} (and are not performance tests). + * <li>Run the instrumentation using "adb shell am instrument -w", + * with the argument '-e class' set to run an individual {@link junit.framework.TestCase}. + * </ol> + * <p/> + * <b>Running all tests:</b> adb shell am instrument -w + * com.android.foo/android.test.InstrumentationTestRunner + * <p/> + * <b>Running unit tests:</b> adb shell am instrument -w -e unit true + * com.android.foo/android.test.InstrumentationTestRunner + * <p/> + * <b>Running instrumentation tests:</b> adb shell am instrument -w -e func true + * 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>To debug your tests, set a break point in your code and pass:</b> + * -e debug true + * <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_UNIT_CLASS = "unit"; + /** @hide */ + public static final String ARGUMENT_FUNC_CLASS = "func"; + /** @hide */ + public static final String ARGUMENT_TEST_PACKAGE = "package"; + + /** + * 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"; + /** + * The test is starting. + */ + public static final int REPORT_VALUE_RESULT_START = 1; + /** + * The test completed successfully. + */ + public static final int REPORT_VALUE_RESULT_OK = 0; + /** + * The test completed with an error. + */ + public static final int REPORT_VALUE_RESULT_ERROR = -1; + /** + * The test completed with a failure. + */ + public static final int REPORT_VALUE_RESULT_FAILURE = -2; + /** + * If included in the status bundle sent to an IInstrumentationWatcher, this key + * identifies a stack trace describing an error or failure. This is sent with any status + * message describing a specific test being completed. + */ + public static final String REPORT_KEY_STACK = "stack"; + + private final Bundle mResults = new Bundle(); + private AndroidTestRunner mTestRunner; + private boolean mDebug; + private boolean mJustCount; + private int mTestCount; + private boolean mBundleOutput; + private boolean mDatabaseOutput; + private String mPackageOfTests; + + @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); + + boolean useUnitTestSuite = false; + boolean useFunctionalTestSuite = false; + + String testClassName = null; + Test test = null; + + if (arguments != null) { + // Test class name passed as an argument should override any meta-data declaration. + testClassName = arguments.getString(ARGUMENT_TEST_CLASS); + mDebug = getBooleanArgument(arguments, "debug"); + mJustCount = getBooleanArgument(arguments, "count"); + mBundleOutput = getBooleanArgument(arguments, "bundle"); + mDatabaseOutput = getBooleanArgument(arguments, "database"); + useUnitTestSuite = getBooleanArgument(arguments, ARGUMENT_UNIT_CLASS); + useFunctionalTestSuite = getBooleanArgument(arguments, ARGUMENT_FUNC_CLASS); + mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE); + } + + if (testClassName == null) { + if (mPackageOfTests != null) { + test = createPackageTestSuite( + getTargetContext(), useUnitTestSuite, + useFunctionalTestSuite, mPackageOfTests); + } else { + test = getTestSuite(); + } + + if (test == null) { + test = createDefaultTestSuite(getTargetContext(), useUnitTestSuite, + useFunctionalTestSuite); + } + mTestCount = test.countTestCases(); + } + + mTestRunner = getAndroidTestRunner(); + mTestRunner.setContext(getTargetContext()); + mTestRunner.setInstrumentaiton(this); + mTestRunner.addTestListener(new TestPrinter("TestRunner", false)); + if (mDatabaseOutput) { + mTestRunner.addTestListener(new TestRecorder()); + } + + if (testClassName != null) { + int methodSeparatorIndex = testClassName.indexOf('#'); + String testMethodName = null; + + if (methodSeparatorIndex > 0) { + testMethodName = testClassName.substring(methodSeparatorIndex + 1); + testClassName = testClassName.substring(0, methodSeparatorIndex); + } + mTestRunner.setTestClassName(testClassName, testMethodName); + mTestCount = mTestRunner.getTestCases().size(); + } else { + mTestRunner.setTest(test); + } + // add this now that we know the count + if (!mBundleOutput) { + mTestRunner.addTestListener(new WatcherResultPrinter(mTestCount)); + } + + start(); + } + + private TestSuite createDefaultTestSuite(Context context, boolean useUnitTestSuite, + boolean useFunctionalTestSuite) { + return createPackageTestSuite(context, useUnitTestSuite, useFunctionalTestSuite, context.getPackageName()); + } + + private TestSuite createPackageTestSuite(Context context, boolean useUnitTestSuite, + boolean useFunctionalTestSuite, String packageName) { + TestSuiteBuilder testSuiteBuilder = null; + + if (useUnitTestSuite) { + testSuiteBuilder = new UnitTestSuiteBuilder(getClass().getName(), + context.getClassLoader()); + } else if (useFunctionalTestSuite) { + testSuiteBuilder = new InstrumentationTestSuiteBuilder(getClass().getName(), + context.getClassLoader()); + } else { + testSuiteBuilder = new TestSuiteBuilder(getClass().getName(), + context.getClassLoader()); + } + + return testSuiteBuilder.includePackages(packageName).build(); + } + + protected AndroidTestRunner getAndroidTestRunner() { + return new AndroidTestRunner(); + } + + private boolean getBooleanArgument(Bundle arguments, String tag) { + String tagString = arguments.getString(tag); + return tagString != null && Boolean.parseBoolean(tagString); + } + + @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); + + if (mBundleOutput) { + mTestRunner.addTestListener(new BundleTestListener(mResults)); + } else { + mTestRunner.addTestListener(resultPrinter); + } + + long startTime = System.currentTimeMillis(); + mTestRunner.runTest(); + long runTime = System.currentTimeMillis() - startTime; + + resultPrinter.print(mTestRunner.getTestResult(), runTime); + } finally { + if (!mBundleOutput) { + mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("\nTest results for %s=%s", + mTestRunner.getTestClassName(), + byteArrayOutputStream.toString())); + } + 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; + } + + // 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); + if (mBundleOutput) { + printErrors(result); + printFailures(result); + } + printFooter(result); + } + } + + /** + * This class sends status reports back to the IInstrumentationWatcher + */ + private class WatcherResultPrinter implements TestListener + { + private final Bundle mResultTemplate; + Bundle mTestResult; + int mTestNum = 0; + int mTestResultCode = 0; + String mTestClass = null; + + public WatcherResultPrinter(int numTests) { + mResultTemplate = new Bundle(); + mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID); + mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests); + } + + /** + * send a status for the start of a each test, so long tests can be seen as "running" + */ + public void startTest(Test test) { + String testClass = test.getClass().getName(); + mTestResult = new Bundle(mResultTemplate); + mTestResult.putString(REPORT_KEY_NAME_CLASS, testClass); + mTestResult.putString(REPORT_KEY_NAME_TEST, ((TestCase) test).getName()); + mTestResult.putInt(REPORT_KEY_NUM_CURRENT, ++mTestNum); + // pretty printing + if (testClass != null && !testClass.equals(mTestClass)) { + mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("\n%s:", testClass)); + mTestClass = testClass; + } else { + mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ""); + } + sendStatus(REPORT_VALUE_RESULT_START, mTestResult); + mTestResultCode = 0; + } + + /** + * @see junit.framework.TestListener#addError(Test, Throwable) + */ + public void addError(Test test, Throwable t) { + mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t)); + mTestResultCode = REPORT_VALUE_RESULT_ERROR; + // pretty printing + mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("\nError in %s:\n%s", + ((TestCase) test).getName(), BaseTestRunner.getFilteredTrace(t))); + } + + /** + * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError) + */ + public void addFailure(Test test, AssertionFailedError t) { + mTestResult.putString(REPORT_KEY_STACK, BaseTestRunner.getFilteredTrace(t)); + mTestResultCode = REPORT_VALUE_RESULT_FAILURE; + // pretty printing + mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("\nFailure in %s:\n%s", + ((TestCase) test).getName(), BaseTestRunner.getFilteredTrace(t))); + } + + /** + * @see junit.framework.TestListener#endTest(Test) + */ + public void endTest(Test test) { + if (mTestResultCode == 0) { + mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "."); + } + sendStatus(mTestResultCode, mTestResult); + } + + // TODO report the end of the cycle + // TODO report runtime for each test + } + + +} diff --git a/test-runner/android/test/InstrumentationUtils.java b/test-runner/android/test/InstrumentationUtils.java new file mode 100644 index 0000000..4c59097 --- /dev/null +++ b/test-runner/android/test/InstrumentationUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import java.lang.reflect.Field; + +/** + * + * The InstrumentationUtils class has all the utility functions needed for + * instrumentation tests. + * + * {@hide} - Not currently used. + */ +public class InstrumentationUtils { + /** + * An utility function that returns the menu identifier for a particular + * menu item. + * + * @param cls Class object of the class that handles the menu ite,. + * @param identifier Menu identifier. + * @return The integer corresponding to the menu item. + */ + public static int getMenuIdentifier(Class cls, String identifier) { + int id = -1; + try { + Integer field = (Integer)cls.getDeclaredField(identifier).get(cls); + id = field.intValue(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return id; + } + +} diff --git a/test-runner/android/test/IsolatedContext.java b/test-runner/android/test/IsolatedContext.java new file mode 100644 index 0000000..2866666 --- /dev/null +++ b/test-runner/android/test/IsolatedContext.java @@ -0,0 +1,85 @@ +package android.test; + +import com.google.android.collect.Lists; + +import android.content.ContextWrapper; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.Context; +import android.content.ServiceConnection; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.Uri; + +import java.util.List; + +/** + * A mock context which prevents its users from talking to the rest of the device while + * stubbing enough methods to satify code that tries to talk to other packages. + */ +public class IsolatedContext extends ContextWrapper { + + private ContentResolver mResolver; + + private List<Intent> mBroadcastIntents = Lists.newArrayList(); + + public IsolatedContext( + ContentResolver resolver, Context targetContext) { + super(targetContext); + mResolver = resolver; + } + + /** Returns the list of intents that were broadcast since the last call to this method. */ + public List<Intent> getAndClearBroadcastIntents() { + List<Intent> intents = mBroadcastIntents; + mBroadcastIntents = Lists.newArrayList(); + return intents; + } + + @Override + public ContentResolver getContentResolver() { + // We need to return the real resolver so that MailEngine.makeRight can get to the + // subscribed feeds provider. TODO: mock out subscribed feeds too. + return mResolver; + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, int flags) { + return false; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return null; + } + + @Override + public void sendBroadcast(Intent intent) { + mBroadcastIntents.add(intent); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission) { + mBroadcastIntents.add(intent); + } + + @Override + public int checkUriPermission( + Uri uri, String readPermission, String writePermission, int pid, + int uid, int modeFlags) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public Object getSystemService(String name) { + // No services exist in this context. + return null; + } + +} diff --git a/test-runner/android/test/LaunchPerformanceBase.java b/test-runner/android/test/LaunchPerformanceBase.java new file mode 100644 index 0000000..c324446 --- /dev/null +++ b/test-runner/android/test/LaunchPerformanceBase.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.Debug; +import android.os.Process; +import android.os.ServiceManager; +import android.os.SystemClock; + +import java.util.ArrayList; + + +/** + * Base class for all launch performance Instrumentation classes. + * + * @hide + */ +public class LaunchPerformanceBase extends Instrumentation { + + public static final String LOG_TAG = "Launch Performance"; + + protected Bundle mResults; + protected Intent mIntent; + + public LaunchPerformanceBase() { + mResults = new Bundle(); + mIntent = new Intent(Intent.ACTION_MAIN); + mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + setAutomaticPerformanceSnapshots(); + } + + /** + * Launches intent, and waits for idle before returning. + * + * @hide + */ + protected void LaunchApp() { + startActivitySync(mIntent); + waitForIdleSync(); + } +} diff --git a/test-runner/android/test/MoreAsserts.java b/test-runner/android/test/MoreAsserts.java new file mode 100644 index 0000000..2e74644 --- /dev/null +++ b/test-runner/android/test/MoreAsserts.java @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import com.google.android.collect.Lists; +import junit.framework.Assert; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Contains additional assertion methods not found in JUnit. + */ +public final class MoreAsserts { + + private MoreAsserts() { } + + /** + * Asserts that the class {@code expected} is assignable from the object + * {@code actual}. This verifies {@code expected} is a parent class or a + * interface that {@code actual} implements. + */ + public static void assertAssignableFrom(Class<?> expected, Object actual) { + assertAssignableFrom(expected, actual.getClass()); + } + + /** + * Asserts that class {@code expected} is assignable from the class + * {@code actual}. This verifies {@code expected} is a parent class or a + * interface that {@code actual} implements. + */ + public static void assertAssignableFrom(Class<?> expected, Class<?> actual) { + Assert.assertTrue( + "Expected " + expected.getCanonicalName() + + " to be assignable from actual class " + actual.getCanonicalName(), + expected.isAssignableFrom(actual)); + } + + /** + * Asserts that {@code actual} is not equal {@code unexpected}, according + * to both {@code ==} and {@link Object#equals}. + */ + public static void assertNotEqual( + String message, Object unexpected, Object actual) { + if (equal(unexpected, actual)) { + failEqual(message, unexpected); + } + } + + /** + * Variant of {@link #assertNotEqual(String,Object,Object)} using a + * generic message. + */ + public static void assertNotEqual(Object unexpected, Object actual) { + assertNotEqual(null, unexpected, actual); + } + + /** + * Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates specific + * element mismatch. + */ + public static void assertEquals( + String message, byte[] expected, byte[] actual) { + if (expected.length != actual.length) { + failWrongLength(message, expected.length, actual.length); + } + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failWrongElement(message, i, expected[i], actual[i]); + } + } + } + + /** + * Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates specific + * element mismatch. + */ + public static void assertEquals(byte[] expected, byte[] actual) { + assertEquals(null, expected, actual); + } + + /** + * Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates first + * specific element mismatch. + */ + public static void assertEquals( + String message, int[] expected, int[] actual) { + if (expected.length != actual.length) { + failWrongLength(message, expected.length, actual.length); + } + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failWrongElement(message, i, expected[i], actual[i]); + } + } + } + + /** + * Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates first + * specific element mismatch. + */ + public static void assertEquals(int[] expected, int[] actual) { + assertEquals(null, expected, actual); + } + + /** + * Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates first + * specific element mismatch. + */ + public static void assertEquals( + String message, double[] expected, double[] actual) { + if (expected.length != actual.length) { + failWrongLength(message, expected.length, actual.length); + } + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failWrongElement(message, i, expected[i], actual[i]); + } + } + } + + /** + * Asserts that array {@code actual} is the same size and every element equals + * those in array {@code expected}. On failure, message indicates first + * specific element mismatch. + */ + public static void assertEquals(double[] expected, double[] actual) { + assertEquals(null, expected, actual); + } + + /** + * Asserts that array {@code actual} is the same size and every element + * is the same as those in array {@code expected}. Note that this uses + * {@code equals()} instead of {@code ==} to compare the objects. + * {@code null} will be considered equal to {code null} (unlike SQL). + * On failure, message indicates first specific element mismatch. + */ + public static void assertEquals( + String message, Object[] expected, Object[] actual) { + if (expected.length != actual.length) { + failWrongLength(message, expected.length, actual.length); + } + for (int i = 0; i < expected.length; i++) { + Object exp = expected[i]; + Object act = actual[i]; + // The following borrowed from java.util.equals(Object[], Object[]). + if (!((exp==null) ? act==null : exp.equals(act))) { + failWrongElement(message, i, exp, act); + } + } + } + + /** + * Asserts that array {@code actual} is the same size and every element + * is the same as those in array {@code expected}. Note that this uses + * {@code ==} instead of {@code equals()} to compare the objects. + * On failure, message indicates first specific element mismatch. + */ + public static void assertEquals(Object[] expected, Object[] actual) { + assertEquals(null, expected, actual); + } + + /** Asserts that two sets contain the same elements. */ + public static void assertEquals( + String message, Set<? extends Object> expected, Set<? extends Object> actual) { + Set<Object> onlyInExpected = new HashSet<Object>(expected); + onlyInExpected.removeAll(actual); + Set<Object> onlyInActual = new HashSet<Object>(actual); + onlyInActual.removeAll(expected); + if (onlyInExpected.size() != 0 || onlyInActual.size() != 0) { + Set<Object> intersection = new HashSet<Object>(expected); + intersection.retainAll(actual); + failWithMessage( + message, + "Sets do not match.\nOnly in expected: " + onlyInExpected + + "\nOnly in actual: " + onlyInActual + + "\nIntersection: " + intersection); + } + } + + /** Asserts that two sets contain the same elements. */ + public static void assertEquals(Set<? extends Object> expected, Set<? extends Object> actual) { + assertEquals(null, expected, actual); + } + + /** + * Asserts that {@code expectedRegex} exactly matches {@code actual} and + * fails with {@code message} if it does not. The MatchResult is returned + * in case the test needs access to any captured groups. Note that you can + * also use this for a literal string, by wrapping your expected string in + * {@link Pattern#quote}. + */ + public static MatchResult assertMatchesRegex( + String message, String expectedRegex, String actual) { + if (actual == null) { + failNotMatches(message, expectedRegex, actual); + } + Matcher matcher = getMatcher(expectedRegex, actual); + if (!matcher.matches()) { + failNotMatches(message, expectedRegex, actual); + } + return matcher; + } + + /** + * Variant of {@link #assertMatchesRegex(String,String,String)} using a + * generic message. + */ + public static MatchResult assertMatchesRegex( + String expectedRegex, String actual) { + return assertMatchesRegex(null, expectedRegex, actual); + } + + /** + * Asserts that {@code expectedRegex} matches any substring of {@code actual} + * and fails with {@code message} if it does not. The Matcher is returned in + * case the test needs access to any captured groups. Note that you can also + * use this for a literal string, by wrapping your expected string in + * {@link Pattern#quote}. + */ + public static MatchResult assertContainsRegex( + String message, String expectedRegex, String actual) { + if (actual == null) { + failNotContains(message, expectedRegex, actual); + } + Matcher matcher = getMatcher(expectedRegex, actual); + if (!matcher.find()) { + failNotContains(message, expectedRegex, actual); + } + return matcher; + } + + /** + * Variant of {@link #assertContainsRegex(String,String,String)} using a + * generic message. + */ + public static MatchResult assertContainsRegex( + String expectedRegex, String actual) { + return assertContainsRegex(null, expectedRegex, actual); + } + + /** + * Asserts that {@code expectedRegex} does not exactly match {@code actual}, + * and fails with {@code message} if it does. Note that you can also use + * this for a literal string, by wrapping your expected string in + * {@link Pattern#quote}. + */ + public static void assertNotMatchesRegex( + String message, String expectedRegex, String actual) { + Matcher matcher = getMatcher(expectedRegex, actual); + if (matcher.matches()) { + failMatch(message, expectedRegex, actual); + } + } + + /** + * Variant of {@link #assertNotMatchesRegex(String,String,String)} using a + * generic message. + */ + public static void assertNotMatchesRegex( + String expectedRegex, String actual) { + assertNotMatchesRegex(null, expectedRegex, actual); + } + + /** + * Asserts that {@code expectedRegex} does not match any substring of + * {@code actual}, and fails with {@code message} if it does. Note that you + * can also use this for a literal string, by wrapping your expected string + * in {@link Pattern#quote}. + */ + public static void assertNotContainsRegex( + String message, String expectedRegex, String actual) { + Matcher matcher = getMatcher(expectedRegex, actual); + if (matcher.find()) { + failContains(message, expectedRegex, actual); + } + } + + /** + * Variant of {@link #assertNotContainsRegex(String,String,String)} using a + * generic message. + */ + public static void assertNotContainsRegex( + String expectedRegex, String actual) { + assertNotContainsRegex(null, expectedRegex, actual); + } + + /** + * Asserts that {@code actual} contains precisely the elements + * {@code expected}, and in the same order. + */ + public static void assertContentsInOrder( + String message, Iterable<?> actual, Object... expected) { + Assert.assertEquals(message, + Arrays.asList(expected), Lists.newArrayList(actual)); + } + + /** + * Variant of assertContentsInOrder(String, Iterable<?>, Object...) + * using a generic message. + */ + public static void assertContentsInOrder( + Iterable<?> actual, Object... expected) { + assertContentsInOrder((String) null, actual, expected); + } + + /** + * Asserts that {@code actual} contains precisely the elements + * {@code expected}, but in any order. + */ + public static void assertContentsInAnyOrder(String message, Iterable<?> actual, + Object... expected) { + HashMap<Object, Object> expectedMap = new HashMap<Object, Object>(expected.length); + for (Object expectedObj : expected) { + expectedMap.put(expectedObj, expectedObj); + } + + for (Object actualObj : actual) { + if (expectedMap.remove(actualObj) == null) { + failWithMessage(message, "Extra object in actual: (" + actualObj.toString() + ")"); + } + } + + if (expectedMap.size() > 0) { + failWithMessage(message, "Extra objects in expected."); + } + } + + /** + * Variant of assertContentsInAnyOrder(String, Iterable<?>, Object...) + * using a generic message. + */ + public static void assertContentsInAnyOrder(Iterable<?> actual, Object... expected) { + assertContentsInAnyOrder((String)null, actual, expected); + } + + /** + * Asserts that {@code iterable} is empty. + */ + public static void assertEmpty(String message, Iterable<?> iterable) { + if (iterable.iterator().hasNext()) { + failNotEmpty(message, iterable.toString()); + } + } + + /** + * Variant of {@link #assertEmpty(String, Iterable)} using a + * generic message. + */ + public static void assertEmpty(Iterable<?> iterable) { + assertEmpty(null, iterable); + } + + /** + * Asserts that {@code map} is empty. + */ + public static void assertEmpty(String message, Map<?,?> map) { + if (!map.isEmpty()) { + failNotEmpty(message, map.toString()); + } + } + + /** + * Variant of {@link #assertEmpty(String, Map)} using a generic + * message. + */ + public static void assertEmpty(Map<?,?> map) { + assertEmpty(null, map); + } + + /** + * Asserts that {@code iterable} is not empty. + */ + public static void assertNotEmpty(String message, Iterable<?> iterable) { + if (!iterable.iterator().hasNext()) { + failEmpty(message); + } + } + + /** + * Variant of assertNotEmpty(String, Iterable<?>) + * using a generic message. + */ + public static void assertNotEmpty(Iterable<?> iterable) { + assertNotEmpty(null, iterable); + } + + /** + * Asserts that {@code map} is not empty. + */ + public static void assertNotEmpty(String message, Map<?,?> map) { + if (map.isEmpty()) { + failEmpty(message); + } + } + + /** + * Variant of {@link #assertNotEmpty(String, Map)} using a generic + * message. + */ + public static void assertNotEmpty(Map<?,?> map) { + assertNotEmpty(null, map); + } + + /** + * Utility for testing equals() and hashCode() results at once. + * Tests that lhs.equals(rhs) matches expectedResult, as well as + * rhs.equals(lhs). Also tests that hashCode() return values are + * equal if expectedResult is true. (hashCode() is not tested if + * expectedResult is false, as unequal objects can have equal hashCodes.) + * + * @param lhs An Object for which equals() and hashCode() are to be tested. + * @param rhs As lhs. + * @param expectedResult True if the objects should compare equal, + * false if not. + */ + public static void checkEqualsAndHashCodeMethods( + String message, Object lhs, Object rhs, boolean expectedResult) { + + if ((lhs == null) && (rhs == null)) { + Assert.assertTrue( + "Your check is dubious...why would you expect null != null?", + expectedResult); + return; + } + + if ((lhs == null) || (rhs == null)) { + Assert.assertFalse( + "Your check is dubious...why would you expect an object " + + "to be equal to null?", expectedResult); + } + + if (lhs != null) { + Assert.assertEquals(message, expectedResult, lhs.equals(rhs)); + } + if (rhs != null) { + Assert.assertEquals(message, expectedResult, rhs.equals(lhs)); + } + + if (expectedResult) { + String hashMessage = + "hashCode() values for equal objects should be the same"; + if (message != null) { + hashMessage += ": " + message; + } + Assert.assertTrue(hashMessage, lhs.hashCode() == rhs.hashCode()); + } + } + + /** + * Variant of + * checkEqualsAndHashCodeMethods(String,Object,Object,boolean...)} + * using a generic message. + */ + public static void checkEqualsAndHashCodeMethods(Object lhs, Object rhs, + boolean expectedResult) { + checkEqualsAndHashCodeMethods((String) null, lhs, rhs, expectedResult); + } + + private static Matcher getMatcher(String expectedRegex, String actual) { + Pattern pattern = Pattern.compile(expectedRegex); + return pattern.matcher(actual); + } + + private static void failEqual(String message, Object unexpected) { + failWithMessage(message, "expected not to be:<" + unexpected + ">"); + } + + private static void failWrongLength( + String message, int expected, int actual) { + failWithMessage(message, "expected array length:<" + expected + + "> but was:<" + actual + '>'); + } + + private static void failWrongElement( + String message, int index, Object expected, Object actual) { + failWithMessage(message, "expected array element[" + index + "]:<" + + expected + "> but was:<" + actual + '>'); + } + + private static void failNotMatches( + String message, String expectedRegex, String actual) { + String actualDesc = (actual == null) ? "null" : ('<' + actual + '>'); + failWithMessage(message, "expected to match regex:<" + expectedRegex + + "> but was:" + actualDesc); + } + + private static void failNotContains( + String message, String expectedRegex, String actual) { + String actualDesc = (actual == null) ? "null" : ('<' + actual + '>'); + failWithMessage(message, "expected to contain regex:<" + expectedRegex + + "> but was:" + actualDesc); + } + + private static void failMatch( + String message, String expectedRegex, String actual) { + failWithMessage(message, "expected not to match regex:<" + expectedRegex + + "> but was:<" + actual + '>'); + } + + private static void failContains( + String message, String expectedRegex, String actual) { + failWithMessage(message, "expected not to contain regex:<" + expectedRegex + + "> but was:<" + actual + '>'); + } + + private static void failNotEmpty( + String message, String actual) { + failWithMessage(message, "expected to be empty, but contained: <" + + actual + ">"); + } + + private static void failEmpty(String message) { + failWithMessage(message, "expected not to be empty, but was"); + } + + private static void failWithMessage(String userMessage, String ourMessage) { + Assert.fail((userMessage == null) + ? ourMessage + : userMessage + ' ' + ourMessage); + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + +} diff --git a/test-runner/android/test/PackageInfoSources.java b/test-runner/android/test/PackageInfoSources.java new file mode 100644 index 0000000..ef37449 --- /dev/null +++ b/test-runner/android/test/PackageInfoSources.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +/** + * {@hide} Not needed for SDK. + */ +public class PackageInfoSources { + + private static ClassPathPackageInfoSource classPathSource; + + private PackageInfoSources() { + } + + public static ClassPathPackageInfoSource forClassPath(ClassLoader classLoader) { + if (classPathSource == null) { + classPathSource = new ClassPathPackageInfoSource(); + classPathSource.setClassLoader(classLoader); + } + return classPathSource; + } + +} diff --git a/test-runner/android/test/PerformanceTestBase.java b/test-runner/android/test/PerformanceTestBase.java new file mode 100644 index 0000000..93ac90c --- /dev/null +++ b/test-runner/android/test/PerformanceTestBase.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.test.PerformanceTestCase; +import junit.framework.TestCase; + +/** + * {@hide} Not needed for SDK. + */ +public abstract class PerformanceTestBase extends TestCase implements PerformanceTestCase { + + public int startPerformance(PerformanceTestCase.Intermediates intermediates) { + return 0; + } + + public boolean isPerformanceOnly() { + return true; + } + + /* + * Temporary hack to get some things working again. + */ + public void testRun() { + throw new RuntimeException("test implementation not provided"); + } +} + diff --git a/test-runner/android/test/ProviderTestCase.java b/test-runner/android/test/ProviderTestCase.java new file mode 100644 index 0000000..5882387 --- /dev/null +++ b/test-runner/android/test/ProviderTestCase.java @@ -0,0 +1,85 @@ +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}. + */ +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 mockContentResolver = new MockContentResolver(); + RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( + new MockContext(), // The context that most methods are delegated to + targetContext, // The context that file methods are delegated to + filenamePrefix); + Context context = new IsolatedContext( + mockContentResolver, targetContextWrapper); + DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql); + + T provider = providerClass.newInstance(); + provider.attachInfo(context, null); + MockContentResolver resolver = new MockContentResolver(); + resolver.addProvider(authority, provider); + + return resolver; + } +} diff --git a/test-runner/android/test/RenamingDelegatingContext.java b/test-runner/android/test/RenamingDelegatingContext.java new file mode 100644 index 0000000..3f64340 --- /dev/null +++ b/test-runner/android/test/RenamingDelegatingContext.java @@ -0,0 +1,198 @@ +package android.test; + +import com.google.android.collect.Sets; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.ContentProvider; +import android.database.sqlite.SQLiteDatabase; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.Set; + +/** + * This is a class which delegates to the given context, but performs database + * and file operations with a renamed database/file name (prefixes default + * names with a given prefix). + */ +public class RenamingDelegatingContext extends ContextWrapper { + + private Context mFileContext; + private String mFilePrefix = null; + + private Set<String> mDatabaseNames = Sets.newHashSet(); + private Set<String> mFileNames = Sets.newHashSet(); + + public static <T extends ContentProvider> T providerWithRenamedContext( + Class<T> contentProvider, Context c, String filePrefix) + throws IllegalAccessException, InstantiationException { + return providerWithRenamedContext(contentProvider, c, filePrefix, false); + } + + public static <T extends ContentProvider> T providerWithRenamedContext( + Class<T> contentProvider, Context c, String filePrefix, + boolean allowAccessToExistingFilesAndDbs) + throws IllegalAccessException, InstantiationException { + Class<T> mProviderClass = contentProvider; + T mProvider = mProviderClass.newInstance(); + RenamingDelegatingContext mContext = new RenamingDelegatingContext(c, filePrefix); + if (allowAccessToExistingFilesAndDbs) { + mContext.makeExistingFilesAndDbsAccessible(); + } + mProvider.attachInfo(mContext, null); + return mProvider; + } + + /** + * Makes accessible all files and databases whose names match the filePrefix that was passed to + * the constructor. Normally only files and databases that were created through this context are + * accessible. + */ + public void makeExistingFilesAndDbsAccessible() { + String[] databaseList = mFileContext.databaseList(); + for (String diskName : databaseList) { + if (shouldDiskNameBeVisible(diskName)) { + mDatabaseNames.add(publicNameFromDiskName(diskName)); + } + } + String[] fileList = mFileContext.fileList(); + for (String diskName : fileList) { + if (shouldDiskNameBeVisible(diskName)) { + mFileNames.add(publicNameFromDiskName(diskName)); + } + } + } + + /** + * Returns if the given diskName starts with the given prefix or not. + * @param diskName name of the database/file. + */ + boolean shouldDiskNameBeVisible(String diskName) { + return diskName.startsWith(mFilePrefix); + } + + /** + * Returns the public name (everything following the prefix) of the given diskName. + * @param diskName name of the database/file. + */ + String publicNameFromDiskName(String diskName) { + if (!shouldDiskNameBeVisible(diskName)) { + throw new IllegalArgumentException("disk file should not be visible: " + diskName); + } + return diskName.substring(mFilePrefix.length(), diskName.length()); + } + + /** + * @param context : the context that will be delagated. + * @param filePrefix : a prefix with which database and file names will be + * prefixed. + */ + public RenamingDelegatingContext(Context context, String filePrefix) { + super(context); + mFileContext = context; + mFilePrefix = filePrefix; + } + + /** + * @param context : the context that will be delagated. + * @param fileContext : the context that file and db methods will be delgated to + * @param filePrefix : a prefix with which database and file names will be + * prefixed. + */ + public RenamingDelegatingContext(Context context, Context fileContext, String filePrefix) { + super(context); + mFileContext = fileContext; + mFilePrefix = filePrefix; + } + + public String getDatabasePrefix() { + return mFilePrefix; + } + + private String renamedFileName(String name) { + return mFilePrefix + name; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, + int mode, SQLiteDatabase.CursorFactory factory) { + final String internalName = renamedFileName(name); + if (!mDatabaseNames.contains(name)) { + mDatabaseNames.add(name); + mFileContext.deleteDatabase(internalName); + } + return mFileContext.openOrCreateDatabase(internalName, mode, factory); + } + + @Override + public boolean deleteDatabase(String name) { + if (mDatabaseNames.contains(name)) { + mDatabaseNames.remove(name); + return mFileContext.deleteDatabase(renamedFileName(name)); + } else { + return false; + } + } + + @Override + public String[] databaseList() { + return mDatabaseNames.toArray(new String[]{}); + } + + @Override + public FileInputStream openFileInput(String name) + throws FileNotFoundException { + final String internalName = renamedFileName(name); + if (mFileNames.contains(name)) { + return mFileContext.openFileInput(internalName); + } else { + throw new FileNotFoundException(internalName); + } + } + + @Override + public FileOutputStream openFileOutput(String name, int mode) + throws FileNotFoundException { + mFileNames.add(name); + return mFileContext.openFileOutput(renamedFileName(name), mode); + } + + @Override + public File getFileStreamPath(String name) { + return mFileContext.getFileStreamPath(renamedFileName(name)); + } + + @Override + public boolean deleteFile(String name) { + if (mFileNames.contains(name)) { + mFileNames.remove(name); + return mFileContext.deleteFile(renamedFileName(name)); + } else { + return false; + } + } + + @Override + public String[] fileList() { + return mFileNames.toArray(new String[]{}); + } + +// /** +// * Given an array of files returns only those whose names indicate that they belong to this +// * context. +// * @param allFiles the original list of files +// * @return the pruned list of files +// */ +// private String[] prunedFileList(String[] allFiles) { +// List<String> files = Lists.newArrayList(); +// for (String file : allFiles) { +// if (file.startsWith(mFilePrefix)) { +// files.add(file); +// } +// } +// return files.toArray(new String[]{}); +// } +}
\ No newline at end of file diff --git a/test-runner/android/test/ServiceLocator.java b/test-runner/android/test/ServiceLocator.java new file mode 100644 index 0000000..3324008 --- /dev/null +++ b/test-runner/android/test/ServiceLocator.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public class ServiceLocator { + + private static TestBrowserController mTestBrowserController = + new TestBrowserControllerImpl(); + + public static TestBrowserController getTestBrowserController() { + return mTestBrowserController; + } + + static void setTestBrowserController(TestBrowserController testBrowserController) { + mTestBrowserController = testBrowserController; + } +} diff --git a/test-runner/android/test/ServiceTestCase.java b/test-runner/android/test/ServiceTestCase.java new file mode 100644 index 0000000..c53bf7d --- /dev/null +++ b/test-runner/android/test/ServiceTestCase.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Application; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.test.mock.MockApplication; + +import java.lang.reflect.Field; +import java.util.Random; + +/** + * This test case provides a framework in which you can test Service classes in + * a controlled environment. It provides basic support for the lifecycle of a + * Service, and hooks by which you can inject various dependencies and control + * the environment in which your Service is tested. + * + * <p><b>Lifecycle Support.</b> + * Every Service is designed to be accessed within a specific sequence of + * calls. <insert link to Service lifecycle doc here>. + * In order to support the lifecycle of a Service, this test case will make the + * following calls at the following times. + * + * <ul><li>The test case will not call onCreate() until your test calls + * {@link #startService} or {@link #bindService}. This gives you a chance + * to set up or adjust any additional framework or test logic before + * onCreate().</li> + * <li>When your test calls {@link #startService} or {@link #bindService} + * the test case will call onCreate(), and then call the corresponding entry point in your service. + * It will record any parameters or other support values necessary to support the lifecycle.</li> + * <li>After your test completes, the test case {@link #tearDown} function is + * automatically called, and it will stop & 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}reference/aidl.html">described using + * aidl</a>. + * + * Note: In order to test with this interface, your service must implement a getService() + * method, as shown in samples.ApiDemos.app.LocalService. + + * @param intent The Intent as if supplied to {@link android.content.Context#bindService}. + * + * @return Return an IBinder for making further calls into the Service. + */ + protected IBinder bindService(Intent intent) { + assertFalse(mServiceStarted); + assertFalse(mServiceBound); + + if (!mServiceAttached) { + setupService(); + } + assertNotNull(mService); + + if (!mServiceCreated) { + mService.onCreate(); + mServiceCreated = true; + } + // no extras are expected by unbind + mServiceIntent = intent.cloneFilter(); + IBinder result = mService.onBind(intent); + + mServiceBound = true; + return result; + } + + /** + * This will make the necessary calls to stop (or unbind) the Service under test, and + * call onDestroy(). Ordinarily this will be called automatically (by {@link #tearDown}, but + * you can call it directly from your test in order to check for proper shutdown behaviors. + */ + protected void shutdownService() { + if (mServiceStarted) { + mService.stopSelf(); + mServiceStarted = false; + } else if (mServiceBound) { + mService.onUnbind(mServiceIntent); + mServiceBound = false; + } + if (mServiceCreated) { + mService.onDestroy(); + } + } + + /** + * Shuts down the Service under test. Also makes sure all resources are cleaned up and + * garbage collected before moving on to the next + * test. Subclasses that override this method should make sure they call super.tearDown() + * at the end of the overriding method. + * + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + shutdownService(); + mService = null; + + // Scrub out members - protects against memory leaks in the case where someone + // creates a non-static inner class (thus referencing the test case) and gives it to + // someone else to hold onto + scrubClass(ServiceTestCase.class); + + super.tearDown(); + } + + /** + * Set the application for use during the test. If your test does not call this function, + * a new {@link android.test.mock.MockApplication MockApplication} object will be generated. + * + * @param application The Application object that will be injected into the Service under test. + */ + public void setApplication(Application application) { + mApplication = application; + } + + /** + * Return the Application object being used by the Service under test. + * + * @return Returns the application object. + * + * @see #setApplication + */ + public Application getApplication() { + return mApplication; + } + + /** + * Return a real (not mocked or instrumented) system Context that can be used when generating + * Mock or other Context objects for your Service under test. + * + * @return Returns a reference to a normal Context. + */ + public Context getSystemContext() { + return mSystemContext; + } + + public void testServiceTestCaseSetUpProperly() throws Exception { + setupService(); + assertNotNull("service should be launched successfully", mService); + } +} diff --git a/test-runner/android/test/SimpleCache.java b/test-runner/android/test/SimpleCache.java new file mode 100644 index 0000000..44424ec --- /dev/null +++ b/test-runner/android/test/SimpleCache.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import java.util.HashMap; +import java.util.Map; + +abstract class SimpleCache<K, V> { + private Map<K, V> map = new HashMap<K, V>(); + + protected abstract V load(K key); + + final V get(K key) { + if (map.containsKey(key)) { + return map.get(key); + } + V value = load(key); + map.put(key, value); + return value; + } +} diff --git a/test-runner/android/test/SingleLaunchActivityTestCase.java b/test-runner/android/test/SingleLaunchActivityTestCase.java new file mode 100644 index 0000000..8d43b73 --- /dev/null +++ b/test-runner/android/test/SingleLaunchActivityTestCase.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Activity; +import android.view.IWindowManager; +import android.os.ServiceManager; + +/** + * If you would like to test a single activity with an + * {@link android.test.InstrumentationTestCase}, this provides some of the boiler plate to + * launch and finish the activity in {@link #setUp} and {@link #tearDown}. + * + * This launches the activity only once for the entire class instead of doing it + * in every setup / teardown call. + */ +public abstract class SingleLaunchActivityTestCase<T extends Activity> + extends InstrumentationTestCase { + + String mPackage; + Class<T> mActivityClass; + private static int sTestCaseCounter = 0; + private static boolean sActivityLaunchedFlag = false; + + /** + * @param pkg The package of the instrumentation. + * @param activityClass The activity to test. + */ + public SingleLaunchActivityTestCase(String pkg, Class<T> activityClass) { + mPackage = pkg; + mActivityClass = activityClass; + sTestCaseCounter ++; + } + + /** + * The activity that will be set up for use in each test method. + */ + private static Activity sActivity; + + public T getActivity() { + return (T) sActivity; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + // If it is the first test case, launch the activity. + if (!sActivityLaunchedFlag) { + // by default, not in touch mode + getInstrumentation().setInTouchMode(false); + sActivity = launchActivity(mPackage, mActivityClass, null); + sActivityLaunchedFlag = true; + } + } + + @Override + protected void tearDown() throws Exception { + // If it is the last test case, call finish on the activity. + sTestCaseCounter --; + if (sTestCaseCounter == 1) { + sActivity.finish(); + } + super.tearDown(); + } + + public void testActivityTestCaseSetUpProperly() throws Exception { + assertNotNull("activity should be launched successfully", sActivity); + } +} diff --git a/test-runner/android/test/SyncBaseInstrumentation.java b/test-runner/android/test/SyncBaseInstrumentation.java new file mode 100644 index 0000000..c1d2507 --- /dev/null +++ b/test-runner/android/test/SyncBaseInstrumentation.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContentValues; +import android.os.Bundle; +import android.os.SystemClock; +import android.provider.Sync; +import android.net.Uri; +import java.util.Map; + +/** + * If you would like to test sync a single provider with an + * {@link InstrumentationTestCase}, this provides some of the boiler plate in {@link #setUp} and + * {@link #tearDown}. + */ +public class SyncBaseInstrumentation extends InstrumentationTestCase { + private Context mTargetContext; + ContentResolver mContentResolver; + private static final int MAX_TIME_FOR_SYNC_IN_MINS = 20; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mTargetContext = getInstrumentation().getTargetContext(); + mContentResolver = mTargetContext.getContentResolver(); + } + + /** + * Syncs the specified provider. + * @throws Exception + */ + protected void syncProvider(Uri uri, String account, String authority) throws Exception { + Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true); + extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, account); + + mContentResolver.startSync(uri, extras); + long startTimeInMillis = SystemClock.elapsedRealtime(); + long endTimeInMillis = startTimeInMillis + MAX_TIME_FOR_SYNC_IN_MINS * 60000; + + int counter = 0; + // Making sure race condition does not occur when en entry have been removed from pending + // and active tables and loaded in memory (therefore sync might be still in progress) + while (counter < 2) { + // Sleep for 1 second. + Thread.sleep(1000); + // Finish test if time to sync has exceeded max time. + if (SystemClock.elapsedRealtime() > endTimeInMillis) { + break; + } + + if (isSyncActive(account, authority)) { + counter = 0; + continue; + } + counter++; + } + } + + protected void cancelSyncsandDisableAutoSync() { + Sync.Settings.QueryMap mSyncSettings = + new Sync.Settings.QueryMap(mContentResolver, true, null); + mSyncSettings.setListenForNetworkTickles(false); + mContentResolver.cancelSync(null); + mSyncSettings.close(); + } + + /** + * This method tests if any sync is active or not. Sync is considered to be active if the + * entry is in either the Pending or Active tables. + * @return + */ + private boolean isSyncActive(String account, String authority) { + Sync.Pending.QueryMap pendingQueryMap = null; + Sync.Active.QueryMap activeQueryMap = null; + try { + pendingQueryMap = new Sync.Pending.QueryMap(mContentResolver, false, null); + activeQueryMap = new Sync.Active.QueryMap(mContentResolver, false, null); + + if (pendingQueryMap.isPending(account, authority)) { + return true; + } + if (isActiveInActiveQueryMap(activeQueryMap, account, authority)) { + return true; + } + return false; + } finally { + activeQueryMap.close(); + pendingQueryMap.close(); + } + } + + private boolean isActiveInActiveQueryMap(Sync.Active.QueryMap activemap, String account, + String authority) { + Map<String, ContentValues> rows = activemap.getRows(); + for (ContentValues values : rows.values()) { + if (values.getAsString("account").equals(account) + && values.getAsString("authority").equals(authority)) { + return true; + } + } + return false; + } +} diff --git a/test-runner/android/test/TestBrowserActivity.java b/test-runner/android/test/TestBrowserActivity.java new file mode 100644 index 0000000..ea5f91e --- /dev/null +++ b/test-runner/android/test/TestBrowserActivity.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import com.android.internal.R; + +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import junit.framework.Test; +import junit.framework.TestSuite; + +import java.util.List; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public abstract class TestBrowserActivity extends ListActivity + implements android.test.TestBrowserView, AdapterView.OnItemClickListener, + TestSuiteProvider { + + private TestBrowserController mTestBrowserController; + public static final String BUNDLE_EXTRA_PACKAGE = "package"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getListView().setOnItemClickListener(this); + + mTestBrowserController = ServiceLocator.getTestBrowserController(); + mTestBrowserController.setTargetPackageName(getPackageName()); + mTestBrowserController.registerView(this); + mTestBrowserController.setTargetBrowserActivityClassName(this.getClass().getName()); + + // Apk paths used to search for test classes when using TestSuiteBuilders. + String[] apkPaths = {getPackageCodePath()}; + ClassPathPackageInfoSource.setApkPaths(apkPaths); + } + + @Override + protected void onStart() { + super.onStart(); + TestSuite testSuite = getTestSuiteToBrowse(); + mTestBrowserController.setTestSuite(testSuite); + + String name = testSuite.getName(); + if (name != null) { + setTitle(name.substring(name.lastIndexOf(".") + 1)); + } + } + + /** + * Subclasses will override this method and return the TestSuite specific to their .apk. + * When this method is invoked due to an intent fired from + * {@link #onItemClick(android.widget.AdapterView, android.view.View, int, long)} then get the + * targeted TestSuite from the intent. + * + * @return testSuite to browse + */ + @SuppressWarnings("unchecked") + private TestSuite getTestSuiteToBrowse() { + Intent intent = getIntent(); + if (Intent.ACTION_RUN.equals(intent.getAction())) { + String testClassName = intent.getData().toString(); + + try { + Class<Test> testClass = (Class<Test>) getClassLoader().loadClass(testClassName); + return TestCaseUtil.createTestSuite(testClass); + } catch (ClassNotFoundException e) { + Log.e("TestBrowserActivity", "ClassNotFoundException for " + testClassName, e); + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + Log.e("TestBrowserActivity", "IllegalAccessException for " + testClassName, e); + throw new RuntimeException(e); + } catch (InstantiationException e) { + Log.e("TestBrowserActivity", "InstantiationException for " + testClassName, e); + throw new RuntimeException(e); + } + } else { + // get test classes to browwes from subclass + return getTopTestSuite(); + } + + } + + public TestSuite getTestSuite() { + return getTopTestSuite(); + } + + /** + * @return A TestSuite that should be run for a given application. + */ + public abstract TestSuite getTopTestSuite(); + + public void onItemClick(AdapterView parent, View v, int position, long id) { + Intent intent = mTestBrowserController.getIntentForTestAt(position); + intent.putExtra(BUNDLE_EXTRA_PACKAGE, getPackageName()); + startActivity(intent); + } + + public void setTestNames(List<String> testNames) { + ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this, + R.layout.test_list_item, testNames); + setListAdapter(arrayAdapter); + } +} + diff --git a/test-runner/android/test/TestBrowserController.java b/test-runner/android/test/TestBrowserController.java new file mode 100644 index 0000000..044e39f --- /dev/null +++ b/test-runner/android/test/TestBrowserController.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.content.Intent; +import junit.framework.TestSuite; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public interface TestBrowserController { + String BUNDLE_EXTRA_TEST_METHOD_NAME = "testMethodName"; + + Intent getIntentForTestAt(int position); + + void setTestSuite(TestSuite testSuite); + + void registerView(TestBrowserView testBrowserView); + + void setTargetBrowserActivityClassName(String targetBrowserActivityClassName); + + void setTargetPackageName(String targetPackageName); +} diff --git a/test-runner/android/test/TestBrowserControllerImpl.java b/test-runner/android/test/TestBrowserControllerImpl.java new file mode 100644 index 0000000..b8f8975 --- /dev/null +++ b/test-runner/android/test/TestBrowserControllerImpl.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.content.Intent; +import android.net.Uri; +import com.google.android.collect.Lists; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.util.List; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public class TestBrowserControllerImpl implements TestBrowserController { + + static final String TEST_RUNNER_ACTIVITY_CLASS_NAME = + "com.android.testharness.TestRunnerActivity"; + + private TestSuite mTestSuite; + private TestBrowserView mTestBrowserView; + private static final int RUN_ALL_INDEX = 0; + private String mTargetBrowserActivityClassName; + private String mTargetPackageName; + + public void setTargetPackageName(String targetPackageName) { + mTargetPackageName = targetPackageName; + } + + public Intent getIntentForTestAt(int position) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_RUN); + // We must add the following two flags to make sure that we create a new activity when + // we browse nested test suites. + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + + String packageName = getDefaultPackageNameForTestRunner(); + String className = ""; + String testName = ""; + if (shouldAllTestsBeRun(position)) { + testName = mTestSuite.getName(); + className = TEST_RUNNER_ACTIVITY_CLASS_NAME; + } else { + Test test = TestCaseUtil.getTestAtIndex(mTestSuite, position - 1); + if (TestSuite.class.isAssignableFrom(test.getClass())) { + TestSuite testSuite = (TestSuite) test; + testName = testSuite.getName(); + className = mTargetBrowserActivityClassName; + packageName = mTargetPackageName; + } else if (TestCase.class.isAssignableFrom(test.getClass())) { + TestCase testCase = (TestCase) test; + testName = testCase.getClass().getName(); + className = TEST_RUNNER_ACTIVITY_CLASS_NAME; + String testMethodName = testCase.getName(); + intent.putExtra(BUNDLE_EXTRA_TEST_METHOD_NAME, testMethodName); + } + } + + intent.setClassName(packageName, className); + intent.setData(Uri.parse(testName)); + + return intent; + } + + private String getDefaultPackageNameForTestRunner() { + return TEST_RUNNER_ACTIVITY_CLASS_NAME.substring(0, + TEST_RUNNER_ACTIVITY_CLASS_NAME.lastIndexOf(".")); + } + + private boolean shouldAllTestsBeRun(int position) { + return position == RUN_ALL_INDEX; + } + + public void setTestSuite(TestSuite testSuite) { + mTestSuite = testSuite; + + List<String> testCaseNames = Lists.newArrayList(); + testCaseNames.add("Run All"); + testCaseNames.addAll(TestCaseUtil.getTestCaseNames(testSuite, false)); + + mTestBrowserView.setTestNames(testCaseNames); + } + + public void registerView(TestBrowserView testBrowserView) { + mTestBrowserView = testBrowserView; + } + + + public void setTargetBrowserActivityClassName(String targetBrowserActivityClassName) { + mTargetBrowserActivityClassName = targetBrowserActivityClassName; + } +} diff --git a/test-runner/android/test/TestBrowserView.java b/test-runner/android/test/TestBrowserView.java new file mode 100644 index 0000000..4799f19 --- /dev/null +++ b/test-runner/android/test/TestBrowserView.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import java.util.List; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public interface TestBrowserView { + void setTestNames(List<String> testNames); +} diff --git a/test-runner/android/test/TestCase.java b/test-runner/android/test/TestCase.java new file mode 100644 index 0000000..5432ce8 --- /dev/null +++ b/test-runner/android/test/TestCase.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.content.Context; + +/** + * {@hide} + * More complex interface for test cases. + * + * <p>Just implementing Runnable is enough for many test cases. If you + * have additional setup or teardown, this interface might be for you, + * especially if you need to share it between different test cases, or your + * teardown code must execute regardless of whether your test passed. + * + * <p>See the android.test package documentation (click the more... link) + * for a full description + */ + +@Deprecated +public interface TestCase extends Runnable +{ + /** + * Called before run() is called. + */ + public void setUp(Context context); + + /** + * Called after run() is called, even if run() threw an exception, but + * not if setUp() threw an execption. + */ + public void tearDown(); +} + diff --git a/test-runner/android/test/TestCaseUtil.java b/test-runner/android/test/TestCaseUtil.java new file mode 100644 index 0000000..4109d9c --- /dev/null +++ b/test-runner/android/test/TestCaseUtil.java @@ -0,0 +1,143 @@ +/* + * 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.List; + +/** + * @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) { + List<Test> testCases = Lists.newArrayList(); + if (test != null) { + + Test workingTest = invokeSuiteMethodIfPossible(test.getClass()); + 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)); + } else { + testCases.add(childTest); + } + } + } else { + testCases.add(workingTest); + } + } + return testCases; + } + + private static Test invokeSuiteMethodIfPossible(Class testClass) { + try { + Method suiteMethod = testClass.getMethod( + BaseTestRunner.SUITE_METHODNAME, new Class[0]); + if (Modifier.isStatic(suiteMethod.getModifiers())) { + 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); + if (test == null) { + return new TestSuite(testClass); + + } else if (TestCase.class.isAssignableFrom(test.getClass())) { + TestSuite testSuite = new TestSuite(test.getClass().getName()); + testSuite.addTest(test); + return testSuite; + } + + return (TestSuite) test; + } +} diff --git a/test-runner/android/test/TestListActivity.java b/test-runner/android/test/TestListActivity.java new file mode 100644 index 0000000..a076a70 --- /dev/null +++ b/test-runner/android/test/TestListActivity.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.ListActivity; +import android.content.Intent; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CursorAdapter; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Activity base class to use to implement your application's tests. + * + * <p>Implement the getTestSuite() method to return the name of your + * test suite class. + * + * <p>See the android.test package documentation (click the more... link) + * for a full description + * + * {@hide} Not needed for SDK + */ +public abstract class TestListActivity extends ListActivity { + /** Supplied in the intent extras if we are running performance tests. */ + public static final String PERFORMANCE_TESTS = "android.test.performance"; + + /** "Mode" group in the menu. */ + static final int MODE_GROUP = Menu.FIRST; + + /** Our suite */ + String mSuite; + + /** Our children tests */ + String[] mTests; + + /** which mode, REGRESSION, PERFORMANCE or PROFILING */ + private int mMode = TestRunner.REGRESSION; + + /** "Regression" menu item */ + private MenuItem mRegressionItem; + + /** "Performance" menu item */ + private MenuItem mPerformanceItem; + + /** "Profiling" menu item */ + private MenuItem mProfilingItem; + + private final Comparator<String> sComparator = new Comparator<String>() { + public final int compare(String a, String b) { + String s1 = makeCompareName(a); + String s2 = makeCompareName(b); + + return s1.compareToIgnoreCase(s2); + } + }; + + /** + * Constructor that doesn't do much. + */ + public TestListActivity() { + super(); + } + + /** + * Subclasses should implement this to return the names of the classes + * of their tests. + * + * @return test suite class name + */ + public abstract String getTestSuite(); + + /** + * Typical onCreate(Bundle icicle) implementation. + */ + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + Intent intent = getIntent(); + + mMode = intent.getIntExtra(TestListActivity.PERFORMANCE_TESTS, mMode); + + + if (intent.getAction().equals(Intent.ACTION_MAIN)) { + // if we were called as MAIN, get the test suites, + mSuite = getTestSuite(); + } else if (intent.getAction().equals(Intent.ACTION_RUN)) { + // We should have been provided a status channel. Bail out and + // run the test instead. This is how the TestHarness gets us + // loaded in our process for "Run All Tests." + Intent ntent = new Intent(Intent.ACTION_RUN, + intent.getData() != null + ? intent.getData() + : Uri.parse(getTestSuite())); + ntent.setClassName("com.android.testharness", + "com.android.testharness.RunTest"); + ntent.putExtras(intent); + ntent.putExtra("package", getPackageName()); + startActivity(ntent); + finish(); + return; + } else if (intent.getAction().equals(Intent.ACTION_VIEW)) { + // otherwise use the one in the intent + mSuite = intent.getData() != null ? intent.getData().toString() + : null; + } + + String[] children = TestRunner.getChildren(this, mSuite); + + Arrays.sort(children, sComparator); + + int len = children.length; + mTests = new String[len]; + System.arraycopy(children, 0, mTests, 0, len); + + setTitle(TestRunner.getTitle(mSuite)); + + MatrixCursor cursor = new MatrixCursor(new String[] { "name", "_id" }); + addTestRows(cursor); + + CursorAdapter adapter = new SimpleCursorAdapter( + this, + com.android.internal.R.layout.simple_list_item_1, + cursor, + new String[] {"name"}, + new int[] {com.android.internal.R.id.text1}); + + setListAdapter(adapter); + } + + private void addTestRows(MatrixCursor cursor) { + int id = 0; + cursor.newRow().add("Run All").add(id++); + for (String test : mTests) { + String title = TestRunner.getTitle(test); + String prefix = TestRunner.isTestSuite(this, test) + ? "Browse " : "Run "; + + // I'd rather do this with an icon column, but I don't know how + cursor.newRow().add(prefix + title).add(id++); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + mRegressionItem = menu.add(MODE_GROUP, -1, 0, "Regression Mode"); + mPerformanceItem = menu.add(MODE_GROUP, -1, 0, "Performance Mode"); + mProfilingItem = menu.add(MODE_GROUP, -1, 0, "Profiling Mode"); + menu.setGroupCheckable(MODE_GROUP, true, true); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item == mRegressionItem) { + mMode = TestRunner.REGRESSION; + } else if (item == mPerformanceItem) { + mMode = TestRunner.PERFORMANCE; + } else if (item == mProfilingItem) { + mMode = TestRunner.PROFILING; + } + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + switch (mMode) { + case TestRunner.REGRESSION: + mRegressionItem.setChecked(true); + break; + + case TestRunner.PERFORMANCE: + mPerformanceItem.setChecked(true); + break; + + case TestRunner.PROFILING: + mProfilingItem.setChecked(true); + break; + } + return true; + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = new Intent(); + + if (position == 0) { + if (false) { + intent.setClassName("com.android.testharness", + "com.android.testharness.RunAll"); + intent.putExtra("tests", new String[]{mSuite}); + } else { + intent.setClassName("com.android.testharness", + "com.android.testharness.RunTest"); + intent.setAction(Intent.ACTION_RUN); + intent.setData(Uri.parse(mSuite)); + } + } else { + String test = mTests[position - 1]; + if (TestRunner.isTestSuite(this, test)) { + intent.setClassName(getPackageName(), this.getClass().getName()); + intent.setAction(Intent.ACTION_VIEW); + } else { + intent.setClassName("com.android.testharness", + "com.android.testharness.RunTest"); + } + intent.setData(Uri.parse(test)); + } + + intent.putExtra(PERFORMANCE_TESTS, mMode); + intent.putExtra("package", getPackageName()); + startActivity(intent); + } + + private String makeCompareName(String s) { + int index = s.lastIndexOf('.'); + + if (index == -1) { + return s; + } + + return s.substring(index + 1); + } +} diff --git a/test-runner/android/test/TestLocationProvider.java b/test-runner/android/test/TestLocationProvider.java new file mode 100644 index 0000000..00c1ce8 --- /dev/null +++ b/test-runner/android/test/TestLocationProvider.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + + +import android.location.Criteria; +import android.location.Location; +import android.location.LocationProviderImpl; +import android.os.Bundle; +import android.os.SystemClock; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public class TestLocationProvider extends LocationProviderImpl { + + public static final String PROVIDER_NAME = "test"; + public static final double LAT = 0; + public static final double LON = 1; + public static final double ALTITUDE = 10000; + public static final float SPEED = 10; + public static final float BEARING = 1; + public static final int STATUS = AVAILABLE; + + private Location mLocation; + private boolean mEnabled; + + public TestLocationProvider() { + super(PROVIDER_NAME); + mLocation = new Location(PROVIDER_NAME); + updateLocation(); + } + + //LocationProvider methods + + @Override + public int getAccuracy() { + return Criteria.ACCURACY_COARSE; + } + + @Override + public int getPowerRequirement() { + return Criteria.NO_REQUIREMENT; + } + + @Override + public boolean hasMonetaryCost() { + return false; + } + + @Override + public boolean requiresCell() { + return false; + } + + @Override + public boolean requiresNetwork() { + return false; + } + + @Override + public boolean requiresSatellite() { + return false; + } + + @Override + public boolean supportsAltitude() { + return true; + } + + @Override + public boolean supportsBearing() { + return true; + } + + @Override + public boolean supportsSpeed() { + return true; + } + + //LocationProviderImpl methods + @Override + public void disable() { + mEnabled = false; + } + + @Override + public void enable() { + mEnabled = true; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public boolean getLocation(Location l) { + updateLocation(); + l.set(mLocation); + return true; + } + + @Override + public int getStatus(Bundle extras) { + return STATUS; + } + + private void updateLocation() { + long time = SystemClock.uptimeMillis(); + long multiplier = (time/5000)%500000; + mLocation.setLatitude(LAT*multiplier); + mLocation.setLongitude(LON*multiplier); + mLocation.setAltitude(ALTITUDE); + mLocation.setSpeed(SPEED); + mLocation.setBearing(BEARING*multiplier); + + Bundle extras = new Bundle(); + extras.putInt("extraTest", 24); + mLocation.setExtras(extras); + mLocation.setTime(time); + } + +} diff --git a/test-runner/android/test/TestPrinter.java b/test-runner/android/test/TestPrinter.java new file mode 100644 index 0000000..37bd721 --- /dev/null +++ b/test-runner/android/test/TestPrinter.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.util.Log; +import junit.framework.Test; +import junit.framework.TestListener; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Prints the test progress to stdout. Android includes a default + * implementation and calls these methods to print out test progress; you + * probably will not need to create or extend this class or call its methods manually. + * See the full {@link android.test} package description for information about + * getting test results. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class TestPrinter implements TestRunner.Listener, TestListener { + + private String mTag; + private boolean mOnlyFailures; + private Set<String> mFailedTests = new HashSet<String>(); + + + public TestPrinter(String tag, boolean onlyFailures) { + mTag = tag; + mOnlyFailures = onlyFailures; + } + + public void started(String className) { + if (!mOnlyFailures) { + Log.i(mTag, "started: " + className); + } + } + + public void finished(String className) { + if (!mOnlyFailures) { + Log.i(mTag, "finished: " + className); + } + } + + public void performance(String className, + long itemTimeNS, int iterations, + List<TestRunner.IntermediateTime> intermediates) { + Log.i(mTag, "perf: " + className + " = " + itemTimeNS + "ns/op (done " + + iterations + " times)"); + if (intermediates != null && intermediates.size() > 0) { + int N = intermediates.size(); + for (int i = 0; i < N; i++) { + TestRunner.IntermediateTime time = intermediates.get(i); + Log.i(mTag, " intermediate: " + time.name + " = " + + time.timeInNS + "ns"); + } + } + } + + public void passed(String className) { + if (!mOnlyFailures) { + Log.i(mTag, "passed: " + className); + } + } + + public void failed(String className, Throwable exception) { + Log.i(mTag, "failed: " + className); + Log.i(mTag, "----- begin exception -----"); + Log.i(mTag, "", exception); + Log.i(mTag, "----- end exception -----"); + } + + private void failed(Test test, Throwable t) { + mFailedTests.add(test.toString()); + failed(test.toString(), t); + } + + public void addError(Test test, Throwable t) { + failed(test, t); + } + + public void addFailure(Test test, junit.framework.AssertionFailedError t) { + failed(test, t); + } + + public void endTest(Test test) { + finished(test.toString()); + if (!mFailedTests.contains(test.toString())) { + passed(test.toString()); + } + mFailedTests.remove(test.toString()); + } + + public void startTest(Test test) { + started(test.toString()); + } +} diff --git a/test-runner/android/test/TestRecorder.java b/test-runner/android/test/TestRecorder.java new file mode 100644 index 0000000..7c368a0 --- /dev/null +++ b/test-runner/android/test/TestRecorder.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.content.ContentValues; +import android.database.sqlite.SQLiteDatabase; +import android.os.Environment; +import android.os.FileUtils; +import android.test.TestRunner.IntermediateTime; +import android.util.Log; +import junit.framework.Test; +import junit.framework.TestListener; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * {@hide} Not needed for 1.0 SDK. + */ +public class TestRecorder implements TestRunner.Listener, TestListener { + private static final int DATABASE_VERSION = 1; + private static SQLiteDatabase sDb; + private Set<String> mFailedTests = new HashSet<String>(); + + private static SQLiteDatabase getDatabase() { + if (sDb == null) { + File dir = new File(Environment.getDataDirectory(), "test_results"); + + /* TODO: add a DB version number and bootstrap/upgrade methods + * if the format of the table changes. + */ + String dbName = "TestHarness.db"; + File file = new File(dir, dbName); + sDb = SQLiteDatabase.openOrCreateDatabase(file.getPath(), null); + + if (sDb.getVersion() == 0) { + int code = FileUtils.setPermissions(file.getPath(), + FileUtils.S_IRUSR | FileUtils.S_IWUSR | + FileUtils.S_IRGRP | FileUtils.S_IWGRP | + FileUtils.S_IROTH | FileUtils.S_IWOTH, -1, -1); + + if (code != 0) { + Log.w("TestRecorder", + "Set permissions for " + file.getPath() + " returned = " + code); + } + + try { + sDb.execSQL("CREATE TABLE IF NOT EXISTS tests (_id INT PRIMARY KEY," + + "name TEXT," + + "result TEXT," + + "exception TEXT," + + "started INTEGER," + + "finished INTEGER," + + "time INTEGER," + + "iterations INTEGER," + + "allocations INTEGER," + + "parent INTEGER);"); + sDb.setVersion(DATABASE_VERSION); + } catch (Exception e) { + Log.e("TestRecorder", "failed to create table 'tests'", e); + sDb = null; + } + } + } + + return sDb; + } + + public TestRecorder() { + } + + public void started(String className) { + ContentValues map = new ContentValues(2); + map.put("name", className); + map.put("started", System.currentTimeMillis()); + + // try to update the row first in case we've ran this test before. + int rowsAffected = getDatabase().update("tests", map, "name = '" + className + "'", null); + + if (rowsAffected == 0) { + getDatabase().insert("tests", null, map); + } + } + + public void finished(String className) { + ContentValues map = new ContentValues(1); + map.put("finished", System.currentTimeMillis()); + + getDatabase().update("tests", map, "name = '" + className + "'", null); + } + + public void performance(String className, long itemTimeNS, int iterations, List<IntermediateTime> intermediates) { + ContentValues map = new ContentValues(); + map.put("time", itemTimeNS); + map.put("iterations", iterations); + + getDatabase().update("tests", map, "name = '" + className + "'", null); + + if (intermediates != null && intermediates.size() > 0) { + int n = intermediates.size(); + for (int i = 0; i < n; i++) { + TestRunner.IntermediateTime time = intermediates.get(i); + + getDatabase().execSQL("INSERT INTO tests (name, time, parent) VALUES ('" + + time.name + "', " + time.timeInNS + ", " + + "(SELECT _id FROM tests WHERE name = '" + className + "'));"); + } + } + } + + public void passed(String className) { + ContentValues map = new ContentValues(); + map.put("result", "passed"); + + getDatabase().update("tests", map, "name = '" + className + "'", null); + } + + public void failed(String className, Throwable exception) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + try { + exception.printStackTrace(printWriter); + } finally { + printWriter.close(); + } + ContentValues map = new ContentValues(); + map.put("result", "failed"); + map.put("exception", stringWriter.toString()); + + getDatabase().update("tests", map, "name = '" + className + "'", null); + } + + /** + * Reports a test case failure. + * + * @param className Name of the class/test. + * @param reason Reason for failure. + */ + public void failed(String className, String reason) { + ContentValues map = new ContentValues(); + map.put("result", "failed"); + // The reason is put as the exception. + map.put("exception", reason); + getDatabase().update("tests", map, "name = '" + className + "'", null); + } + + public void addError(Test test, Throwable t) { + mFailedTests.add(test.toString()); + failed(test.toString(), t); + } + + public void addFailure(Test test, junit.framework.AssertionFailedError t) { + mFailedTests.add(test.toString()); + failed(test.toString(), t.getMessage()); + } + + public void endTest(Test test) { + finished(test.toString()); + if (!mFailedTests.contains(test.toString())) { + passed(test.toString()); + } + mFailedTests.remove(test.toString()); + } + + public void startTest(Test test) { + started(test.toString()); + } +} diff --git a/test-runner/android/test/TestRunner.java b/test-runner/android/test/TestRunner.java new file mode 100644 index 0000000..efa2480 --- /dev/null +++ b/test-runner/android/test/TestRunner.java @@ -0,0 +1,723 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.content.Context; +import android.util.Log; +import android.os.Debug; +import android.os.SystemClock; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestSuite; +import junit.framework.TestListener; +import junit.framework.Test; +import junit.framework.TestResult; +import com.google.android.collect.Lists; + +/** + * Support class that actually runs a test. Android uses this class, + * and you probably will not need to instantiate, extend, or call this + * class yourself. See the full {@link android.test} package description + * to learn more about testing Android applications. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class TestRunner implements PerformanceTestCase.Intermediates { + public static final int REGRESSION = 0; + public static final int PERFORMANCE = 1; + public static final int PROFILING = 2; + + public static final int CLEARSCREEN = 0; + private static final String TAG = "TestHarness"; + private Context mContext; + + private int mMode = REGRESSION; + + private List<Listener> mListeners = Lists.newArrayList(); + private int mPassed; + private int mFailed; + + private int mInternalIterations; + private long mStartTime; + private long mEndTime; + + private String mClassName; + + List<IntermediateTime> mIntermediates = null; + + private static Class mRunnableClass; + private static Class mJUnitClass; + + static { + try { + mRunnableClass = Class.forName("java.lang.Runnable", false, null); + mJUnitClass = Class.forName("junit.framework.TestCase", false, null); + } catch (ClassNotFoundException ex) { + throw new RuntimeException("shouldn't happen", ex); + } + } + + public class JunitTestSuite extends TestSuite implements TestListener { + boolean mError = false; + + public JunitTestSuite() { + super(); + } + + public void run(TestResult result) { + result.addListener(this); + super.run(result); + result.removeListener(this); + } + + /** + * Implemented method of the interface TestListener which will listen for the + * start of a test. + * + * @param test + */ + public void startTest(Test test) { + started(test.toString()); + } + + /** + * Implemented method of the interface TestListener which will listen for the + * end of the test. + * + * @param test + */ + public void endTest(Test test) { + finished(test.toString()); + if (!mError) { + passed(test.toString()); + } + } + + /** + * Implemented method of the interface TestListener which will listen for an + * mError while running the test. + * + * @param test + */ + public void addError(Test test, Throwable t) { + mError = true; + failed(test.toString(), t); + } + + public void addFailure(Test test, junit.framework.AssertionFailedError t) { + mError = true; + failed(test.toString(), t); + } + } + + /** + * Listener.performance() 'intermediates' argument is a list of these. + */ + public static class IntermediateTime { + public IntermediateTime(String name, long timeInNS) { + this.name = name; + this.timeInNS = timeInNS; + } + + public String name; + public long timeInNS; + } + + /** + * Support class that receives status on test progress. You should not need to + * extend this interface yourself. + */ + public interface Listener { + void started(String className); + void finished(String className); + void performance(String className, + long itemTimeNS, int iterations, + List<IntermediateTime> itermediates); + void passed(String className); + void failed(String className, Throwable execption); + } + + public TestRunner(Context context) { + mContext = context; + } + + public void addListener(Listener listener) { + mListeners.add(listener); + } + + public void startProfiling() { + File file = new File("/tmp/trace"); + file.mkdir(); + String base = "/tmp/trace/" + mClassName + ".dmtrace"; + Debug.startMethodTracing(base, 8 * 1024 * 1024); + } + + public void finishProfiling() { + Debug.stopMethodTracing(); + } + + private void started(String className) { + + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).started(className); + } + } + + private void finished(String className) { + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).finished(className); + } + } + + private void performance(String className, + long itemTimeNS, + int iterations, + List<IntermediateTime> intermediates) { + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).performance(className, + itemTimeNS, + iterations, + intermediates); + } + } + + public void passed(String className) { + mPassed++; + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).passed(className); + } + } + + public void failed(String className, Throwable exception) { + mFailed++; + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).failed(className, exception); + } + } + + public int passedCount() { + return mPassed; + } + + public int failedCount() { + return mFailed; + } + + public void run(String[] classes) { + for (String cl : classes) { + run(cl); + } + } + + public void setInternalIterations(int count) { + mInternalIterations = count; + } + + public void startTiming(boolean realTime) { + if (realTime) { + mStartTime = System.currentTimeMillis(); + } else { + mStartTime = SystemClock.currentThreadTimeMillis(); + } + } + + public void addIntermediate(String name) { + addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000); + } + + public void addIntermediate(String name, long timeInNS) { + mIntermediates.add(new IntermediateTime(name, timeInNS)); + } + + public void finishTiming(boolean realTime) { + if (realTime) { + mEndTime = System.currentTimeMillis(); + } else { + mEndTime = SystemClock.currentThreadTimeMillis(); + } + } + + public void setPerformanceMode(int mode) { + mMode = mode; + } + + private void missingTest(String className, Throwable e) { + started(className); + finished(className); + failed(className, e); + } + + /* + This class determines if more suites are added to this class then adds all individual + test classes to a test suite for run + */ + public void run(String className) { + try { + mClassName = className; + Class clazz = mContext.getClassLoader().loadClass(className); + Method method = getChildrenMethod(clazz); + if (method != null) { + String[] children = getChildren(method); + run(children); + } else if (mRunnableClass.isAssignableFrom(clazz)) { + Runnable test = (Runnable) clazz.newInstance(); + TestCase testcase = null; + if (test instanceof TestCase) { + testcase = (TestCase) test; + } + Throwable e = null; + boolean didSetup = false; + started(className); + try { + if (testcase != null) { + testcase.setUp(mContext); + didSetup = true; + } + if (mMode == PERFORMANCE) { + runInPerformanceMode(test, className, false, className); + } else if (mMode == PROFILING) { + //Need a way to mark a test to be run in profiling mode or not. + startProfiling(); + test.run(); + finishProfiling(); + } else { + test.run(); + } + } catch (Throwable ex) { + e = ex; + } + if (testcase != null && didSetup) { + try { + testcase.tearDown(); + } catch (Throwable ex) { + e = ex; + } + } + finished(className); + if (e == null) { + passed(className); + } else { + failed(className, e); + } + } else if (mJUnitClass.isAssignableFrom(clazz)) { + Throwable e = null; + //Create a Junit Suite. + JunitTestSuite suite = new JunitTestSuite(); + Method[] methods = getAllTestMethods(clazz); + for (Method m : methods) { + junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance(); + test.setName(m.getName()); + + if (test instanceof AndroidTestCase) { + AndroidTestCase testcase = (AndroidTestCase) test; + try { + testcase.setContext(mContext); + } catch (Exception ex) { + Log.i("TestHarness", ex.toString()); + } + } + suite.addTest(test); + } + if (mMode == PERFORMANCE) { + final int testCount = suite.testCount(); + + for (int j = 0; j < testCount; j++) { + Test test = suite.testAt(j); + started(test.toString()); + try { + runInPerformanceMode(test, className, true, test.toString()); + } catch (Throwable ex) { + e = ex; + } + finished(test.toString()); + if (e == null) { + passed(test.toString()); + } else { + failed(test.toString(), e); + } + } + } else if (mMode == PROFILING) { + //Need a way to mark a test to be run in profiling mode or not. + startProfiling(); + junit.textui.TestRunner.run(suite); + finishProfiling(); + } else { + junit.textui.TestRunner.run(suite); + } + } else { + System.out.println("Test wasn't Runnable and didn't have a" + + " children method: " + className); + } + } catch (ClassNotFoundException e) { + Log.e("ClassNotFoundException for " + className, e.toString()); + if (isJunitTest(className)) { + runSingleJunitTest(className); + } else { + missingTest(className, e); + } + } catch (InstantiationException e) { + System.out.println("InstantiationException for " + className); + missingTest(className, e); + } catch (IllegalAccessException e) { + System.out.println("IllegalAccessException for " + className); + missingTest(className, e); + } + } + + public void runInPerformanceMode(Object testCase, String className, boolean junitTest, + String testNameInDb) throws Exception { + boolean increaseIterations = true; + int iterations = 1; + long duration = 0; + mIntermediates = null; + + mInternalIterations = 1; + Class clazz = mContext.getClassLoader().loadClass(className); + Object perftest = clazz.newInstance(); + + PerformanceTestCase perftestcase = null; + if (perftest instanceof PerformanceTestCase) { + perftestcase = (PerformanceTestCase) perftest; + // only run the test if it is not marked as a performance only test + if (mMode == REGRESSION && perftestcase.isPerformanceOnly()) return; + } + + // First force GCs, to avoid GCs happening during out + // test and skewing its time. + Runtime.getRuntime().runFinalization(); + Runtime.getRuntime().gc(); + + if (perftestcase != null) { + mIntermediates = new ArrayList<IntermediateTime>(); + iterations = perftestcase.startPerformance(this); + if (iterations > 0) { + increaseIterations = false; + } else { + iterations = 1; + } + } + + // Pause briefly to let things settle down... + Thread.sleep(1000); + do { + mEndTime = 0; + if (increaseIterations) { + // Test case does not implement + // PerformanceTestCase or returned 0 iterations, + // so we take care of measure the whole test time. + mStartTime = SystemClock.currentThreadTimeMillis(); + } else { + // Try to make it obvious if the test case + // doesn't call startTiming(). + mStartTime = 0; + } + + if (junitTest) { + for (int i = 0; i < iterations; i++) { + junit.textui.TestRunner.run((junit.framework.Test) testCase); + } + } else { + Runnable test = (Runnable) testCase; + for (int i = 0; i < iterations; i++) { + test.run(); + } + } + + long endTime = mEndTime; + if (endTime == 0) { + endTime = SystemClock.currentThreadTimeMillis(); + } + + duration = endTime - mStartTime; + if (!increaseIterations) { + break; + } + if (duration <= 1) { + iterations *= 1000; + } else if (duration <= 10) { + iterations *= 100; + } else if (duration < 100) { + iterations *= 10; + } else if (duration < 1000) { + iterations *= (int) ((1000 / duration) + 2); + } else { + break; + } + } while (true); + + if (duration != 0) { + iterations *= mInternalIterations; + performance(testNameInDb, (duration * 1000000) / iterations, + iterations, mIntermediates); + } + } + + public void runSingleJunitTest(String className) { + Throwable excep = null; + int index = className.lastIndexOf('$'); + String testName = ""; + String originalClassName = className; + if (index >= 0) { + className = className.substring(0, index); + testName = originalClassName.substring(index + 1); + } + try { + Class clazz = mContext.getClassLoader().loadClass(className); + if (mJUnitClass.isAssignableFrom(clazz)) { + junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance(); + JunitTestSuite newSuite = new JunitTestSuite(); + test.setName(testName); + + if (test instanceof AndroidTestCase) { + AndroidTestCase testcase = (AndroidTestCase) test; + try { + testcase.setContext(mContext); + } catch (Exception ex) { + Log.w(TAG, "Exception encountered while trying to set the context.", ex); + } + } + newSuite.addTest(test); + + if (mMode == PERFORMANCE) { + try { + started(test.toString()); + runInPerformanceMode(test, className, true, test.toString()); + finished(test.toString()); + if (excep == null) { + passed(test.toString()); + } else { + failed(test.toString(), excep); + } + } catch (Throwable ex) { + excep = ex; + } + + } else if (mMode == PROFILING) { + startProfiling(); + junit.textui.TestRunner.run(newSuite); + finishProfiling(); + } else { + junit.textui.TestRunner.run(newSuite); + } + } + } catch (ClassNotFoundException e) { + Log.e("TestHarness", "No test case to run", e); + } catch (IllegalAccessException e) { + Log.e("TestHarness", "Illegal Access Exception", e); + } catch (InstantiationException e) { + Log.e("TestHarness", "Instantiation Exception", e); + } + } + + public static Method getChildrenMethod(Class clazz) { + try { + return clazz.getMethod("children", (Class[]) null); + } catch (NoSuchMethodException e) { + } + + return null; + } + + public static Method getChildrenMethod(Context c, String className) { + try { + return getChildrenMethod(c.getClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + } + return null; + } + + public static String[] getChildren(Context c, String className) { + Method m = getChildrenMethod(c, className); + String[] testChildren = getTestChildren(c, className); + if (m == null & testChildren == null) { + throw new RuntimeException("couldn't get children method for " + + className); + } + if (m != null) { + String[] children = getChildren(m); + if (testChildren != null) { + String[] allChildren = new String[testChildren.length + children.length]; + System.arraycopy(children, 0, allChildren, 0, children.length); + System.arraycopy(testChildren, 0, allChildren, children.length, testChildren.length); + return allChildren; + } else { + return children; + } + } else { + if (testChildren != null) { + return testChildren; + } + } + return null; + } + + public static String[] getChildren(Method m) { + try { + if (!Modifier.isStatic(m.getModifiers())) { + throw new RuntimeException("children method is not static"); + } + return (String[]) m.invoke(null, (Object[]) null); + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + return new String[0]; + } + + public static String[] getTestChildren(Context c, String className) { + try { + Class clazz = c.getClassLoader().loadClass(className); + + if (mJUnitClass.isAssignableFrom(clazz)) { + return getTestChildren(clazz); + } + } catch (ClassNotFoundException e) { + Log.e("TestHarness", "No class found", e); + } + return null; + } + + public static String[] getTestChildren(Class clazz) { + Method[] methods = getAllTestMethods(clazz); + + String[] onScreenTestNames = new String[methods.length]; + int index = 0; + for (Method m : methods) { + onScreenTestNames[index] = clazz.getName() + "$" + m.getName(); + index++; + } + return onScreenTestNames; + } + + public static Method[] getAllTestMethods(Class clazz) { + Method[] allMethods = clazz.getDeclaredMethods(); + int numOfMethods = 0; + for (Method m : allMethods) { + boolean mTrue = isTestMethod(m); + if (mTrue) { + numOfMethods++; + } + } + int index = 0; + Method[] testMethods = new Method[numOfMethods]; + for (Method m : allMethods) { + boolean mTrue = isTestMethod(m); + if (mTrue) { + testMethods[index] = m; + index++; + } + } + return testMethods; + } + + private static boolean isTestMethod(Method m) { + return m.getName().startsWith("test") && + m.getReturnType() == void.class && + m.getParameterTypes().length == 0; + } + + public static int countJunitTests(Class clazz) { + Method[] allTestMethods = getAllTestMethods(clazz); + int numberofMethods = allTestMethods.length; + + return numberofMethods; + } + + public static boolean isTestSuite(Context c, String className) { + boolean childrenMethods = getChildrenMethod(c, className) != null; + + try { + Class clazz = c.getClassLoader().loadClass(className); + if (mJUnitClass.isAssignableFrom(clazz)) { + int numTests = countJunitTests(clazz); + if (numTests > 0) + childrenMethods = true; + } + } catch (ClassNotFoundException e) { + } + return childrenMethods; + } + + + public boolean isJunitTest(String className) { + int index = className.lastIndexOf('$'); + if (index >= 0) { + className = className.substring(0, index); + } + try { + Class clazz = mContext.getClassLoader().loadClass(className); + if (mJUnitClass.isAssignableFrom(clazz)) { + return true; + } + } catch (ClassNotFoundException e) { + } + return false; + } + + /** + * Returns the number of tests that will be run if you try to do this. + */ + public static int countTests(Context c, String className) { + try { + Class clazz = c.getClassLoader().loadClass(className); + Method method = getChildrenMethod(clazz); + if (method != null) { + + String[] children = getChildren(method); + int rv = 0; + for (String child : children) { + rv += countTests(c, child); + } + return rv; + } else if (mRunnableClass.isAssignableFrom(clazz)) { + return 1; + } else if (mJUnitClass.isAssignableFrom(clazz)) { + return countJunitTests(clazz); + } + } catch (ClassNotFoundException e) { + return 1; // this gets the count right, because either this test + // is missing, and it will fail when run or it is a single Junit test to be run. + } + return 0; + } + + /** + * Returns a title to display given the className of a test. + * <p/> + * <p>Currently this function just returns the portion of the + * class name after the last '.' + */ + public static String getTitle(String className) { + int indexDot = className.lastIndexOf('.'); + int indexDollar = className.lastIndexOf('$'); + int index = indexDot > indexDollar ? indexDot : indexDollar; + if (index >= 0) { + className = className.substring(index + 1); + } + return className; + } +} diff --git a/test-runner/android/test/TestRunnerView.java b/test-runner/android/test/TestRunnerView.java new file mode 100644 index 0000000..be90951 --- /dev/null +++ b/test-runner/android/test/TestRunnerView.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import java.util.List; + +/** + * @hide - This is part of a framework that is under development and should not be used for + * active development. + */ +public interface TestRunnerView { + void setTestNames(List<String> testNames); + + void setItemColorAt(int position, int color); + + void setFailureCount(int failureCount); + + void setRunCount(int runCount); + + void setErrorCount(int errorCount); + + void setTotalCount(int totalCount); + + void setProgressBarColor(int color); + + void testsCompleted(); +} diff --git a/test-runner/android/test/TestSuiteProvider.java b/test-runner/android/test/TestSuiteProvider.java new file mode 100644 index 0000000..dc9ce6e --- /dev/null +++ b/test-runner/android/test/TestSuiteProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import junit.framework.TestSuite; + +/** + * Implementors will know how to get a test suite. + */ +public interface TestSuiteProvider { + + TestSuite getTestSuite(); +} diff --git a/test-runner/android/test/TouchUtils.java b/test-runner/android/test/TouchUtils.java new file mode 100644 index 0000000..9174fb8 --- /dev/null +++ b/test-runner/android/test/TouchUtils.java @@ -0,0 +1,535 @@ +/* + * 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.os.SystemClock; +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 ActivityTestCases 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 cast that is being run + */ + public static void dragQuarterScreenDown(ActivityInstrumentationTestCase test) { + int screenHeight = test.getActivity().getWindowManager().getDefaultDisplay().getHeight(); + int screenWidth = test.getActivity().getWindowManager().getDefaultDisplay().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 cast that is being run + */ + public static void dragQuarterScreenUp(ActivityInstrumentationTestCase test) { + int screenHeight = test.getActivity().getWindowManager().getDefaultDisplay().getHeight(); + int screenWidth = test.getActivity().getWindowManager().getDefaultDisplay().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 VirewGroup to the bottom by repeatedly calling + * {@link #dragQuarterScreenUp(ActivityInstrumentationTestCase)} + * + * @param test The test cast that is being run + * @param v The ViewGroup that should be dragged + */ + public static void scrollToBottom(ActivityInstrumentationTestCase test, 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); + 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(ActivityInstrumentationTestCase)} + * + * @param test The test cast that is being run + * @param v The ViewGroup that should be dragged + */ + public static void scrollToTop(ActivityInstrumentationTestCase test, 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); + 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 cast that is being run + * @param v The view that should be dragged + */ + public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v) { + dragViewToBottom(test, v, 4); + } + + /** + * Simulate touching the center of a view and dragging to the bottom of the screen. + * + * @param test The test cast 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 dragViewToBottom(ActivityInstrumentationTestCase test, View v, int stepCount) { + int screenHeight = test.getActivity().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 cast 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(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, + x + (ViewConfiguration.getTouchSlop() / 2.0f), + y + (ViewConfiguration.getTouchSlop() / 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 on click should + * fire, etc). + * + * @param test The test cast 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(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, + x + (ViewConfiguration.getTouchSlop() / 2.0f), + y + (ViewConfiguration.getTouchSlop() / 2.0f), 0); + inst.sendPointerSync(event); + inst.waitForIdleSync(); + + } + + /** + * Simulate touching the center of a view and releasing. + * + * @param test The test cast 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(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, + x + (ViewConfiguration.getTouchSlop() / 2.0f), + y + (ViewConfiguration.getTouchSlop() / 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 cast that is being run + * @param v The view that should be clicked + */ + public static void longClickView(ActivityInstrumentationTestCase 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(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, + x + ViewConfiguration.getTouchSlop() / 2, + y + ViewConfiguration.getTouchSlop() / 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 cast that is being run + * @param v The view that should be dragged + */ + public static void dragViewToTop(ActivityInstrumentationTestCase 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 cast 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(ActivityInstrumentationTestCase 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 cast 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 + */ + public static int dragViewBy(ActivityInstrumentationTestCase 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 cast 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(ActivityInstrumentationTestCase 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 cast 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(ActivityInstrumentationTestCase 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 cast 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(ActivityInstrumentationTestCase 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 cast 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(ActivityInstrumentationTestCase test, float fromX, float toX, float fromY, float toY, + int stepCount) { + Instrumentation inst = test.getInstrumentation(); + + long downTime = SystemClock.uptimeMillis(); + long eventTime = SystemClock.uptimeMillis(); + + float y = fromY; + float x = fromX; + + float yStep = (toY - fromY) / stepCount; + float xStep = (toX - fromX) / stepCount; + + MotionEvent event = MotionEvent.obtain(downTime, eventTime, + MotionEvent.ACTION_DOWN, fromX, y, 0); + inst.sendPointerSync(event); + inst.waitForIdleSync(); + + for (int i = 0; i < stepCount; ++i) { + y += yStep; + x += xStep; + eventTime = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); + inst.sendPointerSync(event); + inst.waitForIdleSync(); + } + + eventTime = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, fromX, y, 0); + inst.sendPointerSync(event); + inst.waitForIdleSync(); + } + +} diff --git a/test-runner/android/test/ViewAsserts.java b/test-runner/android/test/ViewAsserts.java new file mode 100644 index 0000000..c575fc5 --- /dev/null +++ b/test-runner/android/test/ViewAsserts.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import static junit.framework.Assert.*; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Some useful assertions about views. + */ +public class ViewAsserts { + + private ViewAsserts() {} + + /** + * Assert that view is on the screen. + * @param origin The root view of the screen. + * @param view The view. + */ + static public void assertOnScreen(View origin, View view) { + int[] xy = new int[2]; + view.getLocationOnScreen(xy); + + int[] xyRoot = new int[2]; + origin.getLocationOnScreen(xyRoot); + + int y = xy[1] - xyRoot[1]; + + assertTrue("view should have positive y coordinate on screen", + y >= 0); + + assertTrue("view should have y location on screen less than drawing " + + "height of root view", + y <= view.getRootView().getHeight()); + } + + /** + * Assert that view is below the visible screen. + * @param origin The root view of the screen. + * @param view The view + */ + static public void assertOffScreenBelow(View origin, View view) { + int[] xy = new int[2]; + view.getLocationOnScreen(xy); + + int[] xyRoot = new int[2]; + origin.getLocationOnScreen(xyRoot); + + int y = xy[1] - xyRoot[1]; + + assertTrue("view should have y location on screen greater than drawing " + + "height of origen view (" + y + " is not greater than " + + origin.getHeight() + ")", + y > origin.getHeight()); + } + + /** + * Assert that view is above the visible screen. + * @param origin Te root view of the screen. + * @param view The view + */ + static public void assertOffScreenAbove(View origin, View view) { + int[] xy = new int[2]; + view.getLocationOnScreen(xy); + + int[] xyRoot = new int[2]; + origin.getLocationOnScreen(xyRoot); + + int y = xy[1] - xyRoot[1]; + + assertTrue("view should have y location less than that of origin view", + y < 0); + } + + /** + * Assert that a view has a particular x and y position on the visible screen. + * @param origin The root view of the screen. + * @param view The view. + * @param x The expected x coordinate. + * @param y The expected y coordinate. + */ + static public void assertHasScreenCoordinates(View origin, View view, int x, int y) { + int[] xy = new int[2]; + view.getLocationOnScreen(xy); + + int[] xyRoot = new int[2]; + origin.getLocationOnScreen(xyRoot); + + assertEquals("x coordinate", x, xy[0] - xyRoot[0]); + assertEquals("y coordinate", y, xy[1] - xyRoot[1]); + } + + /** + * Assert that two views are aligned on their baseline, that is that their baselines + * are on the same y location. + * + * @param first The first view + * @param second The second view + */ + static public void assertBaselineAligned(View first, View second) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstTop = xy[1] + first.getBaseline(); + + second.getLocationOnScreen(xy); + int secondTop = xy[1] + second.getBaseline(); + + assertEquals("views are not baseline aligned", firstTop, secondTop); + } + + /** + * Assert that two views are right aligned, that is that their right edges + * are on the same x location. + * + * @param first The first view + * @param second The second view + */ + static public void assertRightAligned(View first, View second) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstRight = xy[0] + first.getMeasuredWidth(); + + second.getLocationOnScreen(xy); + int secondRight = xy[0] + second.getMeasuredWidth(); + + assertEquals("views are not right aligned", firstRight, secondRight); + } + + /** + * Assert that two views are right aligned, that is that their right edges + * are on the same x location, with respect to the specified margin. + * + * @param first The first view + * @param second The second view + * @param margin The margin between the first view and the second view + */ + static public void assertRightAligned(View first, View second, int margin) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstRight = xy[0] + first.getMeasuredWidth(); + + second.getLocationOnScreen(xy); + int secondRight = xy[0] + second.getMeasuredWidth(); + + assertEquals("views are not right aligned", Math.abs(firstRight - secondRight), margin); + } + + /** + * Assert that two views are left aligned, that is that their left edges + * are on the same x location. + * + * @param first The first view + * @param second The second view + */ + static public void assertLeftAligned(View first, View second) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstLeft = xy[0]; + + second.getLocationOnScreen(xy); + int secondLeft = xy[0]; + + assertEquals("views are not left aligned", firstLeft, secondLeft); + } + + /** + * Assert that two views are left aligned, that is that their left edges + * are on the same x location, with respect to the specified margin. + * + * @param first The first view + * @param second The second view + * @param margin The margin between the first view and the second view + */ + static public void assertLeftAligned(View first, View second, int margin) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstLeft = xy[0]; + + second.getLocationOnScreen(xy); + int secondLeft = xy[0]; + + assertEquals("views are not left aligned", Math.abs(firstLeft - secondLeft), margin); + } + + /** + * Assert that two views are bottom aligned, that is that their bottom edges + * are on the same y location. + * + * @param first The first view + * @param second The second view + */ + static public void assertBottomAligned(View first, View second) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstBottom = xy[1] + first.getMeasuredHeight(); + + second.getLocationOnScreen(xy); + int secondBottom = xy[1] + second.getMeasuredHeight(); + + assertEquals("views are not bottom aligned", firstBottom, secondBottom); + } + + /** + * Assert that two views are bottom aligned, that is that their bottom edges + * are on the same y location, with respect to the specified margin. + * + * @param first The first view + * @param second The second view + * @param margin The margin between the first view and the second view + */ + static public void assertBottomAligned(View first, View second, int margin) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstBottom = xy[1] + first.getMeasuredHeight(); + + second.getLocationOnScreen(xy); + int secondBottom = xy[1] + second.getMeasuredHeight(); + + assertEquals("views are not bottom aligned", Math.abs(firstBottom - secondBottom), margin); + } + + /** + * Assert that two views are top aligned, that is that their top edges + * are on the same y location. + * + * @param first The first view + * @param second The second view + */ + static public void assertTopAligned(View first, View second) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstTop = xy[1]; + + second.getLocationOnScreen(xy); + int secondTop = xy[1]; + + assertEquals("views are not top aligned", firstTop, secondTop); + } + + /** + * Assert that two views are top aligned, that is that their top edges + * are on the same y location, with respect to the specified margin. + * + * @param first The first view + * @param second The second view + * @param margin The margin between the first view and the second view + */ + static public void assertTopAligned(View first, View second, int margin) { + int[] xy = new int[2]; + first.getLocationOnScreen(xy); + int firstTop = xy[1]; + + second.getLocationOnScreen(xy); + int secondTop = xy[1]; + + assertEquals("views are not top aligned", Math.abs(firstTop - secondTop), margin); + } + + /** + * Assert that the <code>test</code> view is horizontally center aligned + * with respect to the <code>reference</code> view. + * + * @param reference The reference view + * @param test The view that should be center aligned with the reference view + */ + static public void assertHorizontalCenterAligned(View reference, View test) { + int[] xy = new int[2]; + reference.getLocationOnScreen(xy); + int referenceLeft = xy[0]; + + test.getLocationOnScreen(xy); + int testLeft = xy[0]; + + int center = (reference.getMeasuredWidth() - test.getMeasuredWidth()) / 2; + int delta = testLeft - referenceLeft; + + assertEquals("views are not horizontally center aligned", center, delta); + } + + /** + * Assert that the <code>test</code> view is vertically center aligned + * with respect to the <code>reference</code> view. + * + * @param reference The reference view + * @param test The view that should be center aligned with the reference view + */ + static public void assertVerticalCenterAligned(View reference, View test) { + int[] xy = new int[2]; + reference.getLocationOnScreen(xy); + int referenceTop = xy[1]; + + test.getLocationOnScreen(xy); + int testTop = xy[1]; + + int center = (reference.getMeasuredHeight() - test.getMeasuredHeight()) / 2; + int delta = testTop - referenceTop; + + assertEquals("views are not vertically center aligned", center, delta); + } + + /** + * Assert the specified group's integrity. The children count should be >= 0 and each + * child should be non-null. + * + * @param parent The group whose integrity to check + */ + static public void assertGroupIntegrity(ViewGroup parent) { + final int count = parent.getChildCount(); + assertTrue("child count should be >= 0", count >= 0); + + for (int i = 0; i < count; i++) { + assertNotNull("group should not contain null children", parent.getChildAt(i)); + assertSame(parent, parent.getChildAt(i).getParent()); + } + } + + /** + * Assert that the specified group contains a specific child once and only once. + * + * @param parent The group + * @param child The child that should belong to group + */ + static public void assertGroupContains(ViewGroup parent, View child) { + final int count = parent.getChildCount(); + assertTrue("Child count should be >= 0", count >= 0); + + boolean found = false; + for (int i = 0; i < count; i++) { + if (parent.getChildAt(i) == child) { + if (!found) { + found = true; + } else { + assertTrue("child " + child + " is duplicated in parent", false); + } + } + } + + assertTrue("group does not contain " + child, found); + } + + /** + * Assert that the specified group does not contain a specific child. + * + * @param parent The group + * @param child The child that should not belong to group + */ + static public void assertGroupNotContains(ViewGroup parent, View child) { + final int count = parent.getChildCount(); + assertTrue("Child count should be >= 0", count >= 0); + + for (int i = 0; i < count; i++) { + if (parent.getChildAt(i) == child) { + assertTrue("child " + child + " is found in parent", false); + } + } + } +} diff --git a/test-runner/android/test/mock/MockApplication.java b/test-runner/android/test/mock/MockApplication.java new file mode 100644 index 0000000..572dfbf --- /dev/null +++ b/test-runner/android/test/mock/MockApplication.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.mock; + +import android.app.Application; +import android.content.res.Configuration; + +/** + * A mock {@link android.app.Application} class. All methods are non-functional and throw + * {@link java.lang.UnsupportedOperationException}. Override it as necessary to provide the + * operations that you need. + */ +public class MockApplication extends Application { + + public MockApplication() { + } + + @Override + public void onCreate() { + throw new UnsupportedOperationException(); + } + + @Override + public void onTerminate() { + throw new UnsupportedOperationException(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + throw new UnsupportedOperationException(); + } +} diff --git a/test-runner/android/test/mock/MockContentProvider.java b/test-runner/android/test/mock/MockContentProvider.java new file mode 100644 index 0000000..9c00ecf --- /dev/null +++ b/test-runner/android/test/mock/MockContentProvider.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.mock; + +import android.content.ContentValues; +import android.content.IContentProvider; +import android.content.ISyncAdapter; +import android.database.Cursor; +import android.database.CursorWindow; +import android.database.IBulkCursor; +import android.database.IContentObserver; +import android.net.Uri; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +import java.io.FileNotFoundException; + +/** + * Mock implementation of IContentProvider that does nothing. All methods are non-functional and + * throw {@link java.lang.UnsupportedOperationException}. Tests can extend this class to + * implement behavior needed for tests. + * + * @hide - Because IContentProvider hides bulkQuery(), this doesn't pass through JavaDoc + * without generating errors. + * + */ +public class MockContentProvider implements IContentProvider { + + @SuppressWarnings("unused") + public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + @SuppressWarnings("unused") + public IBulkCursor bulkQuery(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder, IContentObserver observer, + CursorWindow window) throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public int delete(Uri url, String selection, String[] selectionArgs) + throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public ISyncAdapter getSyncAdapter() throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public String getType(Uri url) throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, + FileNotFoundException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, + String sortOrder) throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @SuppressWarnings("unused") + public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) + throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public IBinder asBinder() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + +} diff --git a/test-runner/android/test/mock/MockContentResolver.java b/test-runner/android/test/mock/MockContentResolver.java new file mode 100644 index 0000000..66840a1 --- /dev/null +++ b/test-runner/android/test/mock/MockContentResolver.java @@ -0,0 +1,65 @@ +/* + * 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) { + return mProviders.get(name).getIContentProvider(); + } + + /** @hide */ + @Override + public boolean releaseProvider(IContentProvider provider) { + return true; + } + + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + } +} diff --git a/test-runner/android/test/mock/MockContext.java b/test-runner/android/test/mock/MockContext.java new file mode 100644 index 0000000..e733dd1 --- /dev/null +++ b/test-runner/android/test/mock/MockContext.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.mock; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A mock {@link android.content.Context} class. All methods are non-functional and throw + * {@link java.lang.UnsupportedOperationException}. You can use this to inject other dependencies, + * mocks, or monitors into the classes you are testing. + */ +public class MockContext extends Context { + + @Override + public AssetManager getAssets() { + throw new UnsupportedOperationException(); + } + + @Override + public Resources getResources() { + throw new UnsupportedOperationException(); + } + + @Override + public PackageManager getPackageManager() { + throw new UnsupportedOperationException(); + } + + @Override + public ContentResolver getContentResolver() { + throw new UnsupportedOperationException(); + } + + @Override + public Looper getMainLooper() { + throw new UnsupportedOperationException(); + } + + @Override + public Context getApplicationContext() { + throw new UnsupportedOperationException(); + } + + @Override + public void setTheme(int resid) { + throw new UnsupportedOperationException(); + } + + @Override + public Resources.Theme getTheme() { + throw new UnsupportedOperationException(); + } + + @Override + public ClassLoader getClassLoader() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPackageName() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPackageResourcePath() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPackageCodePath() { + throw new UnsupportedOperationException(); + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + throw new UnsupportedOperationException(); + } + + @Override + public FileInputStream openFileInput(String name) throws FileNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean deleteFile(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public File getFileStreamPath(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] fileList() { + throw new UnsupportedOperationException(); + } + + @Override + public File getFilesDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getCacheDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getDir(String name, int mode) { + throw new UnsupportedOperationException(); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String file, int mode, + SQLiteDatabase.CursorFactory factory) { + throw new UnsupportedOperationException(); + } + + @Override + public File getDatabasePath(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] databaseList() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean deleteDatabase(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getWallpaper() { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable peekWallpaper() { + throw new UnsupportedOperationException(); + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + throw new UnsupportedOperationException(); + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + throw new UnsupportedOperationException(); + } + + @Override + public void setWallpaper(Bitmap bitmap) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void setWallpaper(InputStream data) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void clearWallpaper() { + throw new UnsupportedOperationException(); + } + + @Override + public void startActivity(Intent intent) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendBroadcast(Intent intent) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, + String receiverPermission) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendStickyBroadcast(Intent intent) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeStickyBroadcast(Intent intent) { + throw new UnsupportedOperationException(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + throw new UnsupportedOperationException(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + throw new UnsupportedOperationException(); + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + throw new UnsupportedOperationException(); + } + + @Override + public ComponentName startService(Intent service) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean stopService(Intent service) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public void unbindService(ServiceConnection conn) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean startInstrumentation(ComponentName className, + String profileFile, Bundle arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getSystemService(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkCallingPermission(String permission) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + throw new UnsupportedOperationException(); + } + + @Override + public void enforcePermission( + String permission, int pid, int uid, String message) { + throw new UnsupportedOperationException(); + } + + @Override + public void enforceCallingPermission(String permission, String message) { + throw new UnsupportedOperationException(); + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + throw new UnsupportedOperationException(); + } + + @Override + public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public void revokeUriPermission(Uri uri, int modeFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkCallingUriPermission(Uri uri, int modeFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkUriPermission(Uri uri, String readPermission, + String writePermission, int pid, int uid, int modeFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public void enforceUriPermission( + Uri uri, int pid, int uid, int modeFlags, String message) { + throw new UnsupportedOperationException(); + } + + @Override + public void enforceCallingUriPermission( + Uri uri, int modeFlags, String message) { + throw new UnsupportedOperationException(); + } + + @Override + public void enforceCallingOrSelfUriPermission( + Uri uri, int modeFlags, String message) { + throw new UnsupportedOperationException(); + } + + public void enforceUriPermission( + Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags, String message) { + throw new UnsupportedOperationException(); + } + + @Override + public Context createPackageContext(String packageName, int flags) + throws PackageManager.NameNotFoundException { + throw new UnsupportedOperationException(); + } +} diff --git a/test-runner/android/test/mock/MockDialogInterface.java b/test-runner/android/test/mock/MockDialogInterface.java new file mode 100644 index 0000000..e4dd0ba --- /dev/null +++ b/test-runner/android/test/mock/MockDialogInterface.java @@ -0,0 +1,20 @@ +// Copyright 2008 The Android Open Source Project + +package android.test.mock; + +import android.content.DialogInterface; + +/** + * A mock {@link android.content.DialogInterface} class. All methods are non-functional and throw + * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you + * need. + */ +public class MockDialogInterface implements DialogInterface { + public void cancel() { + throw new UnsupportedOperationException("not implemented yet"); + } + + public void dismiss() { + throw new UnsupportedOperationException("not implemented yet"); + } +} diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java new file mode 100644 index 0000000..4f7745b --- /dev/null +++ b/test-runner/android/test/mock/MockPackageManager.java @@ -0,0 +1,378 @@ +/* + * 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.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageInstallObserver; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.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 int[] getPackageGids(String packageName) throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public PermissionInfo getPermissionInfo(String name, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public PermissionGroupInfo getPermissionGroupInfo(String name, + int flags) throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public List<PermissionGroupInfo> getAllPermissionGroups(int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public ActivityInfo getActivityInfo(ComponentName className, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName className, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public ServiceInfo getServiceInfo(ComponentName className, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public List<PackageInfo> getInstalledPackages(int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkPermission(String permName, String pkgName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addPermission(PermissionInfo info) { + throw new UnsupportedOperationException(); + } + + @Override + public void removePermission(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public int checkSignatures(String pkg1, String pkg2) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getPackagesForUid(int uid) { + throw new UnsupportedOperationException(); + } + + @Override + public String getNameForUid(int uid) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ApplicationInfo> getInstalledApplications(int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public ResolveInfo resolveActivity(Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, + Intent[] specifics, Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public ResolveInfo resolveService(Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public ProviderInfo resolveContentProvider(String name, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public List<InstrumentationInfo> queryInstrumentation( + String targetPackage, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getActivityIcon(ComponentName activityName) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getActivityIcon(Intent intent) throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getDefaultActivityIcon() { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getApplicationIcon(ApplicationInfo info) { + throw new UnsupportedOperationException(); + } + + @Override + public Drawable getApplicationIcon(String packageName) throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) { + throw new UnsupportedOperationException(); + } + + @Override + public XmlResourceParser getXml(String packageName, int resid, + ApplicationInfo appInfo) { + throw new UnsupportedOperationException(); + } + + @Override + public CharSequence getApplicationLabel(ApplicationInfo info) { + throw new UnsupportedOperationException(); + } + + @Override + public Resources getResourcesForActivity(ComponentName activityName) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public Resources getResourcesForApplication(ApplicationInfo app) { + throw new UnsupportedOperationException(); + } + + @Override + public Resources getResourcesForApplication(String appPackageName) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public void installPackage(Uri packageURI, IPackageInstallObserver observer, + int flags) { + throw new UnsupportedOperationException(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void clearApplicationUserData( + String packageName, IPackageDataObserver observer) { + throw new UnsupportedOperationException(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void deleteApplicationCacheFiles( + String packageName, IPackageDataObserver observer) { + throw new UnsupportedOperationException(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void freeApplicationCache( + long idealStorageSize, IPackageDataObserver observer) { + throw new UnsupportedOperationException(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void deletePackage( + String packageName, IPackageDeleteObserver observer, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public void addPackageToPreferred(String packageName) { + throw new UnsupportedOperationException(); + } + + @Override + public void removePackageFromPreferred(String packageName) { + throw new UnsupportedOperationException(); + } + + @Override + public List<PackageInfo> getPreferredPackages(int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, + int newState, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public int getComponentEnabledSetting(ComponentName componentName) { + throw new UnsupportedOperationException(); + } + + @Override + public void setApplicationEnabledSetting(String packageName, int newState, int flags) { + throw new UnsupportedOperationException(); + } + + @Override + public int getApplicationEnabledSetting(String packageName) { + throw new UnsupportedOperationException(); + } + + @Override + public void addPreferredActivity(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearPackagePreferredActivities(String packageName) { + throw new UnsupportedOperationException(); + } + + /** + * @hide - to match hiding in superclass + */ + @Override + public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) { + throw new UnsupportedOperationException(); + } + + @Override + public void installPackage(Uri packageURI) { + throw new UnsupportedOperationException(); + } + + @Override + public int getPreferredActivities(List<IntentFilter> outFilters, + List<ComponentName> outActivities, String packageName) { + throw new UnsupportedOperationException(); + } +} diff --git a/test-runner/android/test/mock/MockResources.java b/test-runner/android/test/mock/MockResources.java new file mode 100644 index 0000000..18752ce --- /dev/null +++ b/test-runner/android/test/mock/MockResources.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.mock; + +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.content.res.ColorStateList; +import android.content.res.XmlResourceParser; +import android.content.res.AssetFileDescriptor; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.util.AttributeSet; +import android.graphics.drawable.Drawable; +import android.graphics.Movie; + +import java.io.InputStream; + +/** + * A mock {@link android.content.res.Resources} class. All methods are non-functional and throw + * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you + * need. + */ +public class MockResources extends Resources { + + public MockResources() { + super(new AssetManager(), null, null); + } + + @Override + public void updateConfiguration(Configuration config, DisplayMetrics metrics) { + // this method is called from the constructor, so we just do nothing + } + + @Override + public CharSequence getText(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getString(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getString(int id, Object... formatArgs) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getQuantityString(int id, int quantity, Object... formatArgs) + throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getQuantityString(int id, int quantity) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public CharSequence getText(int id, CharSequence def) { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public CharSequence[] getTextArray(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String[] getStringArray(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public int[] getIntArray(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public TypedArray obtainTypedArray(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public float getDimension(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public int getDimensionPixelOffset(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public int getDimensionPixelSize(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public Drawable getDrawable(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public Movie getMovie(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public int getColor(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public ColorStateList getColorStateList(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public int getInteger(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public XmlResourceParser getLayout(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public XmlResourceParser getAnimation(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public XmlResourceParser getXml(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public InputStream openRawResource(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public void getValue(int id, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public void getValue(String name, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public DisplayMetrics getDisplayMetrics() { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public Configuration getConfiguration() { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public int getIdentifier(String name, String defType, String defPackage) { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getResourceName(int resid) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getResourcePackageName(int resid) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getResourceTypeName(int resid) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } + + @Override + public String getResourceEntryName(int resid) throws NotFoundException { + throw new UnsupportedOperationException("mock object, not implemented"); + } +} diff --git a/test-runner/android/test/mock/package.html b/test-runner/android/test/mock/package.html new file mode 100644 index 0000000..0f1bc6f4 --- /dev/null +++ b/test-runner/android/test/mock/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes providing stubs or mocks of various Android framework building blocks. +</BODY> +</HTML> diff --git a/test-runner/android/test/suitebuilder/AssignableFrom.java b/test-runner/android/test/suitebuilder/AssignableFrom.java new file mode 100644 index 0000000..38b4ee3 --- /dev/null +++ b/test-runner/android/test/suitebuilder/AssignableFrom.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder; + +import com.android.internal.util.Predicate; + +class AssignableFrom implements Predicate<TestMethod> { + + private final Class root; + + AssignableFrom(Class root) { + this.root = root; + } + + public boolean apply(TestMethod testMethod) { + return root.isAssignableFrom(testMethod.getEnclosingClass()); + } +} diff --git a/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java new file mode 100644 index 0000000..128396e --- /dev/null +++ b/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder; + +/** + * A suite builder that finds instrumentation tests. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class InstrumentationTestSuiteBuilder extends TestSuiteBuilder { + + public InstrumentationTestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + + public InstrumentationTestSuiteBuilder(String name, ClassLoader classLoader) { + super(name, classLoader); + addRequirements(TestPredicates.SELECT_INSTRUMENTATION); + } +} diff --git a/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java new file mode 100644 index 0000000..01e7ec6 --- /dev/null +++ b/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder; + +/** + * A suite builder that runs smoke tests. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class SmokeTestSuiteBuilder extends TestSuiteBuilder { + + public SmokeTestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + + public SmokeTestSuiteBuilder(String name, ClassLoader classLoader) { + super(name, classLoader); + addRequirements(TestPredicates.SELECT_SMOKE); + } +} diff --git a/test-runner/android/test/suitebuilder/TestGrouping.java b/test-runner/android/test/suitebuilder/TestGrouping.java new file mode 100644 index 0000000..df6da70 --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestGrouping.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder; + +import android.test.ClassPathPackageInfo; +import android.test.ClassPathPackageInfoSource; +import android.test.PackageInfoSources; +import android.util.Log; +import com.android.internal.util.Predicate; +import junit.framework.TestCase; + +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Represents a collection of test classes present on the classpath. You can add individual classes + * or entire packages. By default sub-packages are included recursively, but methods are + * provided to allow for arbitrary inclusion or exclusion of sub-packages. Typically a + * {@link TestGrouping} will have only one root package, but this is not a requirement. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class TestGrouping { + + SortedSet<Class<? extends TestCase>> testCaseClasses; + + public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME + = new SortBySimpleName(); + + public static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME + = new SortByFullyQualifiedName(); + + protected String firstIncludedPackage = null; + private ClassLoader classLoader; + + public TestGrouping(Comparator<Class<? extends TestCase>> comparator) { + testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator); + } + + /** + * @return A list of all tests in the package, including small, medium, large, + * flaky, and suppressed tests. Includes sub-packages recursively. + */ + public List<TestMethod> getTests() { + List<TestMethod> testMethods = new ArrayList<TestMethod>(); + for (Class<? extends TestCase> testCase : testCaseClasses) { + for (Method testMethod : getTestMethods(testCase)) { + testMethods.add(new TestMethod(testMethod, testCase)); + } + } + return testMethods; + } + + protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) { + List<Method> methods = Arrays.asList(testCaseClass.getMethods()); + return select(methods, new TestMethodPredicate()); + } + + SortedSet<Class<? extends TestCase>> getTestCaseClasses() { + return testCaseClasses; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TestGrouping other = (TestGrouping) o; + if (!this.testCaseClasses.equals(other.testCaseClasses)) { + return false; + } + return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator()); + } + + public int hashCode() { + return testCaseClasses.hashCode(); + } + + /** + * Include all tests in the given packages and all their sub-packages, unless otherwise + * specified. Each of the given packages must contain at least one test class, either directly + * or in a sub-package. + * + * @param packageNames Names of packages to add. + * @return The {@link TestGrouping} for method chaining. + */ + public TestGrouping addPackagesRecursive(String... packageNames) { + for (String packageName : packageNames) { + List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName); + if (addedClasses.isEmpty()) { + Log.w("TestGrouping", "Invalid Package: '" + packageName + + "' could not be found or has no tests"); + } + testCaseClasses.addAll(addedClasses); + if (firstIncludedPackage == null) { + firstIncludedPackage = packageName; + } + } + return this; + } + + /** + * Exclude all tests in the given packages and all their sub-packages, unless otherwise + * specified. + * + * @param packageNames Names of packages to remove. + * @return The {@link TestGrouping} for method chaining. + */ + public TestGrouping removePackagesRecursive(String... packageNames) { + for (String packageName : packageNames) { + testCaseClasses.removeAll(testCaseClassesInPackage(packageName)); + } + return this; + } + + /** + * @return The first package name passed to {@link #addPackagesRecursive(String[])}, or null + * if that method was never called. + */ + public String getFirstIncludedPackage() { + return firstIncludedPackage; + } + + private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) { + ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader); + ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName); + + return selectTestClasses(packageInfo.getTopLevelClassesRecursive()); + } + + @SuppressWarnings("unchecked") + private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) { + List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>(); + for (Class<?> testClass : select(allClasses, + new TestCasePredicate())) { + testClasses.add((Class<? extends TestCase>) testClass); + } + return testClasses; + } + + private <T> List<T> select(Collection<T> items, Predicate<T> predicate) { + ArrayList<T> selectedItems = new ArrayList<T>(); + for (T item : items) { + if (predicate.apply(item)) { + selectedItems.add(item); + } + } + return selectedItems; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * Sort classes by their simple names (i.e. without the package prefix), using + * their packages to sort classes with the same name. + */ + private static class SortBySimpleName + implements Comparator<Class<? extends TestCase>>, Serializable { + + public int compare(Class<? extends TestCase> class1, + Class<? extends TestCase> class2) { + int result = class1.getSimpleName().compareTo(class2.getSimpleName()); + if (result != 0) { + return result; + } + return class1.getName().compareTo(class2.getName()); + } + } + + /** + * Sort classes by their fully qualified names (i.e. with the package + * prefix). + */ + private static class SortByFullyQualifiedName + implements Comparator<Class<? extends TestCase>>, Serializable { + + public int compare(Class<? extends TestCase> class1, + Class<? extends TestCase> class2) { + return class1.getName().compareTo(class2.getName()); + } + } + + private static class TestCasePredicate implements Predicate<Class<?>> { + + public boolean apply(Class aClass) { + int modifiers = ((Class<?>) aClass).getModifiers(); + return TestCase.class.isAssignableFrom((Class<?>) aClass) + && Modifier.isPublic(modifiers) + && !Modifier.isAbstract(modifiers) + && hasValidConstructor((Class<?>) aClass); + } + + @SuppressWarnings("unchecked") + private boolean hasValidConstructor(java.lang.Class<?> aClass) { + // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler, + // where the return type of Class.getDeclaredConstructors() was changed + // from Constructor<T>[] to Constructor<?>[] + Constructor<? extends TestCase>[] constructors + = (Constructor<? extends TestCase>[]) aClass.getConstructors(); + for (Constructor<? extends TestCase> constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers())) { + java.lang.Class[] parameterTypes = constructor.getParameterTypes(); + if (parameterTypes.length == 0 || + (parameterTypes.length == 1 && parameterTypes[0] == String.class)) { + return true; + } + } + } + return false; + } + } + + private static class TestMethodPredicate implements Predicate<Method> { + + public boolean apply(Method method) { + return ((method.getParameterTypes().length == 0) && + (method.getName().startsWith("test")) && + (method.getReturnType().getSimpleName().equals("void"))); + } + } +} diff --git a/test-runner/android/test/suitebuilder/TestMethod.java b/test-runner/android/test/suitebuilder/TestMethod.java new file mode 100644 index 0000000..3a936c2 --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestMethod.java @@ -0,0 +1,140 @@ +/* + * 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. + * + * {@hide} Not needed for 1.0 SDK. + */ +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.enclosingClass = enclosingClass; + this.enclosingClassname = enclosingClass.getName(); + this.testMethodName = method.getName(); + } + + 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) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + public Class<? extends TestCase> getEnclosingClass() { + return enclosingClass; + } + + public TestCase createTest() + throws InvocationTargetException, IllegalAccessException, InstantiationException { + return instantiateTest(enclosingClass, testMethodName); + } + + @SuppressWarnings("unchecked") + private TestCase instantiateTest(Class testCaseClass, String testName) + throws InvocationTargetException, IllegalAccessException, InstantiationException { + Constructor[] constructors = testCaseClass.getConstructors(); + + if (constructors.length == 0) { + return instantiateTest(testCaseClass.getSuperclass(), testName); + } else { + for (Constructor constructor : constructors) { + Class[] params = constructor.getParameterTypes(); + if (noargsConstructor(params)) { + TestCase test = ((Constructor<? extends TestCase>) constructor).newInstance(); + // JUnit will run just the one test if you call + // {@link TestCase#setName(String)} + test.setName(testName); + return test; + } else if (singleStringConstructor(params)) { + return ((Constructor<? extends TestCase>) constructor) + .newInstance(testName); + } + } + } + throw new RuntimeException("Unable to locate a constructor for " + + testCaseClass.getName()); + } + + private boolean singleStringConstructor(Class[] params) { + return (params.length == 1) && (params[0].equals(String.class)); + } + + private boolean noargsConstructor(Class[] params) { + return params.length == 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TestMethod that = (TestMethod) o; + + if (enclosingClassname != null + ? !enclosingClassname.equals(that.enclosingClassname) + : that.enclosingClassname != null) { + return false; + } + if (testMethodName != null + ? !testMethodName.equals(that.testMethodName) + : that.testMethodName != null) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result; + result = (enclosingClassname != null ? enclosingClassname.hashCode() : 0); + result = 31 * result + (testMethodName != null ? testMethodName.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return enclosingClassname + "." + testMethodName; + } +} diff --git a/test-runner/android/test/suitebuilder/TestPredicates.java b/test-runner/android/test/suitebuilder/TestPredicates.java new file mode 100644 index 0000000..ff75217 --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestPredicates.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.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.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> REJECT_SUPPRESSED = + Predicates.not(new HasAnnotation(Suppress.class)); + public static final Predicate<TestMethod> REJECT_PERFORMANCE = + Predicates.not(new AssignableFrom(PerformanceTestBase.class)); + +} diff --git a/test-runner/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java new file mode 100644 index 0000000..99d0eb9 --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java @@ -0,0 +1,242 @@ +/* + * 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.util.Log; +import com.android.internal.util.Predicate; +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.TestCase; +import junit.framework.TestSuite; + +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 final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME); + private final Set<Predicate<TestMethod>> predicates = new HashSet<Predicate<TestMethod>>(); + 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); + addRequirements(REJECT_SUPPRESSED); + addRequirements(REJECT_PERFORMANCE); + } + + /** + * 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); + } + } + } 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); + suiteForCurrentClass.addTest(testMethod.createTest()); + } + + private void addSuiteIfNecessary(TestMethod test) { + String parentClassname = test.getEnclosingClassname(); + if (!parentClassname.equals(currentClassname)) { + currentClassname = parentClassname; + suiteForCurrentClass = new TestSuite(parentClassname); + rootSuite.addTest(suiteForCurrentClass); + } + } + + private static String parsePackageNameFromClassName(String className) { + return className.substring(0, className.lastIndexOf('.')); + } +} diff --git a/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java new file mode 100644 index 0000000..8cf4c86 --- /dev/null +++ b/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder; + +/** + * A suite builder that finds unit tests. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class UnitTestSuiteBuilder extends TestSuiteBuilder { + + public UnitTestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + + public UnitTestSuiteBuilder(String name, ClassLoader classLoader) { + super(name, classLoader); + addRequirements(TestPredicates.REJECT_INSTRUMENTATION); + addRequirements(TestPredicates.REJECT_PERFORMANCE); + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java new file mode 100644 index 0000000..a2868fc --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder.annotation; + +import static com.android.internal.util.Predicates.or; +import com.android.internal.util.Predicate; +import android.test.suitebuilder.TestMethod; + +import java.lang.annotation.Annotation; + +/** + * A predicate that checks to see if a {@link TestMethod} has a specific annotation, either on the + * method or on the containing class. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class HasAnnotation implements Predicate<TestMethod> { + + private Predicate<TestMethod> hasMethodOrClassAnnotation; + + public HasAnnotation(Class<? extends Annotation> annotationClass) { + this.hasMethodOrClassAnnotation = or( + new HasMethodAnnotation(annotationClass), + new HasClassAnnotation(annotationClass)); + } + + public boolean apply(TestMethod testMethod) { + return hasMethodOrClassAnnotation.apply(testMethod); + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java new file mode 100644 index 0000000..ac76f4c --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder.annotation; + +import java.lang.annotation.Annotation; + +import android.test.suitebuilder.TestMethod; +import com.android.internal.util.Predicate; + +/** + * A predicate that checks to see if a {@link android.test.suitebuilder.TestMethod} has a specific annotation on the + * containing class. Consider using the public {@link HasAnnotation} class instead of this class. + * + * {@hide} Not needed for 1.0 SDK. + */ +class HasClassAnnotation implements Predicate<TestMethod> { + + private Class<? extends Annotation> annotationClass; + + public HasClassAnnotation(Class<? extends Annotation> annotationClass) { + this.annotationClass = annotationClass; + } + + public boolean apply(TestMethod testMethod) { + return testMethod.getEnclosingClass().getAnnotation(annotationClass) != null; + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java new file mode 100644 index 0000000..96bd922 --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test.suitebuilder.annotation; + +import com.android.internal.util.Predicate; +import android.test.suitebuilder.TestMethod; + +import java.lang.annotation.Annotation; + +/** + * A predicate that checks to see if a the method represented by {@link TestMethod} has a certain + * annotation on it. Consider using the public {@link HasAnnotation} class instead of this class. + * + * {@hide} Not needed for 1.0 SDK. + */ +class HasMethodAnnotation implements Predicate<TestMethod> { + + private final Class<? extends Annotation> annotationClass; + + public HasMethodAnnotation(Class<? extends Annotation> annotationClass) { + this.annotationClass = annotationClass; + } + + public boolean apply(TestMethod testMethod) { + return testMethod.getAnnotation(annotationClass) != null; + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/package.html b/test-runner/android/test/suitebuilder/annotation/package.html new file mode 100644 index 0000000..ffba2e9 --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes supporting the test runner classes. +</BODY> +</HTML> diff --git a/test-runner/android/test/suitebuilder/package.html b/test-runner/android/test/suitebuilder/package.html new file mode 100644 index 0000000..ffba2e9 --- /dev/null +++ b/test-runner/android/test/suitebuilder/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes supporting the test runner classes. +</BODY> +</HTML> |