/* * Copyright 2015-2017 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 v1.0 which * accompanies this distribution and is available at * * http://www.eclipse.org/legal/epl-v10.html */ package org.junit.jupiter.engine.descriptor; 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.platform.commons.meta.API.Usage.Internal; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Function; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ContainerExtensionContext; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; 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.execution.TestInstanceProvider; import org.junit.jupiter.engine.execution.ThrowableCollector; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.meta.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; 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; /** * {@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(Internal) public class ClassTestDescriptor extends JupiterTestDescriptor { private static final ExecutableInvoker executableInvoker = new ExecutableInvoker(); private final Class<?> testClass; private final List<Method> beforeAllMethods; private final List<Method> afterAllMethods; private final List<Method> beforeEachMethods; private final List<Method> afterEachMethods; public ClassTestDescriptor(UniqueId uniqueId, Class<?> testClass) { this(uniqueId, ClassTestDescriptor::generateDefaultDisplayName, testClass); } protected ClassTestDescriptor(UniqueId uniqueId, Function<Class<?>, String> defaultDisplayNameGenerator, Class<?> testClass) { super(uniqueId, determineDisplayName(Preconditions.notNull(testClass, "Class must not be null"), defaultDisplayNameGenerator)); this.testClass = testClass; this.beforeAllMethods = findBeforeAllMethods(testClass); this.afterAllMethods = findAfterAllMethods(testClass); this.beforeEachMethods = findBeforeEachMethods(testClass); this.afterEachMethods = findAfterEachMethods(testClass); setSource(new ClassSource(testClass)); } // --- TestDescriptor ------------------------------------------------------ @Override public Set<TestTag> getTags() { return getTags(this.testClass); } public final Class<?> getTestClass() { return this.testClass; } @Override public Type getType() { return Type.CONTAINER; } @Override public String getLegacyReportingName() { return testClass.getName(); } private static String generateDefaultDisplayName(Class<?> testClass) { String name = testClass.getName(); int index = name.lastIndexOf('.'); return name.substring(index + 1); } // --- Node ---------------------------------------------------------------- @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { ExtensionRegistry registry = populateNewExtensionRegistryFromExtendWith(this.testClass, context.getExtensionRegistry()); registerBeforeEachMethodAdapters(registry); registerAfterEachMethodAdapters(registry); ContainerExtensionContext containerExtensionContext = new ClassBasedContainerExtensionContext( context.getExtensionContext(), context.getExecutionListener(), this); // @formatter:off return context.extend() .withTestInstanceProvider(testInstanceProvider(context, registry, containerExtensionContext)) .withExtensionRegistry(registry) .withExtensionContext(containerExtensionContext) .withThrowableCollector(new ThrowableCollector()) .build(); // @formatter:on } @Override public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) throws Exception { return shouldContainerBeSkipped(context); } @Override public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) throws Exception { ThrowableCollector throwableCollector = context.getThrowableCollector(); invokeBeforeAllCallbacks(context); if (throwableCollector.isEmpty()) { context.beforeAllMethodsExecuted(true); invokeBeforeAllMethods(context); } throwableCollector.assertEmpty(); return context; } @Override public void after(JupiterEngineExecutionContext context) throws Exception { if (context.beforeAllMethodsExecuted()) { invokeAfterAllMethods(context); } invokeAfterAllCallbacks(context); context.getThrowableCollector().assertEmpty(); } protected TestInstanceProvider testInstanceProvider(JupiterEngineExecutionContext parentExecutionContext, ExtensionRegistry registry, ExtensionContext extensionContext) { return childExtensionRegistry -> { Constructor<?> constructor = ReflectionUtils.getDeclaredConstructor(this.testClass); Object instance = executableInvoker.invoke(constructor, extensionContext, childExtensionRegistry.orElse(registry)); invokeTestInstancePostProcessors(instance, childExtensionRegistry.orElse(registry), extensionContext); return instance; }; } protected 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(); ContainerExtensionContext extensionContext = (ContainerExtensionContext) 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(); ContainerExtensionContext extensionContext = (ContainerExtensionContext) context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); for (Method method : this.beforeAllMethods) { throwableCollector.execute(() -> executableInvoker.invoke(method, extensionContext, registry)); if (throwableCollector.isNotEmpty()) { break; } } } private void invokeAfterAllMethods(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); ContainerExtensionContext extensionContext = (ContainerExtensionContext) context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); this.afterAllMethods.forEach( method -> throwableCollector.execute(() -> executableInvoker.invoke(method, extensionContext, registry))); } private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); ContainerExtensionContext extensionContext = (ContainerExtensionContext) context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); registry.getReversedExtensions(AfterAllCallback.class)// .forEach(extension -> throwableCollector.execute(() -> extension.afterAll(extensionContext))); } private void registerBeforeEachMethodAdapters(ExtensionRegistry registry) { registerMethodsAsExtensions(this.beforeEachMethods, registry, this::synthesizeBeforeEachMethodAdapter); } private void registerAfterEachMethodAdapters(ExtensionRegistry registry) { // Since the bottom-up ordering of afterEachMethods will later be reversed when the // synthesized AfterEachMethodAdapters are executed within MethodTestDescriptor, we // have to reverse the afterEachMethods list to put them in top-down order before we // register them as synthesized extensions. List<Method> reversed = new ArrayList<>(this.afterEachMethods); Collections.reverse(reversed); registerMethodsAsExtensions(reversed, 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) -> invokeMethodInTestExtensionContext(method, extensionContext, registry); } private AfterEachMethodAdapter synthesizeAfterEachMethodAdapter(Method method) { return (extensionContext, registry) -> invokeMethodInTestExtensionContext(method, extensionContext, registry); } private void invokeMethodInTestExtensionContext(Method method, TestExtensionContext context, ExtensionRegistry registry) { Object instance = ReflectionUtils.getOuterInstance(context.getTestInstance(), method.getDeclaringClass()).orElseThrow( () -> new JUnitException("Failed to find instance for method: " + method.toGenericString())); executableInvoker.invoke(method, instance, context, registry); } }