InstrumentationTestCase.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 17package android.test; 18 19import junit.framework.TestCase; 20 21import android.app.Activity; 22import android.app.Instrumentation; 23import android.content.Intent; 24import android.os.Bundle; 25import android.util.Log; 26import android.view.KeyEvent; 27 28import java.lang.reflect.Field; 29import java.lang.reflect.Method; 30import java.lang.reflect.Modifier; 31import java.lang.reflect.InvocationTargetException; 32 33/** 34 * A test case that has access to {@link Instrumentation}. See 35 * <code>InstrumentationTestRunner</code>. 36 */ 37public class InstrumentationTestCase extends TestCase { 38 39 private Instrumentation mInstrumentation; 40 41 /** 42 * Injects instrumentation into this test case. This method is 43 * called by the test runner during test setup. 44 * 45 * @param instrumentation the instrumentation to use with this instance 46 */ 47 public void injectInsrumentation(Instrumentation instrumentation) { 48 mInstrumentation = instrumentation; 49 } 50 51 /** 52 * Inheritors can access the instrumentation using this. 53 * @return instrumentation 54 */ 55 public Instrumentation getInstrumentation() { 56 return mInstrumentation; 57 } 58 59 /** 60 * Utility method for launching an activity. 61 * @param pkg The package hosting the activity to be launched. 62 * @param activityCls The activity class to launch. 63 * @param extras Optional extra stuff to pass to the activity. 64 * @return The activity, or null if non launched. 65 */ 66 @SuppressWarnings("unchecked") 67 public final <T extends Activity> T launchActivity( 68 String pkg, 69 Class<T> activityCls, 70 Bundle extras) { 71 Intent intent = new Intent(Intent.ACTION_MAIN); 72 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 73 intent.setClassName(pkg, activityCls.getName()); 74 if (extras != null) { 75 intent.putExtras(extras); 76 } 77 T activity = (T) getInstrumentation().startActivitySync(intent); 78 getInstrumentation().waitForIdleSync(); 79 return activity; 80 } 81 82 /** 83 * Runs the current unit test. If the unit test is annotated with 84 * {@link android.test.UiThreadTest}, the test is run on the UI thread. 85 */ 86 protected void runTest() throws Throwable { 87 String fName = getName(); 88 assertNotNull(fName); 89 Method method = null; 90 try { 91 // use getMethod to get all public inherited 92 // methods. getDeclaredMethods returns all 93 // methods of this class but excludes the 94 // inherited ones. 95 method = getClass().getMethod(fName, (Class[]) null); 96 } catch (NoSuchMethodException e) { 97 fail("Method \""+fName+"\" not found"); 98 } 99 100 if (!Modifier.isPublic(method.getModifiers())) { 101 fail("Method \""+fName+"\" should be public"); 102 } 103 104 int runCount = 1; 105 if (method.isAnnotationPresent(FlakyTest.class)) { 106 runCount = method.getAnnotation(FlakyTest.class).tolerance(); 107 } 108 109 if (method.isAnnotationPresent(UiThreadTest.class)) { 110 final int tolerance = runCount; 111 final Method testMethod = method; 112 final Throwable[] exceptions = new Throwable[1]; 113 getInstrumentation().runOnMainSync(new Runnable() { 114 public void run() { 115 try { 116 runMethod(testMethod, tolerance); 117 } catch (Throwable throwable) { 118 exceptions[0] = throwable; 119 } 120 } 121 }); 122 if (exceptions[0] != null) { 123 throw exceptions[0]; 124 } 125 } else { 126 runMethod(method, runCount); 127 } 128 } 129 130 private void runMethod(Method runMethod, int tolerance) throws Throwable { 131 Throwable exception = null; 132 133 int runCount = 0; 134 do { 135 try { 136 runMethod.invoke(this, (Object[]) null); 137 exception = null; 138 } catch (InvocationTargetException e) { 139 e.fillInStackTrace(); 140 exception = e.getTargetException(); 141 } catch (IllegalAccessException e) { 142 e.fillInStackTrace(); 143 exception = e; 144 } finally { 145 runCount++; 146 } 147 } while ((runCount < tolerance) && (exception != null)); 148 149 if (exception != null) { 150 throw exception; 151 } 152 } 153 154 /** 155 * Sends a series of key events through instrumentation and waits for idle. The sequence 156 * of keys is a string containing the key names as specified in KeyEvent, without the 157 * KEYCODE_ prefix. For instance: sendKeys("DPAD_LEFT A B C DPAD_CENTER"). Each key can 158 * be repeated by using the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use 159 * the following: sendKeys("2*DPAD_LEFT"). 160 * 161 * @param keysSequence The sequence of keys. 162 */ 163 public void sendKeys(String keysSequence) { 164 final String[] keys = keysSequence.split(" "); 165 final int count = keys.length; 166 167 final Instrumentation instrumentation = getInstrumentation(); 168 169 for (int i = 0; i < count; i++) { 170 String key = keys[i]; 171 int repeater = key.indexOf('*'); 172 173 int keyCount; 174 try { 175 keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater)); 176 } catch (NumberFormatException e) { 177 Log.w("ActivityTestCase", "Invalid repeat count: " + key); 178 continue; 179 } 180 181 if (repeater != -1) { 182 key = key.substring(repeater + 1); 183 } 184 185 for (int j = 0; j < keyCount; j++) { 186 try { 187 final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key); 188 final int keyCode = keyCodeField.getInt(null); 189 instrumentation.sendKeyDownUpSync(keyCode); 190 } catch (NoSuchFieldException e) { 191 Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); 192 break; 193 } catch (IllegalAccessException e) { 194 Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); 195 break; 196 } 197 } 198 } 199 200 instrumentation.waitForIdleSync(); 201 } 202 203 /** 204 * Sends a series of key events through instrumentation and waits for idle. For instance: 205 * sendKeys(KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER). 206 * 207 * @param keys The series of key codes to send through instrumentation. 208 */ 209 public void sendKeys(int... keys) { 210 final int count = keys.length; 211 final Instrumentation instrumentation = getInstrumentation(); 212 213 for (int i = 0; i < count; i++) { 214 instrumentation.sendKeyDownUpSync(keys[i]); 215 } 216 217 instrumentation.waitForIdleSync(); 218 } 219 220 /** 221 * Sends a series of key events through instrumentation and waits for idle. Each key code 222 * must be preceded by the number of times the key code must be sent. For instance: 223 * sendRepeatedKeys(1, KEYCODE_DPAD_CENTER, 2, KEYCODE_DPAD_LEFT). 224 * 225 * @param keys The series of key repeats and codes to send through instrumentation. 226 */ 227 public void sendRepeatedKeys(int... keys) { 228 final int count = keys.length; 229 if ((count & 0x1) == 0x1) { 230 throw new IllegalArgumentException("The size of the keys array must " 231 + "be a multiple of 2"); 232 } 233 234 final Instrumentation instrumentation = getInstrumentation(); 235 236 for (int i = 0; i < count; i += 2) { 237 final int keyCount = keys[i]; 238 final int keyCode = keys[i + 1]; 239 for (int j = 0; j < keyCount; j++) { 240 instrumentation.sendKeyDownUpSync(keyCode); 241 } 242 } 243 244 instrumentation.waitForIdleSync(); 245 } 246 247 /** 248 * Make sure all resources are cleaned up and garbage collected before moving on to the next 249 * test. Subclasses that override this method should make sure they call super.tearDown() 250 * at the end of the overriding method. 251 * 252 * @throws Exception 253 */ 254 protected void tearDown() throws Exception { 255 Runtime.getRuntime().gc(); 256 Runtime.getRuntime().runFinalization(); 257 Runtime.getRuntime().gc(); 258 super.tearDown(); 259 } 260} 261