Support meta-annotation attr overrides in the TCF
Prior to this commit, the Spring TestContext Framework (TCF) supported the use of test-related annotations as meta-annotations for composing custom test stereotype annotations; however, attributes in custom stereotypes could not be used to override meta-annotation attributes. This commit addresses this by allowing attributes from the following annotations (when used as meta-annotations) to be overridden in custom stereotypes. - @ContextConfiguration - @ActiveProfiles - @DirtiesContext - @TransactionConfiguration - @Timed - @TestExecutionListeners This support depends on functionality provided by AnnotatedElementUtils. See the 'Notes' below for further details and ramifications. Notes: - AnnotatedElementUtils does not support overrides for the 'value' attribute of an annotation. It is therefore not possible or not feasible to support meta-annotation attribute overrides for some test-related annotations. - @ContextHierarchy, @WebAppConfiguration, @Rollback, @Repeat, and @ProfileValueSourceConfiguration define single 'value' attributes which cannot be overridden via Spring's meta-annotation attribute support. - Although @IfProfileValue has 'values' and 'name' attributes, the typical usage scenario involves the 'value' attribute which is not supported for meta-annotation attribute overrides. Furthermore, 'name' and 'values' are so generic that it is deemed unfeasible to provide meta-annotation attribute override support for these. - @BeforeTransaction and @AfterTransaction do not define any attributes that can be overridden. - Support for meta-annotation attribute overrides for @Transactional is provided indirectly via SpringTransactionAnnotationParser. Implementation Details: - MetaAnnotationUtils.AnnotationDescriptor now provides access to the AnnotationAttributes for the described annotation. - MetaAnnotationUtils.AnnotationDescriptor now provides access to the root declaring class as well as the declaring class. - ContextLoaderUtils now retrieves AnnotationAttributes from AnnotationDescriptor to look up annotation attributes for @ContextConfiguration and @ActiveProfiles. - ContextConfigurationAttributes now provides a constructor to have its attributes sourced from an instance of AnnotationAttributes. - ContextLoaderUtils.resolveContextHierarchyAttributes() now throws an IllegalStateException if no class in the class hierarchy declares @ContextHierarchy. - TransactionalTestExecutionListener now uses AnnotatedElementUtils to look up annotation attributes for @TransactionConfiguration. - Implemented missing unit tests for @Rollback resolution in TransactionalTestExecutionListener. - SpringJUnit4ClassRunner now uses AnnotatedElementUtils to look up annotation attributes for @Timed. - TestContextManager now retrieves AnnotationAttributes from AnnotationDescriptor to look up annotation attributes for @TestExecutionListeners. - DirtiesContextTestExecutionListener now uses AnnotatedElementUtils to look up annotation attributes for @DirtiesContext. Issue: SPR-11038
This commit is contained in:
@@ -20,9 +20,9 @@ import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -68,21 +68,29 @@ public class ContextConfigurationAttributes {
|
||||
* @throws IllegalStateException if both the locations and value attributes have been declared
|
||||
*/
|
||||
private static String[] resolveLocations(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
|
||||
return resolveLocations(declaringClass, contextConfiguration.locations(), contextConfiguration.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve resource locations from the supplied {@code locations} and
|
||||
* {@code value} arrays, which correspond to attributes of the same names in
|
||||
* the {@link ContextConfiguration} annotation.
|
||||
*
|
||||
* @throws IllegalStateException if both the locations and value attributes have been declared
|
||||
*/
|
||||
private static String[] resolveLocations(Class<?> declaringClass, String[] locations, String[] value) {
|
||||
Assert.notNull(declaringClass, "declaringClass must not be null");
|
||||
|
||||
String[] locations = contextConfiguration.locations();
|
||||
String[] valueLocations = contextConfiguration.value();
|
||||
|
||||
if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) {
|
||||
if (!ObjectUtils.isEmpty(value) && !ObjectUtils.isEmpty(locations)) {
|
||||
String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'value' %s "
|
||||
+ "and 'locations' %s attributes. Only one declaration of resource "
|
||||
+ "locations is permitted per @ContextConfiguration annotation.", declaringClass.getName(),
|
||||
ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations));
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(locations));
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
else if (!ObjectUtils.isEmpty(valueLocations)) {
|
||||
locations = valueLocations;
|
||||
else if (!ObjectUtils.isEmpty(value)) {
|
||||
locations = value;
|
||||
}
|
||||
|
||||
return locations;
|
||||
@@ -101,6 +109,25 @@ public class ContextConfigurationAttributes {
|
||||
contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link ContextConfigurationAttributes} instance for the
|
||||
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
|
||||
* the {@linkplain Class test class} that declared it.
|
||||
* @param declaringClass the test class that declared {@code @ContextConfiguration}
|
||||
* @param annAttrs the annotation attributes from which to retrieve the attributes
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
||||
this(
|
||||
declaringClass,
|
||||
resolveLocations(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getStringArray("value")),
|
||||
annAttrs.getClassArray("classes"),
|
||||
annAttrs.getBoolean("inheritLocations"),
|
||||
(Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"),
|
||||
annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"),
|
||||
(Class<? extends ContextLoader>) annAttrs.getClass("loader"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link ContextConfigurationAttributes} instance for the
|
||||
* {@linkplain Class test class} that declared the
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
|
||||
import org.springframework.test.context.MetaAnnotationUtils.UntypedAnnotationDescriptor;
|
||||
@@ -192,9 +193,9 @@ abstract class ContextLoaderUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for creating a {@link ContextConfigurationAttributes} instance
|
||||
* from the supplied {@link ContextConfiguration} and declaring class and then adding
|
||||
* the attributes to the supplied list.
|
||||
* Convenience method for creating a {@link ContextConfigurationAttributes}
|
||||
* instance from the supplied {@link ContextConfiguration} annotation and
|
||||
* declaring class and then adding the attributes to the supplied list.
|
||||
*/
|
||||
private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration,
|
||||
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
|
||||
@@ -211,6 +212,27 @@ abstract class ContextLoaderUtils {
|
||||
attributesList.add(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for creating a {@link ContextConfigurationAttributes}
|
||||
* instance from the supplied {@link AnnotationAttributes} and declaring
|
||||
* class and then adding the attributes to the supplied list.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs,
|
||||
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
|
||||
annAttrs, declaringClass.getName()));
|
||||
}
|
||||
|
||||
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved context configuration attributes: " + attributes);
|
||||
}
|
||||
attributesList.add(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
|
||||
* configuration attributes} for the supplied {@linkplain Class test class} and its
|
||||
@@ -243,6 +265,8 @@ abstract class ContextLoaderUtils {
|
||||
* <em>present</em> on the supplied class; or if a given class in the class hierarchy
|
||||
* declares both {@code @ContextConfiguration} and {@code @ContextHierarchy} as
|
||||
* top-level annotations.
|
||||
* @throws IllegalStateException if no class in the class hierarchy declares
|
||||
* {@code @ContextHierarchy}.
|
||||
*
|
||||
* @since 3.2.2
|
||||
* @see #buildContextHierarchyMap(Class)
|
||||
@@ -251,6 +275,7 @@ abstract class ContextLoaderUtils {
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
|
||||
Assert.notNull(testClass, "Class must not be null");
|
||||
Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");
|
||||
|
||||
final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
|
||||
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
|
||||
@@ -263,17 +288,16 @@ abstract class ContextLoaderUtils {
|
||||
contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));
|
||||
|
||||
while (descriptor != null) {
|
||||
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
|
||||
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
|
||||
: rootDeclaringClass;
|
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
|
||||
boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
|
||||
boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
|
||||
|
||||
if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
|
||||
String msg = String.format("Test class [%s] has been configured with both @ContextConfiguration "
|
||||
String msg = String.format("Class [%s] has been configured with both @ContextConfiguration "
|
||||
+ "and @ContextHierarchy. Only one of these annotations may be declared on a test class "
|
||||
+ "or custom stereotype annotation.", rootDeclaringClass.getName());
|
||||
+ "or custom stereotype annotation.", declaringClass.getName());
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
@@ -281,9 +305,8 @@ abstract class ContextLoaderUtils {
|
||||
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
|
||||
|
||||
if (contextConfigDeclaredLocally) {
|
||||
ContextConfiguration contextConfiguration = getAnnotation(declaringClass, contextConfigType);
|
||||
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
|
||||
configAttributesList);
|
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
|
||||
declaringClass, configAttributesList);
|
||||
}
|
||||
else if (contextHierarchyDeclaredLocally) {
|
||||
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
|
||||
@@ -293,7 +316,7 @@ abstract class ContextLoaderUtils {
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This should theoretically actually never happen...
|
||||
// This should theoretically never happen...
|
||||
String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
|
||||
+ "nor @ContextHierarchy as a class-level annotation.", rootDeclaringClass.getName());
|
||||
logger.error(msg);
|
||||
@@ -405,13 +428,9 @@ abstract class ContextLoaderUtils {
|
||||
annotationType.getName(), testClass.getName()));
|
||||
|
||||
while (descriptor != null) {
|
||||
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
|
||||
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
|
||||
: rootDeclaringClass;
|
||||
|
||||
convertContextConfigToConfigAttributesAndAddToList(descriptor.getAnnotation(), declaringClass,
|
||||
attributesList);
|
||||
descriptor = findAnnotationDescriptor(rootDeclaringClass.getSuperclass(), annotationType);
|
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
|
||||
descriptor.getDeclaringClass(), attributesList);
|
||||
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
|
||||
}
|
||||
|
||||
return attributesList;
|
||||
@@ -489,20 +508,18 @@ abstract class ContextLoaderUtils {
|
||||
final Set<String> activeProfiles = new HashSet<String>();
|
||||
|
||||
while (descriptor != null) {
|
||||
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
|
||||
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
|
||||
: rootDeclaringClass;
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
|
||||
ActiveProfiles annotation = descriptor.getAnnotation();
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
|
||||
declaringClass.getName()));
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
|
||||
annAttrs, declaringClass.getName()));
|
||||
}
|
||||
validateActiveProfilesConfiguration(declaringClass, annotation);
|
||||
validateActiveProfilesConfiguration(declaringClass, annAttrs);
|
||||
|
||||
String[] profiles = annotation.profiles();
|
||||
String[] valueProfiles = annotation.value();
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
|
||||
String[] profiles = annAttrs.getStringArray("profiles");
|
||||
String[] valueProfiles = annAttrs.getStringArray("value");
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
|
||||
|
||||
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
|
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
|
||||
@@ -538,17 +555,17 @@ abstract class ContextLoaderUtils {
|
||||
}
|
||||
}
|
||||
|
||||
descriptor = annotation.inheritProfiles() ? findAnnotationDescriptor(rootDeclaringClass.getSuperclass(),
|
||||
annotationType) : null;
|
||||
descriptor = annAttrs.getBoolean("inheritProfiles") ? findAnnotationDescriptor(
|
||||
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null;
|
||||
}
|
||||
|
||||
return StringUtils.toStringArray(activeProfiles);
|
||||
}
|
||||
|
||||
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, ActiveProfiles annotation) {
|
||||
String[] valueProfiles = annotation.value();
|
||||
String[] profiles = annotation.profiles();
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
|
||||
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, AnnotationAttributes annAttrs) {
|
||||
String[] valueProfiles = annAttrs.getStringArray("value");
|
||||
String[] profiles = annAttrs.getStringArray("profiles");
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
|
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
|
||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
|
||||
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.test.context;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -124,7 +126,7 @@ abstract class MetaAnnotationUtils {
|
||||
* <p>
|
||||
* If the annotation is used as a meta-annotation, the descriptor also includes
|
||||
* the {@linkplain #getStereotype() stereotype} on which the annotation is
|
||||
* present. In such cases, the <em>declaring class</em> is not directly
|
||||
* present. In such cases, the <em>root declaring class</em> is not directly
|
||||
* annotated with the annotation but rather indirectly via the stereotype.
|
||||
*
|
||||
* <p>
|
||||
@@ -133,6 +135,7 @@ abstract class MetaAnnotationUtils {
|
||||
* properties of the {@code AnnotationDescriptor} would be as follows.
|
||||
*
|
||||
* <ul>
|
||||
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
|
||||
* <li>declaringClass: {@code TransactionalTests} class object</li>
|
||||
* <li>stereotype: {@code null}</li>
|
||||
* <li>annotation: instance of the {@code Transactional} annotation</li>
|
||||
@@ -150,7 +153,8 @@ abstract class MetaAnnotationUtils {
|
||||
* properties of the {@code AnnotationDescriptor} would be as follows.
|
||||
*
|
||||
* <ul>
|
||||
* <li>declaringClass: {@code UserRepositoryTests} class object</li>
|
||||
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
|
||||
* <li>declaringClass: {@code RepositoryTests} class object</li>
|
||||
* <li>stereotype: instance of the {@code RepositoryTests} annotation</li>
|
||||
* <li>annotation: instance of the {@code Transactional} annotation</li>
|
||||
* </ul>
|
||||
@@ -170,22 +174,31 @@ abstract class MetaAnnotationUtils {
|
||||
*/
|
||||
public static class AnnotationDescriptor<T extends Annotation> {
|
||||
|
||||
private final Class<?> rootDeclaringClass;
|
||||
private final Class<?> declaringClass;
|
||||
private final Annotation stereotype;
|
||||
private final T annotation;
|
||||
private final AnnotationAttributes annotationAttributes;
|
||||
|
||||
|
||||
public AnnotationDescriptor(Class<?> declaringClass, T annotation) {
|
||||
this(declaringClass, null, annotation);
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
|
||||
this(rootDeclaringClass, null, annotation);
|
||||
}
|
||||
|
||||
public AnnotationDescriptor(Class<?> declaringClass, Annotation stereotype, T annotation) {
|
||||
Assert.notNull(declaringClass, "declaringClass must not be null");
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, Annotation stereotype, T annotation) {
|
||||
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
|
||||
Assert.notNull(annotation, "annotation must not be null");
|
||||
|
||||
this.declaringClass = declaringClass;
|
||||
this.rootDeclaringClass = rootDeclaringClass;
|
||||
this.declaringClass = (stereotype != null) ? stereotype.annotationType() : rootDeclaringClass;
|
||||
this.stereotype = stereotype;
|
||||
this.annotation = annotation;
|
||||
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
|
||||
annotation.annotationType().getName());
|
||||
}
|
||||
|
||||
public Class<?> getRootDeclaringClass() {
|
||||
return this.rootDeclaringClass;
|
||||
}
|
||||
|
||||
public Class<?> getDeclaringClass() {
|
||||
@@ -200,6 +213,10 @@ abstract class MetaAnnotationUtils {
|
||||
return this.annotation.annotationType();
|
||||
}
|
||||
|
||||
public AnnotationAttributes getAnnotationAttributes() {
|
||||
return this.annotationAttributes;
|
||||
}
|
||||
|
||||
public Annotation getStereotype() {
|
||||
return this.stereotype;
|
||||
}
|
||||
@@ -214,6 +231,7 @@ abstract class MetaAnnotationUtils {
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)//
|
||||
.append("rootDeclaringClass", rootDeclaringClass)//
|
||||
.append("declaringClass", declaringClass)//
|
||||
.append("stereotype", stereotype)//
|
||||
.append("annotation", annotation)//
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -172,13 +173,13 @@ public class TestContextManager {
|
||||
* @param clazz the test class for which the listeners should be retrieved
|
||||
* @return an array of TestExecutionListeners for the specified class
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private TestExecutionListener[] retrieveTestExecutionListeners(Class<?> clazz) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
|
||||
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
|
||||
|
||||
AnnotationDescriptor<TestExecutionListeners> descriptor = findAnnotationDescriptor(clazz, annotationType);
|
||||
|
||||
boolean defaultListeners = false;
|
||||
|
||||
// Use defaults?
|
||||
@@ -192,21 +193,20 @@ public class TestContextManager {
|
||||
else {
|
||||
// Traverse the class hierarchy...
|
||||
while (descriptor != null) {
|
||||
Class<?> rootDeclaringClass = descriptor.getDeclaringClass();
|
||||
Class<?> declaringClass = (descriptor.getStereotype() != null) ? descriptor.getStereotypeType()
|
||||
: rootDeclaringClass;
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
|
||||
TestExecutionListeners testExecutionListeners = declaringClass.getAnnotation(annotationType);
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Retrieved @TestExecutionListeners [" + testExecutionListeners
|
||||
+ "] for declaring class [" + declaringClass + "].");
|
||||
logger.trace(String.format(
|
||||
"Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs,
|
||||
declaringClass));
|
||||
}
|
||||
|
||||
Class<? extends TestExecutionListener>[] valueListenerClasses = testExecutionListeners.value();
|
||||
Class<? extends TestExecutionListener>[] listenerClasses = testExecutionListeners.listeners();
|
||||
Class<? extends TestExecutionListener>[] valueListenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("value");
|
||||
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
|
||||
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
|
||||
String msg = String.format(
|
||||
"Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] "
|
||||
"Class [%s] has been configured with @TestExecutionListeners' 'value' [%s] "
|
||||
+ "and 'listeners' [%s] attributes. Use one or the other, but not both.",
|
||||
declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses),
|
||||
ObjectUtils.nullSafeToString(listenerClasses));
|
||||
@@ -221,8 +221,8 @@ public class TestContextManager {
|
||||
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
|
||||
}
|
||||
|
||||
descriptor = (testExecutionListeners.inheritListeners() ? findAnnotationDescriptor(
|
||||
rootDeclaringClass.getSuperclass(), annotationType) : null);
|
||||
descriptor = (annAttrs.getBoolean("inheritListeners") ? findAnnotationDescriptor(
|
||||
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.junit.runners.BlockJUnit4ClassRunner;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.test.annotation.ProfileValueUtils;
|
||||
import org.springframework.test.annotation.Repeat;
|
||||
@@ -411,8 +413,15 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
|
||||
* @return the timeout, or {@code 0} if none was specified.
|
||||
*/
|
||||
protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
|
||||
Timed timedAnnotation = AnnotationUtils.getAnnotation(frameworkMethod.getMethod(), Timed.class);
|
||||
return (timedAnnotation != null && timedAnnotation.millis() > 0 ? timedAnnotation.millis() : 0);
|
||||
AnnotationAttributes annAttrs = AnnotatedElementUtils.getAnnotationAttributes(frameworkMethod.getMethod(),
|
||||
Timed.class.getName());
|
||||
if (annAttrs == null) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
long millis = annAttrs.<Long> getNumber("millis").longValue();
|
||||
return millis > 0 ? millis : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,13 +21,15 @@ import java.lang.reflect.Method;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
import static org.springframework.test.annotation.DirtiesContext.ClassMode.*;
|
||||
|
||||
/**
|
||||
* {@code TestExecutionListener} which provides support for marking the
|
||||
@@ -81,24 +83,22 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
||||
Method testMethod = testContext.getTestMethod();
|
||||
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
|
||||
|
||||
final Class<DirtiesContext> annotationType = DirtiesContext.class;
|
||||
|
||||
DirtiesContext methodDirtiesContextAnnotation = findAnnotation(testMethod, annotationType);
|
||||
boolean methodDirtiesContext = methodDirtiesContextAnnotation != null;
|
||||
|
||||
DirtiesContext classDirtiesContextAnnotation = findAnnotation(testClass, annotationType);
|
||||
boolean classDirtiesContext = classDirtiesContextAnnotation != null;
|
||||
ClassMode classMode = classDirtiesContext ? classDirtiesContextAnnotation.classMode() : null;
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testMethod, annotationType);
|
||||
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
|
||||
boolean methodDirtiesContext = methodAnnAttrs != null;
|
||||
boolean classDirtiesContext = classAnnAttrs != null;
|
||||
ClassMode classMode = classDirtiesContext ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("After test method: context [" + testContext + "], class dirties context ["
|
||||
+ classDirtiesContext + "], class mode [" + classMode + "], method dirties context ["
|
||||
+ methodDirtiesContext + "].");
|
||||
logger.debug(String.format(
|
||||
"After test method: context %s, class dirties context [%s], class mode [%s], method dirties context [%s].",
|
||||
testContext, classDirtiesContext, classMode, methodDirtiesContext));
|
||||
}
|
||||
|
||||
if (methodDirtiesContext || (classMode == ClassMode.AFTER_EACH_TEST_METHOD)) {
|
||||
HierarchyMode hierarchyMode = methodDirtiesContext ? methodDirtiesContextAnnotation.hierarchyMode()
|
||||
: classDirtiesContextAnnotation.hierarchyMode();
|
||||
if (methodDirtiesContext || (classMode == AFTER_EACH_TEST_METHOD)) {
|
||||
HierarchyMode hierarchyMode = methodDirtiesContext ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode")
|
||||
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
||||
* If the test class of the supplied {@linkplain TestContext test context} is
|
||||
* annotated with {@link DirtiesContext @DirtiesContext}, the
|
||||
* {@linkplain ApplicationContext application context} of the test context will
|
||||
* be {@linkplain TestContext#markApplicationContextDirty() marked as dirty} ,
|
||||
* be {@linkplain TestContext#markApplicationContextDirty() marked as dirty},
|
||||
* and the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
|
||||
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
|
||||
@@ -118,15 +118,16 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
|
||||
|
||||
final Class<DirtiesContext> annotationType = DirtiesContext.class;
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes annAttrs = AnnotatedElementUtils.getAnnotationAttributes(testClass, annotationType);
|
||||
boolean dirtiesContext = annAttrs != null;
|
||||
|
||||
DirtiesContext dirtiesContextAnnotation = findAnnotation(testClass, annotationType);
|
||||
boolean dirtiesContext = dirtiesContextAnnotation != null;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("After test class: context [" + testContext + "], dirtiesContext [" + dirtiesContext + "].");
|
||||
logger.debug(String.format("After test class: context %s, dirtiesContext [%s].", testContext,
|
||||
dirtiesContext));
|
||||
}
|
||||
if (dirtiesContext) {
|
||||
HierarchyMode hierarchyMode = dirtiesContextAnnotation.hierarchyMode();
|
||||
HierarchyMode hierarchyMode = annAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
@@ -414,15 +416,17 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||
if (rollbackAnnotation != null) {
|
||||
boolean rollbackOverride = rollbackAnnotation.value();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback [" + rollback
|
||||
+ "] for test context " + testContext);
|
||||
logger.debug(String.format(
|
||||
"Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.",
|
||||
rollbackOverride, rollback, testContext));
|
||||
}
|
||||
rollback = rollbackOverride;
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No method-level @Rollback override: using default rollback [" + rollback
|
||||
+ "] for test context " + testContext);
|
||||
logger.debug(String.format(
|
||||
"No method-level @Rollback override: using default rollback [%s] for test context %s.", rollback,
|
||||
testContext));
|
||||
}
|
||||
}
|
||||
return rollback;
|
||||
@@ -524,21 +528,25 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||
* {@code @TransactionConfiguration} will be used instead.
|
||||
* @param testContext the test context for which the configuration
|
||||
* attributes should be retrieved
|
||||
* @return a new TransactionConfigurationAttributes instance
|
||||
* @return the TransactionConfigurationAttributes instance for this listener,
|
||||
* potentially cached
|
||||
*/
|
||||
private TransactionConfigurationAttributes retrieveConfigurationAttributes(TestContext testContext) {
|
||||
TransactionConfigurationAttributes retrieveConfigurationAttributes(TestContext testContext) {
|
||||
if (this.configurationAttributes == null) {
|
||||
Class<?> clazz = testContext.getTestClass();
|
||||
TransactionConfiguration config = findAnnotation(clazz, TransactionConfiguration.class);
|
||||
|
||||
AnnotationAttributes annAttrs = AnnotatedElementUtils.getAnnotationAttributes(clazz,
|
||||
TransactionConfiguration.class.getName());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Retrieved @TransactionConfiguration [" + config + "] for test class [" + clazz + "]");
|
||||
logger.debug(String.format("Retrieved @TransactionConfiguration attributes [%s] for test class [%s].",
|
||||
annAttrs, clazz));
|
||||
}
|
||||
|
||||
String transactionManagerName;
|
||||
boolean defaultRollback;
|
||||
if (config != null) {
|
||||
transactionManagerName = config.transactionManager();
|
||||
defaultRollback = config.defaultRollback();
|
||||
if (annAttrs != null) {
|
||||
transactionManagerName = annAttrs.getString("transactionManager");
|
||||
defaultRollback = annAttrs.getBoolean("defaultRollback");
|
||||
}
|
||||
else {
|
||||
transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME;
|
||||
@@ -548,8 +556,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||
TransactionConfigurationAttributes configAttributes = new TransactionConfigurationAttributes(
|
||||
transactionManagerName, defaultRollback);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Retrieved TransactionConfigurationAttributes " + configAttributes + " for class ["
|
||||
+ clazz + "]");
|
||||
logger.debug(String.format("Retrieved TransactionConfigurationAttributes %s for class [%s].",
|
||||
configAttributes, clazz));
|
||||
}
|
||||
this.configurationAttributes = configAttributes;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user