From f3146abddd7386a5c212f8f3f5e7dda56d7429ee Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 28 Jan 2025 11:23:55 +0100 Subject: [PATCH 01/60] Add initial API and failing test --- .../junit/jupiter/api/ContainerTemplate.java | 35 +++++++ .../ContainerTemplateInvocationContext.java | 41 ++++++++ ...inerTemplateInvocationContextProvider.java | 61 ++++++++++++ .../api/extension/KitchenSinkExtension.java | 15 ++- .../ContainerTemplateInvocationTests.java | 93 +++++++++++++++++++ 5 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java new file mode 100644 index 000000000000..c00d5a894164 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.platform.commons.annotation.Testable; + +/** + * @since 5.13 + * @see org.junit.jupiter.api.extension.ContainerTemplateInvocationContext + * @see org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = EXPERIMENTAL, since = "5.13") +@Testable +public @interface ContainerTemplate { +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java new file mode 100644 index 000000000000..60627e73477c --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * @since 5.13 + */ +@API(status = EXPERIMENTAL, since = "5.13") +public interface ContainerTemplateInvocationContext { + + /** + * Get the display name for this invocation. + * + *

The supplied {@code invocationIndex} is incremented by the framework + * with each container invocation. Thus, in the case of multiple active + * {@linkplain ContainerTemplateInvocationContextProvider providers}, only + * the first active provider receives indices starting with {@code 1}. + * + *

The default implementation returns the supplied {@code invocationIndex} + * wrapped in brackets — for example, {@code [1]}, {@code [42]}, etc. + * + * @param invocationIndex the index of this invocation (1-based). + * @return the display name for this invocation; never {@code null} or blank + */ + default String getDisplayName(int invocationIndex) { + return "[" + invocationIndex + "]"; + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java new file mode 100644 index 000000000000..1bc4fb3655e1 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.stream.Stream; + +import org.apiguardian.api.API; + +/** + * @since 5.13 + */ +@API(status = EXPERIMENTAL, since = "5.13") +public interface ContainerTemplateInvocationContextProvider extends Extension { + + /** + * Determine if this provider supports providing invocation contexts for the + * container template class represented by the supplied {@code context}. + * + * @param context the extension context for the container template class + * about to be invoked; never {@code null} + * @return {@code true} if this provider can provide invocation contexts + * @see #provideContainerTemplateInvocationContexts + * @see ExtensionContext + */ + boolean supportsContainerTemplate(ExtensionContext context); + + /** + * Provide + * {@linkplain ContainerTemplateInvocationContext invocation contexts} for + * the container template class represented by the supplied + * {@code context}. + * + *

This method is only called by the framework if + * {@link #supportsContainerTemplate} previously returned {@code true} for + * the same {@link ExtensionContext}; this method is allowed to return an + * empty {@code Stream} but not {@code null}. + * + *

The returned {@code Stream} will be properly closed by calling + * {@link Stream#close()}, making it safe to use a resource such as + * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. + * + * @param context the extension context for the container template class + * about to be invoked; never {@code null} + * @return a {@code Stream} of {@code ContainerTemplateInvocationContext} + * instances for the invocation of the container template class; never {@code null} + * @see #supportsContainerTemplate + * @see ExtensionContext + */ + Stream provideContainerTemplateInvocationContexts(ExtensionContext context); + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java index 0d77cac0882e..7cf57e11dae6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java @@ -55,8 +55,9 @@ public class KitchenSinkExtension implements // Conditional Test Execution ExecutionCondition, - // @TestTemplate + // @TestTemplate and @ContainerTemplate TestTemplateInvocationContextProvider, + ContainerTemplateInvocationContextProvider, // Miscellaneous TestWatcher, @@ -174,6 +175,18 @@ public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext cont return false; } + // --- @ContainerTemplate ------------------------------------------------------- + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return false; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return null; + } // --- TestWatcher --------------------------------------------------------- @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java new file mode 100644 index 000000000000..5384e58c62ed --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * @since 5.13 + */ +public class ContainerTemplateInvocationTests extends AbstractJupiterTestEngineTests { + + @Disabled("not yet implemented") + @Test + void executesContainerTemplateClassTwice() { + var results = executeTestsForClass(TwoInvocationsTestCase.class); + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(TwoInvocationsTestCase.class), uniqueIdSubstring("container-template"), started()), // + event(dynamicTestRegistered("container-template-invocation:#1"), displayName("[1] A")), // + event(container("container-template-invocation:#1"), started()), // + event(dynamicTestRegistered("test")), // + event(test("test"), started()), // + event(test("test"), finishedSuccessfully()), // + event(container("container-template-invocation:#1"), finishedSuccessfully()), // + event(dynamicTestRegistered("container-template-invocation:#2"), displayName("[2] B")), // + event(container("container-template-invocation:#2"), started()), // + event(dynamicTestRegistered("test")), // + event(test("test"), started()), // + event(test("test"), finishedSuccessfully()), // + event(container("container-template-invocation:#2"), finishedSuccessfully()), // + event(container(TwoInvocationsTestCase.class), uniqueIdSubstring("container-template"), + finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + static class TwoInvocationsTestCase { + @Test + void test() { + } + } + + static class TwoInvocationsContainerTemplateInvocationContextProvider + implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return TwoInvocationsTestCase.class.equals(context.getRequiredTestClass()); + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new Ctx("A"), new Ctx("B")); + } + + record Ctx(String displayName) implements ContainerTemplateInvocationContext { + @Override + public String getDisplayName(int invocationIndex) { + var defaultDisplayName = ContainerTemplateInvocationContext.super.getDisplayName(invocationIndex); + return "%s %s".formatted(defaultDisplayName, displayName); + } + } + } +} From 85116dae98e6599c654ada73bb17c495a08516a8 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 28 Jan 2025 12:04:34 +0100 Subject: [PATCH 02/60] Implement discovery by class selector --- .../ContainerTemplateTestDescriptor.java | 72 +++++++++++++++++++ .../discovery/ClassSelectorResolver.java | 20 ++++++ .../DiscoverySelectorResolverTests.java | 24 +++++++ 3 files changed, 116 insertions(+) create mode 100644 junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java new file mode 100644 index 000000000000..2adb158bef56 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.Collections.emptyList; +import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.parallel.ResourceLocksProvider; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.ExtensionContextSupplier; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; + +public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor { + + public static final String SEGMENT_TYPE = "container-template"; + + public ContainerTemplateTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { + super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); + } + + // --- TestDescriptor ------------------------------------------------------ + + @Override + public Set getTags() { + // return modifiable copy + return new LinkedHashSet<>(this.tags); + } + + @Override + public boolean mayRegisterTests() { + return true; + } + + // --- ClassBasedTestDescriptor --------------------------------------------- + + @Override + public List> getEnclosingTestClasses() { + return emptyList(); + } + + @Override + protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, + ExtensionContextSupplier extensionContext, ExtensionRegistry registry, + JupiterEngineExecutionContext context) { + return instantiateTestClass(Optional.empty(), registry, extensionContext); + } + + // --- ResourceLockAware --------------------------------------------------- + + @Override + public Function> getResourceLocksProviderEvaluator() { + return provider -> provider.provideForClass(getTestClass()); + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 0f809dcbde47..e6846304d321 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -14,6 +14,7 @@ import static java.util.stream.Collectors.toCollection; import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.support.ReflectionSupport.streamNestedClasses; @@ -31,9 +32,11 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; @@ -69,6 +72,9 @@ public Resolution resolve(ClassSelector selector, Context context) { if (isTestClassWithTests.test(testClass)) { // Nested tests are never filtered out if (classNameFilter.test(testClass.getName())) { + if (isAnnotated(testClass, ContainerTemplate.class)) { + return resolveContainerTemplate(context, testClass); + } return toResolution( context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass)))); } @@ -128,6 +134,20 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration); } + private Resolution resolveContainerTemplate(Context context, Class testClass) { + return context.addToParent(parent -> Optional.of(newContainerTemplateTestDescriptor(parent, testClass))) // + .map(Match::exact) // + .map(Resolution::match) // + .orElse(unresolved()); + } + + private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestDescriptor parent, + Class testClass) { + return new ContainerTemplateTestDescriptor( + parent.getUniqueId().append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, testClass.getName()), testClass, + configuration); + } + private Resolution toResolution(Optional testDescriptor) { return testDescriptor.map(it -> { Class testClass = it.getTestClass(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index 6c74b09dbbb4..219047d11456 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -53,6 +53,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; @@ -192,6 +193,22 @@ void classResolutionOfStaticNestedClass() { assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); } + @Test + void classResolutionOfContainerTemplate() { + var selector = selectClass(ContainerTemplateTestCase.class); + + resolve(request().selectors(selector)); + + assertThat(engineDescriptor.getDescendants()).hasSize(1); + + TestDescriptor containerTemplateDescriptor = getOnlyElement(engineDescriptor.getDescendants()); + assertThat(containerTemplateDescriptor.mayRegisterTests()).isTrue(); + + var containerTemplateSegment = containerTemplateDescriptor.getUniqueId().getLastSegment(); + assertThat(containerTemplateSegment.getType()).isEqualTo("container-template"); + assertThat(containerTemplateSegment.getValue()).isEqualTo(ContainerTemplateTestCase.class.getName()); + } + @Test void methodResolution() throws NoSuchMethodException { Method test1 = MyTestClass.class.getDeclaredMethod("test1"); @@ -899,3 +916,10 @@ class OtherClass { void test() { } } + +@ContainerTemplate +class ContainerTemplateTestCase { + @Test + void test() { + } +} From f2871c3c0789861ee0665c5e799850a1e72cfaeb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 28 Jan 2025 18:56:50 +0100 Subject: [PATCH 03/60] Add container template support for testable methods --- ...inerTemplateInvocationContextProvider.java | 20 ++++ ...ainerTemplateInvocationTestDescriptor.java | 54 +++++++++++ .../ContainerTemplateTestDescriptor.java | 97 +++++++++++++++++++ .../discovery/DiscoverySelectorResolver.java | 17 ++++ .../discovery/MethodSelectorResolver.java | 6 +- .../api/extension/KitchenSinkExtension.java | 6 ++ .../ContainerTemplateInvocationTests.java | 27 ++++-- 7 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java index 1bc4fb3655e1..3427f0c2e53f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java @@ -58,4 +58,24 @@ public interface ContainerTemplateInvocationContextProvider extends Extension { */ Stream provideContainerTemplateInvocationContexts(ExtensionContext context); + /** + * Signal that this provider may provide zero + * {@linkplain ContainerTemplateInvocationContext invocation contexts} for + * the container template class represented by the supplied {@code context}. + * + *

If this method returns {@code false} (which is the default) and the + * provider returns an empty stream from + * {@link #provideContainerTemplateInvocationContexts}, this will be considered + * an execution error. Override this method to return {@code true} to ignore + * the absence of invocation contexts for this provider. + * + * @param context the extension context for the container template class + * about to be invoked; never {@code null} + * @return {@code true} to allow zero contexts, {@code false} to fail + * execution in case of zero contexts + */ + default boolean mayReturnZeroContainerTemplateInvocationContexts(ExtensionContext context) { + return false; + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java new file mode 100644 index 000000000000..2357a30b7cdb --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.engine.EngineExecutionListener; + +/** + * @since 5.13 + */ +@API(status = INTERNAL, since = "5.13") +public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescriptor { + + public static final String SEGMENT_TYPE = "container-template-invocation"; + + private final ClassBasedTestDescriptor delegate; + + ContainerTemplateInvocationTestDescriptor(ClassBasedTestDescriptor delegate, + ContainerTemplateInvocationContext invocationContext, int index) { + super(delegate.getUniqueId(), invocationContext.getDisplayName(index), delegate.getSource().orElse(null), + delegate.configuration); + this.delegate = delegate; + } + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception { + return context.extend().build(); + } + + @Override + public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) throws Exception { + EngineExecutionListener executionListener = context.getExecutionListener(); + getChildren().forEach(executionListener::dynamicTestRegistered); + return context; + } + + @Override + public Type getType() { + return delegate.getType(); + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 2adb158bef56..564e022d5c28 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -11,23 +11,42 @@ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; +import static org.junit.jupiter.engine.discovery.DiscoverySelectorResolver.createTestDescriptor; +import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; +import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.support.ReflectionSupport.streamMethods; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.stream.Stream; +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.Node; +/** + * @since 5.13 + */ +@API(status = INTERNAL, since = "5.13") public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "container-template"; @@ -69,4 +88,82 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren public Function> getResourceLocksProviderEvaluator() { return provider -> provider.provideForClass(getTestClass()); } + + // --- Node ---------------------------------------------------------------- + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { + return super.prepare(context); + } + + // TODO copied from ContainerTemplateTestDescriptor + + @Override + public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) throws Exception { + ExtensionContext extensionContext = context.getExtensionContext(); + List providers = validateProviders(extensionContext, + context.getExtensionRegistry()); + AtomicInteger invocationIndex = new AtomicInteger(); + for (ContainerTemplateInvocationContextProvider provider : providers) { + executeForProvider(provider, invocationIndex, dynamicTestExecutor, extensionContext); + } + return context; + } + + private void executeForProvider(ContainerTemplateInvocationContextProvider provider, AtomicInteger invocationIndex, + DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { + + int initialValue = invocationIndex.get(); + + try (Stream stream = invocationContexts(provider, extensionContext)) { + stream.forEach(invocationContext -> toTestDescriptor(invocationContext, invocationIndex.incrementAndGet()) // + .ifPresent(testDescriptor -> execute(dynamicTestExecutor, testDescriptor))); + } + + Preconditions.condition( + invocationIndex.get() != initialValue + || provider.mayReturnZeroContainerTemplateInvocationContexts(extensionContext), + String.format( + "Provider [%s] did not provide any invocation contexts, but was expected to do so. " + + "You may override mayReturnZeroContainerTemplateInvocationContexts() to allow this.", + provider.getClass().getSimpleName())); + } + + private static Stream invocationContexts( + ContainerTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { + return provider.provideContainerTemplateInvocationContexts(extensionContext); + } + + private List validateProviders(ExtensionContext extensionContext, + ExtensionRegistry extensionRegistry) { + + // @formatter:off + List providers = extensionRegistry.stream(ContainerTemplateInvocationContextProvider.class) + .filter(provider -> provider.supportsContainerTemplate(extensionContext)) + .collect(toList()); + // @formatter:on + + return Preconditions.notEmpty(providers, + () -> String.format("You must register at least one %s that supports @ContainerTemplate class [%s]", + ContainerTemplateInvocationContextProvider.class.getSimpleName(), getTestClass())); + } + + private Optional toTestDescriptor(ContainerTemplateInvocationContext invocationContext, int index) { + UniqueId uniqueId = getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); + // TODO #871 filter descendants + // TODO #871 support @Nested classes + ClassTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, getTestClass(), this.configuration); + ContainerTemplateInvocationTestDescriptor containerDescriptor = new ContainerTemplateInvocationTestDescriptor( + classDescriptor, invocationContext, index); + streamMethods(getTestClass(), isTestOrTestFactoryOrTestTemplateMethod, TOP_DOWN) // + .map(method -> createTestDescriptor(classDescriptor, getTestClass(), method, this.configuration)) // + .forEach(methodDescriptor -> methodDescriptor.ifPresent(containerDescriptor::addChild)); + return Optional.of(containerDescriptor); + } + + private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { + testDescriptor.setParent(this); + dynamicTestExecutor.execute(testDescriptor); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java index a828889cc000..58d6250e78ad 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java @@ -12,7 +12,14 @@ import static org.apiguardian.api.API.Status.INTERNAL; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + import org.apiguardian.api.API; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; import org.junit.platform.engine.EngineDiscoveryRequest; @@ -48,4 +55,14 @@ public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescri resolver.resolve(request, engineDescriptor); } + @API(status = INTERNAL, since = "5.13") + public static Optional createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, + Method method, JupiterConfiguration configuration) { + return Arrays.stream(MethodSelectorResolver.MethodType.values()) // + .map(methodType -> methodType.methodPredicate.test(method) + ? methodType.createTestDescriptor(parent, testClass, method, configuration) + : null) // + .filter(Objects::nonNull).findFirst(); + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java index 9d5af96aa103..7a24445ed912 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -161,7 +161,7 @@ private Supplier> expansionCallback(TestDescrip }; } - private enum MethodType { + enum MethodType { TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { @Override @@ -192,7 +192,7 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl } }; - private final Predicate methodPredicate; + final Predicate methodPredicate; private final String segmentType; private final Set dynamicDescendantSegmentTypes; @@ -239,7 +239,7 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq return Optional.empty(); } - private TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, Method method, + TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, Method method, JupiterConfiguration configuration) { UniqueId uniqueId = createUniqueId(method, parent); return createTestDescriptor(uniqueId, testClass, method, parent::getEnclosingTestClasses, configuration); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java index 7cf57e11dae6..73c0e3edc3d8 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java @@ -187,6 +187,12 @@ public Stream provideContainerTemplateInvoca ExtensionContext context) { return null; } + + @Override + public boolean mayReturnZeroContainerTemplateInvocationContexts(ExtensionContext context) { + return false; + } + // --- TestWatcher --------------------------------------------------------- @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 5384e58c62ed..f454c8521964 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -23,7 +23,6 @@ import java.util.stream.Stream; import org.junit.jupiter.api.ContainerTemplate; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; @@ -35,7 +34,7 @@ */ public class ContainerTemplateInvocationTests extends AbstractJupiterTestEngineTests { - @Disabled("not yet implemented") + // @Disabled("not yet implemented") @Test void executesContainerTemplateClassTwice() { var results = executeTestsForClass(TwoInvocationsTestCase.class); @@ -44,15 +43,21 @@ void executesContainerTemplateClassTwice() { event(container(TwoInvocationsTestCase.class), uniqueIdSubstring("container-template"), started()), // event(dynamicTestRegistered("container-template-invocation:#1"), displayName("[1] A")), // event(container("container-template-invocation:#1"), started()), // - event(dynamicTestRegistered("test")), // - event(test("test"), started()), // - event(test("test"), finishedSuccessfully()), // + event(dynamicTestRegistered("a")), // + event(dynamicTestRegistered("b")), // + event(test("a"), started()), // + event(test("a"), finishedSuccessfully()), // + event(test("b"), started()), // + event(test("b"), finishedSuccessfully()), // event(container("container-template-invocation:#1"), finishedSuccessfully()), // event(dynamicTestRegistered("container-template-invocation:#2"), displayName("[2] B")), // event(container("container-template-invocation:#2"), started()), // - event(dynamicTestRegistered("test")), // - event(test("test"), started()), // - event(test("test"), finishedSuccessfully()), // + event(dynamicTestRegistered("a")), // + event(dynamicTestRegistered("b")), // + event(test("a"), started()), // + event(test("a"), finishedSuccessfully()), // + event(test("b"), started()), // + event(test("b"), finishedSuccessfully()), // event(container("container-template-invocation:#2"), finishedSuccessfully()), // event(container(TwoInvocationsTestCase.class), uniqueIdSubstring("container-template"), finishedSuccessfully()), // @@ -64,7 +69,11 @@ void executesContainerTemplateClassTwice() { @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) static class TwoInvocationsTestCase { @Test - void test() { + void a() { + } + + @Test + void b() { } } From fce9386ed7fbdd97e2d57385ab08dacaf805a617 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 28 Jan 2025 19:01:36 +0100 Subject: [PATCH 04/60] Register all descendants of a template invocation as dynamic tests --- .../ContainerTemplateInvocationTestDescriptor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 2357a30b7cdb..d567098ff9c4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -15,7 +15,6 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.platform.engine.EngineExecutionListener; /** * @since 5.13 @@ -42,8 +41,8 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { - EngineExecutionListener executionListener = context.getExecutionListener(); - getChildren().forEach(executionListener::dynamicTestRegistered); + Visitor visitor = context.getExecutionListener()::dynamicTestRegistered; + getChildren().forEach(child -> child.accept(visitor)); return context; } From e8ec03193cde057dddd3572f55b5615030cc14c7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 30 Jan 2025 15:25:55 +0100 Subject: [PATCH 05/60] Add support for `Nested` classes inside container templates --- .../descriptor/ClassBasedTestDescriptor.java | 11 +++ .../descriptor/ClassTestDescriptor.java | 14 +++ ...ainerTemplateInvocationTestDescriptor.java | 22 +++-- .../ContainerTemplateTestDescriptor.java | 86 +++++++++++++++--- .../DynamicContainerTestDescriptor.java | 6 ++ .../descriptor/DynamicNodeTestDescriptor.java | 2 +- .../descriptor/DynamicTestTestDescriptor.java | 6 ++ .../descriptor/JupiterTestDescriptor.java | 18 ++++ .../descriptor/NestedClassTestDescriptor.java | 14 +++ .../descriptor/TestFactoryTestDescriptor.java | 13 +++ .../descriptor/TestMethodTestDescriptor.java | 15 ++++ .../TestTemplateInvocationTestDescriptor.java | 10 +++ .../TestTemplateTestDescriptor.java | 13 +++ .../discovery/ClassSelectorResolver.java | 18 ++-- .../ContainerTemplateInvocationTests.java | 87 +++++++++++++------ 15 files changed, 275 insertions(+), 60 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java index 5c514362e8d9..3113e42804f7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java @@ -107,6 +107,17 @@ public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor imp this.exclusiveResourceCollector = ExclusiveResourceCollector.from(testClass); } + ClassBasedTestDescriptor(UniqueId uniqueId, Class testClass, String displayName, + JupiterConfiguration configuration) { + super(uniqueId, displayName, 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); + this.exclusiveResourceCollector = ExclusiveResourceCollector.from(testClass); + } + // --- TestDescriptor ------------------------------------------------------ public final Class getTestClass() { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index e935db38e782..190d8cc9f049 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -51,6 +51,18 @@ public ClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfigu super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); } + private ClassTestDescriptor(UniqueId uniqueId, Class testClass, String displayName, + JupiterConfiguration configuration) { + super(uniqueId, testClass, displayName, configuration); + } + + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected ClassTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new ClassTestDescriptor(newUniqueId, getTestClass(), getDisplayName(), configuration); + } + // --- TestDescriptor ------------------------------------------------------ @Override @@ -59,6 +71,8 @@ public Set getTags() { return new LinkedHashSet<>(this.tags); } + // --- ClassBasedTestDescriptor -------------------------------------------- + @Override public List> getEnclosingTestClasses() { return emptyList(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index d567098ff9c4..e967e5d49e61 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -13,8 +13,10 @@ import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; /** * @since 5.13 @@ -24,17 +26,19 @@ public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescri public static final String SEGMENT_TYPE = "container-template-invocation"; - private final ClassBasedTestDescriptor delegate; + ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, + JupiterConfiguration configuration) { + super(uniqueId, displayName, source, configuration); + } - ContainerTemplateInvocationTestDescriptor(ClassBasedTestDescriptor delegate, - ContainerTemplateInvocationContext invocationContext, int index) { - super(delegate.getUniqueId(), invocationContext.getDisplayName(index), delegate.getSource().orElse(null), - delegate.configuration); - this.delegate = delegate; + @Override + protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new ContainerTemplateInvocationTestDescriptor(newUniqueId, getDisplayName(), getSource().orElse(null), + this.configuration); } @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception { + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { return context.extend().build(); } @@ -48,6 +52,6 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte @Override public Type getType() { - return delegate.getType(); + return Type.CONTAINER; } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 564e022d5c28..c554b4a3af66 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -14,17 +14,15 @@ import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; -import static org.junit.jupiter.engine.discovery.DiscoverySelectorResolver.createTestDescriptor; -import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; -import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; -import static org.junit.platform.commons.support.ReflectionSupport.streamMethods; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -51,6 +49,8 @@ public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "container-template"; + private final List childrenPrototypes = new ArrayList<>(); + public ContainerTemplateTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); } @@ -63,9 +63,25 @@ public Set getTags() { return new LinkedHashSet<>(this.tags); } + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected ContainerTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new ContainerTemplateTestDescriptor(newUniqueId, getTestClass(), configuration); + } + + // --- TestDescriptor ------------------------------------------------------ + + @Override + public void prune() { + super.prune(); + this.childrenPrototypes.addAll(this.children); + this.children.clear(); + } + @Override public boolean mayRegisterTests() { - return true; + return !childrenPrototypes.isEmpty(); } // --- ClassBasedTestDescriptor --------------------------------------------- @@ -150,16 +166,58 @@ private List validateProviders(Exten } private Optional toTestDescriptor(ContainerTemplateInvocationContext invocationContext, int index) { - UniqueId uniqueId = getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); + UniqueId invocationUniqueId = getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#" + index); + ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( + invocationUniqueId, invocationContext.getDisplayName(index), getSource().orElse(null), this.configuration); + + UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); + // TODO #871 filter descendants - // TODO #871 support @Nested classes - ClassTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, getTestClass(), this.configuration); - ContainerTemplateInvocationTestDescriptor containerDescriptor = new ContainerTemplateInvocationTestDescriptor( - classDescriptor, invocationContext, index); - streamMethods(getTestClass(), isTestOrTestFactoryOrTestTemplateMethod, TOP_DOWN) // - .map(method -> createTestDescriptor(classDescriptor, getTestClass(), method, this.configuration)) // - .forEach(methodDescriptor -> methodDescriptor.ifPresent(containerDescriptor::addChild)); - return Optional.of(containerDescriptor); + + this.childrenPrototypes.stream() // + .map(JupiterTestDescriptor.class::cast) // + .map(it -> it.copyIncludingDescendants(transformer)) // + .forEach(containerInvocationDescriptor::addChild); + + return Optional.of(containerInvocationDescriptor); + } + + private static class UniqueIdPrefixTransformer implements UnaryOperator { + + private final UniqueId oldPrefix; + private final UniqueId newPrefix; + private final int oldPrefixLength; + + UniqueIdPrefixTransformer(UniqueId oldPrefix, UniqueId newPrefix) { + this.oldPrefix = oldPrefix; + this.newPrefix = newPrefix; + this.oldPrefixLength = oldPrefix.getSegments().size(); + } + + @Override + public UniqueId apply(UniqueId uniqueId) { + Preconditions.condition(uniqueId.hasPrefix(oldPrefix), + () -> String.format("Unique ID %s does not have the expected prefix %s", uniqueId, oldPrefix)); + List oldSegments = uniqueId.getSegments(); + List suffix = oldSegments.subList(oldPrefixLength, oldSegments.size()); + UniqueId newValue = newPrefix; + for (UniqueId.Segment segment : suffix) { + newValue = newValue.append(segment); + } + return newValue; + } + } + + private static UniqueId changePrefix(UniqueId oldValue, UniqueId oldPrefix, UniqueId newPrefix) { + List oldSegments = oldValue.getSegments(); + Preconditions.condition(oldValue.hasPrefix(oldPrefix), () -> "Old value does not have the expected prefix"); + List suffix = oldSegments.subList(oldPrefix.getSegments().size(), oldSegments.size()); + UniqueId newValue = newPrefix; + for (UniqueId.Segment newSegment : suffix) { + newValue = newValue.append(newSegment); + } + return newValue; } private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java index 96b7d6e132ba..cca538ff7659 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java @@ -46,6 +46,12 @@ class DynamicContainerTestDescriptor extends DynamicNodeTestDescriptor { this.dynamicDescendantFilter = dynamicDescendantFilter; } + @Override + protected DynamicContainerTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new DynamicContainerTestDescriptor(newUniqueId, this.index, this.dynamicContainer, this.testSource, + this.dynamicDescendantFilter, this.configuration); + } + @Override public Type getType() { return Type.CONTAINER; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java index c5e83da28df1..9e8ce366cf50 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -24,7 +24,7 @@ */ abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { - private final int index; + protected final int index; DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, TestSource testSource, JupiterConfiguration configuration) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java index e504b311a5df..ba9c00b21363 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java @@ -41,6 +41,12 @@ class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { this.dynamicTest = dynamicTest; } + @Override + protected DynamicTestTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new DynamicTestTestDescriptor(newUniqueId, this.index, this.dynamicTest, this.getSource().orElse(null), + this.configuration); + } + @Override public Type getType() { return Type.TEST; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java index 0101f9d2b5c6..d6be1be68f28 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -26,6 +26,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.Tag; @@ -212,6 +213,23 @@ public void cleanUp(JupiterEngineExecutionContext context) throws Exception { context.close(); } + /** + * {@return a deep copy (with copies of children) of this descriptor with the supplied unique ID} + */ + protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator uniqueIdTransformer) { + JupiterTestDescriptor result = withUniqueId(uniqueIdTransformer.apply(getUniqueId())); + getChildren().forEach(oldChild -> { + TestDescriptor newChild = ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer); + result.addChild(newChild); + }); + return result; + } + + /** + * {@return shallow copy (without children) of this descriptor with the supplied unique ID} + */ + protected abstract JupiterTestDescriptor withUniqueId(UniqueId newUniqueId); + /** * @since 5.5 */ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index b0619fa09cff..9f683017017b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -55,6 +55,18 @@ public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration); } + private NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, String displayName, + JupiterConfiguration configuration) { + super(uniqueId, testClass, displayName, configuration); + } + + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected NestedClassTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new NestedClassTestDescriptor(newUniqueId, getTestClass(), getDisplayName(), configuration); + } + // --- TestDescriptor ------------------------------------------------------ @Override @@ -65,6 +77,8 @@ public final Set getTags() { return allTags; } + // --- ClassBasedTestDescriptor --------------------------------------------- + @Override public List> getEnclosingTestClasses() { return getEnclosingTestClasses(getParent().orElse(null)); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index f0d37814bbf2..143f693045b1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -68,6 +68,19 @@ public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method t super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); } + private TestFactoryTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, + JupiterConfiguration configuration) { + super(uniqueId, displayName, testClass, testMethod, configuration); + } + + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected TestFactoryTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new TestFactoryTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), + this.configuration); + } + // --- Filterable ---------------------------------------------------------- @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index 67fa136a52d1..eaf948a8f458 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -82,12 +82,27 @@ public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method te this.interceptorCall = defaultInterceptorCall; } + TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, + JupiterConfiguration configuration) { + this(uniqueId, displayName, testClass, testMethod, configuration, defaultInterceptorCall); + } + TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration, ReflectiveInterceptorCall interceptorCall) { super(uniqueId, displayName, testClass, testMethod, configuration); this.interceptorCall = interceptorCall; } + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected TestMethodTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new TestMethodTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), + this.configuration, interceptorCall); + } + + // --- TestDescriptor ------------------------------------------------------ + @Override public Type getType() { return Type.TEST; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java index cbb66f12d9b9..724c58a03642 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java @@ -51,6 +51,16 @@ public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescript this.index = index; } + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected TestTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new TestTemplateInvocationTestDescriptor(newUniqueId, getTestClass(), getTestMethod(), + this.invocationContext, this.index, this.configuration); + } + + // --- TestDescriptor ------------------------------------------------------ + @Override public Set getExclusiveResources() { // Resources are already collected and returned by the enclosing container diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index e592ba3a6326..bbec52b34ff2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -51,6 +51,19 @@ public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration); } + private TestTemplateTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method templateMethod, + JupiterConfiguration configuration) { + super(uniqueId, displayName, testClass, templateMethod, configuration); + } + + // --- JupiterTestDescriptor ----------------------------------------------- + + @Override + protected TestTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new TestTemplateTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), + this.configuration); + } + // --- Filterable ---------------------------------------------------------- @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index e6846304d321..a026dee6d3cf 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -72,11 +72,8 @@ public Resolution resolve(ClassSelector selector, Context context) { if (isTestClassWithTests.test(testClass)) { // Nested tests are never filtered out if (classNameFilter.test(testClass.getName())) { - if (isAnnotated(testClass, ContainerTemplate.class)) { - return resolveContainerTemplate(context, testClass); - } return toResolution( - context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass)))); + context.addToParent(parent -> Optional.of(newStaticClassTestDescriptor(parent, testClass)))); } } else if (isNestedTestClass.test(testClass)) { @@ -122,6 +119,12 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { return unresolved(); } + private ClassBasedTestDescriptor newStaticClassTestDescriptor(TestDescriptor parent, Class testClass) { + return isAnnotated(testClass, ContainerTemplate.class) // + ? newContainerTemplateTestDescriptor(parent, testClass) // + : newClassTestDescriptor(parent, testClass); + } + private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class testClass) { return new ClassTestDescriptor( parent.getUniqueId().append(ClassTestDescriptor.SEGMENT_TYPE, testClass.getName()), testClass, @@ -134,13 +137,6 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration); } - private Resolution resolveContainerTemplate(Context context, Class testClass) { - return context.addToParent(parent -> Optional.of(newContainerTemplateTestDescriptor(parent, testClass))) // - .map(Match::exact) // - .map(Resolution::match) // - .orElse(unresolved()); - } - private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestDescriptor parent, Class testClass) { return new ContainerTemplateTestDescriptor( diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index f454c8521964..9280f595ca5f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -10,6 +10,8 @@ package org.junit.jupiter.engine; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; @@ -18,52 +20,84 @@ import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; +import java.util.function.Predicate; import java.util.stream.Stream; +import org.assertj.core.api.Condition; import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.descriptor.ContainerTemplateInvocationTestDescriptor; +import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.testkit.engine.Event; /** * @since 5.13 */ public class ContainerTemplateInvocationTests extends AbstractJupiterTestEngineTests { - // @Disabled("not yet implemented") @Test void executesContainerTemplateClassTwice() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); + var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); + var invocation1MethodAId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); + var invocation1NestedClassId = invocationId1.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var invocation1NestedMethodBId = invocation1NestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var invocation2MethodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); + var invocation2NestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var invocation2NestedMethodBId = invocation2NestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + var results = executeTestsForClass(TwoInvocationsTestCase.class); + results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // - event(container(TwoInvocationsTestCase.class), uniqueIdSubstring("container-template"), started()), // - event(dynamicTestRegistered("container-template-invocation:#1"), displayName("[1] A")), // - event(container("container-template-invocation:#1"), started()), // - event(dynamicTestRegistered("a")), // - event(dynamicTestRegistered("b")), // - event(test("a"), started()), // - event(test("a"), finishedSuccessfully()), // - event(test("b"), started()), // - event(test("b"), finishedSuccessfully()), // - event(container("container-template-invocation:#1"), finishedSuccessfully()), // - event(dynamicTestRegistered("container-template-invocation:#2"), displayName("[2] B")), // - event(container("container-template-invocation:#2"), started()), // - event(dynamicTestRegistered("a")), // - event(dynamicTestRegistered("b")), // - event(test("a"), started()), // - event(test("a"), finishedSuccessfully()), // - event(test("b"), started()), // - event(test("b"), finishedSuccessfully()), // - event(container("container-template-invocation:#2"), finishedSuccessfully()), // - event(container(TwoInvocationsTestCase.class), uniqueIdSubstring("container-template"), - finishedSuccessfully()), // + event(container(uniqueId(containerTemplateId)), started()), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // + event(container(uniqueId(invocationId1)), started()), // + event(dynamicTestRegistered(uniqueId(invocation1MethodAId))), // + event(dynamicTestRegistered(uniqueId(invocation1NestedClassId))), // + event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // + event(test(uniqueId(invocation1MethodAId)), started()), // + event(test(uniqueId(invocation1MethodAId)), finishedSuccessfully()), // + event(container(uniqueId(invocation1NestedClassId)), started()), // + event(test(uniqueId(invocation1NestedMethodBId)), started()), // + event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(invocation1NestedClassId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId1)), finishedSuccessfully()), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(invocation2MethodAId))), // + event(dynamicTestRegistered(uniqueId(invocation2NestedClassId))), // + event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // + event(test(uniqueId(invocation2MethodAId)), started()), // + event(test(uniqueId(invocation2MethodAId)), finishedSuccessfully()), // + event(container(uniqueId(invocation2NestedClassId)), started()), // + event(test(uniqueId(invocation2NestedMethodBId)), started()), // + event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(invocation2NestedClassId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } + // TODO #871 Consider moving to EventConditions + private static Condition uniqueId(UniqueId uniqueId) { + return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), + "descriptor with uniqueId '%s'", uniqueId); + } + @SuppressWarnings("JUnitMalformedDeclaration") @ContainerTemplate @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) @@ -72,8 +106,11 @@ static class TwoInvocationsTestCase { void a() { } - @Test - void b() { + @Nested + class NestedTestCase { + @Test + void b() { + } } } From 031e5730a27bbd9418d266e49c5d1cc7fbcef7bb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 30 Jan 2025 15:38:05 +0100 Subject: [PATCH 06/60] Delete unused code --- .../discovery/DiscoverySelectorResolver.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java index 58d6250e78ad..a828889cc000 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java @@ -12,14 +12,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; - import org.apiguardian.api.API; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; import org.junit.platform.engine.EngineDiscoveryRequest; @@ -55,14 +48,4 @@ public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescri resolver.resolve(request, engineDescriptor); } - @API(status = INTERNAL, since = "5.13") - public static Optional createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, - Method method, JupiterConfiguration configuration) { - return Arrays.stream(MethodSelectorResolver.MethodType.values()) // - .map(methodType -> methodType.methodPredicate.test(method) - ? methodType.createTestDescriptor(parent, testClass, method, configuration) - : null) // - .filter(Objects::nonNull).findFirst(); - } - } From 3240af9814f6c782de7fd471f47b037fa1e450c8 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 30 Jan 2025 15:39:09 +0100 Subject: [PATCH 07/60] Add tests for method selectors --- .../ContainerTemplateInvocationTests.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 9280f595ca5f..c91f14063569 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -11,6 +11,8 @@ package org.junit.jupiter.engine; import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; @@ -21,12 +23,14 @@ import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; +import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; @@ -49,7 +53,8 @@ public class ContainerTemplateInvocationTests extends AbstractJupiterTestEngineT @Test void executesContainerTemplateClassTwice() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + TwoInvocationsTestCase.class.getName()); var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1MethodAId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var invocation1NestedClassId = invocationId1.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); @@ -92,6 +97,24 @@ void executesContainerTemplateClassTwice() { event(engine(), finishedSuccessfully())); } + @Test + void executesOnlySelectedMethodsDeclaredInContainerTemplate() { + var results = executeTests(selectMethod(TwoInvocationsTestCase.class, "a")); + + results.testEvents() // + .assertStatistics(stats -> stats.started(2).succeeded(2)) // + .assertEventsMatchLoosely(event(test(displayName("a()")), finishedSuccessfully())); + } + + @Test + void executesOnlySelectedMethodsDeclaredInNestedClassOfContainerTemplate() { + var results = executeTests(selectNestedMethod(List.of(TwoInvocationsTestCase.class), + TwoInvocationsTestCase.NestedTestCase.class, "b")); + + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)) // + .assertEventsMatchLoosely(event(test(displayName("b()")), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -109,6 +132,7 @@ void a() { @Nested class NestedTestCase { @Test + @Tag("nested") void b() { } } From 27e6575c09364f5acdf1ecb51518a25116135a6a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 30 Jan 2025 15:59:15 +0100 Subject: [PATCH 08/60] Add support for applying `PostDiscoveryFilters` --- .../ContainerTemplateTestDescriptor.java | 5 ++++ .../descriptor/JupiterTestDescriptor.java | 4 +++ .../discovery/DiscoverySelectorResolver.java | 27 ++++++++++++------- .../AbstractJupiterTestEngineTests.java | 9 ++++++- .../ContainerTemplateInvocationTests.java | 12 +++++++++ .../DiscoverySelectorResolverTests.java | 11 +++++--- 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index c554b4a3af66..bfc4cdcc8da2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -70,6 +70,11 @@ protected ContainerTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { return new ContainerTemplateTestDescriptor(newUniqueId, getTestClass(), configuration); } + @Override + public void prunePriorToFiltering() { + // do nothing to allow PostDiscoveryFilters to be applied first + } + // --- TestDescriptor ------------------------------------------------------ @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java index d6be1be68f28..a046bc950a4d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -213,6 +213,10 @@ public void cleanUp(JupiterEngineExecutionContext context) throws Exception { context.close(); } + public void prunePriorToFiltering() { + prune(); + } + /** * {@return a deep copy (with copies of children) of this descriptor with the supplied unique ID} */ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java index a828889cc000..97f4e8da22fd 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java @@ -13,11 +13,14 @@ import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; +import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; +import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.InitializationContext; /** * {@code DiscoverySelectorResolver} resolves {@link TestDescriptor TestDescriptors} @@ -33,16 +36,22 @@ @API(status = INTERNAL, since = "5.0") public class DiscoverySelectorResolver { - // @formatter:off - private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() - .addClassContainerSelectorResolver(new IsTestClassWithTests()) - .addSelectorResolver(context -> new ClassSelectorResolver(context.getClassNameFilter(), context.getEngineDescriptor().getConfiguration())) - .addSelectorResolver(context -> new MethodSelectorResolver(context.getEngineDescriptor().getConfiguration())) - .addTestDescriptorVisitor(context -> new ClassOrderingVisitor(context.getEngineDescriptor().getConfiguration())) - .addTestDescriptorVisitor(context -> new MethodOrderingVisitor(context.getEngineDescriptor().getConfiguration())) - .addTestDescriptorVisitor(context -> TestDescriptor::prune) + private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver. builder() // + .addClassContainerSelectorResolver(new IsTestClassWithTests()) // + .addSelectorResolver(ctx -> new ClassSelectorResolver(ctx.getClassNameFilter(), getConfiguration(ctx))) // + .addSelectorResolver(ctx -> new MethodSelectorResolver(getConfiguration(ctx))) // + .addTestDescriptorVisitor(ctx -> new ClassOrderingVisitor(getConfiguration(ctx))) // + .addTestDescriptorVisitor(ctx -> new MethodOrderingVisitor(getConfiguration(ctx))) // + .addTestDescriptorVisitor(ctx -> descriptor -> { + if (descriptor instanceof JupiterTestDescriptor) { + ((JupiterTestDescriptor) descriptor).prunePriorToFiltering(); + } + }) // .build(); - // @formatter:on + + private static JupiterConfiguration getConfiguration(InitializationContext context) { + return context.getEngineDescriptor().getConfiguration(); + } public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor) { resolver.resolve(request, engineDescriptor); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 3ec1035ebb5c..6e3813373345 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -17,6 +17,7 @@ import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import java.util.Set; +import java.util.function.Consumer; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; @@ -40,7 +41,13 @@ protected EngineExecutionResults executeTestsForClass(Class testClass) { } protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { - return executeTests(request().selectors(selectors).outputDirectoryProvider(dummyOutputDirectoryProvider())); + return executeTests(request -> request.selectors(selectors)); + } + + protected EngineExecutionResults executeTests(Consumer configurer) { + var builder = request().outputDirectoryProvider(dummyOutputDirectoryProvider()); + configurer.accept(builder); + return executeTests(builder); } protected EngineExecutionResults executeTests(LauncherDiscoveryRequestBuilder builder) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index c91f14063569..f548b3a39635 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -11,8 +11,10 @@ package org.junit.jupiter.engine; import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; +import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; @@ -115,6 +117,16 @@ void executesOnlySelectedMethodsDeclaredInNestedClassOfContainerTemplate() { .assertEventsMatchLoosely(event(test(displayName("b()")), finishedSuccessfully())); } + @Test + void executesOnlyTestsPassingPostDiscoveryFilter() { + var results = executeTests(request -> request // + .selectors(selectClass(TwoInvocationsTestCase.class)) // + .filters(includeTags("nested"))); + + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)) // + .assertEventsMatchLoosely(event(test(displayName("b()")), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index 219047d11456..a6f9dd4fc2d8 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -199,14 +199,19 @@ void classResolutionOfContainerTemplate() { resolve(request().selectors(selector)); - assertThat(engineDescriptor.getDescendants()).hasSize(1); + assertThat(engineDescriptor.getChildren()).hasSize(1); - TestDescriptor containerTemplateDescriptor = getOnlyElement(engineDescriptor.getDescendants()); - assertThat(containerTemplateDescriptor.mayRegisterTests()).isTrue(); + TestDescriptor containerTemplateDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(containerTemplateDescriptor.mayRegisterTests()).isFalse(); + assertThat(containerTemplateDescriptor.getDescendants()).hasSize(1); var containerTemplateSegment = containerTemplateDescriptor.getUniqueId().getLastSegment(); assertThat(containerTemplateSegment.getType()).isEqualTo("container-template"); assertThat(containerTemplateSegment.getValue()).isEqualTo(ContainerTemplateTestCase.class.getName()); + + containerTemplateDescriptor.prune(); + assertThat(containerTemplateDescriptor.mayRegisterTests()).isTrue(); + assertThat(containerTemplateDescriptor.getDescendants()).isEmpty(); } @Test From 15cda8a284b966ee3d2f18c0b44e9e6f9524f61b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 3 Feb 2025 14:05:33 +0100 Subject: [PATCH 09/60] Prune empty nested containers --- .../ContainerTemplateTestDescriptor.java | 1 + .../engine/ContainerTemplateInvocationTests.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index bfc4cdcc8da2..d9df013c2e69 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -80,6 +80,7 @@ public void prunePriorToFiltering() { @Override public void prune() { super.prune(); + this.children.forEach(child -> child.accept(TestDescriptor::prune)); this.childrenPrototypes.addAll(this.children); this.children.clear(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index f548b3a39635..ac68679108c0 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -14,6 +14,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; +import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.container; @@ -127,6 +128,19 @@ void executesOnlyTestsPassingPostDiscoveryFilter() { .assertEventsMatchLoosely(event(test(displayName("b()")), finishedSuccessfully())); } + @Test + void prunesEmptyNestedTestClasses() { + var results = executeTests(request -> request // + .selectors(selectClass(TwoInvocationsTestCase.class)) // + .filters(excludeTags("nested"))); + + results.containerEvents().assertThatEvents() // + .noneMatch(container(TwoInvocationsTestCase.NestedTestCase.class.getSimpleName())::matches); + + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)) // + .assertEventsMatchLoosely(event(test(displayName("a()")), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), From 2da3294abe7134e9c4fda8b8c200c1cce7a56152 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 3 Feb 2025 14:46:14 +0100 Subject: [PATCH 10/60] Add support for nested container templates --- .../descriptor/ClassBasedTestDescriptor.java | 20 ++-- .../ContainerTemplateTestDescriptor.java | 26 ++--- .../descriptor/NestedClassTestDescriptor.java | 2 - .../discovery/ClassSelectorResolver.java | 24 ++-- .../ContainerTemplateInvocationTests.java | 108 +++++++++++++++++- 5 files changed, 143 insertions(+), 37 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java index 3113e42804f7..c6e595320518 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java @@ -127,28 +127,28 @@ public final Class getTestClass() { public abstract List> getEnclosingTestClasses(); @Override - public Type getType() { + public final Type getType() { return Type.CONTAINER; } @Override - public String getLegacyReportingName() { + public final String getLegacyReportingName() { return this.testClass.getName(); } // --- Node ---------------------------------------------------------------- @Override - protected Optional getExplicitExecutionMode() { + protected final Optional getExplicitExecutionMode() { return getExecutionModeFromAnnotation(getTestClass()); } @Override - protected Optional getDefaultChildExecutionMode() { + protected final Optional getDefaultChildExecutionMode() { return Optional.ofNullable(this.defaultChildExecutionMode); } - public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode) { + public final void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode) { this.defaultChildExecutionMode = defaultChildExecutionMode; } @@ -158,7 +158,7 @@ public final ExclusiveResourceCollector getExclusiveResourceCollector() { } @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { + public final JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( context.getExtensionRegistry(), this.testClass); @@ -205,7 +205,7 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte } @Override - public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) { + public final JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); if (isPerClassLifecycle(context)) { @@ -234,7 +234,7 @@ public JupiterEngineExecutionContext before(JupiterEngineExecutionContext contex } @Override - public void after(JupiterEngineExecutionContext context) { + public final void after(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); Throwable previousThrowable = throwableCollector.getThrowable(); @@ -314,8 +314,8 @@ protected abstract TestInstances instantiateTestClass(JupiterEngineExecutionCont ExtensionContextSupplier extensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context); - protected TestInstances instantiateTestClass(Optional outerInstances, ExtensionRegistry registry, - ExtensionContextSupplier extensionContext) { + protected final TestInstances instantiateTestClass(Optional outerInstances, + ExtensionRegistry registry, ExtensionContextSupplier extensionContext) { Optional outerInstance = outerInstances.map(TestInstances::getInnermostInstance); invokeTestInstancePreConstructCallbacks(new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index d9df013c2e69..e914c817f285 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -10,10 +10,8 @@ package org.junit.jupiter.engine.descriptor; -import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -31,7 +29,6 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.parallel.ResourceLocksProvider; -import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; @@ -50,9 +47,11 @@ public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "container-template"; private final List childrenPrototypes = new ArrayList<>(); + private final ClassBasedTestDescriptor delegate; - public ContainerTemplateTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { - super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); + public ContainerTemplateTestDescriptor(UniqueId uniqueId, ClassBasedTestDescriptor delegate) { + super(uniqueId, delegate.getTestClass(), delegate.getDisplayName(), delegate.configuration); + this.delegate = delegate; } // --- TestDescriptor ------------------------------------------------------ @@ -67,7 +66,7 @@ public Set getTags() { @Override protected ContainerTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateTestDescriptor(newUniqueId, getTestClass(), configuration); + return new ContainerTemplateTestDescriptor(newUniqueId, this.delegate); } @Override @@ -94,31 +93,26 @@ public boolean mayRegisterTests() { @Override public List> getEnclosingTestClasses() { - return emptyList(); + return delegate.getEnclosingTestClasses(); } @Override - protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, + public TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context) { - return instantiateTestClass(Optional.empty(), registry, extensionContext); + return delegate.instantiateTestClass(parentExecutionContext, extensionContext, registry, context); } // --- ResourceLockAware --------------------------------------------------- @Override public Function> getResourceLocksProviderEvaluator() { - return provider -> provider.provideForClass(getTestClass()); + return delegate.getResourceLocksProviderEvaluator(); } // --- Node ---------------------------------------------------------------- - @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - return super.prepare(context); - } - - // TODO copied from ContainerTemplateTestDescriptor + // TODO copied from TestTemplateTestDescriptor @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 9f683017017b..bba34eff4b28 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -95,8 +95,6 @@ public static List> getEnclosingTestClasses(TestDescriptor parent) { return emptyList(); } - // --- Node ---------------------------------------------------------------- - @Override protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index a026dee6d3cf..ddadad6d471c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -78,7 +78,7 @@ public Resolution resolve(ClassSelector selector, Context context) { } else if (isNestedTestClass.test(testClass)) { return toResolution(context.addToParent(() -> DiscoverySelectors.selectClass(testClass.getEnclosingClass()), - parent -> Optional.of(newNestedClassTestDescriptor(parent, testClass)))); + parent -> Optional.of(newMemberClassTestDescriptor(parent, testClass)))); } return unresolved(); } @@ -87,7 +87,7 @@ else if (isNestedTestClass.test(testClass)) { public Resolution resolve(NestedClassSelector selector, Context context) { if (isNestedTestClass.test(selector.getNestedClass())) { return toResolution(context.addToParent(() -> selectClass(selector.getEnclosingClasses()), - parent -> Optional.of(newNestedClassTestDescriptor(parent, selector.getNestedClass())))); + parent -> Optional.of(newMemberClassTestDescriptor(parent, selector.getNestedClass())))); } return unresolved(); } @@ -120,9 +120,10 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { } private ClassBasedTestDescriptor newStaticClassTestDescriptor(TestDescriptor parent, Class testClass) { + ClassTestDescriptor classTestDescriptor = newClassTestDescriptor(parent, testClass); return isAnnotated(testClass, ContainerTemplate.class) // - ? newContainerTemplateTestDescriptor(parent, testClass) // - : newClassTestDescriptor(parent, testClass); + ? newContainerTemplateTestDescriptor(parent, classTestDescriptor) // + : classTestDescriptor; } private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class testClass) { @@ -131,6 +132,13 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class< configuration); } + private ClassBasedTestDescriptor newMemberClassTestDescriptor(TestDescriptor parent, Class testClass) { + NestedClassTestDescriptor classTestDescriptor = newNestedClassTestDescriptor(parent, testClass); + return isAnnotated(testClass, ContainerTemplate.class) // + ? newContainerTemplateTestDescriptor(parent, classTestDescriptor) // + : classTestDescriptor; + } + private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()); @@ -138,10 +146,10 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa } private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestDescriptor parent, - Class testClass) { - return new ContainerTemplateTestDescriptor( - parent.getUniqueId().append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, testClass.getName()), testClass, - configuration); + ClassBasedTestDescriptor delegate) { + String segmentValue = delegate.getUniqueId().getLastSegment().getValue(); + UniqueId uniqueId = parent.getUniqueId().append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, segmentValue); + return new ContainerTemplateTestDescriptor(uniqueId, delegate); } private Resolution toResolution(Optional testDescriptor) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index ac68679108c0..0ffaae9e5af7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.ContainerTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; @@ -72,6 +73,7 @@ void executesContainerTemplateClassTwice() { results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(containerTemplateId)), started()), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1MethodAId))), // @@ -84,6 +86,7 @@ void executesContainerTemplateClassTwice() { event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocation1NestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2MethodAId))), // @@ -96,6 +99,7 @@ void executesContainerTemplateClassTwice() { event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocation2NestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @@ -141,6 +145,92 @@ void prunesEmptyNestedTestClasses() { .assertEventsMatchLoosely(event(test(displayName("a()")), finishedSuccessfully())); } + @Test + void executesNestedContainerTemplateClassTwiceWithClassSelectorForEnclosingClass() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var classId = engineId.append(ClassTestDescriptor.SEGMENT_TYPE, + NestedContainerTemplateWithTwoInvocationsTestCase.class.getName()); + var methodAId = classId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); + var nestedContainerTemplateId = classId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var invocationId1 = nestedContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#1"); + var invocation1NestedMethodBId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + var invocationId2 = nestedContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + var invocation2NestedMethodBId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + + var results = executeTestsForClass(NestedContainerTemplateWithTwoInvocationsTestCase.class); + + results.allEvents().debug().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(classId)), started()), // + + event(test(uniqueId(methodAId)), started()), // + event(test(uniqueId(methodAId)), finishedSuccessfully()), // + + event(container(uniqueId(nestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // + event(container(uniqueId(invocationId1)), started()), // + event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // + event(test(uniqueId(invocation1NestedMethodBId)), started()), // + event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId1)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // + event(test(uniqueId(invocation2NestedMethodBId)), started()), // + event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(nestedContainerTemplateId)), finishedSuccessfully()), // + + event(container(uniqueId(classId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesNestedContainerTemplateClassTwiceWithNestedClassSelector() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var classId = engineId.append(ClassTestDescriptor.SEGMENT_TYPE, + NestedContainerTemplateWithTwoInvocationsTestCase.class.getName()); + var nestedContainerTemplateId = classId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var invocationId1 = nestedContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#1"); + var invocation1NestedMethodBId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + var invocationId2 = nestedContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + var invocation2NestedMethodBId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + + var results = executeTestsForClass(NestedContainerTemplateWithTwoInvocationsTestCase.NestedTestCase.class); + + results.allEvents().debug().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(classId)), started()), // + + event(container(uniqueId(nestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // + event(container(uniqueId(invocationId1)), started()), // + event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // + event(test(uniqueId(invocation1NestedMethodBId)), started()), // + event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId1)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // + event(test(uniqueId(invocation2NestedMethodBId)), started()), // + event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(nestedContainerTemplateId)), finishedSuccessfully()), // + + event(container(uniqueId(classId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -164,12 +254,28 @@ void b() { } } + @SuppressWarnings("JUnitMalformedDeclaration") + static class NestedContainerTemplateWithTwoInvocationsTestCase { + @Test + void a() { + } + + @Nested + @ContainerTemplate + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + class NestedTestCase { + @Test + void b() { + } + } + } + static class TwoInvocationsContainerTemplateInvocationContextProvider implements ContainerTemplateInvocationContextProvider { @Override public boolean supportsContainerTemplate(ExtensionContext context) { - return TwoInvocationsTestCase.class.equals(context.getRequiredTestClass()); + return true; } @Override From 443f3dccabb97a04619fbe92e58b109027e3faa2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 3 Feb 2025 15:15:50 +0100 Subject: [PATCH 11/60] Add support for nesting container templates --- .../ContainerTemplateTestDescriptor.java | 11 +++ .../ContainerTemplateInvocationTests.java | 98 +++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index e914c817f285..940c85072b6c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -64,6 +64,17 @@ public Set getTags() { // --- JupiterTestDescriptor ----------------------------------------------- + @Override + protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator uniqueIdTransformer) { + ContainerTemplateTestDescriptor copy = (ContainerTemplateTestDescriptor) super.copyIncludingDescendants( + uniqueIdTransformer); + this.childrenPrototypes.forEach(oldChild -> { + TestDescriptor newChild = ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer); + copy.childrenPrototypes.add(newChild); + }); + return copy; + } + @Override protected ContainerTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { return new ContainerTemplateTestDescriptor(newUniqueId, this.delegate); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 0ffaae9e5af7..0bede828f246 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -231,6 +231,92 @@ void executesNestedContainerTemplateClassTwiceWithNestedClassSelector() { event(engine(), finishedSuccessfully())); } + @Test + void executesNestedContainerTemplatesTwiceEach() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var outerContainerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + TwoTimesTwoInvocationsTestCase.class.getName()); + + var outerInvocation1Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#1"); + var outerInvocation1NestedContainerTemplateId = outerInvocation1Id.append( + ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var outerInvocation1InnerInvocation1Id = outerInvocation1NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); + var outerInvocation1InnerInvocation1NestedMethodId = outerInvocation1InnerInvocation1Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + var outerInvocation1InnerInvocation2Id = outerInvocation1NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var outerInvocation1InnerInvocation2NestedMethodId = outerInvocation1InnerInvocation2Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + var outerInvocation2Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + var outerInvocation2NestedContainerTemplateId = outerInvocation2Id.append( + ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var outerInvocation2InnerInvocation1Id = outerInvocation2NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); + var outerInvocation2InnerInvocation1NestedMethodId = outerInvocation2InnerInvocation1Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + var outerInvocation2InnerInvocation2Id = outerInvocation2NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + var results = executeTestsForClass(TwoTimesTwoInvocationsTestCase.class); + + results.allEvents().debug().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(outerContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation1Id)), displayName("[1] A")), // + event(container(uniqueId(outerInvocation1Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation1NestedContainerTemplateId))), // + event(container(uniqueId(outerInvocation1NestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1Id))), // + event(container(uniqueId(outerInvocation1InnerInvocation1Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1NestedMethodId))), // + event(test(uniqueId(outerInvocation1InnerInvocation1NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation1InnerInvocation1NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation1InnerInvocation1Id)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2Id))), // + event(container(uniqueId(outerInvocation1InnerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2NestedMethodId))), // + event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation1InnerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerInvocation1NestedContainerTemplateId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation1Id)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), displayName("[2] B")), // + event(container(uniqueId(outerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2NestedContainerTemplateId))), // + event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1Id))), // + event(container(uniqueId(outerInvocation2InnerInvocation1Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1NestedMethodId))), // + event(test(uniqueId(outerInvocation2InnerInvocation1NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation2InnerInvocation1NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2InnerInvocation1Id)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id))), // + event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // + event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2InnerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerContainerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -270,6 +356,18 @@ void b() { } } + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + @ContainerTemplate + static class TwoTimesTwoInvocationsTestCase { + @Nested + @ContainerTemplate + class NestedTestCase { + @Test + void test() { + } + } + } + static class TwoInvocationsContainerTemplateInvocationContextProvider implements ContainerTemplateInvocationContextProvider { From 7fc0cef8cb170a1300158c012a2353510bfee722 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 3 Feb 2025 15:26:19 +0100 Subject: [PATCH 12/60] Improve tests --- .../ContainerTemplateInvocationTests.java | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 0bede828f246..859c1d2a9d88 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -74,7 +74,7 @@ void executesContainerTemplateClassTwice() { event(engine(), started()), // event(container(uniqueId(containerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of TwoInvocationsTestCase")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1MethodAId))), // event(dynamicTestRegistered(uniqueId(invocation1NestedClassId))), // @@ -87,7 +87,7 @@ void executesContainerTemplateClassTwice() { event(container(uniqueId(invocation1NestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2MethodAId))), // event(dynamicTestRegistered(uniqueId(invocation2NestedClassId))), // @@ -161,7 +161,7 @@ void executesNestedContainerTemplateClassTwiceWithClassSelectorForEnclosingClass var results = executeTestsForClass(NestedContainerTemplateWithTwoInvocationsTestCase.class); - results.allEvents().debug().assertEventsMatchExactly( // + results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classId)), started()), // @@ -170,14 +170,14 @@ void executesNestedContainerTemplateClassTwiceWithClassSelectorForEnclosingClass event(container(uniqueId(nestedContainerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of NestedTestCase")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // event(test(uniqueId(invocation1NestedMethodBId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // event(test(uniqueId(invocation2NestedMethodBId)), started()), // @@ -205,20 +205,20 @@ void executesNestedContainerTemplateClassTwiceWithNestedClassSelector() { var results = executeTestsForClass(NestedContainerTemplateWithTwoInvocationsTestCase.NestedTestCase.class); - results.allEvents().debug().assertEventsMatchExactly( // + results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classId)), started()), // event(container(uniqueId(nestedContainerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A")), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of NestedTestCase")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // event(test(uniqueId(invocation1NestedMethodBId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B")), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // event(test(uniqueId(invocation2NestedMethodBId)), started()), // @@ -265,23 +265,26 @@ void executesNestedContainerTemplatesTwiceEach() { var results = executeTestsForClass(TwoTimesTwoInvocationsTestCase.class); - results.allEvents().debug().assertEventsMatchExactly( // + results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(outerContainerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(outerInvocation1Id)), displayName("[1] A")), // + event(dynamicTestRegistered(uniqueId(outerInvocation1Id)), + displayName("[1] A of TwoTimesTwoInvocationsTestCase")), // event(container(uniqueId(outerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1NestedContainerTemplateId))), // event(container(uniqueId(outerInvocation1NestedContainerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1Id))), // + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1Id)), + displayName("[1] A of NestedTestCase")), // event(container(uniqueId(outerInvocation1InnerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1NestedMethodId))), // event(test(uniqueId(outerInvocation1InnerInvocation1NestedMethodId)), started()), // event(test(uniqueId(outerInvocation1InnerInvocation1NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1InnerInvocation1Id)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2Id))), // + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2Id)), + displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation1InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), started()), // @@ -291,19 +294,22 @@ void executesNestedContainerTemplatesTwiceEach() { event(container(uniqueId(outerInvocation1NestedContainerTemplateId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1Id)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), displayName("[2] B")), // + event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), + displayName("[2] B of TwoTimesTwoInvocationsTestCase")), // event(container(uniqueId(outerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2NestedContainerTemplateId))), // event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1Id))), // + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1Id)), + displayName("[1] A of NestedTestCase")), // event(container(uniqueId(outerInvocation2InnerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1NestedMethodId))), // event(test(uniqueId(outerInvocation2InnerInvocation1NestedMethodId)), started()), // event(test(uniqueId(outerInvocation2InnerInvocation1NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2InnerInvocation1Id)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id))), // + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id)), + displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // @@ -379,7 +385,8 @@ public boolean supportsContainerTemplate(ExtensionContext context) { @Override public Stream provideContainerTemplateInvocationContexts( ExtensionContext context) { - return Stream.of(new Ctx("A"), new Ctx("B")); + var suffix = " of %s".formatted(context.getRequiredTestClass().getSimpleName()); + return Stream.of(new Ctx("A" + suffix), new Ctx("B" + suffix)); } record Ctx(String displayName) implements ContainerTemplateInvocationContext { From 6a392fe161d4d48822c530e7f00644cef5d71f04 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 3 Feb 2025 19:36:42 +0100 Subject: [PATCH 13/60] Allow context providers to register additional extensions --- .../ContainerTemplateInvocationContext.java | 20 ++++++ ...ainerTemplateInvocationTestDescriptor.java | 34 ++++++++-- .../ContainerTemplateTestDescriptor.java | 2 +- .../descriptor/JupiterTestDescriptor.java | 4 +- .../ContainerTemplateInvocationTests.java | 65 +++++++++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java index 60627e73477c..6d0b54c0500a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java @@ -10,8 +10,11 @@ package org.junit.jupiter.api.extension; +import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import java.util.List; + import org.apiguardian.api.API; /** @@ -38,4 +41,21 @@ default String getDisplayName(int invocationIndex) { return "[" + invocationIndex + "]"; } + /** + * Get the additional {@linkplain Extension extensions} for this invocation. + * + *

The extensions provided by this method will only be used for this + * invocation of the container template. Thus, it does not make sense to + * return an extension that acts solely on the container level (e.g. + * {@link BeforeAllCallback}). + * + *

The default implementation returns an empty list. + * + * @return the additional extensions for this invocation; never {@code null} + * or containing {@code null} elements, but potentially empty + */ + default List getAdditionalExtensions() { + return emptyList(); + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index e967e5d49e61..4ffa371b7851 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -11,10 +11,17 @@ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom; + +import java.util.List; +import java.util.stream.Stream; import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; @@ -26,20 +33,35 @@ public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescri public static final String SEGMENT_TYPE = "container-template-invocation"; - ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, - JupiterConfiguration configuration) { - super(uniqueId, displayName, source, configuration); + private final ContainerTemplateInvocationContext invocationContext; + private final int index; + + ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, ContainerTemplateInvocationContext invocationContext, + int index, TestSource source, JupiterConfiguration configuration) { + super(uniqueId, invocationContext.getDisplayName(index), source, configuration); + this.invocationContext = invocationContext; + this.index = index; } @Override protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateInvocationTestDescriptor(newUniqueId, getDisplayName(), getSource().orElse(null), - this.configuration); + return new ContainerTemplateInvocationTestDescriptor(newUniqueId, this.invocationContext, this.index, + getSource().orElse(null), this.configuration); } @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - return context.extend().build(); + MutableExtensionRegistry registry = context.getExtensionRegistry(); + List additionalExtensions = invocationContext.getAdditionalExtensions(); + if (!additionalExtensions.isEmpty()) { + MutableExtensionRegistry childRegistry = createRegistryFrom(registry, Stream.empty()); + additionalExtensions.forEach(extension -> childRegistry.registerExtension(extension, invocationContext)); + registry = childRegistry; + } + // TODO #871 Set a new ExtensionContext for each invocation to avoid the parent one from being closed + return context.extend() // + .withExtensionRegistry(registry) // + .build(); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 940c85072b6c..86da741fdcbe 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -180,7 +180,7 @@ private Optional toTestDescriptor(ContainerTemplateInvocationCon UniqueId invocationUniqueId = getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( - invocationUniqueId, invocationContext.getDisplayName(index), getSource().orElse(null), this.configuration); + invocationUniqueId, invocationContext, index, getSource().orElse(null), this.configuration); UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java index a046bc950a4d..3b63e32a0ec6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ConditionEvaluator; @@ -203,7 +204,8 @@ private SkipResult toSkipResult(ConditionEvaluationResult evaluationResult) { } /** - * Must be overridden and return a new context so cleanUp() does not accidentally close the parent context. + * Must be overridden and return a new context with a new {@link ExtensionContext} + * so cleanUp() does not accidentally close the parent context. */ @Override public abstract JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 859c1d2a9d88..d32b5fc08c21 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; @@ -38,7 +39,11 @@ import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.ContainerTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; @@ -323,6 +328,14 @@ void executesNestedContainerTemplatesTwiceEach() { event(engine(), finishedSuccessfully())); } + @Test + void invocationContextProviderCanRegisterAdditionalExtensions() { + var results = executeTestsForClass(AdditionalExtensionRegistrationTestCase.class); + + results.allEvents().debug(); + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -397,4 +410,56 @@ public String getDisplayName(int invocationIndex) { } } } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(AdditionalExtensionRegistrationTestCase.Ext.class) + static class AdditionalExtensionRegistrationTestCase { + + @Test + void test(Data data) { + assertNotNull(data); + assertNotNull(data.value()); + } + + static class Ext implements ContainerTemplateInvocationContextProvider { + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new Data("A"), new Data("B")).map(Ctx::new); + } + } + + record Ctx(Data data) implements ContainerTemplateInvocationContext { + @Override + public String getDisplayName(int invocationIndex) { + return this.data.value(); + } + + @Override + public List getAdditionalExtensions() { + return List.of(new ParameterResolver() { + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return Data.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return Ctx.this.data; + } + }); + } + } + + record Data(String value) { + } + } } From 719b1224ab1594d17aa5b1ff31d3e562f05ea982 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 09:54:53 +0100 Subject: [PATCH 14/60] Allow invocation context to be garbage-collected --- .../ContainerTemplateInvocationTestDescriptor.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 4ffa371b7851..375f70b01a23 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -33,7 +33,7 @@ public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescri public static final String SEGMENT_TYPE = "container-template-invocation"; - private final ContainerTemplateInvocationContext invocationContext; + private ContainerTemplateInvocationContext invocationContext; private final int index; ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, ContainerTemplateInvocationContext invocationContext, @@ -43,6 +43,11 @@ public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescri this.index = index; } + @Override + public Type getType() { + return Type.CONTAINER; + } + @Override protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { return new ContainerTemplateInvocationTestDescriptor(newUniqueId, this.invocationContext, this.index, @@ -73,7 +78,8 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte } @Override - public Type getType() { - return Type.CONTAINER; + public void after(JupiterEngineExecutionContext context) throws Exception { + // forget invocationContext so it can be garbage collected + this.invocationContext = null; } } From 2611f6cf35c77c9a9eb4b752b066e0bb6c862fd7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 09:55:41 +0100 Subject: [PATCH 15/60] Avoid class-level extension context from being closed prematurely --- ...ainerTemplateInvocationTestDescriptor.java | 11 ++-- .../descriptor/DynamicExtensionContext.java | 7 ++- .../descriptor/DynamicNodeTestDescriptor.java | 3 +- .../ContainerTemplateInvocationTests.java | 51 +++++++++++++++++++ 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 375f70b01a23..1f62eaa7eb05 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -19,6 +19,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; @@ -57,15 +58,17 @@ protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUni @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = context.getExtensionRegistry(); - List additionalExtensions = invocationContext.getAdditionalExtensions(); + List additionalExtensions = this.invocationContext.getAdditionalExtensions(); if (!additionalExtensions.isEmpty()) { MutableExtensionRegistry childRegistry = createRegistryFrom(registry, Stream.empty()); - additionalExtensions.forEach(extension -> childRegistry.registerExtension(extension, invocationContext)); + additionalExtensions.forEach( + extension -> childRegistry.registerExtension(extension, this.invocationContext)); registry = childRegistry; } - // TODO #871 Set a new ExtensionContext for each invocation to avoid the parent one from being closed + ExtensionContext extensionContext = new DynamicExtensionContext<>(context.getExtensionContext(), + context.getExecutionListener(), this, context.getConfiguration(), registry); return context.extend() // - .withExtensionRegistry(registry) // + .withExtensionContext(extensionContext).withExtensionRegistry(registry) // .build(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java index cd3c292709e2..1e5d99f745ab 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java @@ -22,11 +22,10 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; -class DynamicExtensionContext extends AbstractExtensionContext { +class DynamicExtensionContext extends AbstractExtensionContext { - DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, - DynamicNodeTestDescriptor testDescriptor, JupiterConfiguration configuration, - ExtensionRegistry extensionRegistry) { + DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, + JupiterConfiguration configuration, ExtensionRegistry extensionRegistry) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java index 9e8ce366cf50..e48efe0eae3c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.descriptor; import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.engine.TestDescriptor; @@ -44,7 +45,7 @@ public String getLegacyReportingName() { @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - DynamicExtensionContext extensionContext = new DynamicExtensionContext(context.getExtensionContext(), + ExtensionContext extensionContext = new DynamicExtensionContext<>(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), context.getExtensionRegistry()); // @formatter:off return context.extend() diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index d32b5fc08c21..882d44595687 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -36,11 +37,13 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; @@ -332,6 +335,13 @@ void executesNestedContainerTemplatesTwiceEach() { void invocationContextProviderCanRegisterAdditionalExtensions() { var results = executeTestsForClass(AdditionalExtensionRegistrationTestCase.class); + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + } + + @Test + void eachInvocationHasSeparateExtensionContext() { + var results = executeTestsForClass(SeparateExtensionContextTestCase.class); + results.allEvents().debug(); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); } @@ -462,4 +472,45 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte record Data(String value) { } } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + @ExtendWith(SeparateExtensionContextTestCase.SomeResourceExtension.class) + static class SeparateExtensionContextTestCase { + + @Test + void test(SomeResource someResource) { + assertFalse(someResource.closed); + } + + static class SomeResourceExtension implements BeforeAllCallback, ParameterResolver { + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + context.getStore(ExtensionContext.Namespace.GLOBAL).put("someResource", new SomeResource()); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return SomeResource.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return extensionContext.getStore(ExtensionContext.Namespace.GLOBAL).get("someResource"); + } + } + + static class SomeResource implements CloseableResource { + private boolean closed; + + @Override + public void close() { + this.closed = true; + } + } + } } From 217a8aab4116031214885a6f14627feba7a9eecc Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 10:04:43 +0100 Subject: [PATCH 16/60] Verify combination with test template methods --- .../ContainerTemplateInvocationTests.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 882d44595687..90c4d3df2ffd 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -53,6 +54,10 @@ import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.testkit.engine.Event; @@ -342,10 +347,65 @@ void invocationContextProviderCanRegisterAdditionalExtensions() { void eachInvocationHasSeparateExtensionContext() { var results = executeTestsForClass(SeparateExtensionContextTestCase.class); - results.allEvents().debug(); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); } + @Test + void supportsTestTemplateMethodsInsideContainerTemplateClasses() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + CombinationWithTestTemplateTestCase.class.getName()); + var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); + var testTemplateId1 = invocationId1.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); + var testTemplate1InvocationId1 = testTemplateId1.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#1"); + var testTemplate1InvocationId2 = testTemplateId1.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var testTemplateId2 = invocationId2.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); + var testTemplate2InvocationId1 = testTemplateId2.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#1"); + var testTemplate2InvocationId2 = testTemplateId2.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + + var results = executeTestsForClass(CombinationWithTestTemplateTestCase.class); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId1)), + displayName("[1] A of CombinationWithTestTemplateTestCase")), // + event(container(uniqueId(invocationId1)), started()), // + event(dynamicTestRegistered(uniqueId(testTemplateId1))), // + event(container(uniqueId(testTemplateId1)), started()), // + event(dynamicTestRegistered(uniqueId(testTemplate1InvocationId1))), // + event(test(uniqueId(testTemplate1InvocationId1)), started()), // + event(test(uniqueId(testTemplate1InvocationId1)), finishedSuccessfully()), // + event(dynamicTestRegistered(uniqueId(testTemplate1InvocationId2))), // + event(test(uniqueId(testTemplate1InvocationId2)), started()), // + event(test(uniqueId(testTemplate1InvocationId2)), finishedSuccessfully()), // + event(container(uniqueId(testTemplateId1)), finishedSuccessfully()), // + event(container(uniqueId(invocationId1)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), + displayName("[2] B of CombinationWithTestTemplateTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(testTemplateId2))), // + event(container(uniqueId(testTemplateId2)), started()), // + event(dynamicTestRegistered(uniqueId(testTemplate2InvocationId1))), // + event(test(uniqueId(testTemplate2InvocationId1)), started()), // + event(test(uniqueId(testTemplate2InvocationId1)), finishedSuccessfully()), // + event(dynamicTestRegistered(uniqueId(testTemplate2InvocationId2))), // + event(test(uniqueId(testTemplate2InvocationId2)), started()), // + event(test(uniqueId(testTemplate2InvocationId2)), finishedSuccessfully()), // + event(container(uniqueId(testTemplateId2)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -513,4 +573,15 @@ public void close() { } } } + + @ContainerTemplate + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + static class CombinationWithTestTemplateTestCase { + + @ParameterizedTest + @ValueSource(ints = { 1, 2 }) + void test(int i) { + assertNotEquals(0, i); + } + } } From 8f41b45576eb701331e73af97dad430bb0acd93e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 10:11:22 +0100 Subject: [PATCH 17/60] Verify combination with test factory methods --- .../ContainerTemplateInvocationTests.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 90c4d3df2ffd..10b2c55f07e7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; @@ -31,13 +32,16 @@ import java.util.List; import java.util.function.Predicate; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; @@ -53,6 +57,7 @@ import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; @@ -406,6 +411,62 @@ void supportsTestTemplateMethodsInsideContainerTemplateClasses() { event(engine(), finishedSuccessfully())); } + @Test + void supportsTestFactoryMethodsInsideContainerTemplateClasses() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + CombinationWithTestFactoryTestCase.class.getName()); + var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); + var testFactoryId1 = invocationId1.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); + var testFactory1DynamicTestId1 = testFactoryId1.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, + "#1"); + var testFactory1DynamicTestId2 = testFactoryId1.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, + "#2"); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var testFactoryId2 = invocationId2.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); + var testFactory2DynamicTestId1 = testFactoryId2.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, + "#1"); + var testFactory2DynamicTestId2 = testFactoryId2.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, + "#2"); + + var results = executeTestsForClass(CombinationWithTestFactoryTestCase.class); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId1)), + displayName("[1] A of CombinationWithTestFactoryTestCase")), // + event(container(uniqueId(invocationId1)), started()), // + event(dynamicTestRegistered(uniqueId(testFactoryId1))), // + event(container(uniqueId(testFactoryId1)), started()), // + event(dynamicTestRegistered(uniqueId(testFactory1DynamicTestId1))), // + event(test(uniqueId(testFactory1DynamicTestId1)), started()), // + event(test(uniqueId(testFactory1DynamicTestId1)), finishedSuccessfully()), // + event(dynamicTestRegistered(uniqueId(testFactory1DynamicTestId2))), // + event(test(uniqueId(testFactory1DynamicTestId2)), started()), // + event(test(uniqueId(testFactory1DynamicTestId2)), finishedSuccessfully()), // + event(container(uniqueId(testFactoryId1)), finishedSuccessfully()), // + event(container(uniqueId(invocationId1)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), + displayName("[2] B of CombinationWithTestFactoryTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(testFactoryId2))), // + event(container(uniqueId(testFactoryId2)), started()), // + event(dynamicTestRegistered(uniqueId(testFactory2DynamicTestId1))), // + event(test(uniqueId(testFactory2DynamicTestId1)), started()), // + event(test(uniqueId(testFactory2DynamicTestId1)), finishedSuccessfully()), // + event(dynamicTestRegistered(uniqueId(testFactory2DynamicTestId2))), // + event(test(uniqueId(testFactory2DynamicTestId2)), started()), // + event(test(uniqueId(testFactory2DynamicTestId2)), finishedSuccessfully()), // + event(container(uniqueId(testFactoryId2)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -584,4 +645,15 @@ void test(int i) { assertNotEquals(0, i); } } + + @ContainerTemplate + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + static class CombinationWithTestFactoryTestCase { + + @TestFactory + Stream test() { + return IntStream.of(1, 2) // + .mapToObj(i -> dynamicTest("test" + i, () -> assertNotEquals(0, i))); + } + } } From bb867449ddb83b392b277d7f0197a2b7fccdb543 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 10:20:27 +0100 Subject: [PATCH 18/60] Verify validation of zero provided invocation contexts --- .../ContainerTemplateInvocationTests.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 10b2c55f07e7..a99171f6a2f2 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -27,8 +28,10 @@ import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.List; import java.util.function.Predicate; @@ -467,6 +470,31 @@ void supportsTestFactoryMethodsInsideContainerTemplateClasses() { event(engine(), finishedSuccessfully())); } + @Test + void failsIfProviderReturnsZeroInvocationContextWithoutOptIn() { + var results = executeTestsForClass(InvalidZeroInvocationTestCase.class); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(InvalidZeroInvocationTestCase.class), started()), // + event(container(InvalidZeroInvocationTestCase.class), + finishedWithFailure( + message("Provider [Ext] did not provide any invocation contexts, but was expected to do so. " + + "You may override mayReturnZeroContainerTemplateInvocationContexts() to allow this."))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void succeedsIfProviderReturnsZeroInvocationContextWithOptIn() { + var results = executeTestsForClass(ValidZeroInvocationTestCase.class); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ValidZeroInvocationTestCase.class), started()), // + event(container(ValidZeroInvocationTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -656,4 +684,59 @@ Stream test() { .mapToObj(i -> dynamicTest("test" + i, () -> assertNotEquals(0, i))); } } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(InvalidZeroInvocationTestCase.Ext.class) + static class InvalidZeroInvocationTestCase { + + @Test + void test() { + fail("should not be called"); + } + + static class Ext implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.empty(); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(ValidZeroInvocationTestCase.Ext.class) + static class ValidZeroInvocationTestCase { + + @Test + void test() { + fail("should not be called"); + } + + static class Ext implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.empty(); + } + + @Override + public boolean mayReturnZeroContainerTemplateInvocationContexts(ExtensionContext context) { + return true; + } + } + } } From 2a7da230b62e6f81ddf28f890eb3aff24528c439 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 10:28:11 +0100 Subject: [PATCH 19/60] Verify validation of registered providers --- .../ContainerTemplateTestDescriptor.java | 2 +- .../ContainerTemplateInvocationTests.java | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 86da741fdcbe..2fe7e9754c17 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -173,7 +173,7 @@ private List validateProviders(Exten return Preconditions.notEmpty(providers, () -> String.format("You must register at least one %s that supports @ContainerTemplate class [%s]", - ContainerTemplateInvocationContextProvider.class.getSimpleName(), getTestClass())); + ContainerTemplateInvocationContextProvider.class.getSimpleName(), getTestClass().getName())); } private Optional toTestDescriptor(ContainerTemplateInvocationContext invocationContext, int index) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index a99171f6a2f2..2b2550a836ac 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -495,6 +495,21 @@ void succeedsIfProviderReturnsZeroInvocationContextWithOptIn() { event(engine(), finishedSuccessfully())); } + @ParameterizedTest + @ValueSource(classes = { NoProviderRegisteredTestCase.class, NoSupportingProviderRegisteredTestCase.class }) + void failsIfNoSupportingProviderIsRegistered(Class testClass) { + var results = executeTestsForClass(testClass); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure( + message("You must register at least one ContainerTemplateInvocationContextProvider that supports " + + "@ContainerTemplate class [" + testClass.getName() + "]"))), // + event(engine(), finishedSuccessfully())); + } + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), @@ -739,4 +754,39 @@ public boolean mayReturnZeroContainerTemplateInvocationContexts(ExtensionContext } } } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + static class NoProviderRegisteredTestCase { + + @Test + void test() { + fail("should not be called"); + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(NoSupportingProviderRegisteredTestCase.Ext.class) + static class NoSupportingProviderRegisteredTestCase { + + @Test + void test() { + fail("should not be called"); + } + + static class Ext implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return false; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + throw new RuntimeException("should not be called"); + } + } + } } From 6988b0a0305dd290d6b4b4f23fe890bccf4091e7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 11:00:28 +0100 Subject: [PATCH 20/60] Add support for discovering container templates by unique ID --- .../discovery/ClassSelectorResolver.java | 24 ++++++++++++------- .../ContainerTemplateInvocationTests.java | 10 +++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index ddadad6d471c..bf9c07f6bd2d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -98,24 +98,32 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { UniqueId.Segment lastSegment = uniqueId.getLastSegment(); if (ClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { String className = lastSegment.getValue(); - return ReflectionSupport.tryToLoadClass(className).toOptional().filter(isTestClassWithTests).map( - testClass -> toResolution( - context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( - unresolved()); + return ReflectionSupport.tryToLoadClass(className).toOptional() // + .filter(isTestClassWithTests) // + .map(testClass -> toResolution( + context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( + unresolved()); } if (NestedClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { String simpleClassName = lastSegment.getValue(); return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { if (parent instanceof ClassBasedTestDescriptor) { Class parentTestClass = ((ClassBasedTestDescriptor) parent).getTestClass(); - return ReflectionSupport.findNestedClasses(parentTestClass, - isNestedTestClass.and( - where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst().flatMap( - testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); + return ReflectionSupport.findNestedClasses(parentTestClass, isNestedTestClass.and( + where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst() // + .flatMap(testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); } return Optional.empty(); })); } + if (ContainerTemplateTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { + String className = lastSegment.getValue(); + return ReflectionSupport.tryToLoadClass(className).toOptional() // + .filter(isTestClassWithTests) // + .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // + .map(testClass -> toResolution(context.addToParent( + parent -> Optional.of(newStaticClassTestDescriptor(parent, testClass))))).orElse(unresolved()); + } return unresolved(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 2b2550a836ac..ca0a7b230c20 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -75,8 +75,12 @@ */ public class ContainerTemplateInvocationTests extends AbstractJupiterTestEngineTests { - @Test - void executesContainerTemplateClassTwice() { + @ParameterizedTest + @ValueSource(strings = { // + "class:org.junit.jupiter.engine.ContainerTemplateInvocationTests$TwoInvocationsTestCase", // + "uid:[engine:junit-jupiter]/[container-template:org.junit.jupiter.engine.ContainerTemplateInvocationTests$TwoInvocationsTestCase]" // + }) + void executesContainerTemplateClassTwice(String selectorIdentifier) { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); @@ -89,7 +93,7 @@ void executesContainerTemplateClassTwice() { var invocation2NestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); var invocation2NestedMethodBId = invocation2NestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); - var results = executeTestsForClass(TwoInvocationsTestCase.class); + var results = executeTests(DiscoverySelectors.parse(selectorIdentifier).orElseThrow()); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // From 38716de0746ed0c1975df6e4b95a82c6af113495 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 11:29:36 +0100 Subject: [PATCH 21/60] Add support for discovering single invocation by unique ID --- .../ContainerTemplateTestDescriptor.java | 32 +++++++++----- .../discovery/ClassSelectorResolver.java | 42 +++++++++++++------ .../ContainerTemplateInvocationTests.java | 37 ++++++++++++++++ 3 files changed, 88 insertions(+), 23 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 2fe7e9754c17..9e4a35700a93 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -42,10 +42,11 @@ * @since 5.13 */ @API(status = INTERNAL, since = "5.13") -public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor { +public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor implements Filterable { public static final String SEGMENT_TYPE = "container-template"; + private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); private final List childrenPrototypes = new ArrayList<>(); private final ClassBasedTestDescriptor delegate; @@ -62,6 +63,13 @@ public Set getTags() { return new LinkedHashSet<>(this.tags); } + // --- Filterable ---------------------------------------------------------- + + @Override + public DynamicDescendantFilter getDynamicDescendantFilter() { + return dynamicDescendantFilter; + } + // --- JupiterTestDescriptor ----------------------------------------------- @Override @@ -179,19 +187,23 @@ private List validateProviders(Exten private Optional toTestDescriptor(ContainerTemplateInvocationContext invocationContext, int index) { UniqueId invocationUniqueId = getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); - ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( - invocationUniqueId, invocationContext, index, getSource().orElse(null), this.configuration); + if (getDynamicDescendantFilter().test(invocationUniqueId, index - 1)) { + + ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( + invocationUniqueId, invocationContext, index, getSource().orElse(null), this.configuration); - UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); + UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); - // TODO #871 filter descendants + // TODO #871 filter descendants - this.childrenPrototypes.stream() // - .map(JupiterTestDescriptor.class::cast) // - .map(it -> it.copyIncludingDescendants(transformer)) // - .forEach(containerInvocationDescriptor::addChild); + this.childrenPrototypes.stream() // + .map(JupiterTestDescriptor.class::cast) // + .map(it -> it.copyIncludingDescendants(transformer)) // + .forEach(containerInvocationDescriptor::addChild); - return Optional.of(containerInvocationDescriptor); + return Optional.of(containerInvocationDescriptor); + } + return Optional.empty(); } private static class UniqueIdPrefixTransformer implements UnaryOperator { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index bf9c07f6bd2d..6aa0f8b1391c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -36,7 +36,9 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.ContainerTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; +import org.junit.jupiter.engine.descriptor.Filterable; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; @@ -124,6 +126,17 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { .map(testClass -> toResolution(context.addToParent( parent -> Optional.of(newStaticClassTestDescriptor(parent, testClass))))).orElse(unresolved()); } + if (ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { + return context.resolve(selectUniqueId(uniqueId.removeLastSegment())) // + .map(parent -> { + if (parent instanceof Filterable) { + ((Filterable) parent).getDynamicDescendantFilter().allowUniqueIdPrefix(uniqueId); + } + return Resolution.match( + Match.exact(parent, expansionCallback((ClassBasedTestDescriptor) parent))); + }) // + .orElse(unresolved()); + } return unresolved(); } @@ -161,20 +174,23 @@ private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestD } private Resolution toResolution(Optional testDescriptor) { - return testDescriptor.map(it -> { - Class testClass = it.getTestClass(); - List> testClasses = new ArrayList<>(it.getEnclosingTestClasses()); + return testDescriptor // + .map(it -> Resolution.match(Match.exact(it, expansionCallback(it)))) // + .orElse(unresolved()); + } + + private Supplier> expansionCallback(ClassBasedTestDescriptor testDescriptor) { + return () -> { + Class testClass = testDescriptor.getTestClass(); + List> testClasses = new ArrayList<>(testDescriptor.getEnclosingTestClasses()); testClasses.add(testClass); - // @formatter:off - return Resolution.match(Match.exact(it, () -> { - Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod, TOP_DOWN).stream() - .map(method -> selectMethod(testClasses, method)); - Stream nestedClasses = streamNestedClasses(testClass, isNestedTestClass) - .map(nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); - return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier>) LinkedHashSet::new)); - })); - // @formatter:on - }).orElse(unresolved()); + Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod, + TOP_DOWN).stream().map(method -> selectMethod(testClasses, method)); + Stream nestedClasses = streamNestedClasses(testClass, isNestedTestClass).map( + nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); + return Stream.concat(methods, nestedClasses).collect( + toCollection((Supplier>) LinkedHashSet::new)); + }; } private DiscoverySelector selectClass(List> classes) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index ca0a7b230c20..9a427044dec0 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -19,6 +19,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; @@ -68,6 +69,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.testkit.engine.Event; /** @@ -514,6 +516,41 @@ void failsIfNoSupportingProviderIsRegistered(Class testClass) { event(engine(), finishedSuccessfully())); } + @Test + void containerTemplateInvocationCanBeSelectedByUniqueId() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + TwoInvocationsTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); + var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var nestedMethodBId = nestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + + var results = executeTests(selectUniqueId(invocationId2)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(methodAId))), // + event(dynamicTestRegistered(uniqueId(nestedClassId))), // + event(dynamicTestRegistered(uniqueId(nestedMethodBId))), // + event(test(uniqueId(methodAId)), started()), // + event(test(uniqueId(methodAId)), finishedSuccessfully()), // + event(container(uniqueId(nestedClassId)), started()), // + event(test(uniqueId(nestedMethodBId)), started()), // + event(test(uniqueId(nestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(nestedClassId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + // ------------------------------------------------------------------- + // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), From 560bf91573918f08ecde8c10c38b13e1ac7d4b6a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 4 Feb 2025 13:18:49 +0100 Subject: [PATCH 22/60] Allow selecting a container template invocation by unique ID --- .../descriptor/ClassBasedTestDescriptor.java | 8 ++- ...ainerTemplateInvocationTestDescriptor.java | 27 ++++++-- .../ContainerTemplateTestDescriptor.java | 47 +++++++++++--- .../descriptor/DynamicDescendantFilter.java | 2 +- .../engine/descriptor/TestClassAware.java | 29 +++++++++ .../discovery/ClassSelectorResolver.java | 61 ++++++++++++++----- .../discovery/MethodSelectorResolver.java | 14 ++--- .../ContainerTemplateInvocationTests.java | 18 ++++++ 8 files changed, 169 insertions(+), 37 deletions(-) create mode 100644 junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestClassAware.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java index c6e595320518..0077d5d7f512 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java @@ -82,7 +82,8 @@ * @since 5.5 */ @API(status = INTERNAL, since = "5.5") -public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor implements ResourceLockAware { +public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor + implements ResourceLockAware, TestClassAware { private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); @@ -118,13 +119,14 @@ public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor imp this.exclusiveResourceCollector = ExclusiveResourceCollector.from(testClass); } - // --- TestDescriptor ------------------------------------------------------ + // --- TestClassAware ------------------------------------------------------ + @Override public final Class getTestClass() { return this.testClass; } - public abstract List> getEnclosingTestClasses(); + // --- TestDescriptor ------------------------------------------------------ @Override public final Type getType() { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 1f62eaa7eb05..967637f3dfa6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -30,20 +30,37 @@ * @since 5.13 */ @API(status = INTERNAL, since = "5.13") -public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescriptor { +public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescriptor implements TestClassAware { public static final String SEGMENT_TYPE = "container-template-invocation"; + private final TestClassAware parent; private ContainerTemplateInvocationContext invocationContext; private final int index; - ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, ContainerTemplateInvocationContext invocationContext, - int index, TestSource source, JupiterConfiguration configuration) { + public ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, TestClassAware parent, + ContainerTemplateInvocationContext invocationContext, int index, TestSource source, + JupiterConfiguration configuration) { super(uniqueId, invocationContext.getDisplayName(index), source, configuration); + this.parent = parent; this.invocationContext = invocationContext; this.index = index; } + public int getIndex() { + return index; + } + + @Override + public Class getTestClass() { + return parent.getTestClass(); + } + + @Override + public List> getEnclosingTestClasses() { + return parent.getEnclosingTestClasses(); + } + @Override public Type getType() { return Type.CONTAINER; @@ -51,7 +68,7 @@ public Type getType() { @Override protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateInvocationTestDescriptor(newUniqueId, this.invocationContext, this.index, + return new ContainerTemplateInvocationTestDescriptor(newUniqueId, parent, this.invocationContext, this.index, getSource().orElse(null), this.configuration); } @@ -81,7 +98,7 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte } @Override - public void after(JupiterEngineExecutionContext context) throws Exception { + public void cleanUp(JupiterEngineExecutionContext context) { // forget invocationContext so it can be garbage collected this.invocationContext = null; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 9e4a35700a93..3bfa130df330 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -14,13 +14,16 @@ import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -47,6 +50,7 @@ public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor im public static final String SEGMENT_TYPE = "container-template"; private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); + private final Map> childrenPrototypesByIndex = new HashMap<>(); private final List childrenPrototypes = new ArrayList<>(); private final ClassBasedTestDescriptor delegate; @@ -80,6 +84,12 @@ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator TestDescriptor newChild = ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer); copy.childrenPrototypes.add(newChild); }); + this.childrenPrototypesByIndex.forEach((index, oldChildren) -> { + List newChildren = oldChildren.stream() // + .map(oldChild -> ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer)) // + .collect(Collectors.toCollection(ArrayList::new)); + copy.childrenPrototypesByIndex.put(index, newChildren); + }); return copy; } @@ -99,7 +109,17 @@ public void prunePriorToFiltering() { public void prune() { super.prune(); this.children.forEach(child -> child.accept(TestDescriptor::prune)); - this.childrenPrototypes.addAll(this.children); + // Second iteration to avoid processing children that were pruned in the first iteration + this.children.forEach(child -> { + if (child instanceof ContainerTemplateInvocationTestDescriptor) { + child.accept(it -> this.dynamicDescendantFilter.allowUniqueIdPrefix(it.getUniqueId())); + this.childrenPrototypesByIndex.put(((ContainerTemplateInvocationTestDescriptor) child).getIndex(), + new ArrayList<>(child.getChildren())); + } + else { + this.childrenPrototypes.add(child); + } + }); this.children.clear(); } @@ -146,6 +166,13 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte return context; } + @Override + public void cleanUp(JupiterEngineExecutionContext context) { + this.childrenPrototypes.clear(); + this.childrenPrototypesByIndex.clear(); + this.dynamicDescendantFilter.allowAll(); + } + private void executeForProvider(ContainerTemplateInvocationContextProvider provider, AtomicInteger invocationIndex, DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { @@ -190,15 +217,11 @@ private Optional toTestDescriptor(ContainerTemplateInvocationCon if (getDynamicDescendantFilter().test(invocationUniqueId, index - 1)) { ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( - invocationUniqueId, invocationContext, index, getSource().orElse(null), this.configuration); - - UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); + invocationUniqueId, this, invocationContext, index, getSource().orElse(null), this.configuration); // TODO #871 filter descendants - this.childrenPrototypes.stream() // - .map(JupiterTestDescriptor.class::cast) // - .map(it -> it.copyIncludingDescendants(transformer)) // + collectChildren(index, invocationUniqueId) // .forEach(containerInvocationDescriptor::addChild); return Optional.of(containerInvocationDescriptor); @@ -206,6 +229,16 @@ private Optional toTestDescriptor(ContainerTemplateInvocationCon return Optional.empty(); } + private Stream collectChildren(int index, UniqueId invocationUniqueId) { + if (this.childrenPrototypesByIndex.containsKey(index)) { + return this.childrenPrototypesByIndex.remove(index).stream(); + } + UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); + return this.childrenPrototypes.stream() // + .map(JupiterTestDescriptor.class::cast) // + .map(it -> it.copyIncludingDescendants(transformer)); + } + private static class UniqueIdPrefixTransformer implements UnaryOperator { private final UniqueId oldPrefix; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java index 15b059ca47b4..42a4298587f2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -59,7 +59,7 @@ public boolean test(UniqueId uniqueId, Integer index) { || allowedIndices.contains(index); } - private boolean isEverythingAllowed() { + boolean isEverythingAllowed() { return allowedUniqueIds.isEmpty() && allowedIndices.isEmpty(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestClassAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestClassAware.java new file mode 100644 index 000000000000..f337b53ca046 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestClassAware.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.List; + +import org.apiguardian.api.API; + +/** + * @since 5.13 + */ +@API(status = INTERNAL, since = "5.13") +public interface TestClassAware { + + Class getTestClass(); + + List> getEnclosingTestClasses(); + +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 6aa0f8b1391c..b22f13da1771 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.discovery; +import static java.util.Collections.emptyList; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; @@ -33,6 +34,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; @@ -40,6 +42,7 @@ import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.Filterable; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestClassAware; import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; import org.junit.platform.commons.support.ReflectionSupport; @@ -123,23 +126,27 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { return ReflectionSupport.tryToLoadClass(className).toOptional() // .filter(isTestClassWithTests) // .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // - .map(testClass -> toResolution(context.addToParent( - parent -> Optional.of(newStaticClassTestDescriptor(parent, testClass))))).orElse(unresolved()); + .map(testClass -> toResolution( + context.addToParent(parent -> Optional.of(newStaticClassTestDescriptor(parent, testClass))))) // + .orElse(unresolved()); } if (ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - return context.resolve(selectUniqueId(uniqueId.removeLastSegment())) // - .map(parent -> { - if (parent instanceof Filterable) { - ((Filterable) parent).getDynamicDescendantFilter().allowUniqueIdPrefix(uniqueId); - } - return Resolution.match( - Match.exact(parent, expansionCallback((ClassBasedTestDescriptor) parent))); - }) // - .orElse(unresolved()); + return toInvocationResolution( + context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { + int index = Integer.parseInt(lastSegment.getValue().substring(1)); + return Optional.of(newDummyContainerTemplateInvocationTestDescriptor(parent, lastSegment, index)); + })); } return unresolved(); } + private ContainerTemplateInvocationTestDescriptor newDummyContainerTemplateInvocationTestDescriptor( + TestDescriptor parent, UniqueId.Segment lastSegment, int index) { + return new ContainerTemplateInvocationTestDescriptor(parent.getUniqueId().append(lastSegment), + (TestClassAware) parent, DummyContainerTemplateInvocationContext.INSTANCE, index, + parent.getSource().orElse(null), configuration); + } + private ClassBasedTestDescriptor newStaticClassTestDescriptor(TestDescriptor parent, Class testClass) { ClassTestDescriptor classTestDescriptor = newClassTestDescriptor(parent, testClass); return isAnnotated(testClass, ContainerTemplate.class) // @@ -173,6 +180,15 @@ private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestD return new ContainerTemplateTestDescriptor(uniqueId, delegate); } + private Resolution toInvocationResolution(Optional testDescriptor) { + return testDescriptor // + .map(it -> Resolution.match(Match.exact(it, + expansionCallback(it, + () -> it.getParent().map(parent -> getTestClasses((ClassBasedTestDescriptor) parent)).orElse( + emptyList()))))) // + .orElse(unresolved()); + } + private Resolution toResolution(Optional testDescriptor) { return testDescriptor // .map(it -> Resolution.match(Match.exact(it, expansionCallback(it)))) // @@ -180,10 +196,24 @@ private Resolution toResolution(Optional tes } private Supplier> expansionCallback(ClassBasedTestDescriptor testDescriptor) { + return expansionCallback(testDescriptor, () -> getTestClasses(testDescriptor)); + } + + private static List> getTestClasses(ClassBasedTestDescriptor testDescriptor) { + List> testClasses = new ArrayList<>(testDescriptor.getEnclosingTestClasses()); + testClasses.add(testDescriptor.getTestClass()); + return testClasses; + } + + private Supplier> expansionCallback(TestDescriptor testDescriptor, + Supplier>> testClassesSupplier) { return () -> { - Class testClass = testDescriptor.getTestClass(); - List> testClasses = new ArrayList<>(testDescriptor.getEnclosingTestClasses()); - testClasses.add(testClass); + if (testDescriptor instanceof Filterable) { + Filterable filterable = (Filterable) testDescriptor; + filterable.getDynamicDescendantFilter().allowAll(); + } + List> testClasses = testClassesSupplier.get(); + Class testClass = testClasses.get(testClasses.size() - 1); Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod, TOP_DOWN).stream().map(method -> selectMethod(testClasses, method)); Stream nestedClasses = streamNestedClasses(testClass, isNestedTestClass).map( @@ -209,4 +239,7 @@ private DiscoverySelector selectMethod(List> classes, Method method) { return DiscoverySelectors.selectNestedMethod(classes.subList(0, lastIndex), classes.get(lastIndex), method); } + static class DummyContainerTemplateInvocationContext implements ContainerTemplateInvocationContext { + private static final DummyContainerTemplateInvocationContext INSTANCE = new DummyContainerTemplateInvocationContext(); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java index 7a24445ed912..1f255689bd02 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -30,8 +30,8 @@ import java.util.stream.Stream; import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.Filterable; +import org.junit.jupiter.engine.descriptor.TestClassAware; import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; @@ -208,8 +208,7 @@ private Optional resolve(List> enclosingClasses, Class< return Optional.empty(); } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // - parent -> Optional.of( - createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration))); + parent -> Optional.of(createTestDescriptor(parent, testClass, method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { @@ -225,11 +224,11 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq if (segmentType.equals(lastSegment.getType())) { return context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { String methodSpecPart = lastSegment.getValue(); - Class testClass = ((ClassBasedTestDescriptor) parent).getTestClass(); + Class testClass = ((TestClassAware) parent).getTestClass(); // @formatter:off return methodFinder.findMethod(methodSpecPart, testClass) .filter(methodPredicate) - .map(method -> createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration)); + .map(method -> createTestDescriptor(parent, testClass, method, configuration)); // @formatter:on }); } @@ -239,10 +238,11 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq return Optional.empty(); } - TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, Method method, + TestDescriptor createTestDescriptor(TestDescriptor parent, Class testClass, Method method, JupiterConfiguration configuration) { UniqueId uniqueId = createUniqueId(method, parent); - return createTestDescriptor(uniqueId, testClass, method, parent::getEnclosingTestClasses, configuration); + return createTestDescriptor(uniqueId, testClass, method, ((TestClassAware) parent)::getEnclosingTestClasses, + configuration); } private UniqueId createUniqueId(Method method, TestDescriptor parent) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 9a427044dec0..6bf9c13c941c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -97,6 +97,7 @@ void executesContainerTemplateClassTwice(String selectorIdentifier) { var results = executeTests(DiscoverySelectors.parse(selectorIdentifier).orElseThrow()); + results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(containerTemplateId)), started()), // @@ -549,6 +550,23 @@ void containerTemplateInvocationCanBeSelectedByUniqueId() { event(engine(), finishedSuccessfully())); } + @ParameterizedTest + @ValueSource(strings = { // + "class:org.junit.jupiter.engine.ContainerTemplateInvocationTests$TwoInvocationsTestCase", // + "uid:[engine:junit-jupiter]/[container-template:org.junit.jupiter.engine.ContainerTemplateInvocationTests$TwoInvocationsTestCase]" // + }) + void executesAllInvocationsForRedundantSelectors(String containerTemplateSelectorIdentifier) { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + TwoInvocationsTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + + var results = executeTests(selectUniqueId(invocationId2), + DiscoverySelectors.parse(containerTemplateSelectorIdentifier).orElseThrow()); + + results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); + } + // ------------------------------------------------------------------- // TODO #871 Consider moving to EventConditions From 9a18980b57da6fb218ba5081da55e7b9661d02cf Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 08:54:35 +0100 Subject: [PATCH 23/60] Add support for unique-id-based discovery of nested classes/methods --- .../discovery/ClassSelectorResolver.java | 4 +- .../ContainerTemplateInvocationTests.java | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index b22f13da1771..bd5689bab4fe 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -112,8 +112,8 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { if (NestedClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { String simpleClassName = lastSegment.getValue(); return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { - if (parent instanceof ClassBasedTestDescriptor) { - Class parentTestClass = ((ClassBasedTestDescriptor) parent).getTestClass(); + if (parent instanceof TestClassAware) { + Class parentTestClass = ((TestClassAware) parent).getTestClass(); return ReflectionSupport.findNestedClasses(parentTestClass, isNestedTestClass.and( where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst() // .flatMap(testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 6bf9c13c941c..b22a0411141b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -567,6 +567,60 @@ void executesAllInvocationsForRedundantSelectors(String containerTemplateSelecto results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } + @Test + void methodInContainerTemplateInvocationCanBeSelectedByUniqueId() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + TwoInvocationsTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); + + var results = executeTests(selectUniqueId(methodAId)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(methodAId))), // + event(test(uniqueId(methodAId)), started()), // + event(test(uniqueId(methodAId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void nestedMethodInContainerTemplateInvocationCanBeSelectedByUniqueId() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + TwoInvocationsTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var nestedMethodBId = nestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + + var results = executeTests(selectUniqueId(nestedMethodBId)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(nestedClassId))), // + event(dynamicTestRegistered(uniqueId(nestedMethodBId))), // + event(container(uniqueId(nestedClassId)), started()), // + event(test(uniqueId(nestedMethodBId)), started()), // + event(test(uniqueId(nestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(nestedClassId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // ------------------------------------------------------------------- // TODO #871 Consider moving to EventConditions From d090fb369e698212f0708737d3de612790b11511 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 09:27:09 +0100 Subject: [PATCH 24/60] Add support for unique-id-based discovery of nested container templates --- .../ContainerTemplateTestDescriptor.java | 3 +- .../discovery/ClassSelectorResolver.java | 56 +++++++++++----- .../ContainerTemplateInvocationTests.java | 67 +++++++++++++++---- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 3bfa130df330..26dda1db0ad0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -47,7 +47,8 @@ @API(status = INTERNAL, since = "5.13") public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor implements Filterable { - public static final String SEGMENT_TYPE = "container-template"; + public static final String STATIC_CLASS_SEGMENT_TYPE = "container-template"; + public static final String NESTED_CLASS_SEGMENT_TYPE = "nested-container-template"; private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); private final Map> childrenPrototypesByIndex = new HashMap<>(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index bd5689bab4fe..897d8578bb2b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -109,6 +109,15 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( unresolved()); } + if (ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE.equals(lastSegment.getType())) { + String className = lastSegment.getValue(); + return ReflectionSupport.tryToLoadClass(className).toOptional() // + .filter(isTestClassWithTests) // + .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // + .map(testClass -> toResolution(context.addToParent( + parent -> Optional.of(newStaticContainerTemplateTestDescriptor(parent, testClass))))) // + .orElse(unresolved()); + } if (NestedClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { String simpleClassName = lastSegment.getValue(); return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { @@ -121,14 +130,19 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { return Optional.empty(); })); } - if (ContainerTemplateTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - String className = lastSegment.getValue(); - return ReflectionSupport.tryToLoadClass(className).toOptional() // - .filter(isTestClassWithTests) // - .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // - .map(testClass -> toResolution( - context.addToParent(parent -> Optional.of(newStaticClassTestDescriptor(parent, testClass))))) // - .orElse(unresolved()); + if (ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE.equals(lastSegment.getType())) { + String simpleClassName = lastSegment.getValue(); + return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { + if (parent instanceof TestClassAware) { + Class parentTestClass = ((TestClassAware) parent).getTestClass(); + return ReflectionSupport.findNestedClasses(parentTestClass, isNestedTestClass.and( + where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst() // + .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // + .flatMap( + testClass -> Optional.of(newNestedContainerTemplateTestDescriptor(parent, testClass))); + } + return Optional.empty(); + })); } if (ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { return toInvocationResolution( @@ -148,10 +162,15 @@ private ContainerTemplateInvocationTestDescriptor newDummyContainerTemplateInvoc } private ClassBasedTestDescriptor newStaticClassTestDescriptor(TestDescriptor parent, Class testClass) { - ClassTestDescriptor classTestDescriptor = newClassTestDescriptor(parent, testClass); return isAnnotated(testClass, ContainerTemplate.class) // - ? newContainerTemplateTestDescriptor(parent, classTestDescriptor) // - : classTestDescriptor; + ? newStaticContainerTemplateTestDescriptor(parent, testClass) // + : newClassTestDescriptor(parent, testClass); + } + + private ContainerTemplateTestDescriptor newStaticContainerTemplateTestDescriptor(TestDescriptor parent, + Class testClass) { + return newContainerTemplateTestDescriptor(parent, ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, + newClassTestDescriptor(parent, testClass)); } private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class testClass) { @@ -161,10 +180,15 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class< } private ClassBasedTestDescriptor newMemberClassTestDescriptor(TestDescriptor parent, Class testClass) { - NestedClassTestDescriptor classTestDescriptor = newNestedClassTestDescriptor(parent, testClass); return isAnnotated(testClass, ContainerTemplate.class) // - ? newContainerTemplateTestDescriptor(parent, classTestDescriptor) // - : classTestDescriptor; + ? newNestedContainerTemplateTestDescriptor(parent, testClass) // + : newNestedClassTestDescriptor(parent, testClass); + } + + private ContainerTemplateTestDescriptor newNestedContainerTemplateTestDescriptor(TestDescriptor parent, + Class testClass) { + return newContainerTemplateTestDescriptor(parent, ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, + newNestedClassTestDescriptor(parent, testClass)); } private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { @@ -174,9 +198,9 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa } private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestDescriptor parent, - ClassBasedTestDescriptor delegate) { + String segmentType, ClassBasedTestDescriptor delegate) { String segmentValue = delegate.getUniqueId().getLastSegment().getValue(); - UniqueId uniqueId = parent.getUniqueId().append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, segmentValue); + UniqueId uniqueId = parent.getUniqueId().append(segmentType, segmentValue); return new ContainerTemplateTestDescriptor(uniqueId, delegate); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index b22a0411141b..40ebf5837e4b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -84,7 +84,7 @@ public class ContainerTemplateInvocationTests extends AbstractJupiterTestEngineT }) void executesContainerTemplateClassTwice(String selectorIdentifier) { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1MethodAId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); @@ -179,7 +179,8 @@ void executesNestedContainerTemplateClassTwiceWithClassSelectorForEnclosingClass var classId = engineId.append(ClassTestDescriptor.SEGMENT_TYPE, NestedContainerTemplateWithTwoInvocationsTestCase.class.getName()); var methodAId = classId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); - var nestedContainerTemplateId = classId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var nestedContainerTemplateId = classId.append(ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, + "NestedTestCase"); var invocationId1 = nestedContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1NestedMethodBId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); @@ -223,7 +224,8 @@ void executesNestedContainerTemplateClassTwiceWithNestedClassSelector() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classId = engineId.append(ClassTestDescriptor.SEGMENT_TYPE, NestedContainerTemplateWithTwoInvocationsTestCase.class.getName()); - var nestedContainerTemplateId = classId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var nestedContainerTemplateId = classId.append(ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, + "NestedTestCase"); var invocationId1 = nestedContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1NestedMethodBId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); @@ -262,13 +264,13 @@ void executesNestedContainerTemplateClassTwiceWithNestedClassSelector() { @Test void executesNestedContainerTemplatesTwiceEach() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var outerContainerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var outerContainerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, TwoTimesTwoInvocationsTestCase.class.getName()); var outerInvocation1Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation1NestedContainerTemplateId = outerInvocation1Id.append( - ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation1InnerInvocation1Id = outerInvocation1NestedContainerTemplateId.append( ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation1InnerInvocation1NestedMethodId = outerInvocation1InnerInvocation1Id.append( @@ -281,7 +283,7 @@ void executesNestedContainerTemplatesTwiceEach() { var outerInvocation2Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2NestedContainerTemplateId = outerInvocation2Id.append( - ContainerTemplateTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation2InnerInvocation1Id = outerInvocation2NestedContainerTemplateId.append( ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation2InnerInvocation1NestedMethodId = outerInvocation2InnerInvocation1Id.append( @@ -368,7 +370,7 @@ void eachInvocationHasSeparateExtensionContext() { @Test void supportsTestTemplateMethodsInsideContainerTemplateClasses() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, CombinationWithTestTemplateTestCase.class.getName()); var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var testTemplateId1 = invocationId1.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); @@ -424,7 +426,7 @@ void supportsTestTemplateMethodsInsideContainerTemplateClasses() { @Test void supportsTestFactoryMethodsInsideContainerTemplateClasses() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, CombinationWithTestFactoryTestCase.class.getName()); var invocationId1 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var testFactoryId1 = invocationId1.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); @@ -520,7 +522,7 @@ void failsIfNoSupportingProviderIsRegistered(Class testClass) { @Test void containerTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); @@ -557,7 +559,7 @@ void containerTemplateInvocationCanBeSelectedByUniqueId() { }) void executesAllInvocationsForRedundantSelectors(String containerTemplateSelectorIdentifier) { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); @@ -570,7 +572,7 @@ void executesAllInvocationsForRedundantSelectors(String containerTemplateSelecto @Test void methodInContainerTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); @@ -595,7 +597,7 @@ void methodInContainerTemplateInvocationCanBeSelectedByUniqueId() { @Test void nestedMethodInContainerTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.SEGMENT_TYPE, + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); @@ -621,6 +623,47 @@ void nestedMethodInContainerTemplateInvocationCanBeSelectedByUniqueId() { event(engine(), finishedSuccessfully())); } + @Test + void nestedContainerTemplateInvocationCanBeSelectedByUniqueId() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var outerContainerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, + TwoTimesTwoInvocationsTestCase.class.getName()); + var outerInvocation2Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + var outerInvocation2NestedContainerTemplateId = outerInvocation2Id.append( + ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); + var outerInvocation2InnerInvocation2Id = outerInvocation2NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + var results = executeTests(selectUniqueId(outerInvocation2InnerInvocation2NestedMethodId)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(outerContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), + displayName("[2] B of TwoTimesTwoInvocationsTestCase")), // + event(container(uniqueId(outerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2NestedContainerTemplateId))), // + event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id)), + displayName("[2] B of NestedTestCase")), // + event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // + event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2InnerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerContainerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // ------------------------------------------------------------------- // TODO #871 Consider moving to EventConditions From 1190bf9523384ac5d47c8791f8596580e9e0cb02 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 09:48:30 +0100 Subject: [PATCH 25/60] Remove duplication --- .../discovery/ClassSelectorResolver.java | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 897d8578bb2b..63316259aef2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; @@ -62,6 +63,8 @@ class ClassSelectorResolver implements SelectorResolver { private static final IsTestClassWithTests isTestClassWithTests = new IsTestClassWithTests(); private static final IsNestedTestClass isNestedTestClass = new IsNestedTestClass(); + private static final Predicate> isAnnotatedWithContainerTemplate = testClass -> isAnnotated(testClass, + ContainerTemplate.class); private final Predicate classNameFilter; private final JupiterConfiguration configuration; @@ -102,47 +105,18 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { UniqueId uniqueId = selector.getUniqueId(); UniqueId.Segment lastSegment = uniqueId.getLastSegment(); if (ClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - String className = lastSegment.getValue(); - return ReflectionSupport.tryToLoadClass(className).toOptional() // - .filter(isTestClassWithTests) // - .map(testClass -> toResolution( - context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( - unresolved()); + return resolveStaticClassUniqueId(context, lastSegment, __ -> true, this::newClassTestDescriptor); } if (ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE.equals(lastSegment.getType())) { - String className = lastSegment.getValue(); - return ReflectionSupport.tryToLoadClass(className).toOptional() // - .filter(isTestClassWithTests) // - .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // - .map(testClass -> toResolution(context.addToParent( - parent -> Optional.of(newStaticContainerTemplateTestDescriptor(parent, testClass))))) // - .orElse(unresolved()); + return resolveStaticClassUniqueId(context, lastSegment, isAnnotatedWithContainerTemplate, + this::newStaticContainerTemplateTestDescriptor); } if (NestedClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - String simpleClassName = lastSegment.getValue(); - return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { - if (parent instanceof TestClassAware) { - Class parentTestClass = ((TestClassAware) parent).getTestClass(); - return ReflectionSupport.findNestedClasses(parentTestClass, isNestedTestClass.and( - where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst() // - .flatMap(testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); - } - return Optional.empty(); - })); + return resolveNestedClassUniqueId(context, uniqueId, __ -> true, this::newNestedClassTestDescriptor); } if (ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE.equals(lastSegment.getType())) { - String simpleClassName = lastSegment.getValue(); - return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { - if (parent instanceof TestClassAware) { - Class parentTestClass = ((TestClassAware) parent).getTestClass(); - return ReflectionSupport.findNestedClasses(parentTestClass, isNestedTestClass.and( - where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst() // - .filter(testClass -> isAnnotated(testClass, ContainerTemplate.class)) // - .flatMap( - testClass -> Optional.of(newNestedContainerTemplateTestDescriptor(parent, testClass))); - } - return Optional.empty(); - })); + return resolveNestedClassUniqueId(context, uniqueId, isAnnotatedWithContainerTemplate, + this::newNestedContainerTemplateTestDescriptor); } if (ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { return toInvocationResolution( @@ -154,6 +128,34 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { return unresolved(); } + private Resolution resolveStaticClassUniqueId(Context context, UniqueId.Segment lastSegment, + Predicate> condition, + BiFunction, ClassBasedTestDescriptor> factory) { + + String className = lastSegment.getValue(); + return ReflectionSupport.tryToLoadClass(className).toOptional() // + .filter(isTestClassWithTests) // + .filter(condition) // + .map(testClass -> toResolution( + context.addToParent(parent -> Optional.of(factory.apply(parent, testClass))))) // + .orElse(unresolved()); + } + + private Resolution resolveNestedClassUniqueId(Context context, UniqueId uniqueId, + Predicate> condition, + BiFunction, ClassBasedTestDescriptor> factory) { + + String simpleClassName = uniqueId.getLastSegment().getValue(); + return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { + Class parentTestClass = ((TestClassAware) parent).getTestClass(); + return ReflectionSupport.findNestedClasses(parentTestClass, + isNestedTestClass.and(where(Class::getSimpleName, isEqual(simpleClassName)))).stream() // + .findFirst() // + .filter(condition) // + .map(testClass -> factory.apply(parent, testClass)); + })); + } + private ContainerTemplateInvocationTestDescriptor newDummyContainerTemplateInvocationTestDescriptor( TestDescriptor parent, UniqueId.Segment lastSegment, int index) { return new ContainerTemplateInvocationTestDescriptor(parent.getUniqueId().append(lastSegment), @@ -162,7 +164,7 @@ private ContainerTemplateInvocationTestDescriptor newDummyContainerTemplateInvoc } private ClassBasedTestDescriptor newStaticClassTestDescriptor(TestDescriptor parent, Class testClass) { - return isAnnotated(testClass, ContainerTemplate.class) // + return isAnnotatedWithContainerTemplate.test(testClass) // ? newStaticContainerTemplateTestDescriptor(parent, testClass) // : newClassTestDescriptor(parent, testClass); } @@ -180,7 +182,7 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class< } private ClassBasedTestDescriptor newMemberClassTestDescriptor(TestDescriptor parent, Class testClass) { - return isAnnotated(testClass, ContainerTemplate.class) // + return isAnnotatedWithContainerTemplate.test(testClass) // ? newNestedContainerTemplateTestDescriptor(parent, testClass) // : newNestedClassTestDescriptor(parent, testClass); } From eb82e8ef35065c6764077c1c892b0c5ee3b49f22 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 10:14:15 +0100 Subject: [PATCH 26/60] Add test for filtering --- .../ContainerTemplateTestDescriptor.java | 2 -- .../ContainerTemplateInvocationTests.java | 36 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 26dda1db0ad0..d739f8021dbf 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -220,8 +220,6 @@ private Optional toTestDescriptor(ContainerTemplateInvocationCon ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( invocationUniqueId, this, invocationContext, index, getSource().orElse(null), this.configuration); - // TODO #871 filter descendants - collectChildren(index, invocationUniqueId) // .forEach(containerInvocationDescriptor::addChild); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 40ebf5837e4b..06132958ba6d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -627,7 +627,7 @@ void nestedMethodInContainerTemplateInvocationCanBeSelectedByUniqueId() { void nestedContainerTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var outerContainerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, - TwoTimesTwoInvocationsTestCase.class.getName()); + TwoTimesTwoInvocationsWithMultipleMethodsTestCase.class.getName()); var outerInvocation2Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2NestedContainerTemplateId = outerInvocation2Id.append( @@ -635,7 +635,7 @@ void nestedContainerTemplateInvocationCanBeSelectedByUniqueId() { var outerInvocation2InnerInvocation2Id = outerInvocation2NestedContainerTemplateId.append( ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( - TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTests(selectUniqueId(outerInvocation2InnerInvocation2NestedMethodId)); @@ -644,7 +644,7 @@ void nestedContainerTemplateInvocationCanBeSelectedByUniqueId() { event(container(uniqueId(outerContainerTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), - displayName("[2] B of TwoTimesTwoInvocationsTestCase")), // + displayName("[2] B of TwoTimesTwoInvocationsWithMultipleMethodsTestCase")), // event(container(uniqueId(outerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2NestedContainerTemplateId))), // event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), started()), // @@ -945,4 +945,34 @@ public Stream provideContainerTemplateInvoca } } } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + @ContainerTemplate + static class TwoTimesTwoInvocationsWithMultipleMethodsTestCase { + + @Test + void test() { + } + + @Nested + @ContainerTemplate + class NestedTestCase { + @Test + void a() { + } + + @Test + void b() { + } + } + + @Nested + @ContainerTemplate + class AnotherNestedTestCase { + @Test + void test() { + } + } + } } From d7c3132e72d46abaf848162dd742945c07a798b2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 10:16:59 +0100 Subject: [PATCH 27/60] Ensure `ContainerTemplateTestDescriptor.prune()` is idempotent --- .../ContainerTemplateTestDescriptor.java | 2 +- .../discovery/JupiterUniqueIdBuilder.java | 35 +++++++++++++++---- .../DiscoverySelectorResolverTests.java | 29 +++++++++++++-- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index d739f8021dbf..3c2a2d64174b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -126,7 +126,7 @@ public void prune() { @Override public boolean mayRegisterTests() { - return !childrenPrototypes.isEmpty(); + return !childrenPrototypes.isEmpty() || !childrenPrototypesByIndex.isEmpty(); } // --- ClassBasedTestDescriptor --------------------------------------------- diff --git a/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java b/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java index 063d0527103f..5504efad7eaf 100644 --- a/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java +++ b/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java @@ -12,13 +12,18 @@ import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.ContainerTemplateInvocationTestDescriptor; +import org.junit.jupiter.engine.descriptor.ContainerTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.UniqueId; /** @@ -31,16 +36,30 @@ public class JupiterUniqueIdBuilder { public static UniqueId uniqueIdForClass(Class clazz) { - UniqueId containerId = engineId(); if (isInnerClass(clazz)) { - containerId = uniqueIdForClass(clazz.getEnclosingClass()); - return containerId.append(NestedClassTestDescriptor.SEGMENT_TYPE, clazz.getSimpleName()); + var segmentType = classSegmentType(clazz, NestedClassTestDescriptor.SEGMENT_TYPE, + ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE); + return uniqueIdForClass(clazz.getEnclosingClass()).append(segmentType, clazz.getSimpleName()); } - return containerId.append(ClassTestDescriptor.SEGMENT_TYPE, clazz.getName()); + return uniqueIdForStaticClass(clazz.getName()); } - public static UniqueId uniqueIdForTopLevelClass(String className) { - return engineId().append(ClassTestDescriptor.SEGMENT_TYPE, className); + public static UniqueId uniqueIdForStaticClass(String className) { + return engineId().append(staticClassSegmentType(className), className); + } + + private static String staticClassSegmentType(String className) { + return ReflectionSupport.tryToLoadClass(className).toOptional() // + .map(it -> classSegmentType(it, ClassTestDescriptor.SEGMENT_TYPE, + ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE)) // + .orElse(ClassTestDescriptor.SEGMENT_TYPE); + } + + private static String classSegmentType(Class clazz, String regularSegmentType, + String containerTemplateSegmentType) { + return AnnotationSupport.isAnnotated(clazz, ContainerTemplate.class) // + ? containerTemplateSegmentType // + : regularSegmentType; } public static UniqueId uniqueIdForMethod(Class clazz, String methodPart) { @@ -59,6 +78,10 @@ public static UniqueId appendTestTemplateInvocationSegment(UniqueId parentId, in return parentId.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); } + public static UniqueId appendContainerTemplateInvocationSegment(UniqueId parentId, int index) { + return parentId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); + } + public static UniqueId engineId() { return UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index a6f9dd4fc2d8..8af8e24cb92a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -17,12 +17,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendContainerTemplateInvocationSegment; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.engineId; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForClass; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForStaticClass; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestFactoryMethod; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTopLevelClass; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; import static org.junit.platform.engine.SelectorResolutionResult.Status.RESOLVED; @@ -214,6 +215,28 @@ void classResolutionOfContainerTemplate() { assertThat(containerTemplateDescriptor.getDescendants()).isEmpty(); } + @Test + void uniqueIdResolutionOfContainerTemplateInvocation() { + var selector = selectUniqueId( + appendContainerTemplateInvocationSegment(uniqueIdForClass(ContainerTemplateTestCase.class), 1)); + + resolve(request().selectors(selector)); + + assertThat(engineDescriptor.getChildren()).hasSize(1); + + TestDescriptor containerTemplateDescriptor = getOnlyElement(engineDescriptor.getChildren()); + + containerTemplateDescriptor.prune(); + assertThat(engineDescriptor.getChildren()).hasSize(1); + assertThat(containerTemplateDescriptor.mayRegisterTests()).isTrue(); + assertThat(containerTemplateDescriptor.getDescendants()).isEmpty(); + + containerTemplateDescriptor.prune(); + assertThat(engineDescriptor.getChildren()).hasSize(1); + assertThat(containerTemplateDescriptor.mayRegisterTests()).isTrue(); + assertThat(containerTemplateDescriptor.getDescendants()).isEmpty(); + } + @Test void methodResolution() throws NoSuchMethodException { Method test1 = MyTestClass.class.getDeclaredMethod("test1"); @@ -524,8 +547,8 @@ void classpathResolutionForJarFiles() throws Exception { resolve(request().selectors(selectors)); assertThat(uniqueIds()) // - .contains(uniqueIdForTopLevelClass("com.example.project.FirstTest")) // - .contains(uniqueIdForTopLevelClass("com.example.project.SecondTest")); + .contains(uniqueIdForStaticClass("com.example.project.FirstTest")) // + .contains(uniqueIdForStaticClass("com.example.project.SecondTest")); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); From 22809aa2bd0b2a769de74ebdf629796c583be5a7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 10:19:27 +0100 Subject: [PATCH 28/60] Polishing --- .../ContainerTemplateTestDescriptor.java | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 3c2a2d64174b..f251bc018030 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -152,6 +152,13 @@ public Function> getResou // --- Node ---------------------------------------------------------------- + @Override + public void cleanUp(JupiterEngineExecutionContext context) { + this.childrenPrototypes.clear(); + this.childrenPrototypesByIndex.clear(); + this.dynamicDescendantFilter.allowAll(); + } + // TODO copied from TestTemplateTestDescriptor @Override @@ -167,13 +174,6 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte return context; } - @Override - public void cleanUp(JupiterEngineExecutionContext context) { - this.childrenPrototypes.clear(); - this.childrenPrototypesByIndex.clear(); - this.dynamicDescendantFilter.allowAll(); - } - private void executeForProvider(ContainerTemplateInvocationContextProvider provider, AtomicInteger invocationIndex, DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { @@ -238,6 +238,11 @@ private Stream collectChildren(int index, UniqueId inv .map(it -> it.copyIncludingDescendants(transformer)); } + private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { + testDescriptor.setParent(this); + dynamicTestExecutor.execute(testDescriptor); + } + private static class UniqueIdPrefixTransformer implements UnaryOperator { private final UniqueId oldPrefix; @@ -263,20 +268,4 @@ public UniqueId apply(UniqueId uniqueId) { return newValue; } } - - private static UniqueId changePrefix(UniqueId oldValue, UniqueId oldPrefix, UniqueId newPrefix) { - List oldSegments = oldValue.getSegments(); - Preconditions.condition(oldValue.hasPrefix(oldPrefix), () -> "Old value does not have the expected prefix"); - List suffix = oldSegments.subList(oldPrefix.getSegments().size(), oldSegments.size()); - UniqueId newValue = newPrefix; - for (UniqueId.Segment newSegment : suffix) { - newValue = newValue.append(newSegment); - } - return newValue; - } - - private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { - testDescriptor.setParent(this); - dynamicTestExecutor.execute(testDescriptor); - } } From 811d6e8b2ca5ebb15a0b9fb699e9c1c8e9e91e60 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 10:22:02 +0100 Subject: [PATCH 29/60] Make method private again --- .../jupiter/engine/descriptor/DynamicDescendantFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java index 42a4298587f2..15b059ca47b4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -59,7 +59,7 @@ public boolean test(UniqueId uniqueId, Integer index) { || allowedIndices.contains(index); } - boolean isEverythingAllowed() { + private boolean isEverythingAllowed() { return allowedUniqueIds.isEmpty() && allowedIndices.isEmpty(); } From dce64129a4ad88204e19525b546616493e3378be Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 11:12:27 +0100 Subject: [PATCH 30/60] Make things private again --- .../jupiter/engine/discovery/MethodSelectorResolver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java index 1f255689bd02..44479f7eb0c3 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -161,7 +161,7 @@ private Supplier> expansionCallback(TestDescrip }; } - enum MethodType { + private enum MethodType { TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { @Override @@ -192,7 +192,7 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl } }; - final Predicate methodPredicate; + private final Predicate methodPredicate; private final String segmentType; private final Set dynamicDescendantSegmentTypes; @@ -238,7 +238,7 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq return Optional.empty(); } - TestDescriptor createTestDescriptor(TestDescriptor parent, Class testClass, Method method, + private TestDescriptor createTestDescriptor(TestDescriptor parent, Class testClass, Method method, JupiterConfiguration configuration) { UniqueId uniqueId = createUniqueId(method, parent); return createTestDescriptor(uniqueId, testClass, method, ((TestClassAware) parent)::getEnclosingTestClasses, From a6fc494df166c128f8800433c2a5220e68e830bc Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 11:12:34 +0100 Subject: [PATCH 31/60] Polishing --- .../descriptor/ClassTestDescriptor.java | 6 ++++- ...ainerTemplateInvocationTestDescriptor.java | 26 ++++++++++++------- .../ContainerTemplateTestDescriptor.java | 4 ++- .../descriptor/NestedClassTestDescriptor.java | 6 ++++- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index 190d8cc9f049..ec389ed27dfe 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -71,7 +71,7 @@ public Set getTags() { return new LinkedHashSet<>(this.tags); } - // --- ClassBasedTestDescriptor -------------------------------------------- + // --- TestClassAware ------------------------------------------------------ @Override public List> getEnclosingTestClasses() { @@ -86,6 +86,8 @@ public ExecutionMode getExecutionMode() { () -> JupiterTestDescriptor.toExecutionMode(configuration.getDefaultClassesExecutionMode())); } + // --- ClassBasedTestDescriptor -------------------------------------------- + @Override protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, @@ -93,6 +95,8 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren return instantiateTestClass(Optional.empty(), registry, extensionContext); } + // --- ResourceLockAware --------------------------------------------------- + @Override public Function> getResourceLocksProviderEvaluator() { return provider -> provider.provideForClass(getTestClass()); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 967637f3dfa6..b3159d4154e1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -51,27 +51,35 @@ public int getIndex() { return index; } - @Override - public Class getTestClass() { - return parent.getTestClass(); - } + // --- JupiterTestDescriptor ----------------------------------------------- @Override - public List> getEnclosingTestClasses() { - return parent.getEnclosingTestClasses(); + protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { + return new ContainerTemplateInvocationTestDescriptor(newUniqueId, parent, this.invocationContext, this.index, + getSource().orElse(null), this.configuration); } + // --- TestDescriptor ------------------------------------------------------ + @Override public Type getType() { return Type.CONTAINER; } + // --- TestClassAware ------------------------------------------------------ + + @Override + public Class getTestClass() { + return parent.getTestClass(); + } + @Override - protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateInvocationTestDescriptor(newUniqueId, parent, this.invocationContext, this.index, - getSource().orElse(null), this.configuration); + public List> getEnclosingTestClasses() { + return parent.getEnclosingTestClasses(); } + // --- Node ---------------------------------------------------------------- + @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = context.getExtensionRegistry(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index f251bc018030..0258d11bb3e3 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -129,13 +129,15 @@ public boolean mayRegisterTests() { return !childrenPrototypes.isEmpty() || !childrenPrototypesByIndex.isEmpty(); } - // --- ClassBasedTestDescriptor --------------------------------------------- + // --- TestClassAware ------------------------------------------------------ @Override public List> getEnclosingTestClasses() { return delegate.getEnclosingTestClasses(); } + // --- ClassBasedTestDescriptor -------------------------------------------- + @Override public TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index bba34eff4b28..c365874d1b34 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -77,7 +77,7 @@ public final Set getTags() { return allTags; } - // --- ClassBasedTestDescriptor --------------------------------------------- + // --- TestClassAware ------------------------------------------------------ @Override public List> getEnclosingTestClasses() { @@ -95,6 +95,8 @@ public static List> getEnclosingTestClasses(TestDescriptor parent) { return emptyList(); } + // --- ClassBasedTestDescriptor -------------------------------------------- + @Override protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, @@ -107,6 +109,8 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren return instantiateTestClass(Optional.of(outerInstances), registry, extensionContext); } + // --- ResourceLockAware --------------------------------------------------- + @Override public Function> getResourceLocksProviderEvaluator() { return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, From c180159d046f4ff0edfd46c0c1b246e432f7aca8 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 14:58:48 +0100 Subject: [PATCH 32/60] Add support for discovering static container templates by iteration --- .../ContainerTemplateTestDescriptor.java | 12 ++--- .../descriptor/DynamicDescendantFilter.java | 6 +++ .../discovery/ClassSelectorResolver.java | 51 ++++++++++++++----- .../ContainerTemplateInvocationTests.java | 34 +++++++++++++ 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 0258d11bb3e3..a7190b1d6eeb 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -23,7 +24,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -51,7 +51,7 @@ public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor im public static final String NESTED_CLASS_SEGMENT_TYPE = "nested-container-template"; private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); - private final Map> childrenPrototypesByIndex = new HashMap<>(); + private final Map> childrenPrototypesByIndex = new HashMap<>(); private final List childrenPrototypes = new ArrayList<>(); private final ClassBasedTestDescriptor delegate; @@ -88,7 +88,7 @@ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator this.childrenPrototypesByIndex.forEach((index, oldChildren) -> { List newChildren = oldChildren.stream() // .map(oldChild -> ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer)) // - .collect(Collectors.toCollection(ArrayList::new)); + .collect(toList()); copy.childrenPrototypesByIndex.put(index, newChildren); }); return copy; @@ -113,9 +113,9 @@ public void prune() { // Second iteration to avoid processing children that were pruned in the first iteration this.children.forEach(child -> { if (child instanceof ContainerTemplateInvocationTestDescriptor) { - child.accept(it -> this.dynamicDescendantFilter.allowUniqueIdPrefix(it.getUniqueId())); - this.childrenPrototypesByIndex.put(((ContainerTemplateInvocationTestDescriptor) child).getIndex(), - new ArrayList<>(child.getChildren())); + int index = ((ContainerTemplateInvocationTestDescriptor) child).getIndex(); + this.dynamicDescendantFilter.allowIndex(index - 1); + this.childrenPrototypesByIndex.put(index, child.getChildren()); } else { this.childrenPrototypes.add(child); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java index 15b059ca47b4..72e58f391784 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -40,6 +40,12 @@ public void allowUniqueIdPrefix(UniqueId uniqueId) { } } + public void allowIndex(int index) { + if (this.mode == Mode.EXPLICIT) { + this.allowedIndices.add(index); + } + } + public void allowIndex(Set indices) { if (this.mode == Mode.EXPLICIT) { this.allowedIndices.addAll(indices); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 63316259aef2..82fb9835bafc 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -13,6 +13,7 @@ import static java.util.Collections.emptyList; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; @@ -52,6 +53,7 @@ import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.NestedClassSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.discovery.SelectorResolver; @@ -119,15 +121,40 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { this::newNestedContainerTemplateTestDescriptor); } if (ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - return toInvocationResolution( - context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { + Optional testDescriptor = context.addToParent( + () -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { int index = Integer.parseInt(lastSegment.getValue().substring(1)); - return Optional.of(newDummyContainerTemplateInvocationTestDescriptor(parent, lastSegment, index)); - })); + return Optional.of(newDummyContainerTemplateInvocationTestDescriptor(parent, index)); + }); + return toInvocationMatch(testDescriptor) // + .map(Resolution::match) // + .orElse(unresolved()); } return unresolved(); } + @Override + public Resolution resolve(IterationSelector selector, Context context) { + DiscoverySelector parentSelector = selector.getParentSelector(); + if (parentSelector instanceof ClassSelector + && isAnnotatedWithContainerTemplate.test(((ClassSelector) parentSelector).getJavaClass())) { + return resolveIterations(selector, context); + } + return unresolved(); + } + + private Resolution resolveIterations(IterationSelector selector, Context context) { + DiscoverySelector parentSelector = selector.getParentSelector(); + Set matches = selector.getIterationIndices().stream() // + .map(index -> context.addToParent(() -> parentSelector, + parent -> Optional.of(newDummyContainerTemplateInvocationTestDescriptor(parent, index + 1)))) // + .map(this::toInvocationMatch) // + .filter(Optional::isPresent) // + .map(Optional::get) // + .collect(toSet()); + return matches.isEmpty() ? unresolved() : Resolution.matches(matches); + } + private Resolution resolveStaticClassUniqueId(Context context, UniqueId.Segment lastSegment, Predicate> condition, BiFunction, ClassBasedTestDescriptor> factory) { @@ -157,10 +184,11 @@ private Resolution resolveNestedClassUniqueId(Context context, UniqueId uniqueId } private ContainerTemplateInvocationTestDescriptor newDummyContainerTemplateInvocationTestDescriptor( - TestDescriptor parent, UniqueId.Segment lastSegment, int index) { - return new ContainerTemplateInvocationTestDescriptor(parent.getUniqueId().append(lastSegment), - (TestClassAware) parent, DummyContainerTemplateInvocationContext.INSTANCE, index, - parent.getSource().orElse(null), configuration); + TestDescriptor parent, int index) { + UniqueId uniqueId = parent.getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#" + index); + return new ContainerTemplateInvocationTestDescriptor(uniqueId, (TestClassAware) parent, + DummyContainerTemplateInvocationContext.INSTANCE, index, parent.getSource().orElse(null), configuration); } private ClassBasedTestDescriptor newStaticClassTestDescriptor(TestDescriptor parent, Class testClass) { @@ -206,13 +234,12 @@ private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestD return new ContainerTemplateTestDescriptor(uniqueId, delegate); } - private Resolution toInvocationResolution(Optional testDescriptor) { + private Optional toInvocationMatch(Optional testDescriptor) { return testDescriptor // - .map(it -> Resolution.match(Match.exact(it, + .map(it -> Match.exact(it, expansionCallback(it, () -> it.getParent().map(parent -> getTestClasses((ClassBasedTestDescriptor) parent)).orElse( - emptyList()))))) // - .orElse(unresolved()); + emptyList())))); } private Resolution toResolution(Optional testDescriptor) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 06132958ba6d..d5454cfb5311 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; @@ -552,6 +553,39 @@ void containerTemplateInvocationCanBeSelectedByUniqueId() { event(engine(), finishedSuccessfully())); } + @Test + void containerTemplateInvocationCanBeSelectedByIteration() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, + TwoInvocationsTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); + var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); + var nestedMethodBId = nestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); + + var results = executeTests(selectIteration(selectClass(TwoInvocationsTestCase.class), 1)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(methodAId))), // + event(dynamicTestRegistered(uniqueId(nestedClassId))), // + event(dynamicTestRegistered(uniqueId(nestedMethodBId))), // + event(test(uniqueId(methodAId)), started()), // + event(test(uniqueId(methodAId)), finishedSuccessfully()), // + event(container(uniqueId(nestedClassId)), started()), // + event(test(uniqueId(nestedMethodBId)), started()), // + event(test(uniqueId(nestedMethodBId)), finishedSuccessfully()), // + event(container(uniqueId(nestedClassId)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + @ParameterizedTest @ValueSource(strings = { // "class:org.junit.jupiter.engine.ContainerTemplateInvocationTests$TwoInvocationsTestCase", // From 6af1a4890830bf6af646ff7d608a8e13d6e7a030 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 6 Feb 2025 15:22:11 +0100 Subject: [PATCH 33/60] Add support for discovering nested container templates by iteration --- .../ContainerTemplateTestDescriptor.java | 10 ++- .../descriptor/DynamicDescendantFilter.java | 16 +++++ .../descriptor/TestFactoryTestDescriptor.java | 9 ++- .../TestTemplateTestDescriptor.java | 9 ++- .../discovery/ClassSelectorResolver.java | 4 ++ .../ContainerTemplateInvocationTests.java | 68 +++++++++++++++++++ 6 files changed, 108 insertions(+), 8 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index a7190b1d6eeb..5369c3a83770 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -50,14 +50,20 @@ public class ContainerTemplateTestDescriptor extends ClassBasedTestDescriptor im public static final String STATIC_CLASS_SEGMENT_TYPE = "container-template"; public static final String NESTED_CLASS_SEGMENT_TYPE = "nested-container-template"; - private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); private final Map> childrenPrototypesByIndex = new HashMap<>(); private final List childrenPrototypes = new ArrayList<>(); private final ClassBasedTestDescriptor delegate; + private final DynamicDescendantFilter dynamicDescendantFilter; public ContainerTemplateTestDescriptor(UniqueId uniqueId, ClassBasedTestDescriptor delegate) { + this(uniqueId, delegate, new DynamicDescendantFilter()); + } + + private ContainerTemplateTestDescriptor(UniqueId uniqueId, ClassBasedTestDescriptor delegate, + DynamicDescendantFilter dynamicDescendantFilter) { super(uniqueId, delegate.getTestClass(), delegate.getDisplayName(), delegate.configuration); this.delegate = delegate; + this.dynamicDescendantFilter = dynamicDescendantFilter; } // --- TestDescriptor ------------------------------------------------------ @@ -96,7 +102,7 @@ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator @Override protected ContainerTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateTestDescriptor(newUniqueId, this.delegate); + return new ContainerTemplateTestDescriptor(newUniqueId, this.delegate, this.dynamicDescendantFilter.copy()); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java index 72e58f391784..56336ee8819e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -85,6 +85,17 @@ private enum Mode { EXPLICIT, ALLOW_ALL } + public DynamicDescendantFilter copy() { + return configure(new DynamicDescendantFilter()); + } + + protected DynamicDescendantFilter configure(DynamicDescendantFilter copy) { + copy.allowedUniqueIds.addAll(this.allowedUniqueIds); + copy.allowedIndices.addAll(this.allowedIndices); + copy.mode = this.mode; + return copy; + } + private class WithoutIndexFiltering extends DynamicDescendantFilter { @Override @@ -96,5 +107,10 @@ public boolean test(UniqueId uniqueId, Integer index) { public DynamicDescendantFilter withoutIndexFiltering() { return this; } + + @Override + public DynamicDescendantFilter copy() { + return configure(new WithoutIndexFiltering()); + } } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index 143f693045b1..68d28d0a3877 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -61,24 +61,27 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen private static final ReflectiveInterceptorCall interceptorCall = InvocationInterceptor::interceptTestFactoryMethod; private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); - private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); + private final DynamicDescendantFilter dynamicDescendantFilter; public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); + this.dynamicDescendantFilter = new DynamicDescendantFilter(); } private TestFactoryTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, - JupiterConfiguration configuration) { + JupiterConfiguration configuration, DynamicDescendantFilter dynamicDescendantFilter) { super(uniqueId, displayName, testClass, testMethod, configuration); + this.dynamicDescendantFilter = dynamicDescendantFilter; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected TestFactoryTestDescriptor withUniqueId(UniqueId newUniqueId) { + // TODO #871 Check that dynamic descendant filter is copied correctly return new TestFactoryTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), - this.configuration); + this.configuration, new DynamicDescendantFilter()); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index bbec52b34ff2..e01e73a395d7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -44,24 +44,27 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implements Filterable { public static final String SEGMENT_TYPE = "test-template"; - private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); + private final DynamicDescendantFilter dynamicDescendantFilter; public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration); + this.dynamicDescendantFilter = new DynamicDescendantFilter(); } private TestTemplateTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method templateMethod, - JupiterConfiguration configuration) { + JupiterConfiguration configuration, DynamicDescendantFilter dynamicDescendantFilter) { super(uniqueId, displayName, testClass, templateMethod, configuration); + this.dynamicDescendantFilter = dynamicDescendantFilter; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected TestTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { + // TODO #871 Check that dynamic descendant filter is copied correctly return new TestTemplateTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), - this.configuration); + this.configuration, new DynamicDescendantFilter()); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 82fb9835bafc..d99c69a6e2f6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -140,6 +140,10 @@ public Resolution resolve(IterationSelector selector, Context context) { && isAnnotatedWithContainerTemplate.test(((ClassSelector) parentSelector).getJavaClass())) { return resolveIterations(selector, context); } + if (parentSelector instanceof NestedClassSelector + && isAnnotatedWithContainerTemplate.test(((NestedClassSelector) parentSelector).getNestedClass())) { + return resolveIterations(selector, context); + } return unresolved(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index d5454cfb5311..9f6c4feb9f63 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -19,6 +19,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.TagFilter.excludeTags; @@ -698,6 +699,73 @@ void nestedContainerTemplateInvocationCanBeSelectedByUniqueId() { event(engine(), finishedSuccessfully())); } + @Test + void nestedContainerTemplateInvocationCanBeSelectedByIteration() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var outerContainerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, + TwoTimesTwoInvocationsTestCase.class.getName()); + var outerInvocation1Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#1"); + var outerInvocation1NestedContainerTemplateId = outerInvocation1Id.append( + ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); + var outerInvocation1InnerInvocation2Id = outerInvocation1NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var outerInvocation1InnerInvocation2NestedMethodId = outerInvocation1InnerInvocation2Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + var outerInvocation2Id = outerContainerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + var outerInvocation2NestedContainerTemplateId = outerInvocation2Id.append( + ContainerTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); + var outerInvocation2InnerInvocation2Id = outerInvocation2NestedContainerTemplateId.append( + ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( + TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + var results = executeTests(selectIteration(selectNestedClass(List.of(TwoTimesTwoInvocationsTestCase.class), + TwoTimesTwoInvocationsTestCase.NestedTestCase.class), 1)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(outerContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation1Id)), + displayName("[1] A of TwoTimesTwoInvocationsTestCase")), // + event(container(uniqueId(outerInvocation1Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation1NestedContainerTemplateId))), // + event(container(uniqueId(outerInvocation1NestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2Id)), + displayName("[2] B of NestedTestCase")), // + event(container(uniqueId(outerInvocation1InnerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2NestedMethodId))), // + event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation1InnerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerInvocation1NestedContainerTemplateId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation1Id)), finishedSuccessfully()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), + displayName("[2] B of TwoTimesTwoInvocationsTestCase")), // + event(container(uniqueId(outerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2NestedContainerTemplateId))), // + event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id)), + displayName("[2] B of NestedTestCase")), // + event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // + event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // + event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // + event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2InnerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerInvocation2NestedContainerTemplateId)), finishedSuccessfully()), // + event(container(uniqueId(outerInvocation2Id)), finishedSuccessfully()), // + + event(container(uniqueId(outerContainerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + // ------------------------------------------------------------------- // TODO #871 Consider moving to EventConditions From e661aa282f492ab4e5fd72d70b2fd9ca350b8edb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 9 Feb 2025 19:03:46 +0100 Subject: [PATCH 34/60] Implement getLegacyReportingName() --- ...ainerTemplateInvocationTestDescriptor.java | 5 ++++ .../ContainerTemplateInvocationTests.java | 26 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index b3159d4154e1..915a4f22ff80 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -66,6 +66,11 @@ public Type getType() { return Type.CONTAINER; } + @Override + public String getLegacyReportingName() { + return getTestClass().getName() + "[" + index + "]"; + } + // --- TestClassAware ------------------------------------------------------ @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 9f6c4feb9f63..93b01eac5ef2 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine; +import static java.util.function.Predicate.isEqual; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -37,7 +38,6 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.List; -import java.util.function.Predicate; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -99,12 +99,12 @@ void executesContainerTemplateClassTwice(String selectorIdentifier) { var results = executeTests(DiscoverySelectors.parse(selectorIdentifier).orElseThrow()); - results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(containerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of TwoInvocationsTestCase")), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of TwoInvocationsTestCase"), + legacyReportingName("%s[1]".formatted(TwoInvocationsTestCase.class.getName()))), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1MethodAId))), // event(dynamicTestRegistered(uniqueId(invocation1NestedClassId))), // @@ -117,7 +117,8 @@ void executesContainerTemplateClassTwice(String selectorIdentifier) { event(container(uniqueId(invocation1NestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase"), + legacyReportingName("%s[2]".formatted(TwoInvocationsTestCase.class.getName()))), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2MethodAId))), // event(dynamicTestRegistered(uniqueId(invocation2NestedClassId))), // @@ -201,14 +202,18 @@ void executesNestedContainerTemplateClassTwiceWithClassSelectorForEnclosingClass event(container(uniqueId(nestedContainerTemplateId)), started()), // - event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of NestedTestCase")), // + event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of NestedTestCase"), + legacyReportingName("%s[1]".formatted( + NestedContainerTemplateWithTwoInvocationsTestCase.NestedTestCase.class.getName()))), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // event(test(uniqueId(invocation1NestedMethodBId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // - event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of NestedTestCase")), // + event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of NestedTestCase"), + legacyReportingName("%s[2]".formatted( + NestedContainerTemplateWithTwoInvocationsTestCase.NestedTestCase.class.getName()))), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // event(test(uniqueId(invocation2NestedMethodBId)), started()), // @@ -770,10 +775,17 @@ void nestedContainerTemplateInvocationCanBeSelectedByIteration() { // TODO #871 Consider moving to EventConditions private static Condition uniqueId(UniqueId uniqueId) { - return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, Predicate.isEqual(uniqueId))), + return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, isEqual(uniqueId))), "descriptor with uniqueId '%s'", uniqueId); } + // TODO #871 Consider moving to EventConditions + private static Condition legacyReportingName(String legacyReportingName) { + return new Condition<>( + byTestDescriptor(where(TestDescriptor::getLegacyReportingName, isEqual(legacyReportingName))), + "descriptor with legacy reporting name '%s'", legacyReportingName); + } + @SuppressWarnings("JUnitMalformedDeclaration") @ContainerTemplate @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) From cb19c7888925cef7c188ec573703e763b211c109 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 9 Feb 2025 19:14:04 +0100 Subject: [PATCH 35/60] Update build tool integration tests to use container templates --- .../com/example/project/CalculatorTests.java | 39 ++++ .../support/tests/AntStarterTests.java | 4 +- .../support/tests/MavenStarterTests.java | 2 +- .../open-test-report.xml.snapshot | 167 ++++++++++++++++-- .../open-test-report.xml.snapshot | 167 ++++++++++++++++-- .../open-test-report.xml.snapshot | 167 ++++++++++++++++-- 6 files changed, 495 insertions(+), 51 deletions(-) diff --git a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java index d4ab5f97afcc..6846e00d2535 100644 --- a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java @@ -11,12 +11,22 @@ package com.example.project; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class CalculatorTests { @@ -44,4 +54,33 @@ void add(int first, int second, int expectedResult) { assertEquals(expectedResult, calculator.add(first, second), () -> first + " + " + second + " should equal " + expectedResult); } + + @Nested + @ContainerTemplate + @ExtendWith(Twice.class) + class NestedTests { + + @ParameterizedTest + @ValueSource(ints = { 1, 2 }) + void test(int i) { + assertNotEquals(0, i); + } + } + + static class Twice implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new Ctx(), new Ctx()); + } + + static class Ctx implements ContainerTemplateInvocationContext { + } + } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java index 171548801968..ab0aae28c9d5 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -51,13 +51,13 @@ void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputF assertLinesMatch(List.of(">> HEAD >>", // "test.junit.launcher:", // ">>>>", // - "\\[junitlauncher\\] Tests run: 5, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // + "\\[junitlauncher\\] Tests run: 9, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // ">>>>", // "test.console.launcher:", // ">>>>", // " \\[java\\] Test run finished after [\\d]+ ms", // ">>>>", // - " \\[java\\] \\[ 5 tests successful \\]", // + " \\[java\\] \\[ 9 tests successful \\]", // " \\[java\\] \\[ 0 tests failed \\]", // ">> TAIL >>"), // result.stdOutLines()); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index 11ef756bd352..786e0deae5b5 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -57,7 +57,7 @@ void verifyJupiterStarterProject(@TempDir Path workspace, @FilePrefix("maven") O assertEquals(0, result.exitCode()); assertEquals("", result.stdErr()); assertTrue(result.stdOutLines().contains("[INFO] BUILD SUCCESS")); - assertTrue(result.stdOutLines().contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); + assertTrue(result.stdOutLines().contains("[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0")); assertThat(result.stdOut()).contains("Using Java version: 1.8"); var testResultsDir = workspace.resolve("target/surefire-reports"); diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot index a947aa10f920..2166868164d7 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: ant_starter - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -40,7 +40,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -51,11 +51,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -66,7 +66,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -77,11 +77,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -92,11 +92,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -107,11 +107,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -122,19 +122,154 @@ test-method: ant_starter - + - + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests] + com.example.project.CalculatorTests$NestedTests + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1] + com.example.project.CalculatorTests$NestedTests[1] + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)] + test(int) + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] + TEST + + + + + + + + + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2] + com.example.project.CalculatorTests$NestedTests[2] + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)] + test(int) + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] + TEST + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot index 3be1b286c9bb..6224b27d5217 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -40,7 +40,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -51,11 +51,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -66,7 +66,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -77,11 +77,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -92,11 +92,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -107,11 +107,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -122,19 +122,154 @@ test-method: gradle_wrapper - + - + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests] + com.example.project.CalculatorTests$NestedTests + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1] + com.example.project.CalculatorTests$NestedTests[1] + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)] + test(int) + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] + TEST + + + + + + + + + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2] + com.example.project.CalculatorTests$NestedTests[2] + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)] + test(int) + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] + TEST + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot index 385bb5aff6a5..d63ca8e11808 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -40,7 +40,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -51,11 +51,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -66,7 +66,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -77,11 +77,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -92,11 +92,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -107,11 +107,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -122,19 +122,154 @@ test-method: verifyJupiterStarterProject - + - + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests] + com.example.project.CalculatorTests$NestedTests + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1] + com.example.project.CalculatorTests$NestedTests[1] + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)] + test(int) + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] + TEST + + + + + + + + + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2] + com.example.project.CalculatorTests$NestedTests[2] + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)] + test(int) + CONTAINER + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] + TEST + + + + + + + + + + + + + + + + + + + + + + + - + From 19116a47b1149299d5227b2a502e598429839ead Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 09:08:53 +0100 Subject: [PATCH 36/60] Avoid exception when logging the condition evaluation result --- .../org/junit/jupiter/engine/execution/ConditionEvaluator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java index e1e0a4a89617..e51126bec2ef 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java @@ -72,7 +72,7 @@ private ConditionEvaluationResult evaluate(ExecutionCondition condition, Extensi private void logResult(Class conditionType, ConditionEvaluationResult result, ExtensionContext context) { logger.trace(() -> format("Evaluation of condition [%s] on [%s] resulted in: %s", conditionType.getName(), - context.getElement().get(), result)); + context.getElement().orElse(null), result)); } private ConditionEvaluationException evaluationException(Class conditionType, Exception ex) { From fc1c0f6a043818f6646239dff7cde4c9e648c681 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 09:09:29 +0100 Subject: [PATCH 37/60] Avoid evaluating conditions for container template invocations --- .../ContainerTemplateInvocationTestDescriptor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 915a4f22ff80..121feb7567a7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -85,6 +85,11 @@ public List> getEnclosingTestClasses() { // --- Node ---------------------------------------------------------------- + @Override + public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { + return SkipResult.doNotSkip(); + } + @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = context.getExtensionRegistry(); From 6bd909b43b70c7f5361a9b94bd9fd7bef8bb9e13 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 09:09:46 +0100 Subject: [PATCH 38/60] Add tests for class ordering with container templates --- .../engine/extension/OrderedClassTests.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java index ff8560830382..42af0d90dc04 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java @@ -20,16 +20,22 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.DiscoverySelector; @@ -128,6 +134,42 @@ void random() { .assertStatistics(stats -> stats.succeeded(callSequence.size())); } + @Test + void containerTemplateWithLocalConfig() { + var containerTemplate = ContainerTemplateWithLocalConfigTestCase.class; + var inner0 = ContainerTemplateWithLocalConfigTestCase.Inner0.class; + var inner1 = ContainerTemplateWithLocalConfigTestCase.Inner1.class; + var inner1Inner1 = ContainerTemplateWithLocalConfigTestCase.Inner1.Inner1Inner1.class; + var inner1Inner0 = ContainerTemplateWithLocalConfigTestCase.Inner1.Inner1Inner0.class; + + executeTests(ClassOrderer.Random.class, selectClass(containerTemplate))// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + var inner1InvocationCallSequence = Stream.of(inner1, inner1Inner1, inner1Inner0, inner1Inner0).toList(); + var inner1CallSequence = twice(inner1InvocationCallSequence).toList(); + var outerCallSequence = Stream.concat(Stream.of(containerTemplate), + Stream.concat(inner1CallSequence.stream(), Stream.of(inner0))).toList(); + var expectedCallSequence = twice(outerCallSequence).map(Class::getSimpleName).toList(); + + assertThat(callSequence).containsExactlyElementsOf(expectedCallSequence); + } + + private static Stream twice(List values) { + return Stream.concat(values.stream(), values.stream()); + } + + @Test + void containerTemplateWithGlobalConfig() { + var containerTemplate = ContainerTemplateWithLocalConfigTestCase.class; + var otherClass = A_TestCase.class; + + executeTests(ClassOrderer.OrderAnnotation.class, selectClass(otherClass), selectClass(containerTemplate))// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsSubsequence(containerTemplate.getSimpleName(), otherClass.getSimpleName()); + } + private Events executeTests(Class classOrderer) { return executeTests(classOrderer, selectClass(A_TestCase.class), selectClass(B_TestCase.class), selectClass(C_TestCase.class)); @@ -266,4 +308,73 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") + @Order(1) + @TestClassOrder(ClassOrderer.OrderAnnotation.class) + @ContainerTemplate + @ExtendWith(ContainerTemplateWithLocalConfigTestCase.Twice.class) + static class ContainerTemplateWithLocalConfigTestCase { + + @Test + void test() { + callSequence.add(ContainerTemplateWithLocalConfigTestCase.class.getSimpleName()); + } + + @Nested + @Order(1) + class Inner0 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @ContainerTemplate + @Order(0) + class Inner1 { + + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + + @Nested + @ContainerTemplate + @Order(2) + class Inner1Inner0 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(1) + class Inner1Inner1 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + } + + private static class Twice implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new Ctx(), new Ctx()); + } + + private record Ctx() implements ContainerTemplateInvocationContext { + } + } + } + } From fcbb07dcc4569ba4f7c32667e570329ae7acc905 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 09:22:27 +0100 Subject: [PATCH 39/60] Add test for method ordering with container templates --- .../jupiter/engine/extension/OrderedMethodTests.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java index 0e245db1a2a1..3d555d44f3ee 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java @@ -52,6 +52,8 @@ import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.testkit.engine.EngineTestKit; @@ -173,9 +175,10 @@ void random() { assertThat(threadNames).hasSize(1); } - @Test - void defaultOrderer() { - var tests = executeTestsInParallel(WithoutTestMethodOrderTestCase.class, OrderAnnotation.class); + @ParameterizedTest + @ValueSource(classes = { WithoutTestMethodOrderTestCase.class, ContainerTemplateTestCase.class }) + void defaultOrderer(Class testClass) { + var tests = executeTestsInParallel(testClass, OrderAnnotation.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); @@ -845,4 +848,7 @@ void test1() { } + static class ContainerTemplateTestCase extends WithoutTestMethodOrderTestCase { + } + } From 3c12908ed09ae3bd40e65727eff11892cf52ab4a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 10:51:08 +0100 Subject: [PATCH 40/60] Test integration with `TestInstance.Lifecycle` --- .../org/junit/jupiter/api/TestInstance.java | 5 + .../engine/TestInstanceLifecycleTests.java | 113 +++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java index 80f904a41987..f9effa82b7a7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -88,6 +88,11 @@ enum Lifecycle { * When using this mode, a new test instance will be created once per * test class. * + *

For {@link Nested @Nested}

test classes declared inside an + * enclosing {@link ContainerTemplate @ContainerTemplate} test class, an + * instance of the {@code @Nested} class will be created for each + * invocation of the {@code @ContainerTemplate} test class. + * * @see #PER_METHOD */ PER_CLASS, diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java index 91676086da86..784df0e19a9f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -30,12 +31,14 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -47,6 +50,8 @@ import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; @@ -56,6 +61,8 @@ import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.execution.DefaultTestInstances; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.testkit.engine.EngineExecutionResults; /** @@ -601,6 +608,44 @@ void instancePerMethodOnOuterTestClassWithInstancePerClassOnNestedTestClass() { assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); } + @ParameterizedTest + @EnumSource(Lifecycle.class) + void containerTemplate(Lifecycle lifecycle) { + var containerTemplate = ContainerTemplateWithDefaultLifecycleTestCase.class; + + var results = executeTests(r -> r // + .selectors(selectClass(containerTemplate)) // + .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name())); + + results.allEvents().assertStatistics(stats -> stats.failed(0)); + results.testEvents().assertStatistics(stats -> stats.succeeded(4)); + + assertThat(instanceCount).containsExactly(entry(containerTemplate, lifecycle == Lifecycle.PER_CLASS ? 1 : 4)); + assertThat(lifecyclesMap.keySet()).containsExactly(containerTemplate); + assertThat(lifecyclesMap.get(containerTemplate)).filteredOn(Objects::nonNull).containsOnly(lifecycle); + } + + @ParameterizedTest + @EnumSource(Lifecycle.class) + void containerTemplateWithNestedClass(Lifecycle lifecycle) { + var containerTemplate = ContainerTemplateWithDefaultLifecycleAndNestedClassTestCase.class; + var nestedClass = ContainerTemplateWithDefaultLifecycleAndNestedClassTestCase.InnerTestCase.class; + + var results = executeTests(r -> r // + .selectors(selectClass(containerTemplate)) // + .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name())); + + results.allEvents().assertStatistics(stats -> stats.failed(0)); + results.testEvents().assertStatistics(stats -> stats.succeeded(4)); + + assertThat(instanceCount).containsExactly( // + entry(containerTemplate, lifecycle == Lifecycle.PER_CLASS ? 1 : 4), // + entry(nestedClass, lifecycle == Lifecycle.PER_CLASS ? 2 : 4)); + assertThat(lifecyclesMap.keySet()).containsExactlyInAnyOrder(containerTemplate, nestedClass); + assertThat(lifecyclesMap.get(containerTemplate)).filteredOn(Objects::nonNull).containsOnly(lifecycle); + assertThat(lifecyclesMap.get(nestedClass)).filteredOn(Objects::nonNull).containsOnly(lifecycle); + } + private void performAssertions(Class testClass, int numContainers, int numTests, Map.Entry, Integer>[] instanceCountEntries, int allMethods, int eachMethods) { @@ -623,7 +668,7 @@ private void performAssertions(Class testClass, int numContainers, int numTes @SafeVarargs @SuppressWarnings("varargs") - private final Map.Entry, Integer>[] instanceCounts(Map.Entry, Integer>... entries) { + private Map.Entry, Integer>[] instanceCounts(Map.Entry, Integer>... entries) { return entries; } @@ -983,7 +1028,9 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con String testMethod = context.getTestMethod().map(Method::getName).orElse(null); if (testMethod == null) { assertThat(context.getTestInstance()).isNotPresent(); - assertThat(instanceCount.getOrDefault(context.getRequiredTestClass(), 0)).isEqualTo(0); + if (!isAnnotated(context.getRequiredTestClass().getEnclosingClass(), ContainerTemplate.class)) { + assertThat(instanceCount.getOrDefault(context.getRequiredTestClass(), 0)).isEqualTo(0); + } } instanceMap.put(executionConditionKey(context.getRequiredTestClass(), testMethod), context.getTestInstances().orElse(null)); @@ -1067,4 +1114,66 @@ private static void trackLifecycle(ExtensionContext context) { @interface SingletonTest { } + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(Twice.class) + @ExtendWith(InstanceTrackingExtension.class) + static class ContainerTemplateWithDefaultLifecycleTestCase { + + ContainerTemplateWithDefaultLifecycleTestCase() { + incrementInstanceCount(ContainerTemplateWithDefaultLifecycleTestCase.class); + } + + @Test + void test1() { + } + + @Test + void test2() { + } + } + + @ContainerTemplate + @ExtendWith(Twice.class) + @ExtendWith(InstanceTrackingExtension.class) + static class ContainerTemplateWithDefaultLifecycleAndNestedClassTestCase { + + ContainerTemplateWithDefaultLifecycleAndNestedClassTestCase() { + incrementInstanceCount(ContainerTemplateWithDefaultLifecycleAndNestedClassTestCase.class); + } + + @Nested + class InnerTestCase { + + public InnerTestCase() { + incrementInstanceCount(InnerTestCase.class); + } + + @Test + void test1() { + } + + @Test + void test2() { + } + } + } + + private static class Twice implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new Ctx(), new Ctx()); + } + + private record Ctx() implements ContainerTemplateInvocationContext { + } + } + } From f165c0b8e54ecfc292073e7c08199d10419edbd9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 10:51:55 +0100 Subject: [PATCH 41/60] Disable stack trace pruning to ease reasoning about test failures --- .../junit/jupiter/engine/AbstractJupiterTestEngineTests.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 6e3813373345..63e498ea97f9 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; @@ -45,7 +46,9 @@ protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { } protected EngineExecutionResults executeTests(Consumer configurer) { - var builder = request().outputDirectoryProvider(dummyOutputDirectoryProvider()); + var builder = request() // + .outputDirectoryProvider(dummyOutputDirectoryProvider()) // + .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)); configurer.accept(builder); return executeTests(builder); } From 095fee61770b9371d6e55c22f381471d0b209af4 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 11:27:25 +0100 Subject: [PATCH 42/60] Remove constructor only used in tests --- .../descriptor/ClassExtensionContext.java | 14 --------- .../descriptor/ExtensionContextTests.java | 31 ++++++++++--------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java index aace0e86e16d..546472781200 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java @@ -34,20 +34,6 @@ final class ClassExtensionContext extends AbstractExtensionContext { var classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); var classTestDescriptor = new ClassTestDescriptor(classUniqueId, testClass, configuration); - return new ClassExtensionContext(null, null, classTestDescriptor, configuration, extensionRegistry, - null); + return new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD, configuration, + extensionRegistry, null); }), // named("method", (JupiterConfiguration configuration) -> { var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow(); From e31391cd23377cb7bace863a2119ac58b0096e08 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 11:35:13 +0100 Subject: [PATCH 43/60] Introduce special extension context for container template invocations --- ...nerTemplateInvocationExtensionContext.java | 77 +++++++++++++++++++ ...ainerTemplateInvocationTestDescriptor.java | 4 +- .../descriptor/DynamicExtensionContext.java | 7 +- .../descriptor/DynamicNodeTestDescriptor.java | 2 +- .../ContainerTemplateInvocationTests.java | 28 +++++++ 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationExtensionContext.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationExtensionContext.java new file mode 100644 index 000000000000..82a045824373 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationExtensionContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.Node; + +/** + * @since 5.13 + */ +final class ContainerTemplateInvocationExtensionContext + extends AbstractExtensionContext { + + ContainerTemplateInvocationExtensionContext(ExtensionContext parent, + EngineExecutionListener engineExecutionListener, ContainerTemplateInvocationTestDescriptor testDescriptor, + JupiterConfiguration configuration, ExtensionRegistry extensionRegistry) { + super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry); + } + + @Override + public Optional getElement() { + return Optional.of(getTestDescriptor().getTestClass()); + } + + @Override + public Optional> getTestClass() { + return Optional.of(getTestDescriptor().getTestClass()); + } + + @Override + public Optional getTestInstanceLifecycle() { + return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); + } + + @Override + public Optional getTestInstance() { + return getParent().flatMap(ExtensionContext::getTestInstance); + } + + @Override + public Optional getTestInstances() { + return getParent().flatMap(ExtensionContext::getTestInstances); + } + + @Override + public Optional getTestMethod() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + protected Node.ExecutionMode getPlatformExecutionMode() { + return getTestDescriptor().getExecutionMode(); + } + +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 121feb7567a7..bfbe1859bd5b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -100,8 +100,8 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte extension -> childRegistry.registerExtension(extension, this.invocationContext)); registry = childRegistry; } - ExtensionContext extensionContext = new DynamicExtensionContext<>(context.getExtensionContext(), - context.getExecutionListener(), this, context.getConfiguration(), registry); + ExtensionContext extensionContext = new ContainerTemplateInvocationExtensionContext( + context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), registry); return context.extend() // .withExtensionContext(extensionContext).withExtensionRegistry(registry) // .build(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java index 1e5d99f745ab..cd3c292709e2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java @@ -22,10 +22,11 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; -class DynamicExtensionContext extends AbstractExtensionContext { +class DynamicExtensionContext extends AbstractExtensionContext { - DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, - JupiterConfiguration configuration, ExtensionRegistry extensionRegistry) { + DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, + DynamicNodeTestDescriptor testDescriptor, JupiterConfiguration configuration, + ExtensionRegistry extensionRegistry) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java index e48efe0eae3c..9ed1f81ca6b1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -45,7 +45,7 @@ public String getLegacyReportingName() { @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - ExtensionContext extensionContext = new DynamicExtensionContext<>(context.getExtensionContext(), + ExtensionContext extensionContext = new DynamicExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), context.getExtensionRegistry()); // @formatter:off return context.extend() diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 93b01eac5ef2..eccedff845d5 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -11,6 +11,8 @@ package org.junit.jupiter.engine; import static java.util.function.Predicate.isEqual; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -48,6 +50,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; @@ -831,6 +834,23 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + static class TwoInvocationsWithExtensionTestCase { + @Test + void a() { + } + + @Nested + class NestedTestCase { + @Test + @Tag("nested") + void b() { + } + } + } + static class TwoInvocationsContainerTemplateInvocationContextProvider implements ContainerTemplateInvocationContextProvider { @@ -928,6 +948,14 @@ public void beforeAll(ExtensionContext context) throws Exception { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + var parentContext = extensionContext.getParent().orElseThrow(); + assertAll( // + () -> assertEquals(SeparateExtensionContextTestCase.class, parentContext.getRequiredTestClass()), // + () -> assertEquals(SeparateExtensionContextTestCase.class, + parentContext.getElement().orElseThrow()), // + () -> assertEquals(TestInstance.Lifecycle.PER_METHOD, + parentContext.getTestInstanceLifecycle().orElseThrow()) // + ); return SomeResource.class.equals(parameterContext.getParameter().getType()); } From 23bb5897b825937fa7589c734c77452774112a81 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 10 Feb 2025 12:19:01 +0100 Subject: [PATCH 44/60] Pull up exclusive resources to container template descriptor --- .../jupiter/api/parallel/ResourceLock.java | 6 + ...ainerTemplateInvocationTestDescriptor.java | 22 ++- .../ContainerTemplateTestDescriptor.java | 19 +++ .../discovery/ClassSelectorResolver.java | 2 +- .../parallel/ResourceLockAnnotationTests.java | 130 ++++++++++++++++++ .../AbstractJupiterTestEngineTests.java | 13 +- 6 files changed, 183 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java index 24c85cb268c8..4619fe11d6f0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -21,6 +21,7 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; +import org.junit.jupiter.api.ContainerTemplate; /** * {@code @ResourceLock} is used to declare that the annotated test class or test @@ -70,6 +71,11 @@ * attribute remains applicable, and the target of "dynamic" shared resources added * via implementations of {@link ResourceLocksProvider} is not changed. * + *

Shared resources declared on or provided for methods or nested test + * classes in a {@link ContainerTemplate @ContainerTemplate} are propagated as + * if they were declared on the outermost enclosing {@code @ContainerTemplate} + * class itself. + * * @see Isolated * @see Resources * @see ResourceAccessMode diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index bfbe1859bd5b..da990bf55956 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -14,12 +14,15 @@ import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom; import java.util.List; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; @@ -30,15 +33,16 @@ * @since 5.13 */ @API(status = INTERNAL, since = "5.13") -public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescriptor implements TestClassAware { +public class ContainerTemplateInvocationTestDescriptor extends JupiterTestDescriptor + implements TestClassAware, ResourceLockAware { public static final String SEGMENT_TYPE = "container-template-invocation"; - private final TestClassAware parent; + private final ContainerTemplateTestDescriptor parent; private ContainerTemplateInvocationContext invocationContext; private final int index; - public ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, TestClassAware parent, + public ContainerTemplateInvocationTestDescriptor(UniqueId uniqueId, ContainerTemplateTestDescriptor parent, ContainerTemplateInvocationContext invocationContext, int index, TestSource source, JupiterConfiguration configuration) { super(uniqueId, invocationContext.getDisplayName(index), source, configuration); @@ -83,6 +87,18 @@ public List> getEnclosingTestClasses() { return parent.getEnclosingTestClasses(); } + // --- ResourceLockAware --------------------------------------------------- + + @Override + public ExclusiveResourceCollector getExclusiveResourceCollector() { + return parent.getExclusiveResourceCollector(); + } + + @Override + public Function> getResourceLocksProviderEvaluator() { + return parent.getResourceLocksProviderEvaluator(); + } + // --- Node ---------------------------------------------------------------- @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 5369c3a83770..33b583fd94c4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -24,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -39,6 +41,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.Node; /** @@ -160,6 +163,22 @@ public Function> getResou // --- Node ---------------------------------------------------------------- + @Override + public Set getExclusiveResources() { + Set result = this.determineExclusiveResources().collect( + Collectors.toCollection(HashSet::new)); + Visitor visitor = testDescriptor -> { + if (testDescriptor instanceof ResourceLockAware) { + ((ResourceLockAware) testDescriptor).determineExclusiveResources().forEach(result::add); + } + }; + this.childrenPrototypes.forEach(child -> child.accept(visitor)); + this.childrenPrototypesByIndex.values() // + .forEach(prototypes -> prototypes // + .forEach(child -> child.accept(visitor))); + return result; + } + @Override public void cleanUp(JupiterEngineExecutionContext context) { this.childrenPrototypes.clear(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index d99c69a6e2f6..099fbfa3ff96 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -191,7 +191,7 @@ private ContainerTemplateInvocationTestDescriptor newDummyContainerTemplateInvoc TestDescriptor parent, int index) { UniqueId uniqueId = parent.getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); - return new ContainerTemplateInvocationTestDescriptor(uniqueId, (TestClassAware) parent, + return new ContainerTemplateInvocationTestDescriptor(uniqueId, (ContainerTemplateTestDescriptor) parent, DummyContainerTemplateInvocationContext.INSTANCE, index, parent.getSource().orElse(null), configuration); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index 60e7c36b808f..f79a6786c23a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -12,6 +12,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Throwables.getRootCause; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; @@ -25,6 +29,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; @@ -34,12 +39,14 @@ import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.JUnitException; +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.ExclusiveResource; @@ -180,6 +187,72 @@ void addSharedResourcesViaAnnotationValueAndProviders() { // @formatter:on } + @Test + void addSharedResourcesViaAnnotationValueAndProvidersForContainerTemplate() { + var engineDescriptor = discoverTests( + selectClass(SharedResourcesViaAnnotationValueAndProvidersContainerTemplateTestCase.class)); + engineDescriptor.accept(TestDescriptor::prune); + + var containerTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); + + var expectedResources = List.of( // + new ExclusiveResource("a1", LockMode.READ_WRITE), // + new ExclusiveResource("a2", LockMode.READ_WRITE), // + new ExclusiveResource("a3", LockMode.READ), // + new ExclusiveResource("b1", LockMode.READ), // + new ExclusiveResource("b2", LockMode.READ), // + new ExclusiveResource("c1", LockMode.READ_WRITE), // + new ExclusiveResource("c2", LockMode.READ_WRITE), // + new ExclusiveResource("c3", LockMode.READ_WRITE) // + ); + + assertThat(containerTemplateTestDescriptor.getExclusiveResources()) // + .containsExactlyInAnyOrderElementsOf(expectedResources); + } + + @Test + void addSharedResourcesViaAnnotationValueAndProvidersForContainerTemplateInvocation() { + var engineDescriptor = discoverTests(selectIteration( + selectClass(SharedResourcesViaAnnotationValueAndProvidersContainerTemplateTestCase.class), 0)); + engineDescriptor.accept(TestDescriptor::prune); + + var containerTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); + + var expectedResources = List.of( // + new ExclusiveResource("a1", LockMode.READ_WRITE), // + new ExclusiveResource("a2", LockMode.READ_WRITE), // + new ExclusiveResource("a3", LockMode.READ), // + new ExclusiveResource("b1", LockMode.READ), // + new ExclusiveResource("b2", LockMode.READ), // + new ExclusiveResource("c1", LockMode.READ_WRITE), // + new ExclusiveResource("c2", LockMode.READ_WRITE), // + new ExclusiveResource("c3", LockMode.READ_WRITE) // + ); + + assertThat(containerTemplateTestDescriptor.getExclusiveResources()) // + .containsExactlyInAnyOrderElementsOf(expectedResources); + } + + @Test + void addSharedResourcesViaAnnotationValueAndProvidersForMethodInContainerTemplate() { + var engineDescriptor = discoverTests( + selectMethod(SharedResourcesViaAnnotationValueAndProvidersContainerTemplateTestCase.class, "test")); + engineDescriptor.accept(TestDescriptor::prune); + + var containerTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); + + var expectedResources = List.of( // + new ExclusiveResource("a1", LockMode.READ_WRITE), // + new ExclusiveResource("a2", LockMode.READ_WRITE), // + new ExclusiveResource("a3", LockMode.READ), // + new ExclusiveResource("b1", LockMode.READ), // + new ExclusiveResource("b2", LockMode.READ) // + ); + + assertThat(containerTemplateTestDescriptor.getExclusiveResources()) // + .containsExactlyInAnyOrderElementsOf(expectedResources); + } + @Test void sharedResourcesHavingTheSameValueAndModeAreDeduplicated() { // @formatter:off @@ -523,4 +596,61 @@ class NestedClass { } } + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ResourceLock( // + value = "a1", // + providers = SharedResourcesViaAnnotationValueAndProvidersContainerTemplateTestCase.FirstClassLevelProvider.class // + ) + @ResourceLock( // + value = "a2", // + target = ResourceLockTarget.CHILDREN, // + providers = SharedResourcesViaAnnotationValueAndProvidersContainerTemplateTestCase.SecondClassLevelProvider.class // + ) + static class SharedResourcesViaAnnotationValueAndProvidersContainerTemplateTestCase { + + @Test + @ResourceLock(value = "b1", mode = ResourceAccessMode.READ) + void test() { + } + + @Nested + @ResourceLock(providers = NestedClassLevelProvider.class) + class NestedClass { + @Test + @ResourceLock("c1") + void test() { + } + } + + static class FirstClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForClass(Class testClass) { + return Set.of(new Lock("a3", ResourceAccessMode.READ)); + } + } + + static class SecondClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return Set.of(new Lock("b2", ResourceAccessMode.READ)); + } + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + return Set.of(new Lock("c2")); + } + } + + static class NestedClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + return Set.of(new Lock("c3")); + } + } + } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 63e498ea97f9..194abc7393db 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -46,9 +46,7 @@ protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { } protected EngineExecutionResults executeTests(Consumer configurer) { - var builder = request() // - .outputDirectoryProvider(dummyOutputDirectoryProvider()) // - .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)); + var builder = defaultRequest(); configurer.accept(builder); return executeTests(builder); } @@ -62,8 +60,13 @@ protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) } protected TestDescriptor discoverTests(DiscoverySelector... selectors) { - return discoverTests( - request().selectors(selectors).outputDirectoryProvider(dummyOutputDirectoryProvider()).build()); + return discoverTests(defaultRequest().selectors(selectors).build()); + } + + private static LauncherDiscoveryRequestBuilder defaultRequest() { + return request() // + .outputDirectoryProvider(dummyOutputDirectoryProvider()) // + .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)); } protected TestDescriptor discoverTests(LauncherDiscoveryRequest request) { From 817b6225bc884a11059ab082910634923658c6dd Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 08:58:14 +0100 Subject: [PATCH 45/60] Collect exclusive resources from nested container templates --- .../ContainerTemplateTestDescriptor.java | 9 ++++----- .../parallel/ResourceLockAnnotationTests.java | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 33b583fd94c4..906cac2a1715 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.descriptor; +import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; @@ -25,7 +26,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -165,11 +165,10 @@ public Function> getResou @Override public Set getExclusiveResources() { - Set result = this.determineExclusiveResources().collect( - Collectors.toCollection(HashSet::new)); + Set result = determineExclusiveResources().collect(toCollection(HashSet::new)); Visitor visitor = testDescriptor -> { - if (testDescriptor instanceof ResourceLockAware) { - ((ResourceLockAware) testDescriptor).determineExclusiveResources().forEach(result::add); + if (testDescriptor instanceof Node) { + result.addAll(((Node) testDescriptor).getExclusiveResources()); } }; this.childrenPrototypes.forEach(child -> child.accept(visitor)); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index f79a6786c23a..337224b61eff 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -203,7 +203,9 @@ void addSharedResourcesViaAnnotationValueAndProvidersForContainerTemplate() { new ExclusiveResource("b2", LockMode.READ), // new ExclusiveResource("c1", LockMode.READ_WRITE), // new ExclusiveResource("c2", LockMode.READ_WRITE), // - new ExclusiveResource("c3", LockMode.READ_WRITE) // + new ExclusiveResource("c3", LockMode.READ_WRITE), // + new ExclusiveResource("d1", LockMode.READ_WRITE), // + new ExclusiveResource("d2", LockMode.READ) // ); assertThat(containerTemplateTestDescriptor.getExclusiveResources()) // @@ -226,7 +228,9 @@ void addSharedResourcesViaAnnotationValueAndProvidersForContainerTemplateInvocat new ExclusiveResource("b2", LockMode.READ), // new ExclusiveResource("c1", LockMode.READ_WRITE), // new ExclusiveResource("c2", LockMode.READ_WRITE), // - new ExclusiveResource("c3", LockMode.READ_WRITE) // + new ExclusiveResource("c3", LockMode.READ_WRITE), // + new ExclusiveResource("d1", LockMode.READ_WRITE), // + new ExclusiveResource("d2", LockMode.READ) // ); assertThat(containerTemplateTestDescriptor.getExclusiveResources()) // @@ -623,6 +627,16 @@ void test() { } } + @Nested + @ContainerTemplate + @ResourceLock(value = "d1", target = ResourceLockTarget.CHILDREN) + class NestedContainerTemplate { + @Test + @ResourceLock(value = "d2", mode = ResourceAccessMode.READ) + void test() { + } + } + static class FirstClassLevelProvider implements ResourceLocksProvider { @Override From fb3d5e8c12fb867c5bfc91ee57f42498924ba00d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 09:30:51 +0100 Subject: [PATCH 46/60] Use TestClassAware where possible --- .../engine/descriptor/MethodBasedTestDescriptor.java | 6 +++--- .../engine/descriptor/NestedClassTestDescriptor.java | 4 ++-- .../jupiter/engine/discovery/ClassSelectorResolver.java | 8 +++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 3a5b785591f8..c38ecd72fd77 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -108,9 +108,9 @@ public Function> getResou private List> getEnclosingTestClasses() { return getParent() // - .filter(ClassBasedTestDescriptor.class::isInstance) // - .map(ClassBasedTestDescriptor.class::cast) // - .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // + .filter(TestClassAware.class::isInstance) // + .map(TestClassAware.class::cast) // + .map(TestClassAware::getEnclosingTestClasses) // .orElseGet(Collections::emptyList); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index c365874d1b34..0f33bedb0d12 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -86,8 +86,8 @@ public List> getEnclosingTestClasses() { @API(status = INTERNAL, since = "5.12") public static List> getEnclosingTestClasses(TestDescriptor parent) { - if (parent instanceof ClassBasedTestDescriptor) { - ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent; + if (parent instanceof TestClassAware) { + TestClassAware parentClassDescriptor = (TestClassAware) parent; List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); result.add(parentClassDescriptor.getTestClass()); return result; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 099fbfa3ff96..42ab7cf400e8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -240,10 +240,8 @@ private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestD private Optional toInvocationMatch(Optional testDescriptor) { return testDescriptor // - .map(it -> Match.exact(it, - expansionCallback(it, - () -> it.getParent().map(parent -> getTestClasses((ClassBasedTestDescriptor) parent)).orElse( - emptyList())))); + .map(it -> Match.exact(it, expansionCallback(it, + () -> it.getParent().map(parent -> getTestClasses((TestClassAware) parent)).orElse(emptyList())))); } private Resolution toResolution(Optional testDescriptor) { @@ -256,7 +254,7 @@ private Supplier> expansionCallback(ClassBasedT return expansionCallback(testDescriptor, () -> getTestClasses(testDescriptor)); } - private static List> getTestClasses(ClassBasedTestDescriptor testDescriptor) { + private static List> getTestClasses(TestClassAware testDescriptor) { List> testClasses = new ArrayList<>(testDescriptor.getEnclosingTestClasses()); testClasses.add(testDescriptor.getTestClass()); return testClasses; From c75d9deb0687160b909eed0062197580307e1802 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 09:32:33 +0100 Subject: [PATCH 47/60] Fix integration with `DisplayNameGeneration` --- .../discovery/ClassSelectorResolver.java | 2 + .../api/DisplayNameGenerationTests.java | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 42ab7cf400e8..b39c360829e0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -233,6 +233,8 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa private ContainerTemplateTestDescriptor newContainerTemplateTestDescriptor(TestDescriptor parent, String segmentType, ClassBasedTestDescriptor delegate) { + + delegate.setParent(parent); String segmentValue = delegate.getUniqueId().getLastSegment().getValue(); UniqueId uniqueId = parent.getUniqueId().append(segmentType, segmentValue); return new ContainerTemplateTestDescriptor(uniqueId, delegate); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java index 898dfc1ea9e8..61550ce2676f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -22,7 +22,12 @@ import java.util.EmptyStackException; import java.util.List; import java.util.Stack; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.engine.TestDescriptor; @@ -225,6 +230,32 @@ void indicativeSentencesOnSubClass() { ); } + @Test + void indicativeSentencesOnContainerTemplate() { + check(ContainerTemplateTestCase.class, // + "CONTAINER: Container template", // + "TEST: Container template, some test", // + "CONTAINER: Container template, Regular Nested Test Case", // + "TEST: Container template, Regular Nested Test Case, some nested test", // + "CONTAINER: Container template, Nested Container Template", // + "TEST: Container template, Nested Container Template, some nested test" // + ); + + assertThat(executeTestsForClass(ContainerTemplateTestCase.class).allEvents().started().stream()) // + .map(event -> event.getTestDescriptor().getDisplayName()) // + .containsExactly( // + "JUnit Jupiter", // + "Container template", // + "[1] Container template", // + "Container template, some test", // + "Container template, Regular Nested Test Case", // + "Container template, Regular Nested Test Case, some nested test", // + "Container template, Nested Container Template", // + "[1] Container template, Nested Container Template", // + "Container template, Nested Container Template, some nested test" // + ); + } + private void check(Class testClass, String... expectedDisplayNames) { var request = request().selectors(selectClass(testClass)).build(); var descriptors = discoverTests(request).getDescendants(); @@ -470,4 +501,55 @@ void is_no_longer_empty() { } } } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ContainerTemplate + @ExtendWith(ContainerTemplateTestCase.Once.class) + @DisplayName("Container template") + @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) + @TestClassOrder(ClassOrderer.OrderAnnotation.class) + static class ContainerTemplateTestCase { + + @Test + void some_test() { + } + + @Nested + @Order(1) + class Regular_Nested_Test_Case { + @Test + void some_nested_test() { + } + } + + @Nested + @Order(2) + @ContainerTemplate + class Nested_Container_Template { + @Test + void some_nested_test() { + } + } + + private static class Once implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new ContainerTemplateInvocationContext() { + @Override + public String getDisplayName(int invocationIndex) { + return "%s %s".formatted( + ContainerTemplateInvocationContext.super.getDisplayName(invocationIndex), + context.getDisplayName()); + } + }); + } + } + } } From e295088d668e7e08c6555fb25e742e2639ee948f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 09:36:43 +0100 Subject: [PATCH 48/60] Verify test template invocation selection inside container template --- .../ContainerTemplateInvocationTests.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index eccedff845d5..f5d580f00f8b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -433,6 +433,37 @@ void supportsTestTemplateMethodsInsideContainerTemplateClasses() { event(engine(), finishedSuccessfully())); } + @Test + void testTemplateInvocationInsideContainerTemplateClassCanBeSelectedByUniqueId() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, + CombinationWithTestTemplateTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var testTemplateId2 = invocationId2.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); + var testTemplate2InvocationId2 = testTemplateId2.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, + "#2"); + + var results = executeTests(selectUniqueId(testTemplate2InvocationId2)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), + displayName("[2] B of CombinationWithTestTemplateTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(testTemplateId2))), // + event(container(uniqueId(testTemplateId2)), started()), // + event(dynamicTestRegistered(uniqueId(testTemplate2InvocationId2))), // + event(test(uniqueId(testTemplate2InvocationId2)), started()), // + event(test(uniqueId(testTemplate2InvocationId2)), finishedSuccessfully()), // + event(container(uniqueId(testTemplateId2)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + @Test void supportsTestFactoryMethodsInsideContainerTemplateClasses() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); From a6c1d4e1521dd38b7daa42d36f2c1e5acc6cca9f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 09:38:43 +0100 Subject: [PATCH 49/60] Verify dynamic test selection inside container template --- .../ContainerTemplateInvocationTests.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index f5d580f00f8b..a64c9d1ab3bf 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -520,6 +520,37 @@ void supportsTestFactoryMethodsInsideContainerTemplateClasses() { event(engine(), finishedSuccessfully())); } + @Test + void specificDynamicTestInsideContainerTemplateClassCanBeSelectedByUniqueId() { + var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + var containerTemplateId = engineId.append(ContainerTemplateTestDescriptor.STATIC_CLASS_SEGMENT_TYPE, + CombinationWithTestFactoryTestCase.class.getName()); + var invocationId2 = containerTemplateId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + var testFactoryId2 = invocationId2.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); + var testFactory2DynamicTestId2 = testFactoryId2.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, + "#2"); + + var results = executeTests(selectUniqueId(testFactory2DynamicTestId2)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(uniqueId(containerTemplateId)), started()), // + + event(dynamicTestRegistered(uniqueId(invocationId2)), + displayName("[2] B of CombinationWithTestFactoryTestCase")), // + event(container(uniqueId(invocationId2)), started()), // + event(dynamicTestRegistered(uniqueId(testFactoryId2))), // + event(container(uniqueId(testFactoryId2)), started()), // + event(dynamicTestRegistered(uniqueId(testFactory2DynamicTestId2))), // + event(test(uniqueId(testFactory2DynamicTestId2)), started()), // + event(test(uniqueId(testFactory2DynamicTestId2)), finishedSuccessfully()), // + event(container(uniqueId(testFactoryId2)), finishedSuccessfully()), // + event(container(uniqueId(invocationId2)), finishedSuccessfully()), // + + event(container(uniqueId(containerTemplateId)), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + @Test void failsIfProviderReturnsZeroInvocationContextWithoutOptIn() { var results = executeTestsForClass(InvalidZeroInvocationTestCase.class); From d60eb131aaec6df216c64b21a73a14486fdd7163 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 10:02:56 +0100 Subject: [PATCH 50/60] Move container template in integration test to separate class --- .../CalculatorContainerTemplateTests.java | 52 +++++ .../com/example/project/CalculatorTests.java | 39 ---- .../test/resources/junit-platform.properties | 2 + .../support/tests/AntStarterTests.java | 4 +- .../open-test-report.xml.snapshot | 214 +++++++++--------- .../open-test-report.xml.snapshot | 214 +++++++++--------- .../open-test-report.xml.snapshot | 214 +++++++++--------- 7 files changed, 378 insertions(+), 361 deletions(-) create mode 100644 platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java create mode 100644 platform-tooling-support-tests/projects/jupiter-starter/src/test/resources/junit-platform.properties diff --git a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java new file mode 100644 index 000000000000..1d6e0391667e --- /dev/null +++ b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@ContainerTemplate +@ExtendWith(CalculatorContainerTemplateTests.Twice.class) +class CalculatorContainerTemplateTests { + + @ParameterizedTest + @ValueSource(ints = { 1, 2 }) + void test(int i) { + Calculator calculator = new Calculator(); + assertEquals(i, calculator.add(i, 0)); + } + + static class Twice implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideContainerTemplateInvocationContexts( + ExtensionContext context) { + return Stream.of(new Ctx(), new Ctx()); + } + + static class Ctx implements ContainerTemplateInvocationContext { + } + } +} diff --git a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java index 6846e00d2535..d4ab5f97afcc 100644 --- a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java @@ -11,22 +11,12 @@ package com.example.project; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; -import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; class CalculatorTests { @@ -54,33 +44,4 @@ void add(int first, int second, int expectedResult) { assertEquals(expectedResult, calculator.add(first, second), () -> first + " + " + second + " should equal " + expectedResult); } - - @Nested - @ContainerTemplate - @ExtendWith(Twice.class) - class NestedTests { - - @ParameterizedTest - @ValueSource(ints = { 1, 2 }) - void test(int i) { - assertNotEquals(0, i); - } - } - - static class Twice implements ContainerTemplateInvocationContextProvider { - - @Override - public boolean supportsContainerTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideContainerTemplateInvocationContexts( - ExtensionContext context) { - return Stream.of(new Ctx(), new Ctx()); - } - - static class Ctx implements ContainerTemplateInvocationContext { - } - } } diff --git a/platform-tooling-support-tests/projects/jupiter-starter/src/test/resources/junit-platform.properties b/platform-tooling-support-tests/projects/jupiter-starter/src/test/resources/junit-platform.properties new file mode 100644 index 000000000000..daf7418ffd20 --- /dev/null +++ b/platform-tooling-support-tests/projects/jupiter-starter/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.testclass.order.default = \ + org.junit.jupiter.api.ClassOrderer$ClassName diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java index ab0aae28c9d5..e7cc8d43da47 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -51,7 +51,9 @@ void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputF assertLinesMatch(List.of(">> HEAD >>", // "test.junit.launcher:", // ">>>>", // - "\\[junitlauncher\\] Tests run: 9, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // + "\\[junitlauncher\\] Tests run: 4, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // + "\\[junitlauncher\\] Running com.example.project.CalculatorTests", // + "\\[junitlauncher\\] Tests run: 5, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // ">>>>", // "test.console.launcher:", // ">>>>", // diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot index 2166868164d7..218efe990f6e 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: ant_starter - + [engine:junit-jupiter] JUnit Jupiter @@ -29,247 +29,247 @@ test-method: ant_starter - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] - com.example.project.CalculatorTests + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests] + com.example.project.CalculatorContainerTemplateTests CONTAINER - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] - addsTwoNumbers() - TEST - - - + - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] - add(int, int, int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1] + com.example.project.CalculatorContainerTemplateTests[1] CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] - add(int, int, int)[1] - TEST + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)] + test(int) + CONTAINER - + - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] - add(int, int, int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] - add(int, int, int)[3] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] TEST - + - + - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] - add(int, int, int)[4] - TEST - - - - - - - + - + - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests] - com.example.project.CalculatorTests$NestedTests - CONTAINER - - - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1] - com.example.project.CalculatorTests$NestedTests[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2] + com.example.project.CalculatorContainerTemplateTests[2] CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)] test(int) CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] test(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] test(int)[2] TEST - + - + + + + + - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2] - com.example.project.CalculatorTests$NestedTests[2] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] + com.example.project.CalculatorTests CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] + addsTwoNumbers() + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] + add(int, int, int) CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] + add(int, int, int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] + add(int, int, int)[2] TEST - + - + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] + add(int, int, int)[3] + TEST + + + + + + + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] + add(int, int, int)[4] + TEST + + + + + + + - + - + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot index 6224b27d5217..4ab6e6486605 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter] JUnit Jupiter @@ -29,247 +29,247 @@ test-method: gradle_wrapper - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] - com.example.project.CalculatorTests + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests] + com.example.project.CalculatorContainerTemplateTests CONTAINER - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] - addsTwoNumbers() - TEST - - - + - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] - add(int, int, int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1] + com.example.project.CalculatorContainerTemplateTests[1] CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] - add(int, int, int)[1] - TEST + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)] + test(int) + CONTAINER - + - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] - add(int, int, int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] - add(int, int, int)[3] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] TEST - + - + - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] - add(int, int, int)[4] - TEST - - - - - - - + - + - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests] - com.example.project.CalculatorTests$NestedTests - CONTAINER - - - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1] - com.example.project.CalculatorTests$NestedTests[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2] + com.example.project.CalculatorContainerTemplateTests[2] CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)] test(int) CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] test(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] test(int)[2] TEST - + - + + + + + - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2] - com.example.project.CalculatorTests$NestedTests[2] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] + com.example.project.CalculatorTests CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] + addsTwoNumbers() + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] + add(int, int, int) CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] + add(int, int, int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] + add(int, int, int)[2] TEST - + - + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] + add(int, int, int)[3] + TEST + + + + + + + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] + add(int, int, int)[4] + TEST + + + + + + + - + - + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot index d63ca8e11808..ed414d27fbb7 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter] JUnit Jupiter @@ -29,247 +29,247 @@ test-method: verifyJupiterStarterProject - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] - com.example.project.CalculatorTests + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests] + com.example.project.CalculatorContainerTemplateTests CONTAINER - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] - addsTwoNumbers() - TEST - - - + - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] - add(int, int, int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1] + com.example.project.CalculatorContainerTemplateTests[1] CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] - add(int, int, int)[1] - TEST + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)] + test(int) + CONTAINER - + - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] - add(int, int, int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + test(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] - add(int, int, int)[3] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + test(int)[2] TEST - + - + - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] - add(int, int, int)[4] - TEST - - - - - - - + - + - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests] - com.example.project.CalculatorTests$NestedTests - CONTAINER - - - - - - - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1] - com.example.project.CalculatorTests$NestedTests[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2] + com.example.project.CalculatorContainerTemplateTests[2] CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)] test(int) CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] test(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] test(int)[2] TEST - + - + + + + + - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2] - com.example.project.CalculatorTests$NestedTests[2] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] + com.example.project.CalculatorTests CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] + addsTwoNumbers() + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] + add(int, int, int) CONTAINER - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] + add(int, int, int)[1] TEST - + - + - + - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[nested-container-template:NestedTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] + add(int, int, int)[2] TEST - + - + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] + add(int, int, int)[3] + TEST + + + + + + + - + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] + add(int, int, int)[4] + TEST + + + + + + + - + - + - + From 1ef608213d8798849f6e2ab3bb78a7509754dd49 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 10:32:21 +0100 Subject: [PATCH 51/60] Add regular test method to container template integration test --- .../CalculatorContainerTemplateTests.java | 9 +- .../support/tests/AntStarterTests.java | 4 +- .../support/tests/MavenStarterTests.java | 2 +- .../open-test-report.xml.snapshot | 134 +++++++++++------- .../open-test-report.xml.snapshot | 134 +++++++++++------- .../open-test-report.xml.snapshot | 134 +++++++++++------- 6 files changed, 257 insertions(+), 160 deletions(-) diff --git a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java index 1d6e0391667e..00b58cce65e8 100644 --- a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java +++ b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorContainerTemplateTests.java @@ -15,6 +15,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,9 +27,15 @@ @ExtendWith(CalculatorContainerTemplateTests.Twice.class) class CalculatorContainerTemplateTests { + @Test + void regularTest() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + @ParameterizedTest @ValueSource(ints = { 1, 2 }) - void test(int i) { + void parameterizedTest(int i) { Calculator calculator = new Calculator(); assertEquals(i, calculator.add(i, 0)); } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java index e7cc8d43da47..084cdcb89032 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -51,7 +51,7 @@ void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputF assertLinesMatch(List.of(">> HEAD >>", // "test.junit.launcher:", // ">>>>", // - "\\[junitlauncher\\] Tests run: 4, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // + "\\[junitlauncher\\] Tests run: 6, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // "\\[junitlauncher\\] Running com.example.project.CalculatorTests", // "\\[junitlauncher\\] Tests run: 5, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // ">>>>", // @@ -59,7 +59,7 @@ void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputF ">>>>", // " \\[java\\] Test run finished after [\\d]+ ms", // ">>>>", // - " \\[java\\] \\[ 9 tests successful \\]", // + " \\[java\\] \\[ 11 tests successful \\]", // " \\[java\\] \\[ 0 tests failed \\]", // ">> TAIL >>"), // result.stdOutLines()); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index 786e0deae5b5..6ba3da7a9126 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -57,7 +57,7 @@ void verifyJupiterStarterProject(@TempDir Path workspace, @FilePrefix("maven") O assertEquals(0, result.exitCode()); assertEquals("", result.stdErr()); assertTrue(result.stdOutLines().contains("[INFO] BUILD SUCCESS")); - assertTrue(result.stdOutLines().contains("[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0")); + assertTrue(result.stdOutLines().contains("[INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0")); assertThat(result.stdOut()).contains("Using Java version: 1.8"); var testResultsDir = workspace.resolve("target/surefire-reports"); diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot index 218efe990f6e..7144e4830014 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: ant_starter - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests] com.example.project.CalculatorContainerTemplateTests @@ -40,7 +40,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1] com.example.project.CalculatorContainerTemplateTests[1] @@ -51,56 +51,71 @@ test-method: ant_starter - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[method:regularTest()] + regularTest() + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)] + parameterizedTest(int) CONTAINER - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] + parameterizedTest(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] + parameterizedTest(int)[2] TEST - + - + - + - + - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2] com.example.project.CalculatorContainerTemplateTests[2] @@ -111,60 +126,75 @@ test-method: ant_starter - + + + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[method:regularTest()] + regularTest() + TEST + + + + + + + + + + + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)] + parameterizedTest(int) CONTAINER - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] + parameterizedTest(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] + parameterizedTest(int)[2] TEST - + - + - + - + - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -175,7 +205,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -186,11 +216,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -201,7 +231,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -212,11 +242,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -227,11 +257,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -242,11 +272,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -257,19 +287,19 @@ test-method: ant_starter - + - + - + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot index 4ab6e6486605..9f5fb870a28a 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests] com.example.project.CalculatorContainerTemplateTests @@ -40,7 +40,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1] com.example.project.CalculatorContainerTemplateTests[1] @@ -51,56 +51,71 @@ test-method: gradle_wrapper - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[method:regularTest()] + regularTest() + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)] + parameterizedTest(int) CONTAINER - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] + parameterizedTest(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] + parameterizedTest(int)[2] TEST - + - + - + - + - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2] com.example.project.CalculatorContainerTemplateTests[2] @@ -111,60 +126,75 @@ test-method: gradle_wrapper - + + + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[method:regularTest()] + regularTest() + TEST + + + + + + + + + + + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)] + parameterizedTest(int) CONTAINER - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] + parameterizedTest(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] + parameterizedTest(int)[2] TEST - + - + - + - + - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -175,7 +205,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -186,11 +216,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -201,7 +231,7 @@ test-method: gradle_wrapper - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -212,11 +242,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -227,11 +257,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -242,11 +272,11 @@ test-method: gradle_wrapper - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -257,19 +287,19 @@ test-method: gradle_wrapper - + - + - + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot index ed414d27fbb7..4c7776b69c9d 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot @@ -21,7 +21,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests] com.example.project.CalculatorContainerTemplateTests @@ -40,7 +40,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1] com.example.project.CalculatorContainerTemplateTests[1] @@ -51,56 +51,71 @@ test-method: verifyJupiterStarterProject - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[method:regularTest()] + regularTest() + TEST + + + + + + + + + + + + + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)] + parameterizedTest(int) CONTAINER - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] + parameterizedTest(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] + parameterizedTest(int)[2] TEST - + - + - + - + - + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2] com.example.project.CalculatorContainerTemplateTests[2] @@ -111,60 +126,75 @@ test-method: verifyJupiterStarterProject - + + + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[method:regularTest()] + regularTest() + TEST + + + + + + + + + + + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)] - test(int) + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)] + parameterizedTest(int) CONTAINER - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#1] - test(int)[1] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] + parameterizedTest(int)[1] TEST - + - + - + - [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:test(int)]/[test-template-invocation:#2] - test(int)[2] + [engine:junit-jupiter]/[container-template:com.example.project.CalculatorContainerTemplateTests]/[container-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] + parameterizedTest(int)[2] TEST - + - + - + - + - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -175,7 +205,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -186,11 +216,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -201,7 +231,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -212,11 +242,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -227,11 +257,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -242,11 +272,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -257,19 +287,19 @@ test-method: verifyJupiterStarterProject - + - + - + - + From a431915025c1062309028e8efb65673dc68c876a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 11 Feb 2025 11:09:22 +0100 Subject: [PATCH 52/60] Verify Gradle/Maven can run specific method in container template --- .../support/tests/GradleStarterTests.java | 68 ++++++++++++++++--- .../support/tests/MavenStarterTests.java | 54 +++++++++++---- .../tests/UnalignedClasspathTests.java | 3 +- .../open-test-report.xml.snapshot | 2 +- 4 files changed, 103 insertions(+), 24 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java index b25ca6589027..07756177eef8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java @@ -21,9 +21,11 @@ import de.skuzzle.test.snapshots.Snapshot; import de.skuzzle.test.snapshots.junit5.EnableSnapshotTests; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; +import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; @@ -36,23 +38,71 @@ @EnableSnapshotTests class GradleStarterTests { + @TempDir + Path workspace; + + @BeforeEach + void prepareWorkspace() throws Exception { + copyToWorkspace(Projects.JUPITER_STARTER, workspace); + } + @Test - void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles, Snapshot snapshot) - throws Exception { + void buildJupiterStarterProject(@FilePrefix("gradle") OutputFiles outputFiles, Snapshot snapshot) throws Exception { + + var result = runGradle(outputFiles, "build"); + + assertThat(result.stdOut()) // + .contains( // + "CalculatorContainerTemplateTests > [1] > regularTest() PASSED", // + "CalculatorContainerTemplateTests > [2] > regularTest() PASSED", // + "CalculatorContainerTemplateTests > [1] > parameterizedTest(int)", // + "CalculatorContainerTemplateTests > [2] > parameterizedTest(int)", // + "Using Java version: 1.8", // + "CalculatorTests > 1 + 1 = 2 PASSED", // + "CalculatorTests > add(int, int, int) > 0 + 1 = 1 PASSED", // + "CalculatorTests > add(int, int, int) > 1 + 2 = 3 PASSED", // + "CalculatorTests > add(int, int, int) > 49 + 51 = 100 PASSED", // + "CalculatorTests > add(int, int, int) > 1 + 100 = 101 PASSED" // + ); + var testResultsDir = workspace.resolve("build/test-results/test"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); + } + + @Test + void runOnlyOneMethodInContainerTemplate(@FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + + var result = runGradle(outputFiles, "test", "--tests", "CalculatorContainer*.regular*"); + + assertThat(result.stdOut()) // + .contains( // + "CalculatorContainerTemplateTests > [1] > regularTest() PASSED", // + "CalculatorContainerTemplateTests > [2] > regularTest() PASSED" // + ) // + .doesNotContain("parameterizedTest(int)", "CalculatorTests"); + + result = runGradle(outputFiles, "test", "--tests", "*ContainerTemplateTests.parameterized*"); + + assertThat(result.stdOut()) // + .contains( // + "CalculatorContainerTemplateTests > [1] > parameterizedTest(int)", // + "CalculatorContainerTemplateTests > [2] > parameterizedTest(int)" // + ) // + .doesNotContain("regularTest()", "CalculatorTests"); + } + + private ProcessResult runGradle(OutputFiles outputFiles, String... extraArgs) throws InterruptedException { var result = ProcessStarters.gradlew() // - .workingDir(copyToWorkspace(Projects.JUPITER_STARTER, workspace)) // + .workingDir(workspace) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // - .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .addArguments("--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .addArguments(extraArgs).putEnvironment("JDK8", + Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - assertThat(result.stdOut()).contains("Using Java version: 1.8"); - - var testResultsDir = workspace.resolve("build/test-results/test"); - verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); + return result; } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index 6ba3da7a9126..cece033b4aa5 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -12,7 +12,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; @@ -21,9 +20,11 @@ import de.skuzzle.test.snapshots.Snapshot; import de.skuzzle.test.snapshots.junit5.EnableSnapshotTests; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; +import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; @@ -42,25 +43,54 @@ class MavenStarterTests { @ManagedResource MavenRepoProxy mavenRepoProxy; + @TempDir + Path workspace; + + @BeforeEach + void prepareWorkspace() throws Exception { + copyToWorkspace(Projects.JUPITER_STARTER, workspace); + } + @Test - void verifyJupiterStarterProject(@TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles, - Snapshot snapshot) throws Exception { + void verifyJupiterStarterProject(@FilePrefix("maven") OutputFiles outputFiles, Snapshot snapshot) throws Exception { + + var result = runMaven(outputFiles, "verify"); + + assertThat(result.stdOutLines()).contains("[INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0"); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); + var testResultsDir = workspace.resolve("target/surefire-reports"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); + } + + @Test + void runOnlyOneMethodInContainerTemplate(@FilePrefix("maven") OutputFiles outputFiles) throws Exception { + + var result = runMaven(outputFiles, "test", "-Dtest=CalculatorContainerTemplateTests#regularTest"); + + assertThat(result.stdOutLines()) // + .doesNotContain("CalculatorTests") // + .contains("[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0"); + + result = runMaven(outputFiles, "test", "-Dtest=CalculatorContainerTemplateTests#parameterizedTest"); + + assertThat(result.stdOutLines()) // + .doesNotContain("CalculatorTests") // + .contains("[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0"); + } + + private ProcessResult runMaven(OutputFiles outputFiles, String... extraArgs) throws InterruptedException { var result = ProcessStarters.maven(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .workingDir(copyToWorkspace(Projects.JUPITER_STARTER, workspace)) // + .workingDir(workspace) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // - .addArguments("--update-snapshots", "--batch-mode", "verify") // - .redirectOutput(outputFiles) // + .addArguments("--update-snapshots", "--batch-mode") // + .addArguments(extraArgs).redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertEquals("", result.stdErr()); - assertTrue(result.stdOutLines().contains("[INFO] BUILD SUCCESS")); - assertTrue(result.stdOutLines().contains("[INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0")); - assertThat(result.stdOut()).contains("Using Java version: 1.8"); - - var testResultsDir = workspace.resolve("target/surefire-reports"); - verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); + assertThat(result.stdOutLines()).contains("[INFO] BUILD SUCCESS"); + return result; } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java index 80028afebcd4..6ce670bc5ecf 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java @@ -12,7 +12,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static platform.tooling.support.ProcessStarters.currentJdkHome; import static platform.tooling.support.tests.Projects.copyToWorkspace; @@ -60,7 +59,7 @@ void verifyErrorMessageForUnalignedClasspath(JRE jre, Path javaHome, @TempDir Pa assertEquals(1, result.exitCode()); assertEquals("", result.stdErr()); - assertTrue(result.stdOutLines().contains("[INFO] BUILD FAILURE")); + assertThat(result.stdOutLines()).contains("[INFO] BUILD FAILURE"); assertThat(result.stdOut()) // .contains("The wrapped NoClassDefFoundError is likely caused by the versions of JUnit jars " + "on the classpath/module path not being properly aligned"); diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot index 9f5fb870a28a..020b5160c082 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot @@ -2,7 +2,7 @@ dynamic-directory: false snapshot-name: open-test-report.xml snapshot-number: 0 test-class: platform.tooling.support.tests.GradleStarterTests -test-method: gradle_wrapper +test-method: buildJupiterStarterProject Date: Wed, 12 Feb 2025 09:50:16 +0100 Subject: [PATCH 53/60] Add documentation --- .../src/docs/asciidoc/link-attributes.adoc | 3 + .../docs/asciidoc/user-guide/extensions.adoc | 40 ++++++++ .../asciidoc/user-guide/writing-tests.adoc | 17 +++- .../java/example/ContainerTemplateDemo.java | 99 +++++++++++++++++++ .../junit/jupiter/api/ContainerTemplate.java | 29 ++++++ .../java/org/junit/jupiter/api/Nested.java | 4 + .../org/junit/jupiter/api/TestInstance.java | 2 +- .../org/junit/jupiter/api/TestTemplate.java | 1 + .../ContainerTemplateInvocationContext.java | 9 ++ ...inerTemplateInvocationContextProvider.java | 39 ++++++++ 10 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 documentation/src/test/java/example/ContainerTemplateDemo.java diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index 4010e54b7085..9862695d6d76 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -111,6 +111,7 @@ endif::[] :ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation] :ClassOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random] :ClassOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer] +:ContainerTemplate: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ContainerTemplate.html[@ContainerTemplate] :Disabled: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled] :MethodOrderer_Alphanumeric: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html[MethodOrderer.Alphanumeric] :MethodOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName] @@ -142,6 +143,8 @@ endif::[] :BeforeAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback] :BeforeEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback] :BeforeTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback] +:ContainerTemplateInvocationContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.html[ContainerTemplateInvocationContext] +:ContainerTemplateInvocationContextProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.html[ContainerTemplateInvocationContextProvider] :ExecutableInvoker: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker] :ExecutionCondition: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition] :ExtendWith: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith] diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index 11185fe05019..47bbd8947661 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -765,6 +765,46 @@ You may override the `getTestInstantiationExtensionContextScope(...)` method to on the test method level. ==== +[[extensions-container-templates]] +=== Providing Invocation Contexts for Container Templates + +A `{ContainerTemplate}` class can only be executed when at least one +`{ContainerTemplateInvocationContextProvider}` is registered. Each such provider is +responsible for providing a `Stream` of `{ContainerTemplateInvocationContext}` instances. +Each context may specify a custom display name and a list of additional extensions that +will only be used for the next invocation of the `{ContainerTemplate}` class. + +The following example shows how to write a container template as well as how to register +and implement a `{ContainerTemplateInvocationContextProvider}`. + +[source,java,indent=0] +.A container template with accompanying extension +---- +include::{testDir}/example/ContainerTemplateDemo.java[tags=user_guide] +---- + +In this example, the container template will be invoked twice, meaning all test methods in +the container template class will be executed twice. The display names of the container +invocations will be `apple` and `banana` as specified by the invocation context. Each +invocation registers a custom `{TestInstancePostProcessor}` which is used to inject a +value into a field. The output when using the `ConsoleLauncher` is as follows. + +.... +└─ ContainerTemplateDemo ✔ + ├─ apple ✔ + │ ├─ notNull() ✔ + │ └─ wellKnown() ✔ + └─ banana ✔ + ├─ notNull() ✔ + └─ wellKnown() ✔ +.... + +The `{ContainerTemplateInvocationContextProvider}` extension API is primarily intended for +implementing different kinds of tests that rely on repetitive invocation of _all_ test +methods in a test class albeit in different contexts — for example, with different +parameters, by preparing the test class instance differently, or multiple times without +modifying the context. + [[extensions-test-templates]] === Providing Invocation Contexts for Test Templates diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 8442461ae2e1..7ea683a04df6 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -32,7 +32,7 @@ in the `junit-jupiter-api` module. | `@ParameterizedTest` | Denotes that a method is a <>. Such methods are inherited unless they are overridden. | `@RepeatedTest` | Denotes that a method is a test template for a <>. Such methods are inherited unless they are overridden. | `@TestFactory` | Denotes that a method is a test factory for <>. Such methods are inherited unless they are overridden. -| `@TestTemplate` | Denotes that a method is a <> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <>. Such methods are inherited unless they are overridden. +| `@TestTemplate` | Denotes that a method is a <> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <>. Such methods are inherited unless they are overridden. | `@TestClassOrder` | Used to configure the <> for `@Nested` test classes in the annotated test class. Such annotations are inherited. | `@TestMethodOrder` | Used to configure the <> for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are inherited. | `@TestInstance` | Used to configure the <> for the annotated test class. Such annotations are inherited. @@ -42,6 +42,7 @@ in the `junit-jupiter-api` module. | `@AfterEach` | Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are inherited unless they are overridden. | `@BeforeAll` | Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@BeforeClass`. Such methods are inherited unless they are overridden and must be `static` unless the "per-class" <> is used. | `@AfterAll` | Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@AfterClass`. Such methods are inherited unless they are overridden and must be `static` unless the "per-class" <> is used. +| `@ContainerTemplate` | Denotes that the annotated class is a <> designed to be executed multiple times depending on the number of invocation contexts returned by the registered <>. | `@Nested` | Denotes that the annotated class is a non-static <>. On Java 8 through Java 15, `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <> is used. Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as `static` in a `@Nested` test class with either test instance lifecycle mode. Such annotations are not inherited. | `@Tag` | Used to declare <>, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level. | `@Disabled` | Used to <> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not inherited. @@ -2448,12 +2449,22 @@ lifecycle methods (e.g. `@BeforeEach`) and test class constructors. include::{testDir}/example/ParameterizedTestDemo.java[tags=ParameterResolver_example] ---- +[[writing-tests-container-templates]] +=== Container Templates + +A `{ContainerTemplate}` class is not a regular test class but rather a template for the +contained test cases. As such, it is designed to be invoked multiple times depending on +invocation contexts returned by the registered providers. Thus, it must be used in +conjunction with a registered `{ContainerTemplateInvocationContextProvider}` extension. +Each invocation of a container template class behaves like the execution of a regular test +class with full support for the same lifecycle callbacks and extensions. Please refer to +<> for usage examples. [[writing-tests-test-templates]] === Test Templates -A `{TestTemplate}` method is not a regular test case but rather a template for test -cases. As such, it is designed to be invoked multiple times depending on the number of +A `{TestTemplate}` method is not a regular test case but rather a template for a test +case. As such, it is designed to be invoked multiple times depending on the number of invocation contexts returned by the registered providers. Thus, it must be used in conjunction with a registered `{TestTemplateInvocationContextProvider}` extension. Each invocation of a test template method behaves like the execution of a regular `@Test` diff --git a/documentation/src/test/java/example/ContainerTemplateDemo.java b/documentation/src/test/java/example/ContainerTemplateDemo.java new file mode 100644 index 000000000000..4baadb340b23 --- /dev/null +++ b/documentation/src/test/java/example/ContainerTemplateDemo.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import example.ContainerTemplateDemo.MyContainerTemplateInvocationContextProvider; + +import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; +import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +// tag::user_guide[] +@ContainerTemplate +@ExtendWith(MyContainerTemplateInvocationContextProvider.class) +class ContainerTemplateDemo { + + static final List WELL_KNOWN_FRUITS + // tag::custom_line_break[] + = unmodifiableList(Arrays.asList("apple", "banana", "lemon")); + + private String fruit; + + @Test + void notNull() { + assertNotNull(fruit); + } + + @Test + void wellKnown() { + assertTrue(WELL_KNOWN_FRUITS.contains(fruit)); + } + + // end::user_guide[] + static + // tag::user_guide[] + public class MyContainerTemplateInvocationContextProvider + // tag::custom_line_break[] + implements ContainerTemplateInvocationContextProvider { + + @Override + public boolean supportsContainerTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream + // tag::custom_line_break[] + provideContainerTemplateInvocationContexts(ExtensionContext context) { + + return Stream.of(invocationContext("apple"), invocationContext("banana")); + } + + private ContainerTemplateInvocationContext invocationContext(String parameter) { + return new ContainerTemplateInvocationContext() { + @Override + public String getDisplayName(int invocationIndex) { + return parameter; + } + + // end::user_guide[] + @SuppressWarnings("Convert2Lambda") + // tag::user_guide[] + @Override + public List getAdditionalExtensions() { + return singletonList(new TestInstancePostProcessor() { + @Override + public void postProcessTestInstance( + // tag::custom_line_break[] + Object testInstance, ExtensionContext context) { + ((ContainerTemplateDemo) testInstance).fruit = parameter; + } + }); + } + }; + } + } +} +// end::user_guide[] diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java index c00d5a894164..b267d45fe3b2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java @@ -22,7 +22,36 @@ import org.junit.platform.commons.annotation.Testable; /** + * {@code @ContainerTemplate} is used to signal that the annotated class is a + * container template. + * + *

In contrast to regular test classes, a container template is not directly + * a test class but rather a template for a set of test cases. As such, it is + * designed to be invoked multiple times depending on the number of {@linkplain + * org.junit.jupiter.api.extension.ContainerTemplateInvocationContext invocation + * contexts} returned by the registered {@linkplain + * org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider + * providers}. Must be used together with at least one provider. Otherwise, + * execution will fail. + * + *

Each invocation of a container template method behaves like the execution + * of a regular test class with full support for the same lifecycle callbacks + * and extensions. + * + *

{@code @ContainerTemplate} may be combined with {@link Nested @Nested} and + * a container template may contain nested container templates. + * + *

{@code @ContainerTemplate} may also be used as a meta-annotation in order + * to create a custom composed annotation that inherits the semantics + * of {@code @ContainerTemplate}. + * + *

Inheritance

+ * + *

The {@code @ContainerTemplate} annotation is not inherited to subclasses + * but needs to be declared on each container template class individually. + * * @since 5.13 + * @see TestTemplate * @see org.junit.jupiter.api.extension.ContainerTemplateInvocationContext * @see org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider */ diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java index 4d96618238d0..476efdb83d12 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java @@ -31,6 +31,9 @@ *

{@code @Nested} test classes may be ordered via * {@link TestClassOrder @TestClassOrder} or a global {@link ClassOrderer}. * + *

{@code @Nested} may be combined with + * {@link ContainerTemplate @ContainerTemplate}. + * *

Test Instance Lifecycle

* *
    @@ -42,6 +45,7 @@ *
* * @since 5.0 + * @see ContainerTemplate * @see Test * @see TestInstance * @see TestClassOrder diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java index f9effa82b7a7..0202e3017f64 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -86,7 +86,7 @@ enum Lifecycle { /** * When using this mode, a new test instance will be created once per - * test class. + * test or container template class. * *

For {@link Nested @Nested}

test classes declared inside an * enclosing {@link ContainerTemplate @ContainerTemplate} test class, an diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java index 9636a2d01679..2a60a011e1a6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java @@ -79,6 +79,7 @@ * * @since 5.0 * @see Test + * @see ContainerTemplate * @see org.junit.jupiter.api.extension.TestTemplateInvocationContext * @see org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider */ diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java index 6d0b54c0500a..41477537df6c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.java @@ -18,7 +18,16 @@ import org.apiguardian.api.API; /** + * {@code ContainerTemplateInvocationContext} represents the context of + * a single invocation of a {@linkplain org.junit.jupiter.api.ContainerTemplate + * container template}. + * + *

Each context is provided by a + * {@link ContainerTemplateInvocationContextProvider}. + * * @since 5.13 + * @see org.junit.jupiter.api.ContainerTemplate + * @see ContainerTemplateInvocationContextProvider */ @API(status = EXPERIMENTAL, since = "5.13") public interface ContainerTemplateInvocationContext { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java index 3427f0c2e53f..6efe00f2c17e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java @@ -17,7 +17,46 @@ import org.apiguardian.api.API; /** + * {@code ContainerTemplateInvocationContextProvider} defines the API for + * {@link Extension Extensions} that wish to provide one or multiple contexts + * for the invocation of a + * {@link org.junit.jupiter.api.ContainerTemplate @ContainerTemplate} class. + * + *

This extension point makes it possible to execute a container template in + * different contexts — for example, with different parameters, by + * preparing the test class instance differently, or multiple times without + * modifying the context. + * + *

This interface defines two main methods: + * {@link #supportsContainerTemplate} and + * {@link #provideContainerTemplateInvocationContexts}. The former is called by + * the framework to determine whether this extension wants to act on a container + * template that is about to be executed. If so, the latter is called and must + * return a {@link Stream} of {@link ContainerTemplateInvocationContext} + * instances. Otherwise, this provider is ignored for the execution of the + * current container template. + * + *

A provider that has returned {@code true} from its + * {@link #supportsContainerTemplate} method is called active. When + * multiple providers are active for a container template class, the + * {@code Streams} returned by their + * {@link #provideContainerTemplateInvocationContexts} methods will be chained, + * and the container template method will be invoked using the contexts of all + * active providers. + * + *

An active provider may return zero invocation contexts from its + * {@link #provideContainerTemplateInvocationContexts} method if it overrides + * {@link #mayReturnZeroContainerTemplateInvocationContexts} to return + * {@code true}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * * @since 5.13 + * @see org.junit.jupiter.api.ContainerTemplate + * @see ContainerTemplateInvocationContext */ @API(status = EXPERIMENTAL, since = "5.13") public interface ContainerTemplateInvocationContextProvider extends Extension { From eb29f16bcc5cb27c032272059a7cfc53ec765676 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 12 Feb 2025 14:38:02 +0100 Subject: [PATCH 54/60] Add to release notes --- .../asciidoc/release-notes/release-notes-5.13.0-M1.adoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc index e7614bc3a786..15c3a64fbb5f 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc @@ -45,7 +45,11 @@ repository on GitHub. [[release-notes-5.13.0-M1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Introduce `@ContainerTemplate` and `ContainerTemplateInvocationContextProvider` that + allow declaring a top-level or `@Nested` test class as a template to be invoked multiple + times. This may be used, for example, to inject different parameters to be used by all + tests in the container template class or to set up each invocation of the container + template differently. [[release-notes-5.13.0-M1-junit-vintage]] From 1d13d1ec16306e38be8f30e8eea65d7bdd6a360a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 14 Feb 2025 15:21:58 +0100 Subject: [PATCH 55/60] Test and clarify lifecycle method executions --- .../java/org/junit/jupiter/api/AfterAll.java | 8 +- .../java/org/junit/jupiter/api/BeforeAll.java | 8 +- .../junit/jupiter/api/ContainerTemplate.java | 3 +- .../ContainerTemplateInvocationTests.java | 94 +++++++++++++++++++ 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java index 171f530fb324..bce909ae8d2d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -25,7 +25,13 @@ * executed after all tests in the current test class. * *

In contrast to {@link AfterEach @AfterEach} methods, {@code @AfterAll} - * methods are only executed once for a given test class. + * methods are only executed once per execution of a given test class. If the + * test class is annotated with {@link ContainerTemplate @ContainerTemplate}, + * the {@code @AfterAll} methods are executed once after the last invocation of + * the container template. If a {@link Nested @Nested} test class is declared in + * a {@link ContainerTemplate @ContainerTemplate} class, its {@code @AfterAll} + * methods are called once per execution of the nested test class, namely, once + * per invocation of the outer container template. * *

Method Signatures

* diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java index e327653c46c3..6b0332d66d53 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java @@ -25,7 +25,13 @@ * executed before all tests in the current test class. * *

In contrast to {@link BeforeEach @BeforeEach} methods, {@code @BeforeAll} - * methods are only executed once for a given test class. + * methods are only executed once per execution of a given test class. If the + * test class is annotated with {@link ContainerTemplate @ContainerTemplate}, + * the {@code @BeforeAll} methods are executed once before the first invocation + * of the container template. If a {@link Nested @Nested} test class is declared + * in a {@link ContainerTemplate @ContainerTemplate} class, its + * {@code @BeforeAll} methods are called once per execution of the nested test + * class, namely, once per invocation of the outer container template. * *

Method Signatures

* diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java index b267d45fe3b2..6330e63c31d2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ContainerTemplate.java @@ -39,7 +39,8 @@ * and extensions. * *

{@code @ContainerTemplate} may be combined with {@link Nested @Nested} and - * a container template may contain nested container templates. + * a container template may contain regular nested test classes or nested + * container templates. * *

{@code @ContainerTemplate} may also be used as a meta-annotation in order * to create a custom composed annotation that inherits the semantics diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index a64c9d1ab3bf..1a13c749dd94 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine; import static java.util.function.Predicate.isEqual; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -39,18 +40,27 @@ import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.IntStream; import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ContainerTemplate; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; @@ -75,6 +85,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.testkit.engine.Event; /** @@ -836,6 +847,50 @@ void nestedContainerTemplateInvocationCanBeSelectedByIteration() { event(engine(), finishedSuccessfully())); } + @Test + void executesLifecycleCallbackMethodsInNestedContainerTemplates() { + var results = executeTestsForClass(TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase.class); + + results.containerEvents().assertStatistics(stats -> stats.started(10).succeeded(10)); + results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); + + var callSequence = results.allEvents().reportingEntryPublished() // + .map(event -> event.getRequiredPayload(ReportEntry.class)) // + .map(ReportEntry::getKeyValuePairs) // + .map(Map::values) // + .flatMap(Collection::stream); + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeAll: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase", + "beforeAll: NestedTestCase", + "beforeEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "beforeEach: test [NestedTestCase]", + "test", + "afterEach: test [NestedTestCase]", + "afterEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "beforeEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "beforeEach: test [NestedTestCase]", + "test", + "afterEach: test [NestedTestCase]", + "afterEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "afterAll: NestedTestCase", + "beforeAll: NestedTestCase", + "beforeEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "beforeEach: test [NestedTestCase]", + "test", + "afterEach: test [NestedTestCase]", + "afterEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "beforeEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "beforeEach: test [NestedTestCase]", + "test", + "afterEach: test [NestedTestCase]", + "afterEach: test [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", + "afterAll: NestedTestCase", + "afterAll: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase" + ); + // @formatter:on + } + // ------------------------------------------------------------------- // TODO #871 Consider moving to EventConditions @@ -1179,4 +1234,43 @@ void test() { } } } + + @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) + @ContainerTemplate + static class TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase extends LifecycleCallbacks { + @Nested + @ContainerTemplate + class NestedTestCase extends LifecycleCallbacks { + @Test + @DisplayName("test") + void test(TestReporter testReporter) { + testReporter.publishEntry("test"); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class LifecycleCallbacks { + @BeforeAll + static void beforeAll(TestReporter testReporter, TestInfo testInfo) { + testReporter.publishEntry("beforeAll: " + testInfo.getTestClass().orElseThrow().getSimpleName()); + } + + @BeforeEach + void beforeEach(TestReporter testReporter, TestInfo testInfo) { + testReporter.publishEntry( + "beforeEach: " + testInfo.getDisplayName() + " [" + getClass().getSimpleName() + "]"); + } + + @AfterEach + void afterEach(TestReporter testReporter, TestInfo testInfo) { + testReporter.publishEntry( + "afterEach: " + testInfo.getDisplayName() + " [" + getClass().getSimpleName() + "]"); + } + + @AfterAll + static void afterAll(TestReporter testReporter, TestInfo testInfo) { + testReporter.publishEntry("afterAll: " + testInfo.getTestClass().orElseThrow().getSimpleName()); + } + } } From 03da39735762162535f770ea8bb535481cae5ef2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 14 Feb 2025 15:43:02 +0100 Subject: [PATCH 56/60] Move conditions to `EventConditions` --- .../testkit/engine/EventConditions.java | 60 +++++++++++++++++-- .../ContainerTemplateInvocationTests.java | 21 +------ 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java index 5f260fe3dc14..4be237c109e0 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java @@ -249,6 +249,32 @@ public static Condition dynamicTestRegistered(Condition condition) return allOf(type(DYNAMIC_TEST_REGISTERED), condition); } + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getUniqueId() unique id} of an {@link Event}'s + * {@linkplain Event#getTestDescriptor() test descriptor} is equal to the + * {@link UniqueId} parsed from the supplied {@link String}. + * + * @since 1.13 + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static Condition uniqueId(String uniqueId) { + return uniqueId(UniqueId.parse(uniqueId)); + } + + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getUniqueId() unique id} of an {@link Event}'s + * {@linkplain Event#getTestDescriptor() test descriptor} is equal to the + * supplied {@link UniqueId}. + * + * @since 1.13 + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static Condition uniqueId(UniqueId uniqueId) { + return uniqueId(new Condition<>(isEqual(uniqueId), "equal to '%s'", uniqueId)); + } + /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an @@ -260,11 +286,22 @@ public static Condition uniqueIdSubstring(String uniqueIdSubstring) { String text = segment.getType() + ":" + segment.getValue(); return text.contains(uniqueIdSubstring); }; + return uniqueId(new Condition<>(uniqueId -> uniqueId.getSegments().stream().anyMatch(predicate), + "substring '%s'", uniqueIdSubstring)); + } - return new Condition<>( - byTestDescriptor( - where(TestDescriptor::getUniqueId, uniqueId -> uniqueId.getSegments().stream().anyMatch(predicate))), - "descriptor with uniqueId substring '%s'", uniqueIdSubstring); + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getUniqueId() unique id} of an {@link Event}'s + * {@linkplain Event#getTestDescriptor() test descriptor} matches the + * supplied {@link Condition}. + * + * @since 1.13 + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static Condition uniqueId(Condition condition) { + return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, condition::matches)), + "descriptor with uniqueId %s", condition.description().value()); } /** @@ -315,6 +352,21 @@ public static Condition displayName(String displayName) { "descriptor with display name '%s'", displayName); } + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getLegacyReportingName()} () legacy reporting name} + * of an {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} + * is equal to the supplied {@link String}. + * + * @since 1.13 + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static Condition legacyReportingName(String legacyReportingName) { + return new Condition<>( + byTestDescriptor(where(TestDescriptor::getLegacyReportingName, isEqual(legacyReportingName))), + "descriptor with legacy reporting name '%s'", legacyReportingName); + } + /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index 1a13c749dd94..ea817384000f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -10,7 +10,6 @@ package org.junit.jupiter.engine; -import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -19,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; @@ -28,7 +26,6 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; -import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; @@ -36,8 +33,10 @@ import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.legacyReportingName; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.EventConditions.uniqueId; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.Collection; @@ -46,7 +45,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import org.assertj.core.api.Condition; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -82,11 +80,9 @@ import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.testkit.engine.Event; /** * @since 5.13 @@ -893,19 +889,6 @@ void executesLifecycleCallbackMethodsInNestedContainerTemplates() { // ------------------------------------------------------------------- - // TODO #871 Consider moving to EventConditions - private static Condition uniqueId(UniqueId uniqueId) { - return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, isEqual(uniqueId))), - "descriptor with uniqueId '%s'", uniqueId); - } - - // TODO #871 Consider moving to EventConditions - private static Condition legacyReportingName(String legacyReportingName) { - return new Condition<>( - byTestDescriptor(where(TestDescriptor::getLegacyReportingName, isEqual(legacyReportingName))), - "descriptor with legacy reporting name '%s'", legacyReportingName); - } - @SuppressWarnings("JUnitMalformedDeclaration") @ContainerTemplate @ExtendWith(TwoInvocationsContainerTemplateInvocationContextProvider.class) From 4998e02418979065df0090b13d3ec0360a6a0fda Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 14 Feb 2025 16:40:54 +0100 Subject: [PATCH 57/60] Extract TemplateExecutor and use it for container and test templates --- .../ContainerTemplateTestDescriptor.java | 115 ++++++++---------- .../engine/descriptor/TemplateExecutor.java | 100 +++++++++++++++ .../TestTemplateTestDescriptor.java | 94 +++++++------- 3 files changed, 195 insertions(+), 114 deletions(-) create mode 100644 junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TemplateExecutor.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 906cac2a1715..080ec7caee48 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -21,14 +21,13 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; +import org.junit.jupiter.api.ContainerTemplate; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext; import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtensionContext; @@ -185,88 +184,80 @@ public void cleanUp(JupiterEngineExecutionContext context) { this.dynamicDescendantFilter.allowAll(); } - // TODO copied from TestTemplateTestDescriptor - @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { - ExtensionContext extensionContext = context.getExtensionContext(); - List providers = validateProviders(extensionContext, - context.getExtensionRegistry()); - AtomicInteger invocationIndex = new AtomicInteger(); - for (ContainerTemplateInvocationContextProvider provider : providers) { - executeForProvider(provider, invocationIndex, dynamicTestExecutor, extensionContext); - } + + new ContainerTemplateExecutor().execute(context, dynamicTestExecutor); return context; } - private void executeForProvider(ContainerTemplateInvocationContextProvider provider, AtomicInteger invocationIndex, - DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { - - int initialValue = invocationIndex.get(); + class ContainerTemplateExecutor + extends TemplateExecutor { - try (Stream stream = invocationContexts(provider, extensionContext)) { - stream.forEach(invocationContext -> toTestDescriptor(invocationContext, invocationIndex.incrementAndGet()) // - .ifPresent(testDescriptor -> execute(dynamicTestExecutor, testDescriptor))); + public ContainerTemplateExecutor() { + super(ContainerTemplateTestDescriptor.this, ContainerTemplateInvocationContextProvider.class); } - Preconditions.condition( - invocationIndex.get() != initialValue - || provider.mayReturnZeroContainerTemplateInvocationContexts(extensionContext), - String.format( - "Provider [%s] did not provide any invocation contexts, but was expected to do so. " - + "You may override mayReturnZeroContainerTemplateInvocationContexts() to allow this.", - provider.getClass().getSimpleName())); - } + @Override + boolean supports(ContainerTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { + return provider.supportsContainerTemplate(extensionContext); + } - private static Stream invocationContexts( - ContainerTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { - return provider.provideContainerTemplateInvocationContexts(extensionContext); - } + @Override + protected String getNoRegisteredProviderErrorMessage() { + return String.format("You must register at least one %s that supports @%s class [%s]", + ContainerTemplateInvocationContextProvider.class.getSimpleName(), + ContainerTemplate.class.getSimpleName(), getTestClass().getName()); + } - private List validateProviders(ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry) { + @Override + Stream provideContexts( + ContainerTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { + return provider.provideContainerTemplateInvocationContexts(extensionContext); + } - // @formatter:off - List providers = extensionRegistry.stream(ContainerTemplateInvocationContextProvider.class) - .filter(provider -> provider.supportsContainerTemplate(extensionContext)) - .collect(toList()); - // @formatter:on + @Override + boolean mayReturnZeroContexts(ContainerTemplateInvocationContextProvider provider, + ExtensionContext extensionContext) { + return provider.mayReturnZeroContainerTemplateInvocationContexts(extensionContext); + } - return Preconditions.notEmpty(providers, - () -> String.format("You must register at least one %s that supports @ContainerTemplate class [%s]", - ContainerTemplateInvocationContextProvider.class.getSimpleName(), getTestClass().getName())); - } + @Override + protected String getZeroContextsProvidedErrorMessage(ContainerTemplateInvocationContextProvider provider) { + return String.format( + "Provider [%s] did not provide any invocation contexts, but was expected to do so. " + + "You may override mayReturnZeroContainerTemplateInvocationContexts() to allow this.", + provider.getClass().getSimpleName()); + } - private Optional toTestDescriptor(ContainerTemplateInvocationContext invocationContext, int index) { - UniqueId invocationUniqueId = getUniqueId().append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, - "#" + index); - if (getDynamicDescendantFilter().test(invocationUniqueId, index - 1)) { + @Override + UniqueId createInvocationUniqueId(UniqueId parentUniqueId, int index) { + return parentUniqueId.append(ContainerTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); + } + @Override + TestDescriptor createInvocationTestDescriptor(UniqueId uniqueId, + ContainerTemplateInvocationContext invocationContext, int index) { ContainerTemplateInvocationTestDescriptor containerInvocationDescriptor = new ContainerTemplateInvocationTestDescriptor( - invocationUniqueId, this, invocationContext, index, getSource().orElse(null), this.configuration); + uniqueId, ContainerTemplateTestDescriptor.this, invocationContext, index, getSource().orElse(null), + ContainerTemplateTestDescriptor.this.configuration); - collectChildren(index, invocationUniqueId) // + collectChildren(index, uniqueId) // .forEach(containerInvocationDescriptor::addChild); - return Optional.of(containerInvocationDescriptor); + return containerInvocationDescriptor; } - return Optional.empty(); - } - private Stream collectChildren(int index, UniqueId invocationUniqueId) { - if (this.childrenPrototypesByIndex.containsKey(index)) { - return this.childrenPrototypesByIndex.remove(index).stream(); + private Stream collectChildren(int index, UniqueId invocationUniqueId) { + if (ContainerTemplateTestDescriptor.this.childrenPrototypesByIndex.containsKey(index)) { + return ContainerTemplateTestDescriptor.this.childrenPrototypesByIndex.remove(index).stream(); + } + UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); + return ContainerTemplateTestDescriptor.this.childrenPrototypes.stream() // + .map(JupiterTestDescriptor.class::cast) // + .map(it -> it.copyIncludingDescendants(transformer)); } - UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); - return this.childrenPrototypes.stream() // - .map(JupiterTestDescriptor.class::cast) // - .map(it -> it.copyIncludingDescendants(transformer)); - } - - private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { - testDescriptor.setParent(this); - dynamicTestExecutor.execute(testDescriptor); } private static class UniqueIdPrefixTransformer implements UnaryOperator { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TemplateExecutor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TemplateExecutor.java new file mode 100644 index 000000000000..64988963685f --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TemplateExecutor.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.Node; + +abstract class TemplateExecutor

{ + + private final TestDescriptor parent; + private final Class

providerType; + private final DynamicDescendantFilter dynamicDescendantFilter; + + TemplateExecutor(T parent, Class

providerType) { + this.parent = parent; + this.providerType = providerType; + this.dynamicDescendantFilter = parent.getDynamicDescendantFilter(); + } + + void execute(JupiterEngineExecutionContext context, Node.DynamicTestExecutor dynamicTestExecutor) { + ExtensionContext extensionContext = context.getExtensionContext(); + List

providers = validateProviders(extensionContext, context.getExtensionRegistry()); + AtomicInteger invocationIndex = new AtomicInteger(); + for (P provider : providers) { + executeForProvider(provider, invocationIndex, dynamicTestExecutor, extensionContext); + } + } + + private void executeForProvider(P provider, AtomicInteger invocationIndex, + Node.DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { + + int initialValue = invocationIndex.get(); + + try (Stream stream = provideContexts(provider, extensionContext)) { + stream.forEach(invocationContext -> createInvocationTestDescriptor(invocationContext, + invocationIndex.incrementAndGet()) // + .ifPresent(testDescriptor -> execute(dynamicTestExecutor, testDescriptor))); + } + + Preconditions.condition( + invocationIndex.get() != initialValue || mayReturnZeroContexts(provider, extensionContext), + getZeroContextsProvidedErrorMessage(provider)); + } + + private List

validateProviders(ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { + List

providers = extensionRegistry.stream(providerType) // + .filter(provider -> supports(provider, extensionContext)) // + .collect(toList()); + return Preconditions.notEmpty(providers, this::getNoRegisteredProviderErrorMessage); + } + + private Optional createInvocationTestDescriptor(C invocationContext, int index) { + UniqueId invocationUniqueId = createInvocationUniqueId(parent.getUniqueId(), index); + if (this.dynamicDescendantFilter.test(invocationUniqueId, index - 1)) { + return Optional.of(createInvocationTestDescriptor(invocationUniqueId, invocationContext, index)); + } + return Optional.empty(); + } + + private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { + testDescriptor.setParent(parent); + dynamicTestExecutor.execute(testDescriptor); + } + + abstract boolean supports(P provider, ExtensionContext extensionContext); + + protected abstract String getNoRegisteredProviderErrorMessage(); + + abstract Stream provideContexts(P provider, ExtensionContext extensionContext); + + abstract boolean mayReturnZeroContexts(P provider, ExtensionContext extensionContext); + + protected abstract String getZeroContextsProvidedErrorMessage(P provider); + + abstract UniqueId createInvocationUniqueId(UniqueId parentUniqueId, int index); + + abstract TestDescriptor createInvocationTestDescriptor(UniqueId uniqueId, C invocationContext, int index); + +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index e01e73a395d7..86a71e846509 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -10,27 +10,23 @@ package org.junit.jupiter.engine.descriptor; -import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; import java.lang.reflect.Method; import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Stream; import org.apiguardian.api.API; +import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; 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.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; @@ -111,65 +107,59 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { - ExtensionContext extensionContext = context.getExtensionContext(); - List providers = validateProviders(extensionContext, - context.getExtensionRegistry()); - AtomicInteger invocationIndex = new AtomicInteger(); - for (TestTemplateInvocationContextProvider provider : providers) { - executeForProvider(provider, invocationIndex, dynamicTestExecutor, extensionContext); - } + new TestTemplateExecutor().execute(context, dynamicTestExecutor); return context; } - private void executeForProvider(TestTemplateInvocationContextProvider provider, AtomicInteger invocationIndex, - DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { - - int initialValue = invocationIndex.get(); + private class TestTemplateExecutor + extends TemplateExecutor { - try (Stream stream = invocationContexts(provider, extensionContext)) { - stream.forEach(invocationContext -> toTestDescriptor(invocationContext, invocationIndex.incrementAndGet()) // - .ifPresent(testDescriptor -> execute(dynamicTestExecutor, testDescriptor))); + TestTemplateExecutor() { + super(TestTemplateTestDescriptor.this, TestTemplateInvocationContextProvider.class); } - Preconditions.condition( - invocationIndex.get() != initialValue - || provider.mayReturnZeroTestTemplateInvocationContexts(extensionContext), - String.format( - "Provider [%s] did not provide any invocation contexts, but was expected to do so. " - + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this.", - provider.getClass().getSimpleName())); - } + @Override + boolean supports(TestTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { + return provider.supportsTestTemplate(extensionContext); + } - private static Stream invocationContexts( - TestTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { - return provider.provideTestTemplateInvocationContexts(extensionContext); - } + @Override + protected String getNoRegisteredProviderErrorMessage() { + return String.format("You must register at least one %s that supports @%s method [%s]", + TestTemplateInvocationContextProvider.class.getSimpleName(), TestTemplate.class.getSimpleName(), + getTestMethod()); + } - private List validateProviders(ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry) { + @Override + Stream provideContexts(TestTemplateInvocationContextProvider provider, + ExtensionContext extensionContext) { + return provider.provideTestTemplateInvocationContexts(extensionContext); + } - // @formatter:off - List providers = extensionRegistry.stream(TestTemplateInvocationContextProvider.class) - .filter(provider -> provider.supportsTestTemplate(extensionContext)) - .collect(toList()); - // @formatter:on + @Override + boolean mayReturnZeroContexts(TestTemplateInvocationContextProvider provider, + ExtensionContext extensionContext) { + return provider.mayReturnZeroTestTemplateInvocationContexts(extensionContext); + } - return Preconditions.notEmpty(providers, - () -> String.format("You must register at least one %s that supports @TestTemplate method [%s]", - TestTemplateInvocationContextProvider.class.getSimpleName(), getTestMethod())); - } + @Override + protected String getZeroContextsProvidedErrorMessage(TestTemplateInvocationContextProvider provider) { + return String.format( + "Provider [%s] did not provide any invocation contexts, but was expected to do so. " + + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this.", + provider.getClass().getSimpleName()); + } - private Optional toTestDescriptor(TestTemplateInvocationContext invocationContext, int index) { - UniqueId uniqueId = getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); - if (getDynamicDescendantFilter().test(uniqueId, index - 1)) { - return Optional.of(new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), - invocationContext, index, configuration)); + @Override + UniqueId createInvocationUniqueId(UniqueId parentUniqueId, int index) { + return parentUniqueId.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); } - return Optional.empty(); - } - private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { - testDescriptor.setParent(this); - dynamicTestExecutor.execute(testDescriptor); + @Override + TestDescriptor createInvocationTestDescriptor(UniqueId uniqueId, + TestTemplateInvocationContext invocationContext, int index) { + return new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), + invocationContext, index, TestTemplateTestDescriptor.this.configuration); + } } } From 5948df5ad8021cebd7c38821f229386b6b56597d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 14 Feb 2025 17:21:23 +0100 Subject: [PATCH 58/60] Implement copying and transforming of `DynamicDescendantFilter` --- .../descriptor/ClassTestDescriptor.java | 6 +- ...ainerTemplateInvocationTestDescriptor.java | 7 +- .../ContainerTemplateTestDescriptor.java | 31 +-------- .../DynamicContainerTestDescriptor.java | 7 +- .../descriptor/DynamicDescendantFilter.java | 14 ++-- .../descriptor/DynamicTestTestDescriptor.java | 8 ++- .../descriptor/JupiterTestDescriptor.java | 4 +- .../descriptor/NestedClassTestDescriptor.java | 6 +- .../descriptor/TestFactoryTestDescriptor.java | 7 +- .../descriptor/TestMethodTestDescriptor.java | 7 +- .../TestTemplateInvocationTestDescriptor.java | 7 +- .../TestTemplateTestDescriptor.java | 9 +-- .../descriptor/UniqueIdPrefixTransformer.java | 46 +++++++++++++ .../TestFactoryTestDescriptorTests.java | 25 +++++++ .../TestTemplateTestDescriptorTests.java | 69 ++++++++++++------- 15 files changed, 166 insertions(+), 87 deletions(-) create mode 100644 junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/UniqueIdPrefixTransformer.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index ec389ed27dfe..43d5cd8a6d8f 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -19,6 +19,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -59,8 +60,9 @@ private ClassTestDescriptor(UniqueId uniqueId, Class testClass, String displa // --- JupiterTestDescriptor ----------------------------------------------- @Override - protected ClassTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ClassTestDescriptor(newUniqueId, getTestClass(), getDisplayName(), configuration); + protected ClassTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new ClassTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), getDisplayName(), + configuration); } // --- TestDescriptor ------------------------------------------------------ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index da990bf55956..3b0335c5aa2a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Set; import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -58,9 +59,9 @@ public int getIndex() { // --- JupiterTestDescriptor ----------------------------------------------- @Override - protected ContainerTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateInvocationTestDescriptor(newUniqueId, parent, this.invocationContext, this.index, - getSource().orElse(null), this.configuration); + protected ContainerTemplateInvocationTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new ContainerTemplateInvocationTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), parent, + this.invocationContext, this.index, getSource().orElse(null), this.configuration); } // --- TestDescriptor ------------------------------------------------------ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java index 080ec7caee48..d59debe38cc8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateTestDescriptor.java @@ -36,7 +36,6 @@ import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; @@ -103,8 +102,9 @@ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator } @Override - protected ContainerTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new ContainerTemplateTestDescriptor(newUniqueId, this.delegate, this.dynamicDescendantFilter.copy()); + protected ContainerTemplateTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new ContainerTemplateTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.delegate, + this.dynamicDescendantFilter.copy(uniqueIdTransformer)); } @Override @@ -260,29 +260,4 @@ private Stream collectChildren(int index, UniqueId inv } } - private static class UniqueIdPrefixTransformer implements UnaryOperator { - - private final UniqueId oldPrefix; - private final UniqueId newPrefix; - private final int oldPrefixLength; - - UniqueIdPrefixTransformer(UniqueId oldPrefix, UniqueId newPrefix) { - this.oldPrefix = oldPrefix; - this.newPrefix = newPrefix; - this.oldPrefixLength = oldPrefix.getSegments().size(); - } - - @Override - public UniqueId apply(UniqueId uniqueId) { - Preconditions.condition(uniqueId.hasPrefix(oldPrefix), - () -> String.format("Unique ID %s does not have the expected prefix %s", uniqueId, oldPrefix)); - List oldSegments = uniqueId.getSegments(); - List suffix = oldSegments.subList(oldPrefixLength, oldSegments.size()); - UniqueId newValue = newPrefix; - for (UniqueId.Segment segment : suffix) { - newValue = newValue.append(segment); - } - return newValue; - } - } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java index cca538ff7659..8f157b9f8726 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java @@ -14,6 +14,7 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicContainer; @@ -47,9 +48,9 @@ class DynamicContainerTestDescriptor extends DynamicNodeTestDescriptor { } @Override - protected DynamicContainerTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new DynamicContainerTestDescriptor(newUniqueId, this.index, this.dynamicContainer, this.testSource, - this.dynamicDescendantFilter, this.configuration); + protected DynamicContainerTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new DynamicContainerTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.index, + this.dynamicContainer, this.testSource, this.dynamicDescendantFilter, this.configuration); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java index 56336ee8819e..ded323498b1a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; +import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.platform.engine.TestDescriptor; @@ -85,12 +86,13 @@ private enum Mode { EXPLICIT, ALLOW_ALL } - public DynamicDescendantFilter copy() { - return configure(new DynamicDescendantFilter()); + public DynamicDescendantFilter copy(UnaryOperator uniqueIdTransformer) { + return configure(uniqueIdTransformer, new DynamicDescendantFilter()); } - protected DynamicDescendantFilter configure(DynamicDescendantFilter copy) { - copy.allowedUniqueIds.addAll(this.allowedUniqueIds); + protected DynamicDescendantFilter configure(UnaryOperator uniqueIdTransformer, + DynamicDescendantFilter copy) { + this.allowedUniqueIds.stream().map(uniqueIdTransformer).forEach(copy.allowedUniqueIds::add); copy.allowedIndices.addAll(this.allowedIndices); copy.mode = this.mode; return copy; @@ -109,8 +111,8 @@ public DynamicDescendantFilter withoutIndexFiltering() { } @Override - public DynamicDescendantFilter copy() { - return configure(new WithoutIndexFiltering()); + public DynamicDescendantFilter copy(UnaryOperator uniqueIdTransformer) { + return configure(uniqueIdTransformer, new WithoutIndexFiltering()); } } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java index ba9c00b21363..9209b8c1f3d6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java @@ -10,6 +10,8 @@ package org.junit.jupiter.engine.descriptor; +import java.util.function.UnaryOperator; + import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.extension.ExtensionContext; @@ -42,9 +44,9 @@ class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { } @Override - protected DynamicTestTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new DynamicTestTestDescriptor(newUniqueId, this.index, this.dynamicTest, this.getSource().orElse(null), - this.configuration); + protected DynamicTestTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new DynamicTestTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.index, this.dynamicTest, + this.getSource().orElse(null), this.configuration); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java index 3b63e32a0ec6..27344fc499f9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -223,7 +223,7 @@ public void prunePriorToFiltering() { * {@return a deep copy (with copies of children) of this descriptor with the supplied unique ID} */ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator uniqueIdTransformer) { - JupiterTestDescriptor result = withUniqueId(uniqueIdTransformer.apply(getUniqueId())); + JupiterTestDescriptor result = withUniqueId(uniqueIdTransformer); getChildren().forEach(oldChild -> { TestDescriptor newChild = ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer); result.addChild(newChild); @@ -234,7 +234,7 @@ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator /** * {@return shallow copy (without children) of this descriptor with the supplied unique ID} */ - protected abstract JupiterTestDescriptor withUniqueId(UniqueId newUniqueId); + protected abstract JupiterTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer); /** * @since 5.5 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 0f33bedb0d12..aad262a66109 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -63,8 +64,9 @@ private NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, String // --- JupiterTestDescriptor ----------------------------------------------- @Override - protected NestedClassTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new NestedClassTestDescriptor(newUniqueId, getTestClass(), getDisplayName(), configuration); + protected NestedClassTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new NestedClassTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), getDisplayName(), + configuration); } // --- TestDescriptor ------------------------------------------------------ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index 68d28d0a3877..f145e5531a7c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -78,10 +79,10 @@ private TestFactoryTestDescriptor(UniqueId uniqueId, String displayName, Class uniqueIdTransformer) { // TODO #871 Check that dynamic descendant filter is copied correctly - return new TestFactoryTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), - this.configuration, new DynamicDescendantFilter()); + return new TestFactoryTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getDisplayName(), getTestClass(), + getTestMethod(), this.configuration, this.dynamicDescendantFilter.copy(uniqueIdTransformer)); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index eaf948a8f458..8d3b14a82537 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -96,9 +97,9 @@ public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method te // --- JupiterTestDescriptor ----------------------------------------------- @Override - protected TestMethodTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new TestMethodTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), - this.configuration, interceptorCall); + protected TestMethodTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new TestMethodTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getDisplayName(), getTestClass(), + getTestMethod(), this.configuration, interceptorCall); } // --- TestDescriptor ------------------------------------------------------ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java index 724c58a03642..2437c297bd3a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java @@ -15,6 +15,7 @@ import java.lang.reflect.Method; import java.util.Set; +import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.InvocationInterceptor; @@ -54,9 +55,9 @@ public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescript // --- JupiterTestDescriptor ----------------------------------------------- @Override - protected TestTemplateInvocationTestDescriptor withUniqueId(UniqueId newUniqueId) { - return new TestTemplateInvocationTestDescriptor(newUniqueId, getTestClass(), getTestMethod(), - this.invocationContext, this.index, this.configuration); + protected TestTemplateInvocationTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new TestTemplateInvocationTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), + getTestMethod(), this.invocationContext, this.index, this.configuration); } // --- TestDescriptor ------------------------------------------------------ diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index 86a71e846509..05ef02c0c991 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -57,10 +58,10 @@ private TestTemplateTestDescriptor(UniqueId uniqueId, String displayName, Class< // --- JupiterTestDescriptor ----------------------------------------------- @Override - protected TestTemplateTestDescriptor withUniqueId(UniqueId newUniqueId) { - // TODO #871 Check that dynamic descendant filter is copied correctly - return new TestTemplateTestDescriptor(newUniqueId, getDisplayName(), getTestClass(), getTestMethod(), - this.configuration, new DynamicDescendantFilter()); + protected TestTemplateTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { + return new TestTemplateTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getDisplayName(), + getTestClass(), getTestMethod(), this.configuration, + this.dynamicDescendantFilter.copy(uniqueIdTransformer)); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/UniqueIdPrefixTransformer.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/UniqueIdPrefixTransformer.java new file mode 100644 index 000000000000..20a85ba1382b --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/UniqueIdPrefixTransformer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2025 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.util.List; +import java.util.function.UnaryOperator; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.UniqueId; + +/** + * @since 5.13 + */ +class UniqueIdPrefixTransformer implements UnaryOperator { + + private final UniqueId oldPrefix; + private final UniqueId newPrefix; + private final int oldPrefixLength; + + UniqueIdPrefixTransformer(UniqueId oldPrefix, UniqueId newPrefix) { + this.oldPrefix = oldPrefix; + this.newPrefix = newPrefix; + this.oldPrefixLength = oldPrefix.getSegments().size(); + } + + @Override + public UniqueId apply(UniqueId uniqueId) { + Preconditions.condition(uniqueId.hasPrefix(oldPrefix), + () -> String.format("Unique ID %s does not have the expected prefix %s", uniqueId, oldPrefix)); + List oldSegments = uniqueId.getSegments(); + List suffix = oldSegments.subList(oldPrefixLength, oldSegments.size()); + UniqueId newValue = newPrefix; + for (UniqueId.Segment segment : suffix) { + newValue = newValue.append(segment); + } + return newValue; + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index 6ad584b728df..ad1ef49fc983 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -48,6 +48,31 @@ */ class TestFactoryTestDescriptorTests { + @Test + void copyIncludesTransformedDynamicDescendantFilter() throws Exception { + var rootUniqueId = UniqueId.forEngine("engine"); + var parentUniqueId = rootUniqueId.append("class", "myClass"); + var originalUniqueId = parentUniqueId.append("old", "testFactory()"); + + var configuration = mock(JupiterConfiguration.class); + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); + var original = new TestFactoryTestDescriptor(originalUniqueId, CustomStreamTestCase.class, testMethod, List::of, + configuration); + + original.getDynamicDescendantFilter().allowUniqueIdPrefix(originalUniqueId.append("foo", "bar")); + original.getDynamicDescendantFilter().allowIndex(42); + + var newUniqueId = parentUniqueId.append("new", "testFactory()"); + + var copy = original.withUniqueId(new UniqueIdPrefixTransformer(originalUniqueId, newUniqueId)); + + assertThat(copy.getUniqueId()).isEqualTo(newUniqueId); + assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 0)).isTrue(); + assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 42)).isTrue(); + assertThat(copy.getDynamicDescendantFilter().test(originalUniqueId, 1)).isFalse(); + } + /** * @since 5.3 */ diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java index c0dab4d6a66e..59a5ec415b7a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Set; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -33,20 +34,22 @@ * @since 5.0 */ class TestTemplateTestDescriptorTests { + private JupiterConfiguration jupiterConfiguration = mock(); + @BeforeEach + void prepareJupiterConfiguration() { + when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + } + @Test void inheritsTagsFromParent() throws Exception { - UniqueId rootUniqueId = UniqueId.root("segment", "template"); - UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); - AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, - singleton(TestTag.create("foo"))); - - when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + var rootUniqueId = UniqueId.root("segment", "template"); + var parentUniqueId = rootUniqueId.append("class", "myClass"); + var parent = containerTestDescriptorWithTags(parentUniqueId, singleton(TestTag.create("foo"))); - TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( - parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); + var testDescriptor = new TestTemplateTestDescriptor(parentUniqueId.append("tmp", "testTemplate()"), + MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), @@ -55,16 +58,14 @@ void inheritsTagsFromParent() throws Exception { @Test void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exception { - UniqueId rootUniqueId = UniqueId.root("segment", "template"); - UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); - AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, - singleton(TestTag.create("foo"))); + var rootUniqueId = UniqueId.root("segment", "template"); + var parentUniqueId = rootUniqueId.append("class", "myClass"); + var parent = containerTestDescriptorWithTags(parentUniqueId, singleton(TestTag.create("foo"))); when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( - parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); + var testDescriptor = new TestTemplateTestDescriptor(parentUniqueId.append("tmp", "testTemplate()"), + MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); @@ -72,21 +73,39 @@ void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exce @Test void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exception { - UniqueId rootUniqueId = UniqueId.root("segment", "template"); - UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); - AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, - singleton(TestTag.create("foo"))); - - when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + var rootUniqueId = UniqueId.root("segment", "template"); + var parentUniqueId = rootUniqueId.append("class", "myClass"); + var parent = containerTestDescriptorWithTags(parentUniqueId, singleton(TestTag.create("foo"))); - TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( - parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); + var testDescriptor = new TestTemplateTestDescriptor(parentUniqueId.append("tmp", "testTemplate()"), + MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); } + @Test + void copyIncludesTransformedDynamicDescendantFilter() throws Exception { + var rootUniqueId = UniqueId.root("segment", "template"); + var parentUniqueId = rootUniqueId.append("class", "myClass"); + var originalUniqueId = parentUniqueId.append("old", "testTemplate()"); + + var original = new TestTemplateTestDescriptor(originalUniqueId, MyTestCase.class, + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); + + original.getDynamicDescendantFilter().allowUniqueIdPrefix(originalUniqueId.append("foo", "bar")); + original.getDynamicDescendantFilter().allowIndex(42); + + var newUniqueId = parentUniqueId.append("new", "testTemplate()"); + + var copy = original.withUniqueId(new UniqueIdPrefixTransformer(originalUniqueId, newUniqueId)); + + assertThat(copy.getUniqueId()).isEqualTo(newUniqueId); + assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 0)).isTrue(); + assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 42)).isTrue(); + assertThat(copy.getDynamicDescendantFilter().test(originalUniqueId, 1)).isFalse(); + } + private AbstractTestDescriptor containerTestDescriptorWithTags(UniqueId uniqueId, Set tags) { return new AbstractTestDescriptor(uniqueId, "testDescriptor with tags") { From bffb6c79107a8b77d520c3ab7447073661618bc0 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 14 Feb 2025 17:23:43 +0100 Subject: [PATCH 59/60] Allow returning a stream of context implementations --- .../ContainerTemplateInvocationContextProvider.java | 3 ++- .../jupiter/engine/ContainerTemplateInvocationTests.java | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java index 6efe00f2c17e..f0b62eca95dc 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.java @@ -95,7 +95,8 @@ public interface ContainerTemplateInvocationContextProvider extends Extension { * @see #supportsContainerTemplate * @see ExtensionContext */ - Stream provideContainerTemplateInvocationContexts(ExtensionContext context); + Stream provideContainerTemplateInvocationContexts( + ExtensionContext context); /** * Signal that this provider may provide zero diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java index ea817384000f..e621393f1f80 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ContainerTemplateInvocationTests.java @@ -960,8 +960,7 @@ public boolean supportsContainerTemplate(ExtensionContext context) { } @Override - public Stream provideContainerTemplateInvocationContexts( - ExtensionContext context) { + public Stream provideContainerTemplateInvocationContexts(ExtensionContext context) { var suffix = " of %s".formatted(context.getRequiredTestClass().getSimpleName()); return Stream.of(new Ctx("A" + suffix), new Ctx("B" + suffix)); } @@ -993,8 +992,7 @@ public boolean supportsContainerTemplate(ExtensionContext context) { } @Override - public Stream provideContainerTemplateInvocationContexts( - ExtensionContext context) { + public Stream provideContainerTemplateInvocationContexts(ExtensionContext context) { return Stream.of(new Data("A"), new Data("B")).map(Ctx::new); } } From 82748e9f36bc133bd1aced4a39f36a561de17a8d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 17 Feb 2025 16:35:36 +0100 Subject: [PATCH 60/60] Polishing --- .../descriptor/ContainerTemplateInvocationTestDescriptor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java index 3b0335c5aa2a..64cbdbe4296d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ContainerTemplateInvocationTestDescriptor.java @@ -120,7 +120,8 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte ExtensionContext extensionContext = new ContainerTemplateInvocationExtensionContext( context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), registry); return context.extend() // - .withExtensionContext(extensionContext).withExtensionRegistry(registry) // + .withExtensionContext(extensionContext) // + .withExtensionRegistry(registry) // .build(); }