diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java index 11f8b1851da1..e76dc54da4d9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java @@ -15,6 +15,7 @@ import static org.junit.platform.commons.support.ModifierSupport.isStatic; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -89,7 +90,41 @@ public interface DisplayNameGenerator { * @param nestedClass the class to generate a name for; never {@code null} * @return the display name for the nested class; never blank */ - String generateDisplayNameForNestedClass(Class nestedClass); + default String generateDisplayNameForNestedClass(Class nestedClass) { + throw new UnsupportedOperationException( + "Implement generateDisplayNameForNestedClass(List>, Class) instead"); + } + + /** + * Generate a display name for the given {@link Nested @Nested} inner test class. + * + *

If it returns {@code null}, the default display name generator will be used instead. + * + * @param enclosingInstanceTypes concrete type of the enclosing instance for the nested class; never {@code null} + * @param nestedClass the class to generate a name for; never {@code null} + * @return the display name for the nested class; never blank + */ + default String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return generateDisplayNameForNestedClass(nestedClass); + } + + /** + * Generate a display name for the given method. + * + *

If it returns {@code null}, the default display name generator will be used instead. + * + * @implNote The class instance supplied as {@code testClass} may differ from + * the class returned by {@code testMethod.getDeclaringClass()} — for + * example, when a test method is inherited from a superclass. + * + * @param testClass the class the test method is invoked on; never {@code null} + * @param testMethod method to generate a display name for; never {@code null} + * @return the display name for the test; never blank + */ + default String generateDisplayNameForMethod(Class testClass, Method testMethod) { + throw new UnsupportedOperationException( + "Implement generateDisplayNameForMethod(List>, Class, Method) instead"); + } /** * Generate a display name for the given method. @@ -100,11 +135,15 @@ public interface DisplayNameGenerator { * the class returned by {@code testMethod.getDeclaringClass()} — for * example, when a test method is inherited from a superclass. * + * @param enclosingInstanceTypes concrete types of the enclosing instance for the nested class; never {@code null} * @param testClass the class the test method is invoked on; never {@code null} * @param testMethod method to generate a display name for; never {@code null} * @return the display name for the test; never blank */ - String generateDisplayNameForMethod(Class testClass, Method testMethod); + default String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return generateDisplayNameForMethod(testClass, testMethod); + } /** * Generate a string representation of the formal parameters of the supplied @@ -243,17 +282,19 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return getSentenceBeginning(nestedClass); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return getSentenceBeginning(enclosingInstanceTypes, nestedClass); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) - + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass) + + getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass, + testMethod); } - private String getSentenceBeginning(Class testClass) { + private String getSentenceBeginning(List> enclosingInstanceTypes, Class testClass) { Class enclosingClass = testClass.getEnclosingClass(); boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); Optional displayName = findAnnotation(testClass, DisplayName.class)// @@ -273,14 +314,19 @@ private String getSentenceBeginning(Class testClass) { return generateDisplayNameForClass(testClass); } + Class enclosingInstanceType = enclosingInstanceTypes.remove(enclosingInstanceTypes.size() - 1); + // Only build prefix based on the enclosing class if the enclosing // class is also configured to use the IndicativeSentences generator. - boolean buildPrefix = findDisplayNameGeneration(enclosingClass)// + boolean buildPrefix = findDisplayNameGeneration(enclosingInstanceType)// .map(DisplayNameGeneration::value)// .filter(IndicativeSentences.class::equals)// .isPresent(); - String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); + String prefix = (buildPrefix + ? getSentenceBeginning(enclosingInstanceTypes, enclosingInstanceType) + + getFragmentSeparator(testClass) + : ""); return prefix + displayName.orElseGet( () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java index b4cf927b3f97..22583f82fa36 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java @@ -14,6 +14,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -95,6 +96,12 @@ static String determineDisplayNameForMethod(Class testClass, Method testMetho createDisplayNameSupplierForMethod(testClass, testMethod, configuration)); } + static String determineDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { + return determineDisplayName(testMethod, + createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration)); + } + static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { return createDisplayNameSupplier(testClass, configuration, generator -> generator.generateDisplayNameForClass(testClass)); @@ -106,12 +113,24 @@ static Supplier createDisplayNameSupplierForNestedClass(Class testCla generator -> generator.generateDisplayNameForNestedClass(testClass)); } + static Supplier createDisplayNameSupplierForNestedClass(List> enclosingInstanceTypes, + Class testClass, JupiterConfiguration configuration) { + return createDisplayNameSupplier(testClass, configuration, + generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes, testClass)); + } + private static Supplier createDisplayNameSupplierForMethod(Class testClass, Method testMethod, JupiterConfiguration configuration) { return createDisplayNameSupplier(testClass, configuration, generator -> generator.generateDisplayNameForMethod(testClass, testMethod)); } + private static Supplier createDisplayNameSupplierForMethod(List> enclosingInstanceTypes, + Class testClass, Method testMethod, JupiterConfiguration configuration) { + return createDisplayNameSupplier(testClass, configuration, + generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod)); + } + private static Supplier createDisplayNameSupplier(Class testClass, JupiterConfiguration configuration, Function generatorFunction) { return () -> findDisplayNameGenerator(testClass) // 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 9c8e4bcb1ce3..67885533e2e3 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 @@ -62,6 +62,12 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor im configuration); } + MethodBasedTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { + this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration), + testClass, testMethod, configuration); + } + MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration) { super(uniqueId, displayName, MethodSource.from(testClass, testMethod), configuration); 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 f8ddd867239a..6a58b86dd266 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 @@ -50,6 +50,12 @@ public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterC super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration); } + public NestedClassTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, Class testClass, + JupiterConfiguration configuration) { + super(uniqueId, testClass, + createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration); + } + // --- TestDescriptor ------------------------------------------------------ @Override 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 1a86ba2e8d9a..af9f4e89a2e3 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 @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.net.URI; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Stream; @@ -67,6 +68,11 @@ public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method t super(uniqueId, testClass, testMethod, configuration); } + public TestFactoryTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { + super(uniqueId, enclosingInstanceTypes, testClass, testMethod, 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 b2c7830da1a9..28efe996cac6 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 @@ -17,6 +17,7 @@ import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.List; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -80,6 +81,12 @@ public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method te this.interceptorCall = defaultInterceptorCall; } + public TestMethodTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { + super(uniqueId, enclosingInstanceTypes, testClass, testMethod, configuration); + this.interceptorCall = defaultInterceptorCall; + } + TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration, ReflectiveInterceptorCall interceptorCall) { super(uniqueId, displayName, testClass, testMethod, configuration); 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 53206b685d07..c015b19551bf 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 @@ -50,6 +50,11 @@ public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method super(uniqueId, testClass, templateMethod, configuration); } + public TestTemplateTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, Class testClass, + Method templateMethod, JupiterConfiguration configuration) { + super(uniqueId, enclosingInstanceTypes, testClass, templateMethod, 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 77b6f69aa58c..7630e10aa9fc 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 @@ -82,8 +82,9 @@ else if (isNestedTestClass.test(testClass)) { @Override 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())))); + return toResolution( + context.addToParent(() -> selectClass(selector.getEnclosingClasses()), parent -> Optional.of( + newNestedClassTestDescriptor(parent, selector.getEnclosingClasses(), selector.getNestedClass())))); } return unresolved(); } @@ -127,6 +128,13 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa configuration); } + private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, + List> enclosingInstanceTypes, Class testClass) { + return new NestedClassTestDescriptor( + parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), + enclosingInstanceTypes, testClass, configuration); + } + private Resolution toResolution(Optional testDescriptor) { return testDescriptor.map(it -> { Class testClass = it.getTestClass(); 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 57df96514da7..3a761dfd28fd 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 @@ -169,6 +169,12 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl JupiterConfiguration configuration) { return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); } + + @Override + protected TestDescriptor createTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, + Class testClass, Method method, JupiterConfiguration configuration) { + return new TestMethodTestDescriptor(uniqueId, enclosingInstanceTypes, testClass, method, configuration); + } }, TEST_FACTORY(new IsTestFactoryMethod(), TestFactoryTestDescriptor.SEGMENT_TYPE, @@ -179,6 +185,13 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl JupiterConfiguration configuration) { return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); } + + @Override + protected TestDescriptor createTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, + Class testClass, Method method, JupiterConfiguration configuration) { + return new TestFactoryTestDescriptor(uniqueId, enclosingInstanceTypes, testClass, method, + configuration); + } }, TEST_TEMPLATE(new IsTestTemplateMethod(), TestTemplateTestDescriptor.SEGMENT_TYPE, @@ -188,6 +201,13 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl JupiterConfiguration configuration) { return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration); } + + @Override + protected TestDescriptor createTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, + Class testClass, Method method, JupiterConfiguration configuration) { + return new TestTemplateTestDescriptor(uniqueId, enclosingInstanceTypes, testClass, method, + configuration); + } }; private final Predicate methodPredicate; @@ -206,8 +226,8 @@ private Optional resolve(List> enclosingClasses, Class< return Optional.empty(); } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // - parent -> Optional.of( - createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); + parent -> Optional.of(createTestDescriptor(createUniqueId(method, parent), enclosingClasses, testClass, + method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { @@ -246,6 +266,9 @@ private UniqueId createUniqueId(Method method, TestDescriptor parent) { protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, JupiterConfiguration configuration); + protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, List> enclosingInstanceTypes, + Class testClass, Method method, JupiterConfiguration configuration); + } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AbstractBaseTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AbstractBaseTests.java new file mode 100644 index 000000000000..d9dc9a5d9106 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AbstractBaseTests.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2024 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; + +@IndicativeSentencesGeneration +abstract class AbstractBaseTests { + + @Nested + class NestedTests { + + @Test + void test() { + } + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioOneTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioOneTests.java new file mode 100644 index 000000000000..3a0e59403f98 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioOneTests.java @@ -0,0 +1,14 @@ +/* + * Copyright 2015-2024 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; + +public class ScenarioOneTests extends AbstractBaseTests { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioTwoTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioTwoTests.java new file mode 100644 index 000000000000..fcb85bd6d360 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioTwoTests.java @@ -0,0 +1,14 @@ +/* + * Copyright 2015-2024 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; + +public class ScenarioTwoTests extends AbstractBaseTests { +}