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