1 /*
2  * Copyright (C) 2010 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 package com.android.cts.tradefed.testtype;
17 
18 import com.android.cts.tradefed.build.CtsBuildHelper;
19 import com.android.ddmlib.Log;
20 import com.android.ddmlib.testrunner.TestIdentifier;
21 import com.android.tradefed.build.IBuildInfo;
22 import com.android.tradefed.config.ConfigurationException;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.result.ITestInvocationListener;
26 import com.android.tradefed.testtype.DeviceTestResult.RuntimeDeviceNotAvailableException;
27 import com.android.tradefed.testtype.IAbi;
28 import com.android.tradefed.testtype.IAbiReceiver;
29 import com.android.tradefed.testtype.IBuildReceiver;
30 import com.android.tradefed.testtype.IDeviceTest;
31 import com.android.tradefed.testtype.IRemoteTest;
32 import com.android.tradefed.testtype.JUnitRunUtil;
33 import com.android.tradefed.util.CommandStatus;
34 import com.android.tradefed.util.IRunUtil.IRunnableResult;
35 import com.android.tradefed.util.RunUtil;
36 
37 import junit.framework.Test;
38 import junit.framework.TestCase;
39 import junit.framework.TestResult;
40 
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.net.MalformedURLException;
45 import java.net.URL;
46 import java.net.URLClassLoader;
47 import java.util.Collection;
48 
49 /**
50  * A {@link IRemoteTest} that can run a set of JUnit tests from a CTS jar.
51  */
52 public class JarHostTest implements IDeviceTest, IRemoteTest, IBuildReceiver, Test {
53 
54     private static final String LOG_TAG = "JarHostTest";
55 
56     private ITestDevice mDevice;
57     private String mJarFileName;
58     private Collection<TestIdentifier> mTests;
59     private long mTimeoutMs = 10 * 60 * 1000;
60     private String mRunName;
61     private CtsBuildHelper mCtsBuild = null;
62     private IBuildInfo mBuildInfo = null;
63     private IAbi mAbi;
64     private ClassLoader mClassLoader;
65 
66     /**
67      * @param abi the ABI to run the test on
68      */
setAbi(IAbi abi)69     public void setAbi(IAbi abi) {
70         mAbi = abi;
71     }
72 
73     /**
74      * {@inheritDoc}
75      */
76     @Override
setBuild(IBuildInfo buildInfo)77     public void setBuild(IBuildInfo buildInfo) {
78         mBuildInfo = buildInfo;
79         mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
80     }
81 
82     /**
83      * Set the CTS build container.
84      * <p/>
85      * Exposed so unit tests can mock the provided build.
86      *
87      * @param buildHelper
88      */
setBuildHelper(CtsBuildHelper buildHelper)89     void setBuildHelper(CtsBuildHelper buildHelper) {
90         mCtsBuild = buildHelper;
91     }
92 
93     /**
94      * Get the CTS build container.
95      *
96      * @return {@link CtsBuildHelper}
97      */
getBuildHelper()98     CtsBuildHelper getBuildHelper() {
99         return mCtsBuild;
100     }
101 
102     /**
103      * Set the jar file to load tests from.
104      *
105      * @param jarFileName the file name of the CTS host test jar to use
106      */
setJarFileName(String jarFileName)107     void setJarFileName(String jarFileName) {
108         mJarFileName = jarFileName;
109     }
110 
111     /**
112      * Gets the jar file to load tests from.
113      *
114      * @return jarFileName the file name of the CTS host test jar to use
115      */
getJarFileName()116     String getJarFileName() {
117         return mJarFileName;
118     }
119 
120     /**
121      * Sets the collection of tests to run
122      *
123      * @param tests
124      */
setTests(Collection<TestIdentifier> tests)125     void setTests(Collection<TestIdentifier> tests) {
126         mTests = tests;
127     }
128 
129     /**
130      * Gets the collection of tests to run
131      *
132      * @return Collection<{@link TestIdentifier}>
133      */
getTests()134     Collection<TestIdentifier> getTests() {
135         return mTests;
136     }
137 
138     /**
139      * Set the maximum time in ms each test should run.
140      * <p/>
141      * Tests that take longer than this amount will be failed with a {@link TestTimeoutException}
142      * as the cause.
143      *
144      * @param testTimeoutMs
145      */
setTimeout(long testTimeoutMs)146     void setTimeout(long testTimeoutMs) {
147         mTimeoutMs = testTimeoutMs;
148     }
149 
150     /**
151      * Set the run name to report to {@link ITestInvocationListener#testRunStarted(String, int)}
152      *
153      * @param runName
154      */
setRunName(String runName)155     void setRunName(String runName) {
156         mRunName = runName;
157     }
158 
159     /**
160      * {@inheritDoc}
161      */
162     @Override
getDevice()163     public ITestDevice getDevice() {
164         return mDevice;
165     }
166 
167     /**
168      * {@inheritDoc}
169      */
170     @Override
setDevice(ITestDevice device)171     public void setDevice(ITestDevice device) {
172         mDevice = device;
173     }
174 
175     /**
176      * {@inheritDoc}
177      */
178     @SuppressWarnings("unchecked")
179     @Override
run(ITestInvocationListener listener)180     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
181         checkFields();
182         Log.i(LOG_TAG, String.format("Running %s test package from jar, contains %d tests.",
183                 mRunName, mTests.size()));
184         JUnitRunUtil.runTest(listener, this, mRunName);
185     }
186 
187     /**
188      * {@inheritDoc}
189      */
190     @Override
run(TestResult junitResult)191     public void run(TestResult junitResult) {
192         for (TestIdentifier testId : mTests) {
193             Test junitTest = loadTest(testId.getClassName(), testId.getTestName());
194             if (junitTest != null) {
195                 runTest(testId, junitTest, junitResult);
196             }
197         }
198     }
199 
200     /**
201      * Run test with timeout support.
202      */
runTest(TestIdentifier testId, final Test junitTest, final TestResult junitResult)203     private void runTest(TestIdentifier testId, final Test junitTest, final TestResult junitResult) {
204         if (junitTest instanceof IDeviceTest) {
205             ((IDeviceTest)junitTest).setDevice(getDevice());
206         } else if (junitTest instanceof com.android.hosttest.DeviceTest) {
207             // legacy check - see if test uses hosttestlib. This check should go away once
208             // all host tests are converted to use tradefed
209             com.android.hosttest.DeviceTest deviceTest = (com.android.hosttest.DeviceTest)junitTest;
210             deviceTest.setDevice(getDevice().getIDevice());
211             deviceTest.setTestAppPath(mCtsBuild.getTestCasesDir().getAbsolutePath());
212         }
213         if (junitTest instanceof IAbiReceiver) {
214             ((IAbiReceiver)junitTest).setAbi(mAbi);
215         }
216         if (junitTest instanceof IBuildReceiver) {
217             ((IBuildReceiver)junitTest).setBuild(mBuildInfo);
218         }
219         TestRunnable testRunnable = new TestRunnable(junitTest, junitResult);
220 
221         CommandStatus status = RunUtil.getDefault().runTimed(mTimeoutMs, testRunnable, true);
222         if (status.equals(CommandStatus.TIMED_OUT)) {
223             junitResult.addError(junitTest, new TestTimeoutException());
224             junitResult.endTest(junitTest);
225         }
226         if (testRunnable.getException() != null) {
227             throw testRunnable.getException();
228         }
229     }
230 
231     private static class TestRunnable implements IRunnableResult {
232 
233         private final Test mJunitTest;
234         private RuntimeDeviceNotAvailableException mException = null;
235         private TestResult mJunitResult;
236 
TestRunnable(Test junitTest, TestResult junitResult)237         TestRunnable(Test junitTest, TestResult junitResult) {
238             mJunitTest = junitTest;
239             mJunitResult = junitResult;
240         }
241 
242         /**
243          * {@inheritDoc}
244          */
245         @Override
run()246         public boolean run() throws Exception {
247             try {
248                 mJunitTest.run(mJunitResult);
249             } catch (RuntimeDeviceNotAvailableException e) {
250                 mException = e;
251             }
252             return true;
253         }
254 
getException()255         public RuntimeDeviceNotAvailableException getException() {
256             return mException;
257         }
258 
259         /**
260          * {@inheritDoc}
261          */
262         @Override
cancel()263         public void cancel() {
264         }
265 
266     }
267 
268     /**
269      * Load the test with given names from the jar.
270      *
271      * @param className
272      * @param testName
273      * @return the loaded {@link Test} or <code>null</code> if test could not be loaded.
274      */
loadTest(String className, String testName)275     private Test loadTest(String className, String testName) {
276         try {
277             Class<?> testClass = loadClass(className);
278             if (testClass == null) {
279                 return null;
280             }
281             if (TestCase.class.isAssignableFrom(testClass)) {
282                 TestCase testCase = (TestCase)testClass.newInstance();
283                 testCase.setName(testName);
284                 return testCase;
285             } else if (Test.class.isAssignableFrom(testClass)) {
286                 Test test = (Test)testClass.newInstance();
287                 return test;
288             } else {
289                 Log.e(LOG_TAG, String.format("Class '%s' from jar '%s' is not a Test",
290                         className, mJarFileName));
291             }
292         } catch (IllegalAccessException e) {
293             reportLoadError(mJarFileName, className, e);
294         } catch (InstantiationException e) {
295             reportLoadError(mJarFileName, className, e);
296         }
297         return null;
298     }
299 
loadClass(String className)300     private Class<?> loadClass(String className) {
301         try {
302             if (mClassLoader == null) {
303                 File jarFile = mCtsBuild.getTestApp(mJarFileName);
304                 URL urls[] = {jarFile.getCanonicalFile().toURI().toURL()};
305                 mClassLoader = new URLClassLoader(urls);
306             }
307             return mClassLoader.loadClass(className);
308         } catch (FileNotFoundException fnfe) {
309             reportLoadError(mJarFileName, className, fnfe);
310         } catch (MalformedURLException mue) {
311             reportLoadError(mJarFileName, className, mue);
312         } catch (IOException ioe) {
313             reportLoadError(mJarFileName, className, ioe);
314         } catch (ClassNotFoundException cnfe) {
315             reportLoadError(mJarFileName, className, cnfe);
316         }
317         return null;
318     }
319 
320     /**
321      * Loads a class from given URLs.
322      * <p/>
323      * Exposed so unit tests can mock
324      *
325      * @param className
326      * @param urls
327      * @return
328      * @throws ClassNotFoundException
329      */
loadClass(String className, URL[] urls)330     Class<?> loadClass(String className, URL[] urls) throws ClassNotFoundException {
331         URLClassLoader cl = new URLClassLoader(urls);
332         Class<?> testClass = cl.loadClass(className);
333         return testClass;
334     }
335 
reportLoadError(String jarFileName, String className, Exception e)336     private void reportLoadError(String jarFileName, String className, Exception e) {
337         Log.e(LOG_TAG, String.format("Failed to load test class '%s' from jar '%s'",
338                 className, jarFileName));
339         Log.e(LOG_TAG, e);
340     }
341 
342     /**
343      * Checks that all mandatory member fields has been set.
344      */
checkFields()345     protected void checkFields() {
346         if (mRunName == null) {
347             throw new IllegalArgumentException("run name has not been set");
348         }
349         if (mDevice == null) {
350             throw new IllegalArgumentException("Device has not been set");
351         }
352         if (mJarFileName == null) {
353             throw new IllegalArgumentException("jar file name has not been set");
354         }
355         if (mTests == null) {
356             throw new IllegalArgumentException("tests has not been set");
357         }
358         if (mCtsBuild == null) {
359             throw new IllegalArgumentException("build has not been set");
360         }
361         try {
362             mCtsBuild.getTestApp(mJarFileName);
363         } catch (FileNotFoundException e) {
364             throw new IllegalArgumentException(String.format(
365                     "Could not find jar %s in CTS build %s", mJarFileName,
366                     mCtsBuild.getRootDir().getAbsolutePath()));
367         }
368     }
369 
370     /**
371      * {@inheritDoc}
372      */
373     @Override
countTestCases()374     public int countTestCases() {
375         return mTests.size();
376     }
377 }
378