blob: 9d9596ea99a7732684909d0b36bfe5c49f2065d2 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.tradefed.testtype;
import com.android.cts.tradefed.build.CtsBuildHelper;
import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.DeviceTestResult.RuntimeDeviceNotAvailableException;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.JUnitRunUtil;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil.IRunnableResult;
import com.android.tradefed.util.RunUtil;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* A {@link IRemoteTest} that can run a set of JUnit tests from a CTS jar.
*/
public class JarHostTest implements IDeviceTest, IRemoteTest, IBuildReceiver, Test {
private static final String LOG_TAG = "JarHostTest";
private ITestDevice mDevice;
private String mJarFileName;
private Collection<TestIdentifier> mTests;
private long mTimeoutMs = 10 * 60 * 1000;
private String mRunName;
private CtsBuildHelper mCtsBuild = null;
private IBuildInfo mBuildInfo = null;
private IAbi mAbi;
private ClassLoader mClassLoader;
/**
* @param abi the ABI to run the test on
*/
public void setAbi(IAbi abi) {
mAbi = abi;
}
/**
* {@inheritDoc}
*/
@Override
public void setBuild(IBuildInfo buildInfo) {
mBuildInfo = buildInfo;
mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
}
/**
* Set the CTS build container.
* <p/>
* Exposed so unit tests can mock the provided build.
*
* @param buildHelper
*/
void setBuildHelper(CtsBuildHelper buildHelper) {
mCtsBuild = buildHelper;
}
/**
* Get the CTS build container.
*
* @return {@link CtsBuildHelper}
*/
CtsBuildHelper getBuildHelper() {
return mCtsBuild;
}
/**
* Set the jar file to load tests from.
*
* @param jarFileName the file name of the CTS host test jar to use
*/
void setJarFileName(String jarFileName) {
mJarFileName = jarFileName;
}
/**
* Gets the jar file to load tests from.
*
* @return jarFileName the file name of the CTS host test jar to use
*/
String getJarFileName() {
return mJarFileName;
}
/**
* Sets the collection of tests to run
*
* @param tests
*/
void setTests(Collection<TestIdentifier> tests) {
mTests = tests;
}
/**
* Gets the collection of tests to run
*
* @return Collection<{@link TestIdentifier}>
*/
Collection<TestIdentifier> getTests() {
return mTests;
}
/**
* Set the maximum time in ms each test should run.
* <p/>
* Tests that take longer than this amount will be failed with a {@link TestTimeoutException}
* as the cause.
*
* @param testTimeoutMs
*/
void setTimeout(long testTimeoutMs) {
mTimeoutMs = testTimeoutMs;
}
/**
* Set the run name to report to {@link ITestInvocationListener#testRunStarted(String, int)}
*
* @param runName
*/
void setRunName(String runName) {
mRunName = runName;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mDevice;
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
checkFields();
Log.i(LOG_TAG, String.format("Running %s test package from jar, contains %d tests.",
mRunName, mTests.size()));
JUnitRunUtil.runTest(listener, this, mRunName);
}
/**
* {@inheritDoc}
*/
@Override
public void run(TestResult junitResult) {
for (TestIdentifier testId : mTests) {
Test junitTest = loadTest(testId.getClassName(), testId.getTestName());
if (junitTest != null) {
runTest(testId, junitTest, junitResult);
}
}
}
/**
* Run test with timeout support.
*/
private void runTest(TestIdentifier testId, final Test junitTest, final TestResult junitResult) {
if (junitTest instanceof IDeviceTest) {
((IDeviceTest)junitTest).setDevice(getDevice());
} else if (junitTest instanceof com.android.hosttest.DeviceTest) {
// legacy check - see if test uses hosttestlib. This check should go away once
// all host tests are converted to use tradefed
com.android.hosttest.DeviceTest deviceTest = (com.android.hosttest.DeviceTest)junitTest;
deviceTest.setDevice(getDevice().getIDevice());
deviceTest.setTestAppPath(mCtsBuild.getTestCasesDir().getAbsolutePath());
}
if (junitTest instanceof IAbiReceiver) {
((IAbiReceiver)junitTest).setAbi(mAbi);
}
if (junitTest instanceof IBuildReceiver) {
((IBuildReceiver)junitTest).setBuild(mBuildInfo);
}
TestRunnable testRunnable = new TestRunnable(junitTest, junitResult);
CommandStatus status = RunUtil.getDefault().runTimed(mTimeoutMs, testRunnable, true);
if (status.equals(CommandStatus.TIMED_OUT)) {
junitResult.addError(junitTest, new TestTimeoutException());
junitResult.endTest(junitTest);
}
if (testRunnable.getException() != null) {
throw testRunnable.getException();
}
}
private static class TestRunnable implements IRunnableResult {
private final Test mJunitTest;
private RuntimeDeviceNotAvailableException mException = null;
private TestResult mJunitResult;
TestRunnable(Test junitTest, TestResult junitResult) {
mJunitTest = junitTest;
mJunitResult = junitResult;
}
/**
* {@inheritDoc}
*/
@Override
public boolean run() throws Exception {
try {
mJunitTest.run(mJunitResult);
} catch (RuntimeDeviceNotAvailableException e) {
mException = e;
}
return true;
}
public RuntimeDeviceNotAvailableException getException() {
return mException;
}
/**
* {@inheritDoc}
*/
@Override
public void cancel() {
}
}
/**
* Load the test with given names from the jar.
*
* @param className
* @param testName
* @return the loaded {@link Test} or <code>null</code> if test could not be loaded.
*/
private Test loadTest(String className, String testName) {
try {
Class<?> testClass = loadClass(className);
if (testClass == null) {
return null;
}
if (TestCase.class.isAssignableFrom(testClass)) {
TestCase testCase = (TestCase)testClass.newInstance();
testCase.setName(testName);
return testCase;
} else if (Test.class.isAssignableFrom(testClass)) {
Test test = (Test)testClass.newInstance();
return test;
} else {
Log.e(LOG_TAG, String.format("Class '%s' from jar '%s' is not a Test",
className, mJarFileName));
}
} catch (IllegalAccessException e) {
reportLoadError(mJarFileName, className, e);
} catch (InstantiationException e) {
reportLoadError(mJarFileName, className, e);
}
return null;
}
private Class<?> loadClass(String className) {
try {
if (mClassLoader == null) {
File jarFile = mCtsBuild.getTestApp(mJarFileName);
URL urls[] = {jarFile.getCanonicalFile().toURI().toURL()};
mClassLoader = new URLClassLoader(urls);
}
return mClassLoader.loadClass(className);
} catch (FileNotFoundException fnfe) {
reportLoadError(mJarFileName, className, fnfe);
} catch (MalformedURLException mue) {
reportLoadError(mJarFileName, className, mue);
} catch (IOException ioe) {
reportLoadError(mJarFileName, className, ioe);
} catch (ClassNotFoundException cnfe) {
reportLoadError(mJarFileName, className, cnfe);
}
return null;
}
/**
* Loads a class from given URLs.
* <p/>
* Exposed so unit tests can mock
*
* @param className
* @param urls
* @return
* @throws ClassNotFoundException
*/
Class<?> loadClass(String className, URL[] urls) throws ClassNotFoundException {
URLClassLoader cl = new URLClassLoader(urls);
Class<?> testClass = cl.loadClass(className);
return testClass;
}
private void reportLoadError(String jarFileName, String className, Exception e) {
Log.e(LOG_TAG, String.format("Failed to load test class '%s' from jar '%s'",
className, jarFileName));
Log.e(LOG_TAG, e);
}
/**
* Checks that all mandatory member fields has been set.
*/
protected void checkFields() {
if (mRunName == null) {
throw new IllegalArgumentException("run name has not been set");
}
if (mDevice == null) {
throw new IllegalArgumentException("Device has not been set");
}
if (mJarFileName == null) {
throw new IllegalArgumentException("jar file name has not been set");
}
if (mTests == null) {
throw new IllegalArgumentException("tests has not been set");
}
if (mCtsBuild == null) {
throw new IllegalArgumentException("build has not been set");
}
try {
mCtsBuild.getTestApp(mJarFileName);
} catch (FileNotFoundException e) {
throw new IllegalArgumentException(String.format(
"Could not find jar %s in CTS build %s", mJarFileName,
mCtsBuild.getRootDir().getAbsolutePath()));
}
}
/**
* {@inheritDoc}
*/
@Override
public int countTestCases() {
return mTests.size();
}
}