Ignore @NestedTestConfiguration on enclosing class for nested interface

Closes gh-25917
This commit is contained in:
Sam Brannen 2020-10-14 14:11:10 +02:00
parent 69af56c4e9
commit a271a0a2bf
3 changed files with 34 additions and 22 deletions

View File

@ -36,8 +36,11 @@ import org.springframework.lang.Nullable;
* <p>If {@code @NestedTestConfiguration} is not <em>present</em> or
* <em>meta-present</em> on a test class, in its super type hierarchy, or in its
* enclosing class hierarchy, the default <em>enclosing configuration inheritance
* mode</em> will be used. See {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for
* details on how to change the default mode.
* mode</em> will be used. A {@code @NestedTestConfiguration} declaration on an
* enclosing class for a nested interface will be ignored when searching for the
* annotation on classes that implement the interface. See
* {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for details on how to change
* the default mode.
*
* <p>When the {@link EnclosingConfiguration#INHERIT INHERIT} mode is in use,
* configuration from an enclosing test class will be inherited by inner test

View File

@ -19,6 +19,7 @@ package org.springframework.test.util;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import org.springframework.core.SpringProperties;
import org.springframework.core.annotation.AnnotatedElementUtils;
@ -93,7 +94,15 @@ public abstract class MetaAnnotationUtils {
*/
@Nullable
public static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(clazz, annotationType);
return findMergedAnnotation(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass);
}
@Nullable
private static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType,
Predicate<Class<?>> searchEnclosingClass) {
AnnotationDescriptor<T> descriptor =
findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>());
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
}
@ -125,7 +134,8 @@ public abstract class MetaAnnotationUtils {
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
Class<?> clazz, Class<T> annotationType) {
return findAnnotationDescriptor(clazz, annotationType, new HashSet<>());
return findAnnotationDescriptor(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass,
new HashSet<>());
}
/**
@ -140,7 +150,8 @@ public abstract class MetaAnnotationUtils {
*/
@Nullable
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
@Nullable Class<?> clazz, Class<T> annotationType, Set<Annotation> visited) {
@Nullable Class<?> clazz, Class<T> annotationType, Predicate<Class<?>> searchEnclosingClass,
Set<Annotation> visited) {
Assert.notNull(annotationType, "Annotation type must not be null");
if (clazz == null || Object.class == clazz) {
@ -152,11 +163,13 @@ public abstract class MetaAnnotationUtils {
return new AnnotationDescriptor<>(clazz, clazz.getAnnotation(annotationType));
}
AnnotationDescriptor<T> descriptor = null;
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
Class<? extends Annotation> composedType = composedAnn.annotationType();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedType, annotationType, visited);
descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return new AnnotationDescriptor<>(
clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation());
@ -166,7 +179,7 @@ public abstract class MetaAnnotationUtils {
// Declared on an interface?
for (Class<?> ifc : clazz.getInterfaces()) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(ifc, annotationType, visited);
descriptor = findAnnotationDescriptor(ifc, annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(),
descriptor.getComposedAnnotation(), descriptor.getAnnotation());
@ -174,15 +187,14 @@ public abstract class MetaAnnotationUtils {
}
// Declared on a superclass?
AnnotationDescriptor<T> descriptor =
findAnnotationDescriptor(clazz.getSuperclass(), annotationType, visited);
descriptor = findAnnotationDescriptor(clazz.getSuperclass(), annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return descriptor;
}
// Declared on an enclosing class of an inner class?
if (searchEnclosingClass(clazz)) {
descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, visited);
if (searchEnclosingClass.test(clazz)) {
descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, searchEnclosingClass, visited);
if (descriptor != null) {
return descriptor;
}
@ -301,6 +313,7 @@ public abstract class MetaAnnotationUtils {
* class should be searched
* @since 5.3
* @see ClassUtils#isInnerClass(Class)
* @see NestedTestConfiguration @NestedTestConfiguration
*/
public static boolean searchEnclosingClass(Class<?> clazz) {
return (ClassUtils.isInnerClass(clazz) &&
@ -319,11 +332,13 @@ public abstract class MetaAnnotationUtils {
}
private static EnclosingConfiguration lookUpEnclosingConfiguration(Class<?> clazz) {
return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
.stream(NestedTestConfiguration.class)
.map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class))
.findFirst()
.orElseGet(MetaAnnotationUtils::getDefaultEnclosingConfigurationMode);
// @NestedTestConfiguration should not be discovered on an enclosing class
// for a nested interface (which is always static), so our predicate simply
// ensures that the candidate class is an inner class.
Predicate<Class<?>> searchEnclosingClass = ClassUtils::isInnerClass;
NestedTestConfiguration nestedTestConfiguration =
findMergedAnnotation(clazz, NestedTestConfiguration.class, searchEnclosingClass);
return (nestedTestConfiguration != null ? nestedTestConfiguration.value() : getDefaultEnclosingConfigurationMode());
}
private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() {

View File

@ -161,12 +161,6 @@ class TestPropertySourceNestedTests {
}
@Nested
// The following explicit INHERIT is necessary since this nested
// test class implements an interface whose enclosing class is
// annotated with @NestedTestConfiguration(OVERRIDE). In other
// words, the local declaration overrides the declaration
// "inherited" via the interface.
@NestedTestConfiguration(INHERIT)
class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface {
@Autowired