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