package org.junit.jupiter.engine.descriptor;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation;
import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector;
import java.lang.reflect.Method;
import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback;
import org.junit.jupiter.api.extension.TestInstances;
import org.junit.jupiter.api.extension.TestWatcher;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.execution.AfterEachMethodAdapter;
import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter;
import org.junit.jupiter.engine.execution.ExecutableInvoker;
import org.junit.jupiter.engine.execution.ExecutableInvoker.ReflectiveInterceptorCall;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.jupiter.engine.extension.MutableExtensionRegistry;
import org.junit.platform.commons.util.UnrecoverableExceptions;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
@API(status = INTERNAL, since = "5.0")
public class TestMethodTestDescriptor extends MethodBasedTestDescriptor {
public static final String SEGMENT_TYPE = "method";
private static final ExecutableInvoker executableInvoker = new ExecutableInvoker();
private static final ReflectiveInterceptorCall<Method, Void> defaultInterceptorCall = ReflectiveInterceptorCall.ofVoidMethod(
InvocationInterceptor::interceptTestMethod);
private final ReflectiveInterceptorCall<Method, Void> interceptorCall;
public TestMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
this.interceptorCall = defaultInterceptorCall;
}
TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration, ReflectiveInterceptorCall<Method, Void> interceptorCall) {
super(uniqueId, displayName, testClass, testMethod, configuration);
this.interceptorCall = interceptorCall;
}
@Override
public Type getType() {
return Type.TEST;
}
@Override
public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) {
MutableExtensionRegistry registry = populateNewExtensionRegistry(context);
ThrowableCollector throwableCollector = createThrowableCollector();
MethodExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(),
context.getExecutionListener(), this, context.getConfiguration(), throwableCollector);
throwableCollector.execute(() -> {
TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(registry,
throwableCollector);
extensionContext.setTestInstances(testInstances);
});
return context.extend()
.withExtensionRegistry(registry)
.withExtensionContext(extensionContext)
.withThrowableCollector(throwableCollector)
.build();
}
protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) {
return populateNewExtensionRegistryFromExtendWithAnnotation(context.getExtensionRegistry(), getTestMethod());
}
@Override
public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context,
DynamicTestExecutor dynamicTestExecutor) {
ThrowableCollector throwableCollector = context.getThrowableCollector();
invokeBeforeEachCallbacks(context);
if (throwableCollector.isEmpty()) {
invokeBeforeEachMethods(context);
if (throwableCollector.isEmpty()) {
invokeBeforeTestExecutionCallbacks(context);
if (throwableCollector.isEmpty()) {
invokeTestMethod(context, dynamicTestExecutor);
}
invokeAfterTestExecutionCallbacks(context);
}
invokeAfterEachMethods(context);
}
invokeAfterEachCallbacks(context);
return context;
}
@Override
public void cleanUp(JupiterEngineExecutionContext context) throws Exception {
if (isPerMethodLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) {
invokeTestInstancePreDestroyCallbacks(context);
}
super.cleanUp(context);
context.getThrowableCollector().assertEmpty();
}
private boolean isPerMethodLifecycle(JupiterEngineExecutionContext context) {
return context.getExtensionContext().getTestInstanceLifecycle().orElse(
Lifecycle.PER_CLASS) == Lifecycle.PER_METHOD;
}
private void invokeBeforeEachCallbacks(JupiterEngineExecutionContext context) {
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachCallback.class, context,
(callback, extensionContext) -> callback.beforeEach(extensionContext));
}
private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachMethodAdapter.class, context,
(adapter, extensionContext) -> {
try {
adapter.invokeBeforeEachMethod(extensionContext, registry);
}
catch (Throwable throwable) {
invokeBeforeEachExecutionExceptionHandlers(extensionContext, registry, throwable);
}
});
}
private void invokeBeforeEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry,
Throwable throwable) {
invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable,
(handler, handledThrowable) -> handler.handleBeforeEachMethodExecutionException(context, handledThrowable));
}
private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) {
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeTestExecutionCallback.class, context,
(callback, extensionContext) -> callback.beforeTestExecution(extensionContext));
}
private <T extends Extension> void invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(Class<T> type,
JupiterEngineExecutionContext context, CallbackInvoker<T> callbackInvoker) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
for (T callback : registry.getExtensions(type)) {
throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext));
if (throwableCollector.isNotEmpty()) {
break;
}
}
}
protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
throwableCollector.execute(() -> {
try {
Method testMethod = getTestMethod();
Object instance = extensionContext.getRequiredTestInstance();
executableInvoker.invoke(testMethod, instance, extensionContext, context.getExtensionRegistry(),
interceptorCall);
}
catch (Throwable throwable) {
UnrecoverableExceptions.rethrowIfUnrecoverable(throwable);
invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable);
}
});
}
private void invokeTestExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context,
Throwable throwable) {
invokeExecutionExceptionHandlers(TestExecutionExceptionHandler.class, registry, throwable,
(handler, handledThrowable) -> handler.handleTestExecutionException(context, handledThrowable));
}
private void invokeAfterTestExecutionCallbacks(JupiterEngineExecutionContext context) {
invokeAllAfterMethodsOrCallbacks(AfterTestExecutionCallback.class, context,
(callback, extensionContext) -> callback.afterTestExecution(extensionContext));
}
private void invokeAfterEachMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
invokeAllAfterMethodsOrCallbacks(AfterEachMethodAdapter.class, context, (adapter, extensionContext) -> {
try {
adapter.invokeAfterEachMethod(extensionContext, registry);
}
catch (Throwable throwable) {
invokeAfterEachExecutionExceptionHandlers(extensionContext, registry, throwable);
}
});
}
private void invokeAfterEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry,
Throwable throwable) {
invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable,
(handler, handledThrowable) -> handler.handleAfterEachMethodExecutionException(context, handledThrowable));
}
private void invokeAfterEachCallbacks(JupiterEngineExecutionContext context) {
invokeAllAfterMethodsOrCallbacks(AfterEachCallback.class, context,
(callback, extensionContext) -> callback.afterEach(extensionContext));
}
private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) {
invokeAllAfterMethodsOrCallbacks(TestInstancePreDestroyCallback.class, context,
TestInstancePreDestroyCallback::preDestroyTestInstance);
}
private <T extends Extension> void invokeAllAfterMethodsOrCallbacks(Class<T> type,
JupiterEngineExecutionContext context, CallbackInvoker<T> callbackInvoker) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
registry.getReversedExtensions(type).forEach(callback -> {
throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext));
});
}
@Override
public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor,
TestExecutionResult result) {
if (context != null) {
ExtensionContext extensionContext = context.getExtensionContext();
TestExecutionResult.Status status = result.getStatus();
invokeTestWatchers(context, true, watcher -> {
switch (status) {
case SUCCESSFUL:
watcher.testSuccessful(extensionContext);
break;
case ABORTED:
watcher.testAborted(extensionContext, result.getThrowable().orElse(null));
break;
case FAILED:
watcher.testFailed(extensionContext, result.getThrowable().orElse(null));
break;
}
});
}
}
@FunctionalInterface
private interface CallbackInvoker<T extends Extension> {
void invoke(T t, ExtensionContext context) throws Throwable;
}
}