
org.ehoffman.test.junit.JunitRunner Maven / Gradle / Ivy
The newest version!
package org.ehoffman.test.junit;
import java.io.Closeable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Allows advice to be added to unit tests by annotating the test class with an {@link Annotation} that has a field named
* IMPLEMENTED_BY of type {@link Class} the class must be a subclass of {@link MethodInterceptor} and that class must have one
* publicly accessible zero argument constructor.
*
* @author rex
*/
public abstract class JunitRunner extends BlockJUnit4ClassRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(JunitRunner.class);
private static final ShutdownHook HOOK = new ShutdownHook();
private static final AtomicBoolean HOOK_ADDED = new AtomicBoolean(false);
private static final Map, MethodInterceptor> INTERCEPTORS
= Collections.synchronizedMap(new HashMap, MethodInterceptor>());
private static final String ERROR_MSG = "Test interceptor aop class appears to be incorrectly implemented,"
+ " should have one public zero argument constructor";
public JunitRunner(final Class> klass) throws InitializationError {
super(klass);
}
public abstract AssumptionViolatedException convertIfPossible(Throwable t);
protected void registerAnnotationHandler(final Class extends Annotation> annotation,
final MethodInterceptor methodInterceptor) {
synchronized (INTERCEPTORS) {
if (INTERCEPTORS.get(annotation) == null) {
INTERCEPTORS.put(annotation, methodInterceptor);
if (Closeable.class.isAssignableFrom(methodInterceptor.getClass())) {
HOOK.addClosable((Closeable) INTERCEPTORS.get(annotation));
}
}
}
}
private MethodInterceptor getOrBuildInterceptor(final Class extends Annotation> annotationType,
final Class interceptorClass) {
final MethodInterceptor interceptor = INTERCEPTORS.get(annotationType);
if (interceptor == null && interceptorClass != null) {
if (INTERCEPTORS.get(annotationType) == null) {
registerAnnotationHandler(annotationType, buildInterceptor(interceptorClass));
}
}
return INTERCEPTORS.get(annotationType);
}
@Override
protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
final Description description = describeChild(method);
if (!HOOK_ADDED.get()) {
synchronized (HOOK_ADDED) {
if (!HOOK_ADDED.get()) {
Runtime.getRuntime().addShutdownHook(new Thread(HOOK));
HOOK_ADDED.set(true);
}
}
}
runAopLeaf(method, description, notifier);
}
/**
* Runs a {@link Statement} that represents a leaf (aka atomic) test.
*/
@SuppressWarnings("unchecked")
protected final void runAopLeaf(final FrameworkMethod method, final Description description, final RunNotifier notifier) {
final EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
eachNotifier.fireTestStarted();
Statement statement = methodBlock(method);
for (Annotation annotation : method.getAnnotations()) {
MethodInterceptor interceptor = INTERCEPTORS.get(annotation.annotationType());
if (interceptor == null) {
try {
final Object implementor = annotation.getClass().getMethod("IMPLEMENTED_BY").invoke(annotation);
interceptor = getOrBuildInterceptor(annotation.annotationType(), (Class) implementor);
} catch (final InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
LOGGER.debug("Annotation does not have a IMPLEMENTED_BY field of type String with the name of a Class"
+ " extending MethodInterceptor. In Annotation: " + annotation);
}
}
if (interceptor != null) {
statement = apply(interceptor, statement, method, null);
}
}
try {
statement.evaluate();
} catch (final AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (final Throwable e) {
final AssumptionViolatedException assumptionException = convertIfPossible(e);
if (assumptionException != null) {
eachNotifier.addFailedAssumption(assumptionException);
} else {
eachNotifier.addFailure(e);
}
} finally {
eachNotifier.fireTestFinished();
}
}
@SuppressWarnings("unchecked")
public T buildInterceptor(final Class interceptorClass) {
if (interceptorClass == null) {
return null;
}
if (interceptorClass.getConstructors().length == 1
&& interceptorClass.getConstructors()[0].getParameterTypes().length == 0) {
try {
final T interceptor = ((Constructor) interceptorClass.getConstructors()[0]).newInstance(new Object[] {});
return interceptor;
} catch (final InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | SecurityException e) {
LOGGER.warn(ERROR_MSG, e);
}
} else {
LOGGER.warn(ERROR_MSG);
}
return null;
}
private Statement apply(final MethodInterceptor interceptor, final Statement base, final FrameworkMethod method,
final Object target) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
interceptor.invoke(new MethodInvocation() {
@Override
public Object proceed() throws Throwable {
base.evaluate();
return null;
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return null;
}
@Override
public Object[] getArguments() {
return new Object[] {};
}
@Override
public Method getMethod() {
return method.getMethod();
}
});
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy