Ignore @NestedTestConfiguration on enclosing class for nested interface
Closes gh-25917
This commit is contained in:
parent
69af56c4e9
commit
a271a0a2bf
|
@ -36,8 +36,11 @@ import org.springframework.lang.Nullable;
|
||||||
* <p>If {@code @NestedTestConfiguration} is not <em>present</em> or
|
* <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
|
* <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
|
* enclosing class hierarchy, the default <em>enclosing configuration inheritance
|
||||||
* mode</em> will be used. See {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for
|
* mode</em> will be used. A {@code @NestedTestConfiguration} declaration on an
|
||||||
* details on how to change the default mode.
|
* 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,
|
* <p>When the {@link EnclosingConfiguration#INHERIT INHERIT} mode is in use,
|
||||||
* configuration from an enclosing test class will be inherited by inner test
|
* configuration from an enclosing test class will be inherited by inner test
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.test.util;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.springframework.core.SpringProperties;
|
import org.springframework.core.SpringProperties;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
@ -93,7 +94,15 @@ public abstract class MetaAnnotationUtils {
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType) {
|
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);
|
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +134,8 @@ public abstract class MetaAnnotationUtils {
|
||||||
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
|
public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
|
||||||
Class<?> clazz, Class<T> annotationType) {
|
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
|
@Nullable
|
||||||
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
|
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");
|
Assert.notNull(annotationType, "Annotation type must not be null");
|
||||||
if (clazz == null || Object.class == clazz) {
|
if (clazz == null || Object.class == clazz) {
|
||||||
|
@ -152,11 +163,13 @@ public abstract class MetaAnnotationUtils {
|
||||||
return new AnnotationDescriptor<>(clazz, clazz.getAnnotation(annotationType));
|
return new AnnotationDescriptor<>(clazz, clazz.getAnnotation(annotationType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnnotationDescriptor<T> descriptor = null;
|
||||||
|
|
||||||
// Declared on a composed annotation (i.e., as a meta-annotation)?
|
// Declared on a composed annotation (i.e., as a meta-annotation)?
|
||||||
for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
|
for (Annotation composedAnn : clazz.getDeclaredAnnotations()) {
|
||||||
Class<? extends Annotation> composedType = composedAnn.annotationType();
|
Class<? extends Annotation> composedType = composedAnn.annotationType();
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) {
|
||||||
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedType, annotationType, visited);
|
descriptor = findAnnotationDescriptor(composedType, annotationType, searchEnclosingClass, visited);
|
||||||
if (descriptor != null) {
|
if (descriptor != null) {
|
||||||
return new AnnotationDescriptor<>(
|
return new AnnotationDescriptor<>(
|
||||||
clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation());
|
clazz, descriptor.getDeclaringClass(), composedAnn, descriptor.getAnnotation());
|
||||||
|
@ -166,7 +179,7 @@ public abstract class MetaAnnotationUtils {
|
||||||
|
|
||||||
// Declared on an interface?
|
// Declared on an interface?
|
||||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||||
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(ifc, annotationType, visited);
|
descriptor = findAnnotationDescriptor(ifc, annotationType, searchEnclosingClass, visited);
|
||||||
if (descriptor != null) {
|
if (descriptor != null) {
|
||||||
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(),
|
return new AnnotationDescriptor<>(clazz, descriptor.getDeclaringClass(),
|
||||||
descriptor.getComposedAnnotation(), descriptor.getAnnotation());
|
descriptor.getComposedAnnotation(), descriptor.getAnnotation());
|
||||||
|
@ -174,15 +187,14 @@ public abstract class MetaAnnotationUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declared on a superclass?
|
// Declared on a superclass?
|
||||||
AnnotationDescriptor<T> descriptor =
|
descriptor = findAnnotationDescriptor(clazz.getSuperclass(), annotationType, searchEnclosingClass, visited);
|
||||||
findAnnotationDescriptor(clazz.getSuperclass(), annotationType, visited);
|
|
||||||
if (descriptor != null) {
|
if (descriptor != null) {
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declared on an enclosing class of an inner class?
|
// Declared on an enclosing class of an inner class?
|
||||||
if (searchEnclosingClass(clazz)) {
|
if (searchEnclosingClass.test(clazz)) {
|
||||||
descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, visited);
|
descriptor = findAnnotationDescriptor(clazz.getEnclosingClass(), annotationType, searchEnclosingClass, visited);
|
||||||
if (descriptor != null) {
|
if (descriptor != null) {
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
@ -301,6 +313,7 @@ public abstract class MetaAnnotationUtils {
|
||||||
* class should be searched
|
* class should be searched
|
||||||
* @since 5.3
|
* @since 5.3
|
||||||
* @see ClassUtils#isInnerClass(Class)
|
* @see ClassUtils#isInnerClass(Class)
|
||||||
|
* @see NestedTestConfiguration @NestedTestConfiguration
|
||||||
*/
|
*/
|
||||||
public static boolean searchEnclosingClass(Class<?> clazz) {
|
public static boolean searchEnclosingClass(Class<?> clazz) {
|
||||||
return (ClassUtils.isInnerClass(clazz) &&
|
return (ClassUtils.isInnerClass(clazz) &&
|
||||||
|
@ -319,11 +332,13 @@ public abstract class MetaAnnotationUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EnclosingConfiguration lookUpEnclosingConfiguration(Class<?> clazz) {
|
private static EnclosingConfiguration lookUpEnclosingConfiguration(Class<?> clazz) {
|
||||||
return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
|
// @NestedTestConfiguration should not be discovered on an enclosing class
|
||||||
.stream(NestedTestConfiguration.class)
|
// for a nested interface (which is always static), so our predicate simply
|
||||||
.map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class))
|
// ensures that the candidate class is an inner class.
|
||||||
.findFirst()
|
Predicate<Class<?>> searchEnclosingClass = ClassUtils::isInnerClass;
|
||||||
.orElseGet(MetaAnnotationUtils::getDefaultEnclosingConfigurationMode);
|
NestedTestConfiguration nestedTestConfiguration =
|
||||||
|
findMergedAnnotation(clazz, NestedTestConfiguration.class, searchEnclosingClass);
|
||||||
|
return (nestedTestConfiguration != null ? nestedTestConfiguration.value() : getDefaultEnclosingConfigurationMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() {
|
private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() {
|
||||||
|
|
|
@ -161,12 +161,6 @@ class TestPropertySourceNestedTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@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 {
|
class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
Loading…
Reference in New Issue