package org.eclipse.emf.test.ecore.xcore.legacy_xpect_runner;
import static org.eclipse.xtext.util.Exceptions.throwUncheckedException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.test.ecore.xcore.legacy_xpect_runner.IParameterProvider.IExpectation;
import org.eclipse.emf.test.ecore.xcore.legacy_xpect_runner.IParameterProvider.IParameterAcceptor;
import org.eclipse.emf.test.ecore.xcore.legacy_xpect_runner.ParameterizedXtextRunner.ResourceRunner;
import org.eclipse.emf.test.ecore.xcore.legacy_xpect_runner.TestExpectationValidator.ITestExpectationValidator;
import org.eclipse.emf.test.ecore.xcore.legacy_xpect_runner.TestExpectationValidator.TestResult;
import org.eclipse.xtext.junit4.IInjectorProvider;
import org.eclipse.xtext.junit4.IRegistryConfigurator;
import org.eclipse.xtext.junit4.InjectWith;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.Exceptions;
import org.eclipse.xtext.util.ReflectionUtil;
import org.eclipse.xtext.util.Strings;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
/**
* use org.xpect.runner.XpectRunner from www.xpect-tests.org instead.
*
* This class will be removed in the next release after 2.4.2
*
* @author Moritz Eysholdt - Initial contribution and API
*/
public class ParameterizedXtextRunner extends ParentRunner<ResourceRunner> {
protected static class MethodWithExpectation {
protected Method method;
protected ITestExpectationValidator<Object> validator;
public MethodWithExpectation(Method method) throws Throwable {
super();
this.method = method;
this.validator = findValidator();
}
protected ITestExpectationValidator<? extends Object> createValidator(
Test annotation) {
if (method.getReturnType() != void.class)
throw new RuntimeException(
"The method is expected to return void. Method: "
+ method);
return new TestExpectationValidator.NullTestResultValidator(
annotation);
}
protected ITestExpectationValidator<? extends Object> createValidator(
TestExpectationValidator trv, Annotation annotation)
throws Throwable {
Class<? extends ITestExpectationValidator<?>> validatorClass = trv
.validator();
Class<?> expectedResultType = getExpectedResultType(validatorClass);
boolean voidExpected = expectedResultType == Void.TYPE
|| expectedResultType == Void.class;
boolean returnsExpected = method.getReturnType() == Void.TYPE
|| method.getReturnType() == Void.class;
if (!expectedResultType.isAssignableFrom(method.getReturnType())
&& (!voidExpected || !returnsExpected))
throw new RuntimeException("The return type of " + method
+ " is expected to be " + expectedResultType.getName());
Constructor<? extends ITestExpectationValidator<?>> c = validatorClass
.getConstructor(annotation.annotationType());
return c.newInstance(annotation);
}
@SuppressWarnings("unchecked")
protected ITestExpectationValidator<Object> findValidator()
throws Throwable {
for (Annotation an : method.getAnnotations())
if (an instanceof Test)
return (ITestExpectationValidator<Object>) createValidator((Test) an);
else {
TestExpectationValidator trv = an.annotationType()
.getAnnotation(TestExpectationValidator.class);
if (trv != null)
return (ITestExpectationValidator<Object>) createValidator(
trv, an);
}
Class<?>[] annotations = { Test.class, Xpect.class,
XpectString.class, XpectLines.class,
XpectCommaSeparatedValues.class };
List<String> names = Lists.newArrayList();
for (Class<?> o : annotations)
names.add("@" + o.getSimpleName());
throw new RuntimeException("Annotation missing: "
+ Joiner.on(", ").join(names) + ", etc. in: " + method);
}
protected Class<?> getExpectedResultType(
Class<? extends ITestExpectationValidator<?>> clazz) {
for (Method meth : clazz.getMethods()) {
Annotation[][] annotations = meth.getParameterAnnotations();
for (int i = 0; i < annotations.length; i++)
for (Annotation an : annotations[i])
if (an instanceof TestResult)
return meth.getParameterTypes()[i];
}
throw new RuntimeException("One of the method parameters of "
+ clazz.getName() + " must be annotated with @"
+ TestResult.class.getSimpleName());
}
public Method getMethod() {
return method;
}
public ITestExpectationValidator<Object> getValidator() {
return validator;
}
}
protected static class ParameterSetRunner {
protected Description description;
protected IExpectation expectation;
protected boolean ignore;
protected int index = -1;
protected String methodName;
protected Multimap<String, Object> params;
protected ResourceRunner runner;
protected String title;
public Description getDescription() {
if (description == null)
description = Description.createTestDescription(
runner.clazz.getJavaClass(), getFullTitle());
return description;
}
public IExpectation getExpectation() {
return expectation;
}
public String getFullTitle() {
StringBuilder result = new StringBuilder();
result.append(methodName);
if (!Strings.isEmpty(title)) {
result.append(" ");
result.append(title);
}
if (index > -1) {
result.append("#");
result.append(index);
}
result.append(" - ");
result.append(runner.resource.getURI().lastSegment());
return result.toString();
}
public IInjectorProvider getInjectorProvider() {
return runner.injectorProvider;
}
public String getMethdoName() {
return methodName;
}
public Multimap<String, Object> getParams() {
return params;
}
public XtextResource getResource() {
return runner.resource;
}
public Class<?> getTestClass() {
return runner.clazz.getJavaClass();
}
public void init(ResourceRunner runner, String title, String method,
Multimap<String, Object> params, IExpectation expectation,
boolean ignore) {
this.runner = runner;
this.title = title;
this.methodName = method;
this.params = params;
this.expectation = expectation;
this.ignore = ignore;
}
public boolean isIgnore() {
return ignore;
}
}
protected static class ResourceRunner implements IParameterAcceptor {
protected TestClass clazz;
protected Description description;
protected IInjectorProvider injectorProvider;
protected List<ParameterSetRunner> parameterSets = Lists.newArrayList();
protected XtextResource resource;
protected ResourceSet resourceSet;
public void acceptImportURI(URI uri) {
resourceSet.getResource(uri, true);
}
public void acceptTest(String title, String method,
Multimap<String, Object> params, IExpectation expectation,
boolean ignore) {
ParameterSetRunner runner = injectorProvider.getInjector()
.getInstance(ParameterSetRunner.class);
runner.init(this, title, method, params, expectation, ignore);
parameterSets.add(runner);
}
protected void collectParameters() {
IParameterProvider parameterProvider = injectorProvider
.getInjector().getInstance(IParameterProvider.class);
parameterProvider.collectParameters(clazz.getJavaClass(), resource,
this);
}
public Description getDescription() {
if (description == null) {
description = Description.createSuiteDescription(resource
.getURI().lastSegment());
for (ParameterSetRunner child : parameterSets)
description.addChild(child.getDescription());
}
return description;
}
public List<ParameterSetRunner> getParameterSets() {
return parameterSets;
}
public void init(TestClass clazz, IInjectorProvider injector, URI uri) {
this.clazz = clazz;
this.injectorProvider = injector;
this.resourceSet = injectorProvider.getInjector().getInstance(
ResourceSet.class);
this.resource = (XtextResource) resourceSet.getResource(uri, true);
collectParameters();
setIndex();
}
protected void setIndex() {
Set<String> visited = Sets.newHashSet();
Set<String> duplicate = Sets.newHashSet();
for (ParameterSetRunner r : getParameterSets())
if (!visited.add(r.getFullTitle()))
duplicate.add(r.getFullTitle());
Map<String, Integer> counter = Maps.newHashMap();
for (ParameterSetRunner r : getParameterSets())
if (duplicate.contains(r.getFullTitle())) {
String title = r.getFullTitle();
Integer count = counter.get(title);
if (count == null)
count = 1;
else
count++;
counter.put(title, count);
r.index = count;
}
}
}
private static Map<Class<?>, IInjectorProvider> injectorProviderClassCache = Maps
.newHashMap();
protected List<ResourceRunner> children;
protected Filter filter = null;
protected Map<String, MethodWithExpectation> methods = Maps.newHashMap();
public ParameterizedXtextRunner(Class<?> testClass)
throws InitializationError {
super(testClass);
}
protected IInjectorProvider createInjectorProvider() {
IInjectorProvider injectorProvider = null;
InjectWith injectWith = getTestClass().getJavaClass().getAnnotation(
InjectWith.class);
if (injectWith != null) {
try {
injectorProvider = injectWith.value().newInstance();
} catch (Exception e) {
throwUncheckedException(e);
}
}
return injectorProvider;
}
@Override
protected Description describeChild(ResourceRunner child) {
return child.getDescription();
}
@Override
public void filter(Filter filter) throws NoTestsRemainException {
super.filter(filter);
this.filter = filter;
}
protected MethodWithExpectation findTestMethod(String name)
throws Throwable {
MethodWithExpectation result = methods.get(name);
if (result == null) {
Method method = getTestClass().getJavaClass().getMethod(name);
if (method == null)
throw new RuntimeException("Method " + name
+ "() not found in " + getTestClass().getName());
result = new MethodWithExpectation(method);
methods.put(name, result);
}
return result;
}
@Override
protected List<ResourceRunner> getChildren() {
if (children == null) {
children = Lists.newArrayList();
IInjectorProvider injectorProvider = getOrCreateInjectorProvider();
for (URI uri : getURIs()) {
ResourceRunner child = injectorProvider.getInjector()
.getInstance(ResourceRunner.class);
child.init(getTestClass(), injectorProvider, uri);
children.add(child);
}
}
return children;
}
protected IInjectorProvider getInjectorProvider() {
return injectorProviderClassCache.get(getTestClass().getJavaClass());
}
protected IInjectorProvider getOrCreateInjectorProvider() {
IInjectorProvider injectorProvider = getInjectorProvider();
if (injectorProvider == null) {
injectorProvider = createInjectorProvider();
injectorProviderClassCache.put(getTestClass().getJavaClass(),
injectorProvider);
}
return injectorProvider;
}
protected List<URI> getURIs() {
ResourceURIs classAnnotation = getTestClass().getJavaClass()
.getAnnotation(ResourceURIs.class);
if (classAnnotation != null)
return getURIs(classAnnotation);
for (FrameworkMethod method : getTestClass().getAnnotatedMethods(
ResourceURIs.class)) {
int modifiers = method.getMethod().getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
return getURIs(method);
}
throw new RuntimeException(
"The class "
+ getTestClass().getJavaClass()
+ " or one of its static public methods needs to be annotated with @"
+ ResourceURIs.class.getSimpleName());
}
@SuppressWarnings("unchecked")
protected List<URI> getURIs(FrameworkMethod method) {
try {
return (List<URI>) method.invokeExplosively(null);
} catch (Throwable e) {
Exceptions.throwUncheckedException(e);
return Collections.emptyList();
}
}
protected List<URI> getURIs(ResourceURIs uris) {
List<URI> result = Lists.newArrayList();
ResourceURICollector collector = new ResourceURICollector();
if (uris.files().length > 0)
result.addAll(collector.collectFiles(uris.files()));
if (!Strings.isEmpty(uris.baseDir())
|| uris.fileExtensions().length > 0) {
Assert.assertFalse("@ResourceURIs needs a baseURI",
Strings.isEmpty(uris.baseDir()));
Assert.assertTrue("@ResourceURIs needs at least one fileExtension",
uris.fileExtensions().length > 0);
result.addAll(collector.collectFiles(uris.baseDir(),
uris.fileExtensions()));
}
return result;
}
protected Object newTestInstance() throws SecurityException,
NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
Constructor<?> constructor = getTestClass().getJavaClass()
.getConstructor();
return constructor.newInstance();
}
protected void injectParameters(Object test, Multimap<String, Object> params) {
List<Field> fields = Lists.newArrayList();
Class<?> clazz = test.getClass();
while (clazz != null && clazz != Object.class) {
fields.addAll(Lists.newArrayList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
for (Field field : fields) {
InjectParameter annotation = field
.getAnnotation(InjectParameter.class);
if (annotation != null) {
String name = Strings.isEmpty(annotation.value()) ? field
.getName() : annotation.value();
Class<?> fieldType = ReflectionUtil.getObjectType(field
.getType());
for (Object value : params.get(name))
if (fieldType.isInstance(value)) {
field.setAccessible(true);
try {
field.set(test, value);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
}
}
}
}
// protected Object newTestInstance(Object[][] allParams) throws
// IllegalArgumentException, InstantiationException,
// IllegalAccessException, InvocationTargetException {
// for (Object[] params : allParams)
// ROOT: for (Constructor<?> candidate :
// getTestClass().getJavaClass().getConstructors())
// if (candidate.getParameterTypes().length == params.length) {
// for (int i = 0; i < params.length; i++)
// if (params[i] != null
// && !ReflectionUtil.getObjectType(candidate.getParameterTypes()[i])
// .isInstance(params[i]))
// continue ROOT;
// return candidate.newInstance(params);
// }
// List<String> alternatives = Lists.newArrayList();
// for (Object[] params : allParams) {
// List<String> types = Lists.newArrayList();
// for (Object p : params)
// types.add(p == null ? "?" : p.getClass().getName());
// alternatives.add(Joiner.on(", ").join(types));
// }
// throw new RuntimeException("No valid constructor found in class " +
// getTestClass().getJavaClass().getName()
// + " for types " + Joiner.on(" or ").join(alternatives));
// }
protected void runChild(ParameterSetRunner ps) throws Throwable {
MethodWithExpectation method = findTestMethod(ps.getMethdoName());
Object test = newTestInstance();
if (ps.getInjectorProvider() instanceof IRegistryConfigurator)
((IRegistryConfigurator) ps.getInjectorProvider()).setupRegistry();
try {
injectParameters(test, ps.getParams());
ps.getInjectorProvider().getInjector().injectMembers(test);
Object result = method.getMethod().invoke(test);
method.getValidator().validate(ps.getResource(),
ps.getExpectation(), result);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
if (ps.getInjectorProvider() instanceof IRegistryConfigurator)
((IRegistryConfigurator) ps.getInjectorProvider())
.restoreRegistry();
}
}
@Override
protected void runChild(ResourceRunner arg0, RunNotifier notifier) {
for (ParameterSetRunner ps : arg0.getParameterSets())
if (filter == null || filter.shouldRun(ps.getDescription())) {
notifier.fireTestStarted(ps.getDescription());
if (ps.isIgnore())
notifier.fireTestIgnored(ps.getDescription());
else
try {
runChild(ps);
} catch (Throwable e) {
notifier.fireTestFailure(new Failure(ps
.getDescription(), e));
}
notifier.fireTestFinished(ps.getDescription());
}
}
}