All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.testing.junit4.CustomParameterized Maven / Gradle / Ivy

There is a newer version: 5.6.15.Final
Show newest version
/*
 * 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 .
 */
package org.hibernate.testing.junit4;

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;

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;

/**
 * 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 <[email protected]>
 */
public class CustomParameterized extends Suite {

	private static final List NO_RUNNERS = Collections.emptyList();

	private final ArrayList runners = new ArrayList();

	/**
	 * Only called reflectively. Do not use programmatically.
	 */
	public CustomParameterized(Class klass) throws Throwable {
		super(klass, NO_RUNNERS);
		List parametersMethods = getParametersMethods();
		createRunnersForParameters(allParameters(parametersMethods), concatNames(parametersMethods));
	}

	private String concatNames(List 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 getChildren() {
		return runners;
	}

	private Iterable allParameters(List parametersMethods) throws Throwable {
		ArrayList> returnedParameters = new ArrayList>();
		ArrayList allParameters = new ArrayList();
		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) parameters);
			}
			else {
				throw parametersMethodReturnedWrongType(method);
			}
		}
		for (Iterable parameters : returnedParameters) {
			if (allParameters.isEmpty()) {
				for (Object[] array : parameters) {
					allParameters.add(array);
				}
			}
			else {
				ArrayList newAllParameters = new ArrayList();
				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 getParametersMethods() throws Exception {
		List methods = getTestClass().getAnnotatedMethods(
				Parameterized.Parameters.class);
		SortedMap sortedMethods = new TreeMap();
		for (FrameworkMethod each : methods) {
			if (each.isPublic()) {
				if (!each.isStatic()) {
					if (getTestClass().getOnlyConstructor().getParameterCount() != 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(sortedMethods.values());
	}

	private void createRunnersForParameters(Iterable 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 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 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 errors) {
			validateOnlyOneConstructor(errors);
			if (fieldsAreAnnotated()) {
				validateZeroArgConstructor(errors);
			}
		}

		@Override
		protected void validateFields(List errors) {
			super.validateFields(errors);
			if (fieldsAreAnnotated()) {
				List 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();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy