/*
* Copyright 2015-2019 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.jupiter.engine.descriptor;
import static java.util.stream.Collectors.joining;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass;
import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation;
import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromFields;
import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods;
import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods;
import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods;
import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods;
import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle;
import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstanceFactory;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.jupiter.api.extension.TestInstances;
import org.junit.jupiter.api.extension.TestInstantiationException;
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.DefaultTestInstances;
import org.junit.jupiter.engine.execution.ExecutableInvoker;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
import org.junit.jupiter.engine.execution.TestInstancesProvider;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.util.BlacklistedExceptions;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestTag;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
TestDescriptor
for tests based on Java classes. Default Display Names
The default display name for a top-level or nested static test class is
the fully qualified name of the class with the package name and leading dot
(".") removed.
Since: 5.0
/**
* {@link TestDescriptor} for tests based on Java classes.
*
* <h3>Default Display Names</h3>
*
* <p>The default display name for a top-level or nested static test class is
* the fully qualified name of the class with the package name and leading dot
* (".") removed.
*
* @since 5.0
*/
@API(status = INTERNAL, since = "5.0")
public class ClassTestDescriptor extends JupiterTestDescriptor {
private static final ExecutableInvoker executableInvoker = new ExecutableInvoker();
private final Class<?> testClass;
private final Set<TestTag> tags;
protected final Lifecycle lifecycle;
private ExecutionMode defaultChildExecutionMode;
private TestInstanceFactory testInstanceFactory;
private List<Method> beforeAllMethods;
private List<Method> afterAllMethods;
public ClassTestDescriptor(UniqueId uniqueId, Class<?> testClass, JupiterConfiguration configuration) {
this(uniqueId, testClass, createDisplayNameSupplierForClass(testClass), configuration);
}
ClassTestDescriptor(UniqueId uniqueId, Class<?> testClass, Supplier<String> displayNameSupplier,
JupiterConfiguration configuration) {
super(uniqueId, testClass, displayNameSupplier, ClassSource.from(testClass), configuration);
this.testClass = testClass;
this.tags = getTags(testClass);
this.lifecycle = getTestInstanceLifecycle(testClass, configuration);
this.defaultChildExecutionMode = (this.lifecycle == Lifecycle.PER_CLASS ? ExecutionMode.SAME_THREAD : null);
}
// --- TestDescriptor ------------------------------------------------------
@Override
public Set<TestTag> getTags() {
// return modifiable copy
return new LinkedHashSet<>(this.tags);
}
public final Class<?> getTestClass() {
return this.testClass;
}
@Override
public Type getType() {
return Type.CONTAINER;
}
@Override
public String getLegacyReportingName() {
return this.testClass.getName();
}
// --- Node ----------------------------------------------------------------
@Override
protected Optional<ExecutionMode> getExplicitExecutionMode() {
return getExecutionModeFromAnnotation(getTestClass());
}
@Override
protected Optional<ExecutionMode> getDefaultChildExecutionMode() {
return Optional.ofNullable(this.defaultChildExecutionMode);
}
public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode) {
this.defaultChildExecutionMode = defaultChildExecutionMode;
}
@Override
public Set<ExclusiveResource> getExclusiveResources() {
return getExclusiveResourcesFromAnnotation(getTestClass());
}
@Override
public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation(
context.getExtensionRegistry(), this.testClass);
// Register extensions from static fields here, at the class level but
// after extensions registered via @ExtendWith.
registerExtensionsFromFields(registry, this.testClass, null);
// Resolve the TestInstanceFactory at the class level in order to fail
// the entire class in case of configuration errors (e.g., more than
// one factory registered per class).
this.testInstanceFactory = resolveTestInstanceFactory(registry);
registerBeforeEachMethodAdapters(registry);
registerAfterEachMethodAdapters(registry);
ThrowableCollector throwableCollector = createThrowableCollector();
ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(),
context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), throwableCollector);
this.beforeAllMethods = findBeforeAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD);
this.afterAllMethods = findAfterAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD);
// @formatter:off
return context.extend()
.withTestInstancesProvider(testInstancesProvider(context, registry, extensionContext))
.withExtensionRegistry(registry)
.withExtensionContext(extensionContext)
.withThrowableCollector(throwableCollector)
.build();
// @formatter:on
}
@Override
public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) {
ThrowableCollector throwableCollector = context.getThrowableCollector();
Lifecycle lifecycle = context.getExtensionContext().getTestInstanceLifecycle().orElse(Lifecycle.PER_METHOD);
if (lifecycle == Lifecycle.PER_CLASS) {
// Eagerly load test instance for BeforeAllCallbacks, if necessary,
// and store the instance in the ExtensionContext.
ClassExtensionContext extensionContext = (ClassExtensionContext) context.getExtensionContext();
throwableCollector.execute(() -> extensionContext.setTestInstances(
context.getTestInstancesProvider().getTestInstances(Optional.empty())));
}
if (throwableCollector.isEmpty()) {
context.beforeAllCallbacksExecuted(true);
invokeBeforeAllCallbacks(context);
if (throwableCollector.isEmpty()) {
context.beforeAllMethodsExecuted(true);
invokeBeforeAllMethods(context);
}
}
throwableCollector.assertEmpty();
return context;
}
@Override
public void after(JupiterEngineExecutionContext context) {
ThrowableCollector throwableCollector = context.getThrowableCollector();
Throwable previousThrowable = throwableCollector.getThrowable();
if (context.beforeAllMethodsExecuted()) {
invokeAfterAllMethods(context);
}
if (context.beforeAllCallbacksExecuted()) {
invokeAfterAllCallbacks(context);
}
// If the previous Throwable was not null when this method was called,
// that means an exception was already thrown either before or during
// the execution of this Node. If an exception was already thrown, any
// later exceptions were added as suppressed exceptions to that original
// exception unless a more severe exception occurred in the meantime.
if (previousThrowable != throwableCollector.getThrowable()) {
throwableCollector.assertEmpty();
}
}
private TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) {
List<TestInstanceFactory> factories = registry.getExtensions(TestInstanceFactory.class);
if (factories.size() == 1) {
return factories.get(0);
}
if (factories.size() > 1) {
String factoryNames = factories.stream()//
.map(factory -> factory.getClass().getName())//
.collect(joining(", "));
String errorMessage = String.format(
"The following TestInstanceFactory extensions were registered for test class [%s], but only one is permitted: %s",
testClass.getName(), factoryNames);
throw new ExtensionConfigurationException(errorMessage);
}
return null;
}
private TestInstancesProvider testInstancesProvider(JupiterEngineExecutionContext parentExecutionContext,
ExtensionRegistry registry, ClassExtensionContext extensionContext) {
TestInstancesProvider testInstancesProvider = childRegistry -> instantiateAndPostProcessTestInstance(
parentExecutionContext, extensionContext, childRegistry.orElse(registry));
return childRegistry -> extensionContext.getTestInstances().orElseGet(
() -> testInstancesProvider.getTestInstances(childRegistry));
}
private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecutionContext parentExecutionContext,
ExtensionContext extensionContext, ExtensionRegistry registry) {
TestInstances instances = instantiateTestClass(parentExecutionContext, registry, extensionContext);
invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext);
// In addition, we register extensions from instance fields here since the
// best time to do that is immediately following test class instantiation
// and post processing.
registerExtensionsFromFields(registry, this.testClass, instances.getInnermostInstance());
return instances;
}
protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext,
ExtensionRegistry registry, ExtensionContext extensionContext) {
return instantiateTestClass(Optional.empty(), registry, extensionContext);
}
protected TestInstances instantiateTestClass(Optional<TestInstances> outerInstances, ExtensionRegistry registry,
ExtensionContext extensionContext) {
Optional<Object> outerInstance = outerInstances.map(TestInstances::getInnermostInstance);
Object instance = this.testInstanceFactory != null //
? invokeTestInstanceFactory(outerInstance, extensionContext) //
: invokeTestClassConstructor(outerInstance, registry, extensionContext);
return outerInstances.map(instances -> DefaultTestInstances.of(instances, instance)).orElse(
DefaultTestInstances.of(instance));
}
private Object invokeTestInstanceFactory(Optional<Object> outerInstance, ExtensionContext extensionContext) {
Object instance;
try {
instance = this.testInstanceFactory.createTestInstance(
new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), extensionContext);
}
catch (Throwable throwable) {
BlacklistedExceptions.rethrowIfBlacklisted(throwable);
if (throwable instanceof TestInstantiationException) {
throw (TestInstantiationException) throwable;
}
String message = String.format("TestInstanceFactory [%s] failed to instantiate test class [%s]",
this.testInstanceFactory.getClass().getName(), this.testClass.getName());
if (StringUtils.isNotBlank(throwable.getMessage())) {
message += ": " + throwable.getMessage();
}
throw new TestInstantiationException(message, throwable);
}
if (!this.testClass.isInstance(instance)) {
String testClassName = this.testClass.getName();
Class<?> instanceClass = (instance == null ? null : instance.getClass());
String instanceClassName = (instanceClass == null ? "null" : instanceClass.getName());
// If the test instance was loaded via a different ClassLoader, append
// the identity hash codes to the type names to help users disambiguate
// between otherwise identical "fully qualified class names".
if (testClassName.equals(instanceClassName)) {
testClassName += "@" + Integer.toHexString(System.identityHashCode(this.testClass));
instanceClassName += "@" + Integer.toHexString(System.identityHashCode(instanceClass));
}
String message = String.format(
"TestInstanceFactory [%s] failed to return an instance of [%s] and instead returned an instance of [%s].",
this.testInstanceFactory.getClass().getName(), testClassName, instanceClassName);
throw new TestInstantiationException(message);
}
return instance;
}
private Object invokeTestClassConstructor(Optional<Object> outerInstance, ExtensionRegistry registry,
ExtensionContext extensionContext) {
Constructor<?> constructor = ReflectionUtils.getDeclaredConstructor(this.testClass);
return outerInstance.isPresent() //
? executableInvoker.invoke(constructor, outerInstance.get(), extensionContext, registry) //
: executableInvoker.invoke(constructor, extensionContext, registry);
}
private void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry,
ExtensionContext context) {
registry.stream(TestInstancePostProcessor.class).forEach(
extension -> executeAndMaskThrowable(() -> extension.postProcessTestInstance(instance, context)));
}
private void invokeBeforeAllCallbacks(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
for (BeforeAllCallback callback : registry.getExtensions(BeforeAllCallback.class)) {
throwableCollector.execute(() -> callback.beforeAll(extensionContext));
if (throwableCollector.isNotEmpty()) {
break;
}
}
}
private void invokeBeforeAllMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
Object testInstance = extensionContext.getTestInstance().orElse(null);
for (Method method : this.beforeAllMethods) {
throwableCollector.execute(
() -> executableInvoker.invoke(method, testInstance, extensionContext, registry));
if (throwableCollector.isNotEmpty()) {
break;
}
}
}
private void invokeAfterAllMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
Object testInstance = extensionContext.getTestInstance().orElse(null);
this.afterAllMethods.forEach(method -> throwableCollector.execute(
() -> executableInvoker.invoke(method, testInstance, extensionContext, registry)));
}
private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();
registry.getReversedExtensions(AfterAllCallback.class)//
.forEach(extension -> throwableCollector.execute(() -> extension.afterAll(extensionContext)));
}
private void registerBeforeEachMethodAdapters(ExtensionRegistry registry) {
List<Method> beforeEachMethods = findBeforeEachMethods(this.testClass);
registerMethodsAsExtensions(beforeEachMethods, registry, this::synthesizeBeforeEachMethodAdapter);
}
private void registerAfterEachMethodAdapters(ExtensionRegistry registry) {
// Make a local copy since findAfterEachMethods() returns an immutable list.
List<Method> afterEachMethods = new ArrayList<>(findAfterEachMethods(this.testClass));
// Since the bottom-up ordering of afterEachMethods will later be reversed when the
// synthesized AfterEachMethodAdapters are executed within TestMethodTestDescriptor,
// we have to reverse the afterEachMethods list to put them in top-down order before
// we register them as synthesized extensions.
Collections.reverse(afterEachMethods);
registerMethodsAsExtensions(afterEachMethods, registry, this::synthesizeAfterEachMethodAdapter);
}
private void registerMethodsAsExtensions(List<Method> methods, ExtensionRegistry registry,
Function<Method, Extension> extensionSynthesizer) {
methods.forEach(method -> registry.registerExtension(extensionSynthesizer.apply(method), method));
}
private BeforeEachMethodAdapter synthesizeBeforeEachMethodAdapter(Method method) {
return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry);
}
private AfterEachMethodAdapter synthesizeAfterEachMethodAdapter(Method method) {
return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry);
}
private void invokeMethodInExtensionContext(Method method, ExtensionContext context, ExtensionRegistry registry) {
TestInstances testInstances = context.getRequiredTestInstances();
Object target = testInstances.findInstance(method.getDeclaringClass()).orElseThrow(
() -> new JUnitException("Failed to find instance for method: " + method.toGenericString()));
executableInvoker.invoke(method, target, context, registry);
}
}