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