Skip to content

Commit

Permalink
Ignore @NestedTestConfiguration on enclosing class for nested interface
Browse files Browse the repository at this point in the history
Closes gh-25917
  • Loading branch information
sbrannen committed Oct 14, 2020
1 parent 69af56c commit a271a0a
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@
* <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -125,7 +134,8 @@ public static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Clas
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<>());
}

/**
Expand All @@ -140,7 +150,8 @@ public static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescr
*/
@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) {
Expand All @@ -152,11 +163,13 @@ private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDesc
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());
Expand All @@ -166,23 +179,22 @@ private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDesc

// 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());
}
}

// 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;
}
Expand Down Expand Up @@ -301,6 +313,7 @@ private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(@Nul
* class should be searched
* @since 5.3
* @see ClassUtils#isInnerClass(Class)
* @see NestedTestConfiguration @NestedTestConfiguration
*/
public static boolean searchEnclosingClass(Class<?> clazz) {
return (ClassUtils.isInnerClass(clazz) &&
Expand All @@ -319,11 +332,13 @@ private static EnclosingConfiguration getEnclosingConfiguration(Class<?> clazz)
}

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,6 @@ void propertiesInEnvironment() {
}

@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
Expand Down

0 comments on commit a271a0a

Please sign in to comment.