com.nordstrom.automation.junit.RunReflectiveCall Maven / Gradle / Ivy
Show all versions of junit-foundation Show documentation
package com.nordstrom.automation.junit;
import static com.nordstrom.automation.junit.LifecycleHooks.getFieldValue;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.lang.IllegalAccessException;
import org.junit.internal.runners.model.ReflectiveCallable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.nordstrom.common.base.UncheckedThrow;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
/**
* This class declares the interceptor for the {@link org.junit.internal.runners.model.ReflectiveCallable#runReflectiveCall
* runReflectiveCall} method.
*/
@SuppressWarnings("squid:S1118")
public class RunReflectiveCall {
private static final ServiceLoader methodWatcherLoader;
private static final ThreadLocal> methodDepth;
private static final Function newInstance;
private static final Logger LOGGER = LoggerFactory.getLogger(RunReflectiveCall.class);
static {
methodWatcherLoader = ServiceLoader.load(MethodWatcher.class);
methodDepth = new ThreadLocal>() {
@Override
protected ConcurrentMap initialValue() {
return new ConcurrentHashMap<>();
}
};
newInstance = new Function() {
@Override
public DepthGauge apply(Integer input) {
return new DepthGauge();
}
};
}
/**
* Interceptor for the {@link org.junit.internal.runners.model.ReflectiveCallable#runReflectiveCall
* runReflectiveCall} method.
*
* @param callable {@code ReflectiveCallable} object being intercepted
* @param proxy callable proxy for the intercepted method
* @return {@code anything} - value returned by the intercepted method
* @throws Exception {@code anything} (exception thrown by the intercepted method)
*/
@RuntimeType
public static Object intercept(@This final ReflectiveCallable callable, @SuperCall final Callable> proxy)
throws Exception {
Object child = null;
try {
child = getFieldValue(callable, "this$0");
} catch (IllegalAccessException | NoSuchFieldException | SecurityException | IllegalArgumentException e) {
// handled below
}
Object runner = Run.getParentOf(child);
if (runner == null) {
runner = Run.getThreadRunner();
}
Object result = null;
Throwable thrown = null;
try {
fireBeforeInvocation(runner, child, callable);
result = LifecycleHooks.callProxy(proxy);
} catch (Throwable t) {
thrown = t;
} finally {
fireAfterInvocation(runner, child, callable, thrown);
}
if (thrown != null) {
throw UncheckedThrow.throwUnchecked(thrown);
}
return result;
}
/**
* Get reference to an instance of the specified watcher type.
*
* @param watcher type
* @param watcherType watcher type
* @return optional watcher instance
*/
@SuppressWarnings("unchecked")
static Optional getAttachedWatcher(Class watcherType) {
if (MethodWatcher.class.isAssignableFrom(watcherType)) {
synchronized(methodWatcherLoader) {
for (MethodWatcher> watcher : methodWatcherLoader) {
if (watcher.getClass() == watcherType) {
return Optional.of((W) watcher);
}
}
}
}
return Optional.absent();
}
/**
* Fire the {@link MethodWatcher#beforeInvocation(Object, Object, ReflectiveCallable) event.
*
* If the {@code beforeInvocation} event for the specified method has already been fired, do nothing.
*
* @param runner JUnit test runner
* @param child child of {@code runner} that is being invoked
* @param callable {@link ReflectiveCallable} object being intercepted
* @return {@code true} if event the {@code beforeInvocation} was fired; otherwise {@code false}
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private static boolean fireBeforeInvocation(Object runner, Object child, ReflectiveCallable callable) {
if ((runner != null) && (child != null)) {
DepthGauge depthGauge = LifecycleHooks.computeIfAbsent(methodDepth.get(), callable.hashCode(), newInstance);
if (0 == depthGauge.increaseDepth()) {
if (LOGGER.isDebugEnabled()) {
try {
LOGGER.debug("beforeInvocation: {}", LifecycleHooks.invoke(runner, "describeChild", child));
} catch (Throwable t) {
// nothing to do here
}
}
synchronized(methodWatcherLoader) {
for (MethodWatcher watcher : methodWatcherLoader) {
if (watcher.supportedType().isInstance(child)) {
watcher.beforeInvocation(runner, child, callable);
}
}
}
return true;
}
}
return false;
}
/**
* Fire the {@link MethodWatcher#afterInvocation(Object, Object, ReflectiveCallable) event.
*
* If the {@code afterInvocation} event for the specified method has already been fired, do nothing.
*
* @param runner JUnit test runner
* @param child child of {@code runner} that was just invoked
* @param callable {@link ReflectiveCallable} object being intercepted
* @param thrown exception thrown by method; null on normal completion
* @return {@code true} if event the {@code afterInvocation} was fired; otherwise {@code false}
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private static boolean fireAfterInvocation(Object runner, Object child, ReflectiveCallable callable, Throwable thrown) {
if ((runner != null) && (child != null)) {
DepthGauge depthGauge = LifecycleHooks.computeIfAbsent(methodDepth.get(), callable.hashCode(), newInstance);
if (0 == depthGauge.decreaseDepth()) {
if (LOGGER.isDebugEnabled()) {
try {
LOGGER.debug("afterInvocation: {}", LifecycleHooks.invoke(runner, "describeChild", child));
} catch (Throwable t) {
// nothing to do here
}
}
synchronized(methodWatcherLoader) {
for (MethodWatcher watcher : methodWatcherLoader) {
if (watcher.supportedType().isInstance(child)) {
watcher.afterInvocation(runner, child, callable, thrown);
}
}
}
return true;
}
}
return false;
}
}