Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass enclosing instance types to DisplayNameGenerators #4266

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ JUnit repository on GitHub.
[[release-notes-5.12.0-M1-junit-jupiter-bug-fixes]]
==== Bug Fixes

* ❓
* Provide _runtime_ enclosing types of `@Nested` test classes and contained test methods
to `DisplayNameGenerator` implementations. Prior to this change, such generators were
only able to access the enclosing class in which `@Nested` was declared, but they could
not access the concrete runtime type of the enclosing instance.

[[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@

package org.junit.jupiter.api;

import static java.util.Collections.emptyList;
import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
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;

Expand Down Expand Up @@ -82,14 +86,41 @@ public interface DisplayNameGenerator {
String generateDisplayNameForClass(Class<?> testClass);

/**
* Generate a display name for the given {@link Nested @Nested} inner test class.
* Generate a display name for the given {@link Nested @Nested} inner test
* class.
*
* <p>If it returns {@code null}, the default display name generator will be used instead.
* <p>If it returns {@code null}, the default display name generator will be
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
* used instead.
*
* @param nestedClass the class to generate a name for; never {@code null}
* @return the display name for the nested class; never blank
* @deprecated in favor of {@link #generateDisplayNameForNestedClass(List, Class)}
*/
String generateDisplayNameForNestedClass(Class<?> nestedClass);
@SuppressWarnings("DeprecatedIsStillUsed")
@API(status = DEPRECATED, since = "5.12")
@Deprecated
default String generateDisplayNameForNestedClass(@SuppressWarnings("unused") Class<?> nestedClass) {
throw new UnsupportedOperationException(
"Implement generateDisplayNameForNestedClass(List<Class<?>>, Class<?>) instead");
}

/**
* Generate a display name for the given {@link Nested @Nested} inner test
* class.
*
* <p>If it returns {@code null}, the default display name generator will be
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
* used instead.
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
*
* @param enclosingInstanceTypes the runtime types of the enclosing
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
* instances; 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
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
default String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return generateDisplayNameForNestedClass(nestedClass);
}

/**
* Generate a display name for the given method.
Expand All @@ -103,8 +134,41 @@ public interface DisplayNameGenerator {
* @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
* @deprecated in favor of {@link #generateDisplayNameForMethod(List, Class, Method)}
*/
@SuppressWarnings("DeprecatedIsStillUsed")
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
@API(status = DEPRECATED, since = "5.12")
@Deprecated
default String generateDisplayNameForMethod(@SuppressWarnings("unused") Class<?> testClass,
@SuppressWarnings("unused") Method testMethod) {
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
throw new UnsupportedOperationException(
"Implement generateDisplayNameForMethod(List<Class<?>>, Class<?>, Method) instead");
}

/**
* Generate a display name for the given method.
*
* <p>If it returns {@code null}, the default display name generator will be used instead.
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
*
* @implNote The classes supplied as {@code enclosingInstanceTypes} may differ
* from the classes returned from invocations of {@link Class#getEnclosingClass()}
* &mdash; for example, when a nested test class is inherited from a superclass.
* Similarly, the class instance supplied as {@code testClass} may differ from
* the class returned by {@code testMethod.getDeclaringClass()} &mdash; for
* example, when a test method is inherited from a superclass.
*
* @param enclosingInstanceTypes the runtime types of the enclosing
* instances; 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
* @since 5.12
*/
String generateDisplayNameForMethod(Class<?> testClass, Method testMethod);
@API(status = EXPERIMENTAL, since = "5.12")
default String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return generateDisplayNameForMethod(testClass, testMethod);
}

/**
* Generate a string representation of the formal parameters of the supplied
Expand Down Expand Up @@ -142,12 +206,13 @@ public String generateDisplayNameForClass(Class<?> testClass) {
}

@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return nestedClass.getSimpleName();
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return testMethod.getName() + parameterTypesAsString(testMethod);
}
}
Expand All @@ -168,7 +233,8 @@ public Simple() {
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
String displayName = testMethod.getName();
if (hasParameters(testMethod)) {
displayName += ' ' + parameterTypesAsString(testMethod);
Expand Down Expand Up @@ -202,13 +268,15 @@ public String generateDisplayNameForClass(Class<?> testClass) {
}

@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass));
public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass));
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod));
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return replaceUnderscores(
super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod));
}

private static String replaceUnderscores(String name) {
Expand Down Expand Up @@ -243,18 +311,21 @@ public String generateDisplayNameForClass(Class<?> testClass) {
}

@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return getSentenceBeginning(nestedClass);
public String generateDisplayNameForNestedClass(List<Class<?>> 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<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass)
+ getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass,
testMethod);
}

private String getSentenceBeginning(Class<?> testClass) {
Class<?> enclosingClass = testClass.getEnclosingClass();
private String getSentenceBeginning(List<Class<?>> enclosingInstanceTypes, Class<?> testClass) {
Class<?> enclosingClass = enclosingInstanceTypes.isEmpty() ? null
: enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1);
boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass));
Optional<String> displayName = findAnnotation(testClass, DisplayName.class)//
.map(DisplayName::value).map(String::trim);
Expand All @@ -280,10 +351,16 @@ private String getSentenceBeginning(Class<?> testClass) {
.filter(IndicativeSentences.class::equals)//
.isPresent();

String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : "");
List<Class<?>> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList()
: enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1);

String prefix = (buildPrefix
? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass)
+ getFragmentSeparator(testClass)
: "");

return prefix + displayName.orElseGet(
() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass));
return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(
remainingEnclosingInstanceTypes, testClass));
}

/**
Expand Down Expand Up @@ -349,6 +426,7 @@ private static Optional<IndicativeSentencesGeneration> findIndicativeSentencesGe
SearchOption.INCLUDE_ENCLOSING_CLASSES);
}

@SuppressWarnings("SameParameterValue")
marcphilipp marked this conversation as resolved.
Show resolved Hide resolved
private static Predicate<Class<?>> not(Class<?> clazz) {
return ((Predicate<Class<?>>) clazz::equals).negate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -89,27 +90,27 @@ static String determineDisplayName(AnnotatedElement element, Supplier<String> di
return displayNameSupplier.get();
}

static String determineDisplayNameForMethod(Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
static String determineDisplayNameForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod, JupiterConfiguration configuration) {
return determineDisplayName(testMethod,
createDisplayNameSupplierForMethod(testClass, testMethod, configuration));
createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration));
}

static Supplier<String> createDisplayNameSupplierForClass(Class<?> testClass, JupiterConfiguration configuration) {
return createDisplayNameSupplier(testClass, configuration,
generator -> generator.generateDisplayNameForClass(testClass));
}

static Supplier<String> createDisplayNameSupplierForNestedClass(Class<?> testClass,
JupiterConfiguration configuration) {
static Supplier<String> createDisplayNameSupplierForNestedClass(Supplier<List<Class<?>>> enclosingInstanceTypes,
Class<?> testClass, JupiterConfiguration configuration) {
return createDisplayNameSupplier(testClass, configuration,
generator -> generator.generateDisplayNameForNestedClass(testClass));
generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes.get(), testClass));
}

private static Supplier<String> createDisplayNameSupplierForMethod(Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
private static Supplier<String> createDisplayNameSupplierForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes,
Class<?> testClass, Method testMethod, JupiterConfiguration configuration) {
return createDisplayNameSupplier(testClass, configuration,
generator -> generator.generateDisplayNameForMethod(testClass, testMethod));
generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes.get(), testClass, testMethod));
}

private static Supplier<String> createDisplayNameSupplier(Class<?> testClass, JupiterConfiguration configuration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand Down Expand Up @@ -59,9 +60,9 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor im
private final Set<TestTag> tags;

MethodBasedTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod,
configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration),
testClass, testMethod, configuration);
}

MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class<?> testClass, Method testMethod,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.TestInstances;
Expand Down Expand Up @@ -46,8 +47,10 @@ public class NestedClassTestDescriptor extends ClassBasedTestDescriptor {

public static final String SEGMENT_TYPE = "nested-class";

public NestedClassTestDescriptor(UniqueId uniqueId, Class<?> testClass, JupiterConfiguration configuration) {
super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration);
public NestedClassTestDescriptor(UniqueId uniqueId, Class<?> testClass,
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass,
createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration);
}

// --- TestDescriptor ------------------------------------------------------
Expand All @@ -62,7 +65,11 @@ public final Set<TestTag> getTags() {

@Override
public List<Class<?>> getEnclosingTestClasses() {
TestDescriptor parent = getParent().orElse(null);
return getEnclosingTestClasses(getParent().orElse(null));
}

@API(status = INTERNAL, since = "5.12")
public static List<Class<?>> getEnclosingTestClasses(TestDescriptor parent) {
if (parent instanceof ClassBasedTestDescriptor) {
ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent;
List<Class<?>> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,8 +64,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen
private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

public TestFactoryTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration);
}

// --- Filterable ----------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder;

import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance.Lifecycle;
Expand Down Expand Up @@ -75,8 +77,8 @@ public class TestMethodTestDescriptor extends MethodBasedTestDescriptor {
private final ReflectiveInterceptorCall<Method, Void> interceptorCall;

public TestMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration);
this.interceptorCall = defaultInterceptorCall;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down Expand Up @@ -46,8 +47,8 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implem
private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

public TestTemplateTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method templateMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, templateMethod, configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration);
}

// --- Filterable ----------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import static java.util.function.Predicate.isEqual;
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.HierarchyTraversalMode.TOP_DOWN;
import static org.junit.platform.commons.support.ReflectionSupport.findMethods;
Expand Down Expand Up @@ -122,9 +123,9 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class<
}

private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class<?> testClass) {
return new NestedClassTestDescriptor(
parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass,
configuration);
UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE,
testClass.getSimpleName());
return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration);
}

private Resolution toResolution(Optional<? extends ClassBasedTestDescriptor> testDescriptor) {
Expand Down
Loading
Loading