From a271a0a2bf37ad2073badfa28cbc0d0389418360 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 14 Oct 2020 14:11:10 +0200 Subject: [PATCH] Ignore @NestedTestConfiguration on enclosing class for nested interface Closes gh-25917 --- .../test/context/NestedTestConfiguration.java | 7 ++- .../test/util/MetaAnnotationUtils.java | 43 +++++++++++++------ .../nested/TestPropertySourceNestedTests.java | 6 --- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java index a1e3db386a7..fba36b65012 100644 --- a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java @@ -36,8 +36,11 @@ import org.springframework.lang.Nullable; *

If {@code @NestedTestConfiguration} is not present or * meta-present on a test class, in its super type hierarchy, or in its * enclosing class hierarchy, the default enclosing configuration inheritance - * mode will be used. See {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for - * details on how to change the default mode. + * mode 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. * *

When the {@link EnclosingConfiguration#INHERIT INHERIT} mode is in use, * configuration from an enclosing test class will be inherited by inner test diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java index 27f498c8a86..20abe3e7727 100644 --- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java @@ -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 findMergedAnnotation(Class clazz, Class annotationType) { - AnnotationDescriptor descriptor = findAnnotationDescriptor(clazz, annotationType); + return findMergedAnnotation(clazz, annotationType, MetaAnnotationUtils::searchEnclosingClass); + } + + @Nullable + private static T findMergedAnnotation(Class clazz, Class annotationType, + Predicate> searchEnclosingClass) { + + AnnotationDescriptor descriptor = + findAnnotationDescriptor(clazz, annotationType, searchEnclosingClass, new HashSet<>()); return (descriptor != null ? descriptor.synthesizeAnnotation() : null); } @@ -125,7 +134,8 @@ public abstract class MetaAnnotationUtils { public static AnnotationDescriptor findAnnotationDescriptor( Class clazz, Class 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 AnnotationDescriptor findAnnotationDescriptor( - @Nullable Class clazz, Class annotationType, Set visited) { + @Nullable Class clazz, Class annotationType, Predicate> searchEnclosingClass, + Set 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 descriptor = null; + // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnn : clazz.getDeclaredAnnotations()) { Class composedType = composedAnn.annotationType(); if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedType.getName()) && visited.add(composedAnn)) { - AnnotationDescriptor 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 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 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> searchEnclosingClass = ClassUtils::isInnerClass; + NestedTestConfiguration nestedTestConfiguration = + findMergedAnnotation(clazz, NestedTestConfiguration.class, searchEnclosingClass); + return (nestedTestConfiguration != null ? nestedTestConfiguration.value() : getDefaultEnclosingConfigurationMode()); } private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java index fe42ec6d676..3d485974a06 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java @@ -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