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

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 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 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