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 android.app.Instrumentation;
20import android.content.Context;
21import android.os.PerformanceCollector.PerformanceResultsWriter;
22
23import com.google.android.collect.Lists;
24
25import junit.framework.Test;
26import junit.framework.TestCase;
27import junit.framework.TestListener;
28import junit.framework.TestResult;
29import junit.framework.TestSuite;
30import junit.runner.BaseTestRunner;
31
32import java.lang.reflect.Constructor;
33import java.lang.reflect.InvocationTargetException;
34import java.util.List;
35
36public class AndroidTestRunner extends BaseTestRunner {
37
38    private TestResult mTestResult;
39    private String mTestClassName;
40    private List<TestCase> mTestCases;
41    private Context mContext;
42    private boolean mSkipExecution = false;
43
44    private List<TestListener> mTestListeners = Lists.newArrayList();
45    private Instrumentation mInstrumentation;
46    private PerformanceResultsWriter mPerfWriter;
47
48    @SuppressWarnings("unchecked")
49    public void setTestClassName(String testClassName, String testMethodName) {
50        Class testClass = loadTestClass(testClassName);
51
52        if (shouldRunSingleTestMethod(testMethodName, testClass)) {
53            TestCase testCase = buildSingleTestMethod(testClass, testMethodName);
54            mTestCases = Lists.newArrayList(testCase);
55            mTestClassName = testClass.getSimpleName();
56        } else {
57            setTest(getTest(testClass), testClass);
58        }
59    }
60
61    public void setTest(Test test) {
62        setTest(test, test.getClass());
63    }
64
65    private void setTest(Test test, Class<? extends Test> testClass) {
66        mTestCases = (List<TestCase>) TestCaseUtil.getTests(test, true);
67        if (TestSuite.class.isAssignableFrom(testClass)) {
68            mTestClassName = TestCaseUtil.getTestName(test);
69        } else {
70            mTestClassName = testClass.getSimpleName();
71        }
72    }
73
74    public void clearTestListeners() {
75        mTestListeners.clear();
76    }
77
78    public void addTestListener(TestListener testListener) {
79        if (testListener != null) {
80            mTestListeners.add(testListener);
81        }
82    }
83
84    @SuppressWarnings("unchecked")
85    private Class<? extends Test> loadTestClass(String testClassName) {
86        try {
87            return (Class<? extends Test>) mContext.getClassLoader().loadClass(testClassName);
88        } catch (ClassNotFoundException e) {
89            runFailed("Could not find test class. Class: " + testClassName);
90        }
91        return null;
92    }
93
94    private TestCase buildSingleTestMethod(Class testClass, String testMethodName) {
95        try {
96            Constructor c = testClass.getConstructor();
97            return newSingleTestMethod(testClass, testMethodName, c);
98        } catch (NoSuchMethodException e) {
99        }
100
101        try {
102            Constructor c = testClass.getConstructor(String.class);
103            return newSingleTestMethod(testClass, testMethodName, c, testMethodName);
104        } catch (NoSuchMethodException e) {
105        }
106
107        return null;
108    }
109
110    private TestCase newSingleTestMethod(Class testClass, String testMethodName,
111            Constructor constructor, Object... args) {
112        try {
113            TestCase testCase = (TestCase) constructor.newInstance(args);
114            testCase.setName(testMethodName);
115            return testCase;
116        } catch (IllegalAccessException e) {
117            runFailed("Could not access test class. Class: " + testClass.getName());
118        } catch (InstantiationException e) {
119            runFailed("Could not instantiate test class. Class: " + testClass.getName());
120        } catch (IllegalArgumentException e) {
121            runFailed("Illegal argument passed to constructor. Class: " + testClass.getName());
122        } catch (InvocationTargetException e) {
123            runFailed("Constructor thew an exception. Class: " + testClass.getName());
124        }
125        return null;
126    }
127
128    private boolean shouldRunSingleTestMethod(String testMethodName,
129            Class<? extends Test> testClass) {
130        return testMethodName != null && TestCase.class.isAssignableFrom(testClass);
131    }
132
133    private Test getTest(Class clazz) {
134        if (TestSuiteProvider.class.isAssignableFrom(clazz)) {
135            try {
136                TestSuiteProvider testSuiteProvider =
137                        (TestSuiteProvider) clazz.getConstructor().newInstance();
138                return testSuiteProvider.getTestSuite();
139            } catch (InstantiationException e) {
140                runFailed("Could not instantiate test suite provider. Class: " + clazz.getName());
141            } catch (IllegalAccessException e) {
142                runFailed("Illegal access of test suite provider. Class: " + clazz.getName());
143            } catch (InvocationTargetException e) {
144                runFailed("Invocation exception test suite provider. Class: " + clazz.getName());
145            } catch (NoSuchMethodException e) {
146                runFailed("No such method on test suite provider. Class: " + clazz.getName());
147            }
148        }
149        return getTest(clazz.getName());
150    }
151
152    protected TestResult createTestResult() {
153        if (mSkipExecution) {
154            return new NoExecTestResult();
155        }
156        return new TestResult();
157    }
158
159    void setSkipExecution(boolean skip) {
160        mSkipExecution = skip;
161    }
162
163    public List<TestCase> getTestCases() {
164        return mTestCases;
165    }
166
167    public String getTestClassName() {
168        return mTestClassName;
169    }
170
171    public TestResult getTestResult() {
172        return mTestResult;
173    }
174
175    public void runTest() {
176        runTest(createTestResult());
177    }
178
179    public void runTest(TestResult testResult) {
180        mTestResult = testResult;
181
182        for (TestListener testListener : mTestListeners) {
183            mTestResult.addListener(testListener);
184        }
185
186        Context testContext = mInstrumentation == null ? mContext : mInstrumentation.getContext();
187        for (TestCase testCase : mTestCases) {
188            setContextIfAndroidTestCase(testCase, mContext, testContext);
189            setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);
190            setPerformanceWriterIfPerformanceCollectorTestCase(testCase, mPerfWriter);
191            testCase.run(mTestResult);
192        }
193    }
194
195    private void setContextIfAndroidTestCase(Test test, Context context, Context testContext) {
196        if (AndroidTestCase.class.isAssignableFrom(test.getClass())) {
197            ((AndroidTestCase) test).setContext(context);
198            ((AndroidTestCase) test).setTestContext(testContext);
199        }
200    }
201
202    public void setContext(Context context) {
203        mContext = context;
204    }
205
206    private void setInstrumentationIfInstrumentationTestCase(
207            Test test, Instrumentation instrumentation) {
208        if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
209            ((InstrumentationTestCase) test).injectInstrumentation(instrumentation);
210        }
211    }
212
213    private void setPerformanceWriterIfPerformanceCollectorTestCase(
214            Test test, PerformanceResultsWriter writer) {
215        if (PerformanceCollectorTestCase.class.isAssignableFrom(test.getClass())) {
216            ((PerformanceCollectorTestCase) test).setPerformanceResultsWriter(writer);
217        }
218    }
219
220    public void setInstrumentation(Instrumentation instrumentation) {
221        mInstrumentation = instrumentation;
222    }
223
224    /**
225     * @deprecated Incorrect spelling,
226     * use {@link #setInstrumentation(android.app.Instrumentation)} instead.
227     */
228    @Deprecated
229    public void setInstrumentaiton(Instrumentation instrumentation) {
230        setInstrumentation(instrumentation);
231    }
232
233    /**
234     * {@hide} Pending approval for public API.
235     */
236    public void setPerformanceResultsWriter(PerformanceResultsWriter writer) {
237        mPerfWriter = writer;
238    }
239
240    @Override
241    protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
242        return mContext.getClassLoader().loadClass(suiteClassName);
243    }
244
245    public void testStarted(String testName) {
246    }
247
248    public void testEnded(String testName) {
249    }
250
251    public void testFailed(int status, Test test, Throwable t) {
252    }
253
254    protected void runFailed(String message) {
255        throw new RuntimeException(message);
256    }
257}
258