Home | History | Annotate | Download | only in test
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.test;
     18 
     19 import android.app.Activity;
     20 import android.app.Instrumentation;
     21 import android.content.Intent;
     22 import android.os.Bundle;
     23 import android.util.Log;
     24 import android.view.KeyEvent;
     25 
     26 import java.lang.reflect.Field;
     27 import java.lang.reflect.InvocationTargetException;
     28 import java.lang.reflect.Method;
     29 import java.lang.reflect.Modifier;
     30 
     31 import junit.framework.TestCase;
     32 
     33 /**
     34  * A test case that has access to {@link Instrumentation}.
     35  *
     36  * @deprecated Use
     37  * <a href="{@docRoot}reference/android/support/test/InstrumentationRegistry.html">
     38  * InstrumentationRegistry</a> instead. New tests should be written using the
     39  * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
     40  */
     41 @Deprecated
     42 public class InstrumentationTestCase extends TestCase {
     43 
     44     private Instrumentation mInstrumentation;
     45 
     46     /**
     47      * Injects instrumentation into this test case. This method is
     48      * called by the test runner during test setup.
     49      *
     50      * @param instrumentation the instrumentation to use with this instance
     51      */
     52     public void injectInstrumentation(Instrumentation instrumentation) {
     53         mInstrumentation = instrumentation;
     54     }
     55 
     56     /**
     57      * Injects instrumentation into this test case. This method is
     58      * called by the test runner during test setup.
     59      *
     60      * @param instrumentation the instrumentation to use with this instance
     61      *
     62      * @deprecated Incorrect spelling,
     63      * use {@link #injectInstrumentation(android.app.Instrumentation)} instead.
     64      */
     65     @Deprecated
     66     public void injectInsrumentation(Instrumentation instrumentation) {
     67         injectInstrumentation(instrumentation);
     68     }
     69 
     70     /**
     71      * Inheritors can access the instrumentation using this.
     72      * @return instrumentation
     73      */
     74     public Instrumentation getInstrumentation() {
     75         return mInstrumentation;
     76     }
     77 
     78     /**
     79      * Utility method for launching an activity.
     80      *
     81      * <p>The {@link Intent} used to launch the Activity is:
     82      *  action = {@link Intent#ACTION_MAIN}
     83      *  extras = null, unless a custom bundle is provided here
     84      * All other fields are null or empty.
     85      *
     86      * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the
     87      * package hosting the activity to be launched, which is specified in the AndroidManifest.xml
     88      * file.  This is not necessarily the same as the java package name.
     89      *
     90      * @param pkg The package hosting the activity to be launched.
     91      * @param activityCls The activity class to launch.
     92      * @param extras Optional extra stuff to pass to the activity.
     93      * @return The activity, or null if non launched.
     94      */
     95     public final <T extends Activity> T launchActivity(
     96             String pkg,
     97             Class<T> activityCls,
     98             Bundle extras) {
     99         Intent intent = new Intent(Intent.ACTION_MAIN);
    100         if (extras != null) {
    101             intent.putExtras(extras);
    102         }
    103         return launchActivityWithIntent(pkg, activityCls, intent);
    104     }
    105 
    106     /**
    107      * Utility method for launching an activity with a specific Intent.
    108      *
    109      * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the
    110      * package hosting the activity to be launched, which is specified in the AndroidManifest.xml
    111      * file.  This is not necessarily the same as the java package name.
    112      *
    113      * @param pkg The package hosting the activity to be launched.
    114      * @param activityCls The activity class to launch.
    115      * @param intent The intent to launch with
    116      * @return The activity, or null if non launched.
    117      */
    118     @SuppressWarnings("unchecked")
    119     public final <T extends Activity> T launchActivityWithIntent(
    120             String pkg,
    121             Class<T> activityCls,
    122             Intent intent) {
    123         intent.setClassName(pkg, activityCls.getName());
    124         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    125         T activity = (T) getInstrumentation().startActivitySync(intent);
    126         getInstrumentation().waitForIdleSync();
    127         return activity;
    128     }
    129 
    130     /**
    131      * Helper for running portions of a test on the UI thread.
    132      *
    133      * Note, in most cases it is simpler to annotate the test method with
    134      * {@link android.test.UiThreadTest}, which will run the entire test method on the UI thread.
    135      * Use this method if you need to switch in and out of the UI thread to perform your test.
    136      *
    137      * @param r runnable containing test code in the {@link Runnable#run()} method
    138      */
    139     public void runTestOnUiThread(final Runnable r) throws Throwable {
    140         final Throwable[] exceptions = new Throwable[1];
    141         getInstrumentation().runOnMainSync(new Runnable() {
    142             public void run() {
    143                 try {
    144                     r.run();
    145                 } catch (Throwable throwable) {
    146                     exceptions[0] = throwable;
    147                 }
    148             }
    149         });
    150         if (exceptions[0] != null) {
    151             throw exceptions[0];
    152         }
    153     }
    154 
    155     /**
    156      * Runs the current unit test. If the unit test is annotated with
    157      * {@link android.test.UiThreadTest}, the test is run on the UI thread.
    158      */
    159     @Override
    160     protected void runTest() throws Throwable {
    161         String fName = getName();
    162         assertNotNull(fName);
    163         Method method = null;
    164         try {
    165             // use getMethod to get all public inherited
    166             // methods. getDeclaredMethods returns all
    167             // methods of this class but excludes the
    168             // inherited ones.
    169             method = getClass().getMethod(fName, (Class[]) null);
    170         } catch (NoSuchMethodException e) {
    171             fail("Method \""+fName+"\" not found");
    172         }
    173 
    174         if (!Modifier.isPublic(method.getModifiers())) {
    175             fail("Method \""+fName+"\" should be public");
    176         }
    177 
    178         int runCount = 1;
    179         boolean isRepetitive = false;
    180         if (method.isAnnotationPresent(FlakyTest.class)) {
    181             runCount = method.getAnnotation(FlakyTest.class).tolerance();
    182         } else if (method.isAnnotationPresent(RepetitiveTest.class)) {
    183             runCount = method.getAnnotation(RepetitiveTest.class).numIterations();
    184             isRepetitive = true;
    185         }
    186 
    187         if (method.isAnnotationPresent(UiThreadTest.class)) {
    188             final int tolerance = runCount;
    189             final boolean repetitive = isRepetitive;
    190             final Method testMethod = method;
    191             final Throwable[] exceptions = new Throwable[1];
    192             getInstrumentation().runOnMainSync(new Runnable() {
    193                 public void run() {
    194                     try {
    195                         runMethod(testMethod, tolerance, repetitive);
    196                     } catch (Throwable throwable) {
    197                         exceptions[0] = throwable;
    198                     }
    199                 }
    200             });
    201             if (exceptions[0] != null) {
    202                 throw exceptions[0];
    203             }
    204         } else {
    205             runMethod(method, runCount, isRepetitive);
    206         }
    207     }
    208 
    209     // For backwards-compatibility after adding isRepetitive
    210     private void runMethod(Method runMethod, int tolerance) throws Throwable {
    211         runMethod(runMethod, tolerance, false);
    212     }
    213 
    214     private void runMethod(Method runMethod, int tolerance, boolean isRepetitive) throws Throwable {
    215         Throwable exception = null;
    216 
    217         int runCount = 0;
    218         do {
    219             try {
    220                 runMethod.invoke(this, (Object[]) null);
    221                 exception = null;
    222             } catch (InvocationTargetException e) {
    223                 e.fillInStackTrace();
    224                 exception = e.getTargetException();
    225             } catch (IllegalAccessException e) {
    226                 e.fillInStackTrace();
    227                 exception = e;
    228             } finally {
    229                 runCount++;
    230                 // Report current iteration number, if test is repetitive
    231                 if (isRepetitive) {
    232                     Bundle iterations = new Bundle();
    233                     iterations.putInt("currentiterations", runCount);
    234                     getInstrumentation().sendStatus(2, iterations);
    235                 }
    236             }
    237         } while ((runCount < tolerance) && (isRepetitive || exception != null));
    238 
    239         if (exception != null) {
    240             throw exception;
    241         }
    242     }
    243 
    244     /**
    245      * Sends a series of key events through instrumentation and waits for idle. The sequence
    246      * of keys is a string containing the key names as specified in KeyEvent, without the
    247      * KEYCODE_ prefix. For instance: sendKeys("DPAD_LEFT A B C DPAD_CENTER"). Each key can
    248      * be repeated by using the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use
    249      * the following: sendKeys("2*DPAD_LEFT").
    250      *
    251      * @param keysSequence The sequence of keys.
    252      */
    253     public void sendKeys(String keysSequence) {
    254         final String[] keys = keysSequence.split(" ");
    255         final int count = keys.length;
    256 
    257         final Instrumentation instrumentation = getInstrumentation();
    258 
    259         for (int i = 0; i < count; i++) {
    260             String key = keys[i];
    261             int repeater = key.indexOf('*');
    262 
    263             int keyCount;
    264             try {
    265                 keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater));
    266             } catch (NumberFormatException e) {
    267                 Log.w("ActivityTestCase", "Invalid repeat count: " + key);
    268                 continue;
    269             }
    270 
    271             if (repeater != -1) {
    272                 key = key.substring(repeater + 1);
    273             }
    274 
    275             for (int j = 0; j < keyCount; j++) {
    276                 try {
    277                     final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key);
    278                     final int keyCode = keyCodeField.getInt(null);
    279                     try {
    280                         instrumentation.sendKeyDownUpSync(keyCode);
    281                     } catch (SecurityException e) {
    282                         // Ignore security exceptions that are now thrown
    283                         // when trying to send to another app, to retain
    284                         // compatibility with existing tests.
    285                     }
    286                 } catch (NoSuchFieldException e) {
    287                     Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
    288                     break;
    289                 } catch (IllegalAccessException e) {
    290                     Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
    291                     break;
    292                 }
    293             }
    294         }
    295 
    296         instrumentation.waitForIdleSync();
    297     }
    298 
    299     /**
    300      * Sends a series of key events through instrumentation and waits for idle. For instance:
    301      * sendKeys(KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER).
    302      *
    303      * @param keys The series of key codes to send through instrumentation.
    304      */
    305     public void sendKeys(int... keys) {
    306         final int count = keys.length;
    307         final Instrumentation instrumentation = getInstrumentation();
    308 
    309         for (int i = 0; i < count; i++) {
    310             try {
    311                 instrumentation.sendKeyDownUpSync(keys[i]);
    312             } catch (SecurityException e) {
    313                 // Ignore security exceptions that are now thrown
    314                 // when trying to send to another app, to retain
    315                 // compatibility with existing tests.
    316             }
    317         }
    318 
    319         instrumentation.waitForIdleSync();
    320     }
    321 
    322     /**
    323      * Sends a series of key events through instrumentation and waits for idle. Each key code
    324      * must be preceded by the number of times the key code must be sent. For instance:
    325      * sendRepeatedKeys(1, KEYCODE_DPAD_CENTER, 2, KEYCODE_DPAD_LEFT).
    326      *
    327      * @param keys The series of key repeats and codes to send through instrumentation.
    328      */
    329     public void sendRepeatedKeys(int... keys) {
    330         final int count = keys.length;
    331         if ((count & 0x1) == 0x1) {
    332             throw new IllegalArgumentException("The size of the keys array must "
    333                     + "be a multiple of 2");
    334         }
    335 
    336         final Instrumentation instrumentation = getInstrumentation();
    337 
    338         for (int i = 0; i < count; i += 2) {
    339             final int keyCount = keys[i];
    340             final int keyCode = keys[i + 1];
    341             for (int j = 0; j < keyCount; j++) {
    342                 try {
    343                     instrumentation.sendKeyDownUpSync(keyCode);
    344                 } catch (SecurityException e) {
    345                     // Ignore security exceptions that are now thrown
    346                     // when trying to send to another app, to retain
    347                     // compatibility with existing tests.
    348                 }
    349             }
    350         }
    351 
    352         instrumentation.waitForIdleSync();
    353     }
    354 
    355     /**
    356      * Make sure all resources are cleaned up and garbage collected before moving on to the next
    357      * test. Subclasses that override this method should make sure they call super.tearDown()
    358      * at the end of the overriding method.
    359      *
    360      * @throws Exception
    361      */
    362     @Override
    363     protected void tearDown() throws Exception {
    364         Runtime.getRuntime().gc();
    365         Runtime.getRuntime().runFinalization();
    366         Runtime.getRuntime().gc();
    367         super.tearDown();
    368     }
    369 }