blob: 8c67971167ea44c508f4def6ef2c1d4a58a97724 [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.tradefed.testtype;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.JUnitToInvocationResultForwarder;
import com.android.tradefed.testtype.DeviceTestResult.RuntimeDeviceNotAvailableException;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A test runner for JUnit host based tests. If the test to be run implements {@link IDeviceTest}
* this runner will pass a reference to the device.
*/
@OptionClass(alias = "host")
public class HostTest implements IDeviceTest, ITestFilterReceiver, IRemoteTest {
@Option(name="class", description="The JUnit test classes to run, in the format "
+ "<package>.<class>. eg. \"com.android.foo.Bar\". This field can be repeated.",
importance = Importance.IF_UNSET)
private Set<String> mClasses = new HashSet<>();
@Option(name="method", description="The name of the method in the JUnit TestCase to run. "
+ "eg. \"testFooBar\"",
importance = Importance.IF_UNSET)
private String mMethodName;
@Option(name="set-option", description = "Options to be passed down to the class "
+ "under test, key and value should be separated by colon \":\"; for example, if class "
+ "under test supports \"--iteration 1\" from a command line, it should be passed in as"
+ " \"--set-option iteration:1\"; escaping of \":\" is currently not supported")
private List<String> mKeyValueOptions = new ArrayList<>();
private ITestDevice mDevice;
private Set<String> mIncludes = new HashSet<>();
private Set<String> mExcludes = new HashSet<>();
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mDevice;
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* {@inheritDoc}
*/
@Override
public void addIncludeFilter(String filter) {
mIncludes.add(filter);
}
/**
* {@inheritDoc}
*/
@Override
public void addAllIncludeFilters(List<String> filters) {
mIncludes.addAll(filters);
}
/**
* {@inheritDoc}
*/
@Override
public void addExcludeFilter(String filter) {
mExcludes.add(filter);
}
/**
* {@inheritDoc}
*/
@Override
public void addAllExcludeFilters(List<String> filters) {
mExcludes.addAll(filters);
}
/**
* {@inheritDoc}
*/
public int countTestCases() {
int count = 0;
for (Class<?> classObj : getClasses()) {
Object testObj = loadObject(classObj);
if (testObj instanceof Test) {
if (testObj instanceof TestCase
&& !(testObj instanceof DeviceTestCase)) {
// wrap the test in a suite, because the JUnit TestCase.run implementation can
// only run a single method
count += new TestSuite(classObj).countTestCases();
} else {
count += ((Test) testObj).countTestCases();
}
} else {
count++;
}
}
return count;
}
void setClassName(String className) {
mClasses.clear();
mClasses.add(className);
}
void setMethodName(String methodName) {
mMethodName = methodName;
}
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
List<Class<?>> classes = getClasses();
if (classes.isEmpty()) {
throw new IllegalArgumentException("Missing Test class name");
}
if (mMethodName != null && classes.size() > 1) {
throw new IllegalArgumentException("Method name given with multiple test classes");
}
for (Class<?> classObj : classes) {
if (IRemoteTest.class.isAssignableFrom(classObj)) {
IRemoteTest test = (IRemoteTest) loadObject(classObj);
List<String> includes = new ArrayList<>(mIncludes);
if (mMethodName != null) {
includes.add(String.format("%s#%s", classObj.getName(), mMethodName));
}
List<String> excludes = new ArrayList<>(mExcludes);
if (test instanceof ITestFilterReceiver) {
((ITestFilterReceiver) test).addAllIncludeFilters(includes);
((ITestFilterReceiver) test).addAllExcludeFilters(excludes);
} else if (!includes.isEmpty() || !excludes.isEmpty()) {
throw new IllegalArgumentException(String.format(
"%s does not implement ITestFilterReceiver", classObj.getName()));
}
test.run(listener);
} else if (Test.class.isAssignableFrom(classObj)) {
JUnitRunUtil.runTest(listener, collectTests(collectClasses(classObj)),
classObj.getName());
} else {
// TODO: Add junit4 support here
throw new IllegalArgumentException(
String.format("%s is not a test", classObj.getName()));
}
}
}
private Set<Class<?>> collectClasses(Class<?> classObj) {
Set<Class<?>> classes = new HashSet<>();
if (TestSuite.class.isAssignableFrom(classObj)) {
TestSuite testObj = (TestSuite) loadObject(classObj);
classes.addAll(getClassesFromSuite(testObj));
} else {
classes.add(classObj);
}
return classes;
}
private Set<Class<?>> getClassesFromSuite(TestSuite suite) {
Set<Class<?>> classes = new HashSet<>();
Enumeration<Test> tests = suite.tests();
while (tests.hasMoreElements()) {
Test test = tests.nextElement();
if (test instanceof TestSuite) {
classes.addAll(getClassesFromSuite((TestSuite) test));
} else {
classes.addAll(collectClasses(test.getClass()));
}
}
return classes;
}
private TestSuite collectTests(Set<Class<?>> classes) {
TestSuite suite = new TestSuite();
for (Class<?> classObj : classes) {
String packageName = classObj.getPackage().getName();
String className = classObj.getName();
Method[] methods = null;
if (mMethodName == null) {
methods = classObj.getMethods();
} else {
try {
methods = new Method[] {
classObj.getMethod(mMethodName, (Class[]) null)
};
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
String.format("Cannot find %s#%s", className, mMethodName), e);
}
}
for (Method method : methods) {
String methodName = String.format("%s#%s", className, method.getName());
if (!Modifier.isPublic(method.getModifiers())
|| !method.getReturnType().equals(Void.TYPE)
|| method.getParameterTypes().length > 0
|| !method.getName().startsWith("test")
|| !shouldRun(packageName, className, methodName)) {
continue;
}
Test testObj = (Test) loadObject(classObj);
if (testObj instanceof TestCase) {
((TestCase)testObj).setName(method.getName());
}
suite.addTest(testObj);
}
}
return suite;
}
private boolean shouldRun(String packageName, String className, String methodName) {
if (mExcludes.contains(packageName)) {
// Skip package because it was excluded
CLog.i("Skip package because it was excluded");
return false;
}
if (mExcludes.contains(className)) {
// Skip class because it was excluded
CLog.i("Skip class because it was excluded");
return false;
}
if (mExcludes.contains(methodName)) {
// Skip method because it was excluded
CLog.i("Skip method because it was excluded");
return false;
}
return mIncludes.isEmpty()
|| mIncludes.contains(methodName)
|| mIncludes.contains(className)
|| mIncludes.contains(packageName);
}
protected List<Class<?>> getClasses() throws IllegalArgumentException {
List<Class<?>> classes = new ArrayList<>();
for (String className : mClasses) {
try {
classes.add(Class.forName(className));
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(String.format("Could not load Test class %s",
className), e);
}
}
return classes;
}
protected Object loadObject(Class<?> classObj) throws IllegalArgumentException {
final String className = classObj.getName();
try {
Object testObj = classObj.newInstance();
// set options
if (!mKeyValueOptions.isEmpty()) {
try {
OptionSetter setter = new OptionSetter(testObj);
for (String item : mKeyValueOptions) {
String[] fields = item.split(":");
if (fields.length == 2) {
setter.setOptionValue(fields[0], fields[1]);
} else if (fields.length == 3) {
setter.setOptionValue(fields[0], fields[1], fields[2]);
} else {
throw new RuntimeException(
String.format("invalid option spec \"%s\"", item));
}
}
} catch (ConfigurationException ce) {
throw new RuntimeException("error passing options down to test class", ce);
}
}
if (testObj instanceof IDeviceTest) {
if (mDevice == null) {
throw new IllegalArgumentException("Missing device");
}
((IDeviceTest)testObj).setDevice(mDevice);
}
return testObj;
} catch (InstantiationException e) {
throw new IllegalArgumentException(String.format("Could not load Test class %s",
className), e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(String.format("Could not load Test class %s",
className), e);
}
}
}