diff options
Diffstat (limited to 'test-base/src/android/test/InstrumentationTestCase.java')
-rw-r--r-- | test-base/src/android/test/InstrumentationTestCase.java | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/test-base/src/android/test/InstrumentationTestCase.java b/test-base/src/android/test/InstrumentationTestCase.java new file mode 100644 index 000000000000..6b79314a4385 --- /dev/null +++ b/test-base/src/android/test/InstrumentationTestCase.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.test; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import junit.framework.TestCase; + +/** + * A test case that has access to {@link Instrumentation}. + * + * @deprecated Use + * <a href="{@docRoot}reference/android/support/test/InstrumentationRegistry.html"> + * InstrumentationRegistry</a> instead. New tests should be written using the + * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>. + */ +@Deprecated +public class InstrumentationTestCase extends TestCase { + + private Instrumentation mInstrumentation; + + /** + * Injects instrumentation into this test case. This method is + * called by the test runner during test setup. + * + * @param instrumentation the instrumentation to use with this instance + */ + public void injectInstrumentation(Instrumentation instrumentation) { + mInstrumentation = instrumentation; + } + + /** + * Injects instrumentation into this test case. This method is + * called by the test runner during test setup. + * + * @param instrumentation the instrumentation to use with this instance + * + * @deprecated Incorrect spelling, + * use {@link #injectInstrumentation(android.app.Instrumentation)} instead. + */ + @Deprecated + public void injectInsrumentation(Instrumentation instrumentation) { + injectInstrumentation(instrumentation); + } + + /** + * Inheritors can access the instrumentation using this. + * @return instrumentation + */ + public Instrumentation getInstrumentation() { + return mInstrumentation; + } + + /** + * Utility method for launching an activity. + * + * <p>The {@link Intent} used to launch the Activity is: + * action = {@link Intent#ACTION_MAIN} + * extras = null, unless a custom bundle is provided here + * All other fields are null or empty. + * + * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the + * package hosting the activity to be launched, which is specified in the AndroidManifest.xml + * file. This is not necessarily the same as the java package name. + * + * @param pkg The package hosting the activity to be launched. + * @param activityCls The activity class to launch. + * @param extras Optional extra stuff to pass to the activity. + * @return The activity, or null if non launched. + */ + public final <T extends Activity> T launchActivity( + String pkg, + Class<T> activityCls, + Bundle extras) { + Intent intent = new Intent(Intent.ACTION_MAIN); + if (extras != null) { + intent.putExtras(extras); + } + return launchActivityWithIntent(pkg, activityCls, intent); + } + + /** + * Utility method for launching an activity with a specific Intent. + * + * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the + * package hosting the activity to be launched, which is specified in the AndroidManifest.xml + * file. This is not necessarily the same as the java package name. + * + * @param pkg The package hosting the activity to be launched. + * @param activityCls The activity class to launch. + * @param intent The intent to launch with + * @return The activity, or null if non launched. + */ + @SuppressWarnings("unchecked") + public final <T extends Activity> T launchActivityWithIntent( + String pkg, + Class<T> activityCls, + Intent intent) { + intent.setClassName(pkg, activityCls.getName()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + T activity = (T) getInstrumentation().startActivitySync(intent); + getInstrumentation().waitForIdleSync(); + return activity; + } + + /** + * Helper for running portions of a test on the UI thread. + * + * Note, in most cases it is simpler to annotate the test method with + * {@link android.test.UiThreadTest}, which will run the entire test method on the UI thread. + * Use this method if you need to switch in and out of the UI thread to perform your test. + * + * @param r runnable containing test code in the {@link Runnable#run()} method + */ + public void runTestOnUiThread(final Runnable r) throws Throwable { + final Throwable[] exceptions = new Throwable[1]; + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + try { + r.run(); + } catch (Throwable throwable) { + exceptions[0] = throwable; + } + } + }); + if (exceptions[0] != null) { + throw exceptions[0]; + } + } + + /** + * Runs the current unit test. If the unit test is annotated with + * {@link android.test.UiThreadTest}, the test is run on the UI thread. + */ + @Override + protected void runTest() throws Throwable { + String fName = getName(); + assertNotNull(fName); + Method method = null; + try { + // use getMethod to get all public inherited + // methods. getDeclaredMethods returns all + // methods of this class but excludes the + // inherited ones. + method = getClass().getMethod(fName, (Class[]) null); + } catch (NoSuchMethodException e) { + fail("Method \""+fName+"\" not found"); + } + + if (!Modifier.isPublic(method.getModifiers())) { + fail("Method \""+fName+"\" should be public"); + } + + int runCount = 1; + boolean isRepetitive = false; + if (method.isAnnotationPresent(FlakyTest.class)) { + runCount = method.getAnnotation(FlakyTest.class).tolerance(); + } else if (method.isAnnotationPresent(RepetitiveTest.class)) { + runCount = method.getAnnotation(RepetitiveTest.class).numIterations(); + isRepetitive = true; + } + + if (method.isAnnotationPresent(UiThreadTest.class)) { + final int tolerance = runCount; + final boolean repetitive = isRepetitive; + final Method testMethod = method; + final Throwable[] exceptions = new Throwable[1]; + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + try { + runMethod(testMethod, tolerance, repetitive); + } catch (Throwable throwable) { + exceptions[0] = throwable; + } + } + }); + if (exceptions[0] != null) { + throw exceptions[0]; + } + } else { + runMethod(method, runCount, isRepetitive); + } + } + + // For backwards-compatibility after adding isRepetitive + private void runMethod(Method runMethod, int tolerance) throws Throwable { + runMethod(runMethod, tolerance, false); + } + + private void runMethod(Method runMethod, int tolerance, boolean isRepetitive) throws Throwable { + Throwable exception = null; + + int runCount = 0; + do { + try { + runMethod.invoke(this, (Object[]) null); + exception = null; + } catch (InvocationTargetException e) { + e.fillInStackTrace(); + exception = e.getTargetException(); + } catch (IllegalAccessException e) { + e.fillInStackTrace(); + exception = e; + } finally { + runCount++; + // Report current iteration number, if test is repetitive + if (isRepetitive) { + Bundle iterations = new Bundle(); + iterations.putInt("currentiterations", runCount); + getInstrumentation().sendStatus(2, iterations); + } + } + } while ((runCount < tolerance) && (isRepetitive || exception != null)); + + if (exception != null) { + throw exception; + } + } + + /** + * Sends a series of key events through instrumentation and waits for idle. The sequence + * of keys is a string containing the key names as specified in KeyEvent, without the + * KEYCODE_ prefix. For instance: sendKeys("DPAD_LEFT A B C DPAD_CENTER"). Each key can + * be repeated by using the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use + * the following: sendKeys("2*DPAD_LEFT"). + * + * @param keysSequence The sequence of keys. + */ + public void sendKeys(String keysSequence) { + final String[] keys = keysSequence.split(" "); + final int count = keys.length; + + final Instrumentation instrumentation = getInstrumentation(); + + for (int i = 0; i < count; i++) { + String key = keys[i]; + int repeater = key.indexOf('*'); + + int keyCount; + try { + keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater)); + } catch (NumberFormatException e) { + Log.w("ActivityTestCase", "Invalid repeat count: " + key); + continue; + } + + if (repeater != -1) { + key = key.substring(repeater + 1); + } + + for (int j = 0; j < keyCount; j++) { + try { + final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key); + final int keyCode = keyCodeField.getInt(null); + try { + instrumentation.sendKeyDownUpSync(keyCode); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } + } catch (NoSuchFieldException e) { + Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); + break; + } catch (IllegalAccessException e) { + Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); + break; + } + } + } + + instrumentation.waitForIdleSync(); + } + + /** + * Sends a series of key events through instrumentation and waits for idle. For instance: + * sendKeys(KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER). + * + * @param keys The series of key codes to send through instrumentation. + */ + public void sendKeys(int... keys) { + final int count = keys.length; + final Instrumentation instrumentation = getInstrumentation(); + + for (int i = 0; i < count; i++) { + try { + instrumentation.sendKeyDownUpSync(keys[i]); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } + } + + instrumentation.waitForIdleSync(); + } + + /** + * Sends a series of key events through instrumentation and waits for idle. Each key code + * must be preceded by the number of times the key code must be sent. For instance: + * sendRepeatedKeys(1, KEYCODE_DPAD_CENTER, 2, KEYCODE_DPAD_LEFT). + * + * @param keys The series of key repeats and codes to send through instrumentation. + */ + public void sendRepeatedKeys(int... keys) { + final int count = keys.length; + if ((count & 0x1) == 0x1) { + throw new IllegalArgumentException("The size of the keys array must " + + "be a multiple of 2"); + } + + final Instrumentation instrumentation = getInstrumentation(); + + for (int i = 0; i < count; i += 2) { + final int keyCount = keys[i]; + final int keyCode = keys[i + 1]; + for (int j = 0; j < keyCount; j++) { + try { + instrumentation.sendKeyDownUpSync(keyCode); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } + } + } + + instrumentation.waitForIdleSync(); + } + + /** + * Make 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 { + Runtime.getRuntime().gc(); + Runtime.getRuntime().runFinalization(); + Runtime.getRuntime().gc(); + super.tearDown(); + } +}
\ No newline at end of file |