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 java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.apiguardian.api.API;
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.TestExecutionExceptionHandler;
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.JupiterEngineExecutionContext;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.BlacklistedExceptions;
import org.junit.platform.commons.util.ExceptionUtils;
import org.junit.platform.commons.util.ReflectionUtils;
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;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector.Executable;
@API(status = INTERNAL, since = "5.0")
public class TestMethodTestDescriptor extends MethodBasedTestDescriptor {
private static final ExecutableInvoker executableInvoker = new ExecutableInvoker();
private static final Logger logger = LoggerFactory.getLogger(TestMethodTestDescriptor.class);
public TestMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
}
TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, displayName, testClass, testMethod, configuration);
}
@Override
public Type getType() {
return Type.TEST;
}
@Override
public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception {
ExtensionRegistry 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(Optional.of(registry));
extensionContext.setTestInstances(testInstances);
});
return context.extend()
.withExtensionRegistry(registry)
.withExtensionContext(extensionContext)
.withThrowableCollector(throwableCollector)
.build();
}
protected ExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) {
return populateNewExtensionRegistryFromExtendWithAnnotation(context.getExtensionRegistry(), getTestMethod());
}
@Override
public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context,
DynamicTestExecutor dynamicTestExecutor) throws Exception {
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);
throwableCollector.assertEmpty();
return context;
}
private void invokeBeforeEachCallbacks(JupiterEngineExecutionContext context) {
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(context,
((extensionContext, callback) -> () -> callback.beforeEach(extensionContext)), BeforeEachCallback.class);
}
private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(context,
((extensionContext, adapter) -> () -> adapter.invokeBeforeEachMethod(extensionContext, registry)),
BeforeEachMethodAdapter.class);
}
private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) {
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(context,
((extensionContext, callback) -> () -> callback.beforeTestExecution(extensionContext)),
BeforeTestExecutionCallback.class);
}
private <T extends Extension> void invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(
JupiterEngineExecutionContext context, BiFunction<ExtensionContext, T, Executable> generator,
Class<T> type) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
for (T callback : registry.getExtensions(type)) {
Executable executable = generator.apply(extensionContext, callback);
throwableCollector.execute(executable);
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());
}
catch (Throwable throwable) {
BlacklistedExceptions.rethrowIfBlacklisted(throwable);
invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable);
}
});
}
private void invokeTestExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context,
Throwable ex) {
invokeTestExecutionExceptionHandlers(ex, registry.getReversedExtensions(TestExecutionExceptionHandler.class),
context);
}
private void invokeTestExecutionExceptionHandlers(Throwable ex, List<TestExecutionExceptionHandler> handlers,
ExtensionContext context) {
if (handlers.isEmpty()) {
ExceptionUtils.throwAsUncheckedException(ex);
}
try {
handlers.remove(0).handleTestExecutionException(context, ex);
}
catch (Throwable t) {
BlacklistedExceptions.rethrowIfBlacklisted(t);
invokeTestExecutionExceptionHandlers(t, handlers, context);
}
}
private void invokeAfterTestExecutionCallbacks(JupiterEngineExecutionContext context) {
invokeAllAfterMethodsOrCallbacks(context,
((extensionContext, callback) -> () -> callback.afterTestExecution(extensionContext)),
AfterTestExecutionCallback.class);
}
private void invokeAfterEachMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
invokeAllAfterMethodsOrCallbacks(context,
((extensionContext, adapter) -> () -> adapter.invokeAfterEachMethod(extensionContext, registry)),
AfterEachMethodAdapter.class);
}
private void invokeAfterEachCallbacks(JupiterEngineExecutionContext context) {
invokeAllAfterMethodsOrCallbacks(context,
((extensionContext, callback) -> () -> callback.afterEach(extensionContext)), AfterEachCallback.class);
}
private <T extends Extension> void invokeAllAfterMethodsOrCallbacks(JupiterEngineExecutionContext context,
BiFunction<ExtensionContext, T, Executable> generator, Class<T> type) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
registry.getReversedExtensions(type).forEach(callback -> {
Executable executable = generator.apply(extensionContext, callback);
throwableCollector.execute(executable);
});
}
@Override
public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) {
if (context != null) {
invokeTestWatchers(context, false,
watcher -> watcher.testDisabled(context.getExtensionContext(), result.getReason()));
}
}
@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;
}
});
}
}
private void invokeTestWatchers(JupiterEngineExecutionContext context, boolean reverseOrder,
Consumer<TestWatcher> callback) {
ExtensionRegistry registry = context.getExtensionRegistry();
List<TestWatcher> watchers = reverseOrder
? registry.getReversedExtensions(TestWatcher.class)
: registry.getExtensions(TestWatcher.class);
watchers.forEach(watcher -> {
try {
callback.accept(watcher);
}
catch (Throwable throwable) {
BlacklistedExceptions.rethrowIfBlacklisted(throwable);
ExtensionContext extensionContext = context.getExtensionContext();
logger.warn(throwable,
() -> String.format("Failed to invoke TestWatcher [%s] for method [%s] with display name [%s]",
watcher.getClass().getName(),
ReflectionUtils.getFullyQualifiedMethodName(extensionContext.getRequiredTestClass(),
extensionContext.getRequiredTestMethod()),
getDisplayName()));
}
});
}
}