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