mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-22 05:49:04 +00:00
Allow local @BootstrapWith annotation to override a meta-annotation
This commit revises the resolveExplicitTestContextBootstrapper() algorithm in BootstrapUtils to allow a local @BootstrapWith annotation to override a meta-annotation within the same composed annotation. Closes gh-35938
This commit is contained in:
@@ -19,15 +19,21 @@ package org.springframework.test.context;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -169,25 +175,33 @@ public abstract class BootstrapUtils {
|
||||
}
|
||||
|
||||
private static @Nullable Class<?> resolveExplicitTestContextBootstrapper(Class<?> testClass) {
|
||||
Set<BootstrapWith> annotations = new LinkedHashSet<>();
|
||||
AnnotationDescriptor<BootstrapWith> descriptor =
|
||||
TestContextAnnotationUtils.findAnnotationDescriptor(testClass, BootstrapWith.class);
|
||||
while (descriptor != null) {
|
||||
annotations.addAll(descriptor.findAllLocalMergedAnnotations());
|
||||
descriptor = descriptor.next();
|
||||
}
|
||||
Map<Integer, Set<BootstrapWith>> distanceToAnnotationsMap = MergedAnnotations.search(SearchStrategy.TYPE_HIERARCHY)
|
||||
.withEnclosingClasses(TestContextAnnotationUtils::searchEnclosingClass)
|
||||
.from(testClass)
|
||||
.stream(BootstrapWith.class)
|
||||
// The following effectively filters out annotations in the type and
|
||||
// enclosing class hierarchies once annotations have already been found
|
||||
// on a particular class or interface.
|
||||
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
|
||||
// Grouping by "meta-distance" enables us to allow a directly-present
|
||||
// annotation to override annotations that are meta-present.
|
||||
// Collecting synthesized annotations for each meta-distance enables
|
||||
// us to filter out duplicates.
|
||||
.collect(Collectors.groupingBy(MergedAnnotation::getDistance, TreeMap::new,
|
||||
Collectors.mapping(MergedAnnotation::synthesize, Collectors.toCollection(LinkedHashSet::new))));
|
||||
|
||||
if (annotations.isEmpty()) {
|
||||
if (distanceToAnnotationsMap.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (annotations.size() == 1) {
|
||||
return annotations.iterator().next().value();
|
||||
}
|
||||
|
||||
// Allow directly-present annotation to override annotations that are meta-present.
|
||||
BootstrapWith bootstrapWith = testClass.getDeclaredAnnotation(BootstrapWith.class);
|
||||
if (bootstrapWith != null) {
|
||||
return bootstrapWith.value();
|
||||
Set<BootstrapWith> annotations = new LinkedHashSet<>();
|
||||
for (Set<BootstrapWith> currentAnnotations: distanceToAnnotationsMap.values()) {
|
||||
// If we have found a single, non-competing @BootstrapWith annotation, return it.
|
||||
if (annotations.isEmpty() && currentAnnotations.size() == 1) {
|
||||
return currentAnnotations.iterator().next().value();
|
||||
}
|
||||
// Otherwise, track all discovered annotations for error reporting.
|
||||
annotations.addAll(currentAnnotations);
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
|
||||
@@ -146,13 +146,7 @@ class BootstrapUtilsTests {
|
||||
*/
|
||||
@Test // gh-35938
|
||||
void resolveTestContextBootstrapperWithMetaBootstrapWithAnnotationThatOverridesMetaMetaBootstrapWithAnnotation() {
|
||||
BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(
|
||||
MetaAndMetaMetaBootstrapWithAnnotationsClass.class, delegate);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> resolveTestContextBootstrapper(bootstrapContext))
|
||||
.withMessageContaining("Configuration error: found multiple declarations of @BootstrapWith")
|
||||
.withMessageContaining(FooBootstrapper.class.getSimpleName())
|
||||
.withMessageContaining(BarBootstrapper.class.getSimpleName());
|
||||
assertBootstrapper(MetaAndMetaMetaBootstrapWithAnnotationsClass.class, BarBootstrapper.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user