Discover @Sql annotations on enclosing class for nested test class

This commit introduces support for discovering @Sql, @SqlGroup,
@SqlConfig, and @SqlMergeMode on enclosing classes for @Nested test
classes in JUnit Jupiter.

Closes gh-25913
This commit is contained in:
Sam Brannen
2020-10-22 17:01:32 +02:00
parent 95110d8257
commit b8b854db8c
10 changed files with 376 additions and 97 deletions

View File

@@ -78,6 +78,9 @@ import org.springframework.lang.Nullable;
* <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
* <li>{@link org.springframework.test.annotation.Rollback @Rollback}</li>
* <li>{@link org.springframework.test.annotation.Commit @Commit}</li>
* <li>{@link org.springframework.test.context.jdbc.Sql @Sql}</li>
* <li>{@link org.springframework.test.context.jdbc.SqlConfig @SqlConfig}</li>
* <li>{@link org.springframework.test.context.jdbc.SqlMergeMode @SqlMergeMode}</li>
* <li>{@link TestConstructor @TestConstructor}</li>
* </ul>
*

View File

@@ -19,7 +19,6 @@ package org.springframework.test.context.jdbc;
import java.lang.reflect.Array;
import java.util.Arrays;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.ToStringCreator;
@@ -27,6 +26,7 @@ import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.lang.Nullable;
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import org.springframework.test.util.MetaAnnotationUtils;
import org.springframework.util.Assert;
/**
@@ -100,14 +100,15 @@ class MergedSqlConfig {
enforceCommentPrefixAliases(localAttributes);
// Get global attributes, if any.
AnnotationAttributes globalAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
testClass, SqlConfig.class.getName(), false, false);
SqlConfig globalSqlConfig = MetaAnnotationUtils.findMergedAnnotation(testClass, SqlConfig.class);
// Use local attributes only?
if (globalAttributes == null) {
if (globalSqlConfig == null) {
return localAttributes;
}
AnnotationAttributes globalAttributes = AnnotationUtils.getAnnotationAttributes(globalSqlConfig, false, false);
// Enforce comment prefix aliases within the global @SqlConfig.
enforceCommentPrefixAliases(globalAttributes);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.test.context.jdbc;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
@@ -42,6 +41,7 @@ import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.transaction.TestContextTransactionUtils;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.test.util.MetaAnnotationUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
@@ -162,18 +162,33 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
}
/**
* Get the {@code @SqlMergeMode} annotation declared on the supplied {@code element}.
* Get the {@code @SqlMergeMode} annotation declared on the supplied class.
*/
@Nullable
private SqlMergeMode getSqlMergeModeFor(AnnotatedElement element) {
return AnnotatedElementUtils.findMergedAnnotation(element, SqlMergeMode.class);
private SqlMergeMode getSqlMergeModeFor(Class<?> clazz) {
return MetaAnnotationUtils.findMergedAnnotation(clazz, SqlMergeMode.class);
}
/**
* Get the {@code @Sql} annotations declared on the supplied {@code element}.
* Get the {@code @SqlMergeMode} annotation declared on the supplied method.
*/
private Set<Sql> getSqlAnnotationsFor(AnnotatedElement element) {
return AnnotatedElementUtils.getMergedRepeatableAnnotations(element, Sql.class, SqlGroup.class);
@Nullable
private SqlMergeMode getSqlMergeModeFor(Method method) {
return AnnotatedElementUtils.findMergedAnnotation(method, SqlMergeMode.class);
}
/**
* Get the {@code @Sql} annotations declared on the supplied class.
*/
private Set<Sql> getSqlAnnotationsFor(Class<?> clazz) {
return MetaAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class);
}
/**
* Get the {@code @Sql} annotations declared on the supplied method.
*/
private Set<Sql> getSqlAnnotationsFor(Method method) {
return AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Sql.class, SqlGroup.class);
}
/**

View File

@@ -17,6 +17,7 @@
package org.springframework.test.util;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
@@ -85,12 +86,16 @@ public abstract class MetaAnnotationUtils {
* {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the
* supplied class. The enclosing class hierarchy will only be searched if
* appropriate.
* <p>{@link org.springframework.core.annotation.AliasFor @AliasFor} semantics
* are fully supported, both within a single annotation and within annotation
* hierarchies.
* @param clazz the class to look for annotations on
* @param annotationType the type of annotation to look for
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* @since 5.3
* @see AnnotatedElementUtils#findMergedAnnotation(java.lang.reflect.AnnotatedElement, Class)
* @see #findAnnotationDescriptor(Class, Class)
* @see #searchEnclosingClass(Class)
*/
@Nullable
public static <T extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType) {
@@ -106,6 +111,53 @@ public abstract class MetaAnnotationUtils {
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
}
/**
* Get all <em>repeatable annotations</em> of the specified {@code annotationType}
* within the annotation hierarchy <em>above</em> the supplied class; and for
* each annotation found, merge that annotation's attributes with <em>matching</em>
* attributes from annotations in lower levels of the annotation hierarchy and
* synthesize the results back into an annotation of the specified {@code annotationType}.
* <p>This method will find {@link java.lang.annotation.Inherited @Inherited}
* annotations declared on superclasses if the supplied class does not have
* any local declarations of the repeatable annotation. If no inherited
* annotations are found, this method will search within the
* {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the
* supplied class. The enclosing class hierarchy will only be searched if
* appropriate.
* <p>The container type that holds the repeatable annotations will be looked up
* via {@link java.lang.annotation.Repeatable}.
* <p>{@link org.springframework.core.annotation.AliasFor @AliasFor} semantics
* are fully supported, both within a single annotation and within annotation
* hierarchies.
* @param clazz the class on which to search for annotations (never {@code null})
* @param annotationType the annotation type to find (never {@code null})
* @return the set of all merged repeatable annotations found, or an empty set
* if none were found
* @since 5.3
* @see AnnotatedElementUtils#getMergedRepeatableAnnotations(java.lang.reflect.AnnotatedElement, Class)
* @see #searchEnclosingClass(Class)
*/
public static <T extends Annotation> Set<T> getMergedRepeatableAnnotations(
Class<?> clazz, Class<T> annotationType) {
// Present (via @Inherited semantics), directly present, or meta-present?
Set<T> mergedAnnotations = MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS)
.stream(annotationType)
.collect(MergedAnnotationCollectors.toAnnotationSet());
if (!mergedAnnotations.isEmpty()) {
return mergedAnnotations;
}
// Declared on an enclosing class of an inner class?
if (searchEnclosingClass(clazz)) {
// Then mimic @Inherited semantics within the enclosing class hierarchy.
return getMergedRepeatableAnnotations(clazz.getEnclosingClass(), annotationType);
}
return Collections.emptySet();
}
/**
* Find the {@link AnnotationDescriptor} for the supplied {@code annotationType}
* on the supplied {@link Class}, traversing its annotations, interfaces, and