package org.unitils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.MethodValidator;
import org.junit.runner.Runner;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Suite;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.TestClass;
/**
* Parameterized runner.
*
* @author Jeroen Horemans
* @author Thomas De Rycke
* @author Willemijn Wouters
*
* @since 3.4
*
*/
public class UnitilsParameterized extends Suite {
private Class<?> clazz;
private static final Log LOGGER = LogFactory.getLog(UnitilsParameterized.class);
private static final String METHOD = "Method ";
/**
*
* TestClassRunnerForParameters.
*
* @author wiw
*
* @since
*
*/
protected class TestClassRunnerForParameters extends UnitilsJUnit4TestClassRunner {
private final int fParameterSetNumber;
private final List<Object[]> fParameterList;
private org.junit.internal.runners.TestClass testClassInternalRunners;
/**
* @param javaClass
* @param parametersList
* @param i
* @throws Exception
*/
public TestClassRunnerForParameters(Class<?> javaClass, List<Object[]> parametersList, int i) throws Exception {
super(javaClass);
this.fParameterList = parametersList;
this.fParameterSetNumber = i;
this.testClassInternalRunners = new org.junit.internal.runners.TestClass(javaClass);
}
/**
* @see org.junit.internal.runners.JUnit4ClassRunner#createTest()
*/
@Override
protected Object createTest() throws Exception {
return getfTestClass().getOnlyConstructor().newInstance(computeParams());
}
/**
* @return
*/
private TestClass getfTestClass() {
return new TestClass(clazz);
}
/**
* @return
* @throws Exception
*/
protected Object[] computeParams() throws Exception {
try {
return fParameterList.get(fParameterSetNumber);
} catch (ClassCastException e) {
throw new Exception(String.format("%s.%s() must return a Collection of arrays.", getTestClass().getName(), getParametersMethod(getfTestClass()).getName()));
}
}
/**
* @see org.junit.internal.runners.JUnit4ClassRunner#getName()
*/
@Override
protected String getName() {
StringBuffer name = new StringBuffer();
try {
Object[] data = fParameterList.get(fParameterSetNumber);
for (Object object : data) {
if (object != null) {
name.append(object.toString());
name.append(",");
} else {
name.append("null,");
}
}
} catch (IndexOutOfBoundsException e) {
LOGGER.error(e.getMessage(), e);
}
name = new StringBuffer(name.substring(0, name.length() - 1));
return String.format("dataset [%s]", name.toString());
}
/**
* @see org.junit.internal.runners.JUnit4ClassRunner#testName(java.lang.reflect.Method)
*/
@Override
protected String testName(Method method) {
return String.format("%s[%s]", method.getName(), fParameterSetNumber);
}
/**
* @see org.junit.internal.runners.JUnit4ClassRunner#validate()
*/
@Override
protected void validate() throws InitializationError {
testClassInternalRunners = new org.junit.internal.runners.TestClass(clazz);
UnitilsMethodValidator validator = new UnitilsMethodValidator(testClassInternalRunners);
List<Throwable> errors = validator.validateMethodsForParameterizedRunner();
if (!errors.isEmpty()) {
throw new InitializationError(errors);
}
}
}
private final List<Runner> runners= new ArrayList<Runner>();
/**
* Only called reflectively. Do not use programmatically.
* @param klass
* @throws Throwable
*/
public UnitilsParameterized(Class<?> klass) throws Throwable {
super(klass, Collections.<Runner>emptyList());
this.clazz = klass;
List<Object[]> parametersList = getParametersList(getTestClass());
for (int i= 0; i < parametersList.size(); i++) {
runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(), parametersList, i));
}
}
/**
* @param testClass
* @return {@link List}
* @throws Throwable
* @throws Exception
*/
@SuppressWarnings("unchecked")
private List<Object[]> getParametersList(TestClass testClass) throws Exception, Throwable {
return (List<Object[]>) getParametersMethod(testClass).invokeExplosively(null);
}
/**
* @param testClass
* @return
*/
protected FrameworkMethod getParametersMethod(TestClass testClass) throws Exception {
List<FrameworkMethod> methods = testClass.getAnnotatedMethods(Parameters.class);
for (FrameworkMethod each : methods) {
int modifiers = each.getMethod().getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
return each;
}
}
throw new Exception("No public static parameters method on class " + testClass.getName());
}
/**
* @see org.junit.runners.Suite#getChildren()
*/
@Override
protected List<Runner> getChildren() {
return Collections.unmodifiableList(runners);
}
/**
*
* UnitilsMethodValidator.
*
* @author wiw
*
* @since
*
*/
protected static class UnitilsMethodValidator extends MethodValidator {
private org.junit.internal.runners.TestClass testclass;
private List<Throwable> errors = new ArrayList<Throwable>();
/**
* @param testClass
*/
public UnitilsMethodValidator(org.junit.internal.runners.TestClass testClass) {
super(testClass);
this.testclass = testClass;
}
public List<Throwable> validateMethodsForParameterizedRunner() {
validateArgConstructor();
validateStaticMethods();
validateInstanceMethods();
return errors;
}
//private methods
protected void validateTestMethods(Class<? extends Annotation> annotation, boolean isStatic) {
List<Method> methods= testclass.getAnnotatedMethods(annotation);
for (Method each : methods) {
if (Modifier.isStatic(each.getModifiers()) != isStatic) {
String state= isStatic ? "should" : "should not";
errors.add(new Exception(METHOD + each.getName() + "() "
+ state + " be static"));
}
if (!Modifier.isPublic(each.getDeclaringClass().getModifiers())) {
errors.add(new Exception("Class " + each.getDeclaringClass().getName() + " should be public"));
}
if (!Modifier.isPublic(each.getModifiers())) {
errors.add(new Exception(METHOD + each.getName() + " should be public"));
}
if (each.getReturnType() != Void.TYPE) {
errors.add(new Exception(METHOD + each.getName() + " should be void"));
}
if (each.getParameterTypes().length != 0) {
errors.add(new Exception(METHOD + each.getName() + " should have no parameters"));
}
}
}
/**
* @see org.junit.internal.runners.MethodValidator#validateInstanceMethods()
*/
@Override
public void validateInstanceMethods() {
validateTestMethods(After.class, false);
validateTestMethods(Before.class, false);
validateTestMethods(Test.class, false);
List<Method> methods= testclass.getAnnotatedMethods(Test.class);
if (methods.size() == 0) {
errors.add(new Exception("No runnable methods"));
}
}
/**
* @see org.junit.internal.runners.MethodValidator#validateStaticMethods()
*/
@Override
public void validateStaticMethods() {
validateTestMethods(BeforeClass.class, true);
validateTestMethods(AfterClass.class, true);
}
public void validateArgConstructor() {
org.junit.runners.model.TestClass clazz = new org.junit.runners.model.TestClass(testclass.getJavaClass());
Constructor<?> onlyConstructor = clazz.getOnlyConstructor();
if (onlyConstructor.getParameterTypes().length == 0) {
errors.add(new Exception("Test class shouldn't have a public zero-argument constructor"));
}
}
/**
* @return the errors
*/
protected List<Throwable> getErrors() {
return errors;
}
}
}