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