HHH-10058 Parameterized test runner compatible with CustomRunner
This commit is contained in:
parent
64137c3619
commit
d9b456b03a
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.testing.junit4;
|
||||
|
||||
import org.junit.runner.Runner;
|
||||
import org.junit.runner.manipulation.NoTestsRemainException;
|
||||
import org.junit.runner.notification.RunNotifier;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.model.FrameworkField;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Allows the {@link CustomRunner} features in parameterized tests.
|
||||
* This is mostly copy-paste from {@link Parameterized} since the methods could not be overridden.
|
||||
*
|
||||
* The static {@link org.junit.BeforeClass} and {@link org.junit.AfterClass} methods will be executed
|
||||
* only once before and after all tests (since these should prepare static members).
|
||||
* Hibernate-specific {@link org.hibernate.testing.BeforeClassOnce} and {@link org.hibernate.testing.AfterClassOnce}
|
||||
* will be executed before and after each set of tests with given parameters.
|
||||
*
|
||||
* Class can override the parameters list (annotated by {@link org.junit.runners.Parameterized.Parameters}
|
||||
* by defining static method of the same name in inheriting class (this works although usually static
|
||||
* methods cannot override each other in Java).
|
||||
*
|
||||
* When there are multiple methods providing the parameters list, the used parameters list is a cross product
|
||||
* of all the options, concatenating the argument list according to {@link Order} values.
|
||||
*
|
||||
* Contrary to {@link Parameterized}, non-static parameters methods are allowed, but the test class needs
|
||||
* to have parameterless constructor, and therefore use {@link org.junit.runners.Parameterized.Parameter}
|
||||
* for setting these parameters. This allow type-safe overriding of the method; note that only the base
|
||||
* method needs the {@link org.junit.runners.Parameterized.Parameters} annotation, overriding methods
|
||||
* are invoked automatically.
|
||||
*
|
||||
* @author Radim Vansa <rvansa@redhat.com>
|
||||
*/
|
||||
public class CustomParameterized extends Suite {
|
||||
|
||||
private static final List<Runner> NO_RUNNERS = Collections.emptyList();
|
||||
|
||||
private final ArrayList<Runner> runners = new ArrayList<Runner>();
|
||||
|
||||
/**
|
||||
* Only called reflectively. Do not use programmatically.
|
||||
*/
|
||||
public CustomParameterized(Class<?> klass) throws Throwable {
|
||||
super(klass, NO_RUNNERS);
|
||||
List<FrameworkMethod> parametersMethods = getParametersMethods();
|
||||
createRunnersForParameters(allParameters(parametersMethods), concatNames(parametersMethods));
|
||||
}
|
||||
|
||||
private String concatNames(List<FrameworkMethod> parametersMethods) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (FrameworkMethod method : parametersMethods) {
|
||||
Parameterized.Parameters parameters = method.getAnnotation(Parameterized.Parameters.class);
|
||||
if (sb.length() != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(parameters.name());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Runner> getChildren() {
|
||||
return runners;
|
||||
}
|
||||
|
||||
private Iterable<Object[]> allParameters(List<FrameworkMethod> parametersMethods) throws Throwable {
|
||||
ArrayList<Iterable<Object[]>> returnedParameters = new ArrayList<Iterable<Object[]>>();
|
||||
ArrayList<Object[]> allParameters = new ArrayList<Object[]>();
|
||||
Object cachedInstance = null;
|
||||
for (FrameworkMethod method : parametersMethods) {
|
||||
Object parameters;
|
||||
if (method.isStatic()) {
|
||||
parameters = method.invokeExplosively(null);
|
||||
}
|
||||
else {
|
||||
if (cachedInstance == null) {
|
||||
cachedInstance = getTestClass().getOnlyConstructor().newInstance();
|
||||
}
|
||||
parameters = method.invokeExplosively(cachedInstance);
|
||||
}
|
||||
if (parameters instanceof Iterable) {
|
||||
returnedParameters.add((Iterable<Object[]>) parameters);
|
||||
}
|
||||
else {
|
||||
throw parametersMethodReturnedWrongType(method);
|
||||
}
|
||||
}
|
||||
for (Iterable<Object[]> parameters : returnedParameters) {
|
||||
if (allParameters.isEmpty()) {
|
||||
for (Object[] array : parameters) {
|
||||
allParameters.add(array);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ArrayList<Object[]> newAllParameters = new ArrayList<Object[]>();
|
||||
for (Object[] prev : allParameters) {
|
||||
for (Object[] array : parameters) {
|
||||
Object[] next = Arrays.copyOf(prev, prev.length + array.length);
|
||||
System.arraycopy(array, 0, next, prev.length, array.length);
|
||||
newAllParameters.add(next);
|
||||
}
|
||||
}
|
||||
allParameters = newAllParameters;
|
||||
}
|
||||
}
|
||||
return allParameters;
|
||||
}
|
||||
|
||||
private List<FrameworkMethod> getParametersMethods() throws Exception {
|
||||
List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(
|
||||
Parameterized.Parameters.class);
|
||||
SortedMap<Integer, FrameworkMethod> sortedMethods = new TreeMap<Integer, FrameworkMethod>();
|
||||
for (FrameworkMethod each : methods) {
|
||||
if (each.isPublic()) {
|
||||
if (!each.isStatic()) {
|
||||
if (getTestClass().getOnlyConstructor().getParameterTypes().length != 0) {
|
||||
throw new Exception("Method " + each.getMethod() + " is annotated with @Parameters, it is not static and there is no parameter-less constructor!");
|
||||
}
|
||||
}
|
||||
Order order = each.getAnnotation(Order.class);
|
||||
int value = order == null ? 0 : order.value();
|
||||
FrameworkMethod prev = sortedMethods.put(value, each);
|
||||
if (prev != null) {
|
||||
throw new Exception(String.format("There are more methods annotated with @Parameters and @Order(value=%d): %s (%s) and %s (%s)",
|
||||
value, prev.getMethod(), prev.getAnnotation(Order.class), each.getMethod(), order));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Exception("Method " + each.getMethod() + " is annotated with @Parameters but it is not public!");
|
||||
}
|
||||
}
|
||||
if (sortedMethods.isEmpty()) {
|
||||
throw new Exception("No public static parameters method on class "
|
||||
+ getTestClass().getName());
|
||||
}
|
||||
return new ArrayList<FrameworkMethod>(sortedMethods.values());
|
||||
}
|
||||
|
||||
private void createRunnersForParameters(Iterable<Object[]> allParameters, String namePattern) throws Exception {
|
||||
int i = 0;
|
||||
for (Object[] parametersOfSingleTest : allParameters) {
|
||||
String name = nameFor(namePattern, i, parametersOfSingleTest);
|
||||
CustomRunnerForParameters runner = new CustomRunnerForParameters(
|
||||
getTestClass().getJavaClass(), parametersOfSingleTest,
|
||||
name);
|
||||
runners.add(runner);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
private String nameFor(String namePattern, int index, Object[] parameters) {
|
||||
String finalPattern = namePattern.replaceAll("\\{index\\}",
|
||||
Integer.toString(index));
|
||||
String name = MessageFormat.format(finalPattern, parameters);
|
||||
return "[" + name + "]";
|
||||
}
|
||||
|
||||
private Exception parametersMethodReturnedWrongType(FrameworkMethod method) throws Exception {
|
||||
String className = getTestClass().getName();
|
||||
String methodName = method.getName();
|
||||
String message = MessageFormat.format(
|
||||
"{0}.{1}() must return an Iterable of arrays.",
|
||||
className, methodName);
|
||||
return new Exception(message);
|
||||
}
|
||||
|
||||
private List<FrameworkField> getAnnotatedFieldsByParameter() {
|
||||
return getTestClass().getAnnotatedFields(Parameterized.Parameter.class);
|
||||
}
|
||||
|
||||
private boolean fieldsAreAnnotated() {
|
||||
return !getAnnotatedFieldsByParameter().isEmpty();
|
||||
}
|
||||
|
||||
private class CustomRunnerForParameters extends CustomRunner {
|
||||
private final Object[] parameters;
|
||||
private final String name;
|
||||
|
||||
CustomRunnerForParameters(Class<?> type, Object[] parameters, String name) throws InitializationError, NoTestsRemainException {
|
||||
super(type);
|
||||
this.parameters = parameters;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getTestInstance() throws Exception {
|
||||
if (testInstance == null) {
|
||||
if (fieldsAreAnnotated()) {
|
||||
testInstance = createTestUsingFieldInjection();
|
||||
}
|
||||
else {
|
||||
testInstance = createTestUsingConstructorInjection();
|
||||
}
|
||||
}
|
||||
return testInstance;
|
||||
}
|
||||
|
||||
private Object createTestUsingConstructorInjection() throws Exception {
|
||||
return getTestClass().getOnlyConstructor().newInstance(parameters);
|
||||
}
|
||||
|
||||
private Object createTestUsingFieldInjection() throws Exception {
|
||||
List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
|
||||
if (annotatedFieldsByParameter.size() != parameters.length) {
|
||||
throw new Exception("Wrong number of parameters and @Parameter fields." +
|
||||
" @Parameter fields counted: " + annotatedFieldsByParameter.size() + ", available parameters: " + parameters.length + ".");
|
||||
}
|
||||
Object testClassInstance = getTestClass().getJavaClass().newInstance();
|
||||
for (FrameworkField each : annotatedFieldsByParameter) {
|
||||
Field field = each.getField();
|
||||
Parameterized.Parameter annotation = field.getAnnotation(Parameterized.Parameter.class);
|
||||
int index = annotation.value();
|
||||
try {
|
||||
field.set(testClassInstance, parameters[index]);
|
||||
}
|
||||
catch (IllegalArgumentException iare) {
|
||||
throw new Exception(getTestClass().getName() + ": Trying to set " + field.getName() +
|
||||
" with the value " + parameters[index] +
|
||||
" that is not the right type (" + parameters[index].getClass().getSimpleName() + " instead of " +
|
||||
field.getType().getSimpleName() + ").", iare);
|
||||
}
|
||||
}
|
||||
return testClassInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String testName(FrameworkMethod method) {
|
||||
return method.getName() + getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateConstructor(List<Throwable> errors) {
|
||||
validateOnlyOneConstructor(errors);
|
||||
if (fieldsAreAnnotated()) {
|
||||
validateZeroArgConstructor(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateFields(List<Throwable> errors) {
|
||||
super.validateFields(errors);
|
||||
if (fieldsAreAnnotated()) {
|
||||
List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
|
||||
int[] usedIndices = new int[annotatedFieldsByParameter.size()];
|
||||
for (FrameworkField each : annotatedFieldsByParameter) {
|
||||
int index = each.getField().getAnnotation(Parameterized.Parameter.class).value();
|
||||
if (index < 0 || index > annotatedFieldsByParameter.size() - 1) {
|
||||
errors.add(
|
||||
new Exception("Invalid @Parameter value: " + index + ". @Parameter fields counted: " +
|
||||
annotatedFieldsByParameter.size() + ". Please use an index between 0 and " +
|
||||
(annotatedFieldsByParameter.size() - 1) + ".")
|
||||
);
|
||||
}
|
||||
else {
|
||||
usedIndices[index]++;
|
||||
}
|
||||
}
|
||||
for (int index = 0; index < usedIndices.length; index++) {
|
||||
int numberOfUse = usedIndices[index];
|
||||
if (numberOfUse == 0) {
|
||||
errors.add(new Exception("@Parameter(" + index + ") is never used."));
|
||||
}
|
||||
else if (numberOfUse > 1) {
|
||||
errors.add(new Exception("@Parameter(" + index + ") is used more than once (" + numberOfUse + ")."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Statement classBlock(RunNotifier notifier) {
|
||||
Statement statement = childrenInvoker(notifier);
|
||||
statement = withBeforeClasses(statement);
|
||||
statement = withAfterClasses(statement);
|
||||
// no class rules executed! These will be executed for the whole suite.
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Statement withBeforeClasses(Statement statement) {
|
||||
if ( isAllTestsIgnored() ) {
|
||||
return statement;
|
||||
}
|
||||
return new BeforeClassCallbackHandler( this, statement );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Statement withAfterClasses(Statement statement) {
|
||||
if ( isAllTestsIgnored() ) {
|
||||
return statement;
|
||||
}
|
||||
return new AfterClassCallbackHandler( this, statement );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Annotation[] getRunnerAnnotations() {
|
||||
return new Annotation[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Order {
|
||||
int value();
|
||||
}
|
||||
}
|
|
@ -65,7 +65,7 @@ public class CustomRunner extends BlockJUnit4ClassRunner {
|
|||
|
||||
private Boolean isAllTestsIgnored;
|
||||
|
||||
private boolean isAllTestsIgnored() {
|
||||
protected boolean isAllTestsIgnored() {
|
||||
if ( isAllTestsIgnored == null ) {
|
||||
if ( computeTestMethods().isEmpty() ) {
|
||||
isAllTestsIgnored = true;
|
||||
|
@ -132,7 +132,7 @@ public class CustomRunner extends BlockJUnit4ClassRunner {
|
|||
);
|
||||
}
|
||||
|
||||
private Object testInstance;
|
||||
protected Object testInstance;
|
||||
|
||||
protected Object getTestInstance() throws Exception {
|
||||
if ( testInstance == null ) {
|
||||
|
|
Loading…
Reference in New Issue