From 4276dd414071d7b76a7bfc72df5cc6ff0542216a Mon Sep 17 00:00:00 2001 From: neronsoda Date: Wed, 27 Nov 2024 20:57:12 +0900 Subject: [PATCH] DisplayNameGenerator cannot access runtime enclosing type for @Nested test class: #4130 --- .../jupiter/api/DisplayNameGenerator.java | 61 +++++++++++++++---- .../engine/descriptor/DisplayNameUtils.java | 19 ++++++ .../descriptor/MethodBasedTestDescriptor.java | 6 ++ .../descriptor/NestedClassTestDescriptor.java | 4 ++ .../descriptor/TestFactoryTestDescriptor.java | 6 ++ .../descriptor/TestMethodTestDescriptor.java | 7 +++ .../TestTemplateTestDescriptor.java | 5 ++ .../discovery/ClassSelectorResolver.java | 8 ++- .../discovery/MethodSelectorResolver.java | 23 ++++++- .../junit/jupiter/api/AbstractBaseTests.java | 13 ++++ .../junit/jupiter/api/ScenarioOneTests.java | 4 ++ .../junit/jupiter/api/ScenarioTwoTests.java | 4 ++ 12 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/AbstractBaseTests.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioOneTests.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioTwoTests.java 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..4e945a3eb43d 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,39 @@ 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 +133,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 +280,17 @@ 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,17 +310,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)); + () -> 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..713f86520b6c 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..b57f9cb17ec0 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..6321ec07123c 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,10 @@ 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..371fd40f450e 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 @@ -83,7 +83,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(newNestedClassTestDescriptor(parent, selector.getEnclosingClasses(), selector.getNestedClass())))); } return unresolved(); } @@ -127,6 +127,12 @@ 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..1c62e8a8513c 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,12 @@ 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 +200,12 @@ 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; @@ -207,7 +225,7 @@ private Optional resolve(List> enclosingClasses, Class< } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // parent -> Optional.of( - createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); + createTestDescriptor(createUniqueId(method, parent), enclosingClasses, testClass, method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { @@ -246,6 +264,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..b4c0ad2a2b66 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AbstractBaseTests.java @@ -0,0 +1,13 @@ +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..e9504aa381ca --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioOneTests.java @@ -0,0 +1,4 @@ +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..841d10dd22ce --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/ScenarioTwoTests.java @@ -0,0 +1,4 @@ +package org.junit.jupiter.api; + +public class ScenarioTwoTests extends AbstractBaseTests { +}