From 64f593db8f0209d4971722314ff159a5e7266b76 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 20 Nov 2013 19:44:55 +0100 Subject: [PATCH] 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 --- .../ContextConfigurationAttributes.java | 43 +++- .../test/context/ContextLoaderUtils.java | 87 ++++---- .../test/context/MetaAnnotationUtils.java | 32 ++- .../test/context/TestContextManager.java | 24 +-- .../junit4/SpringJUnit4ClassRunner.java | 13 +- .../DirtiesContextTestExecutionListener.java | 43 ++-- .../TransactionalTestExecutionListener.java | 34 ++-- .../AbstractContextLoaderUtilsTests.java | 19 ++ ...ContextLoaderUtilsActiveProfilesTests.java | 32 +++ ...aderUtilsConfigurationAttributesTests.java | 18 ++ ...ntextLoaderUtilsContextHierarchyTests.java | 74 ++++--- .../context/MetaAnnotationUtilsTests.java | 92 +++++++-- ...erriddenMetaAnnotationAttributesTests.java | 155 ++++++++++++++ .../context/TestExecutionListenersTests.java | 75 ++++++- .../junit4/SpringJUnit4ClassRunnerTests.java | 58 +++++- .../junit4/TimedSpringRunnerTests.java | 24 ++- .../junit4/annotation/meta/MetaConfig.java | 68 +++++++ .../meta/MetaConfigDefaultsTests.java | 45 +++++ .../meta/MetaConfigOverrideTests.java | 66 ++++++ ...tiesContextTestExecutionListenerTests.java | 70 ++++++- ...ansactionalTestExecutionListenerTests.java | 191 +++++++++++++++++- 21 files changed, 1099 insertions(+), 164 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfig.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigOverrideTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java index 035483a71e..fcf1ab05b8 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java @@ -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>[]) annAttrs.getClassArray("initializers"), + annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), + (Class) annAttrs.getClass("loader")); + } + /** * Construct a new {@link ContextConfigurationAttributes} instance for the * {@linkplain Class test class} that declared the diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index 30a2799494..0785630604 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -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 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 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 { * present 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> resolveContextHierarchyAttributes(Class testClass) { Assert.notNull(testClass, "Class must not be null"); + Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present"); final Class contextConfigType = ContextConfiguration.class; final Class 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 configAttributesList = new ArrayList(); 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 activeProfiles = new HashSet(); 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 resolverClass = annotation.resolver(); + String[] profiles = annAttrs.getStringArray("profiles"); + String[] valueProfiles = annAttrs.getStringArray("value"); + Class 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 resolverClass = annotation.resolver(); + private static void validateActiveProfilesConfiguration(Class declaringClass, AnnotationAttributes annAttrs) { + String[] valueProfiles = annAttrs.getStringArray("value"); + String[] profiles = annAttrs.getStringArray("profiles"); + Class resolverClass = annAttrs.getClass("resolver"); boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); diff --git a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java index de842020d0..06b3e8d963 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java @@ -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 { *

* 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 declaring class is not directly + * present. In such cases, the root declaring class is not directly * annotated with the annotation but rather indirectly via the stereotype. * *

@@ -133,6 +135,7 @@ abstract class MetaAnnotationUtils { * properties of the {@code AnnotationDescriptor} would be as follows. * *

    + *
  • rootDeclaringClass: {@code TransactionalTests} class object
  • *
  • declaringClass: {@code TransactionalTests} class object
  • *
  • stereotype: {@code null}
  • *
  • annotation: instance of the {@code Transactional} annotation
  • @@ -150,7 +153,8 @@ abstract class MetaAnnotationUtils { * properties of the {@code AnnotationDescriptor} would be as follows. * *
      - *
    • declaringClass: {@code UserRepositoryTests} class object
    • + *
    • rootDeclaringClass: {@code UserRepositoryTests} class object
    • + *
    • declaringClass: {@code RepositoryTests} class object
    • *
    • stereotype: instance of the {@code RepositoryTests} annotation
    • *
    • annotation: instance of the {@code Transactional} annotation
    • *
    @@ -170,22 +174,31 @@ abstract class MetaAnnotationUtils { */ public static class AnnotationDescriptor { + 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)// diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java index 22c6919bb3..736b724e67 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java @@ -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 annotationType = TestExecutionListeners.class; List> classesList = new ArrayList>(); AnnotationDescriptor 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[] valueListenerClasses = testExecutionListeners.value(); - Class[] listenerClasses = testExecutionListeners.listeners(); + Class[] valueListenerClasses = (Class[]) annAttrs.getClassArray("value"); + Class[] listenerClasses = (Class[]) 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.> asList(listenerClasses)); } - descriptor = (testExecutionListeners.inheritListeners() ? findAnnotationDescriptor( - rootDeclaringClass.getSuperclass(), annotationType) : null); + descriptor = (annAttrs.getBoolean("inheritListeners") ? findAnnotationDescriptor( + descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java index 79c577b764..aa0fd1d0bc 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java @@ -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. getNumber("millis").longValue(); + return millis > 0 ? millis : 0; + } } /** diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java index 50bb5aa323..22b57df589 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java @@ -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 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. 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. getEnum("hierarchyMode") + : classAnnAttrs. 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 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. getEnum("hierarchyMode"); dirtyContext(testContext, hierarchyMode); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index 428d178f8a..7223e3ff8f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -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; } diff --git a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java index e2a6fe305e..4dbd94bbbc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java @@ -110,6 +110,17 @@ abstract class AbstractContextLoaderUtilsTests { public static @interface MetaLocationsFooConfig { } + @ContextConfiguration + @ActiveProfiles + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface MetaLocationsFooConfigWithOverrides { + + String[] locations() default "/foo.xml"; + + String[] profiles() default "foo"; + } + @ContextConfiguration("/bar.xml") @ActiveProfiles(profiles = "bar") @Retention(RetentionPolicy.RUNTIME) @@ -125,6 +136,14 @@ abstract class AbstractContextLoaderUtilsTests { static class MetaLocationsBar extends MetaLocationsFoo { } + @MetaLocationsFooConfigWithOverrides + static class MetaLocationsFooWithOverrides { + } + + @MetaLocationsFooConfigWithOverrides(locations = { "foo1.xml", "foo2.xml" }, profiles = { "foo1", "foo2" }) + static class MetaLocationsFooWithOverriddenAttributes { + } + @ContextConfiguration(locations = "/foo.xml", inheritLocations = false) @ActiveProfiles(profiles = "foo") static class LocationsFoo { diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java index d1c49ad278..74ad3c2e19 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java @@ -121,6 +121,26 @@ public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoader assertArrayEquals(new String[] { "foo" }, profiles); } + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithMetaAnnotationAndOverrides() { + String[] profiles = resolveActiveProfiles(MetaLocationsFooWithOverrides.class); + assertNotNull(profiles); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithMetaAnnotationAndOverriddenAttributes() { + String[] profiles = resolveActiveProfiles(MetaLocationsFooWithOverriddenAttributes.class); + assertNotNull(profiles); + assertArrayEquals(new String[] { "foo1", "foo2" }, profiles); + } + /** * @since 4.0 */ @@ -256,6 +276,18 @@ public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoader private static @interface MetaAnimalsConfig { } + @ActiveProfiles + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + private static @interface MetaProfilesWithOverrides { + + String[] profiles() default { "dog", "cat" }; + + Class resolver() default ActiveProfilesResolver.class; + + boolean inheritProfiles() default false; + } + @MetaAnimalsConfig private static class MetaAnimals extends MetaLocationsBar { } diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java index 471243eb42..d41708a120 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java @@ -83,6 +83,24 @@ public class ContextLoaderUtilsConfigurationAttributesTests extends AbstractCont EMPTY_CLASS_ARRAY, ContextLoader.class, true); } + @Test + public void resolveConfigAttributesWithMetaAnnotationAndLocationsAndOverrides() { + List attributesList = resolveContextConfigurationAttributes(MetaLocationsFooWithOverrides.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertAttributes(attributesList.get(0), MetaLocationsFooConfigWithOverrides.class, new String[] { "/foo.xml" }, + EMPTY_CLASS_ARRAY, ContextLoader.class, true); + } + + @Test + public void resolveConfigAttributesWithMetaAnnotationAndLocationsAndOverriddenAttributes() { + List attributesList = resolveContextConfigurationAttributes(MetaLocationsFooWithOverriddenAttributes.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertAttributes(attributesList.get(0), MetaLocationsFooConfigWithOverrides.class, new String[] { "foo1.xml", + "foo2.xml" }, EMPTY_CLASS_ARRAY, ContextLoader.class, true); + } + @Test public void resolveConfigAttributesWithMetaAnnotationAndLocationsInClassHierarchy() { List attributesList = resolveContextConfigurationAttributes(MetaLocationsBar.class); diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java index 2b839bbbca..2a0b24820c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java @@ -55,13 +55,9 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextLoad resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchyOnSingleMetaAnnotation.class); } - @Test + @Test(expected = IllegalStateException.class) public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class); - assertEquals(1, hierarchyAttributes.size()); - List configAttributesList = hierarchyAttributes.get(0); - assertEquals(1, configAttributesList.size()); - debugConfigAttributes(configAttributesList); + resolveContextHierarchyAttributes(BareAnnotations.class); } @Test @@ -98,8 +94,8 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextLoad debugConfigAttributes(configAttributesList); assertAttributes(configAttributesList.get(0), testClass, new String[] { "A.xml" }, EMPTY_CLASS_ARRAY, ContextLoader.class, true); - assertAttributes(configAttributesList.get(1), testClass, new String[] { "B.xml" }, - EMPTY_CLASS_ARRAY, ContextLoader.class, true); + assertAttributes(configAttributesList.get(1), testClass, new String[] { "B.xml" }, EMPTY_CLASS_ARRAY, + ContextLoader.class, true); assertAttributes(configAttributesList.get(2), testClass, new String[] { "C.xml" }, EMPTY_CLASS_ARRAY, ContextLoader.class, true); } @@ -154,36 +150,39 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextLoad EMPTY_CLASS_ARRAY, ContextLoader.class, true); } - @Test - public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSubclass() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSubclass.class); + private void assertOneTwo(List> hierarchyAttributes) { assertEquals(2, hierarchyAttributes.size()); List configAttributesListClassLevel1 = hierarchyAttributes.get(0); + List configAttributesListClassLevel2 = hierarchyAttributes.get(1); debugConfigAttributes(configAttributesListClassLevel1); + debugConfigAttributes(configAttributesListClassLevel2); + assertEquals(1, configAttributesListClassLevel1.size()); assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); - List configAttributesListClassLevel2 = hierarchyAttributes.get(1); - debugConfigAttributes(configAttributesListClassLevel2); assertEquals(1, configAttributesListClassLevel2.size()); assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml")); } @Test public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSuperclass() { - List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSuperclass.class); - assertEquals(2, hierarchyAttributes.size()); + assertOneTwo(resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSuperclass.class)); + } - List configAttributesListClassLevel1 = hierarchyAttributes.get(0); - debugConfigAttributes(configAttributesListClassLevel1); - assertEquals(1, configAttributesListClassLevel1.size()); - assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml")); + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSubclass() { + assertOneTwo(resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSubclass.class)); + } - List configAttributesListClassLevel2 = hierarchyAttributes.get(1); - debugConfigAttributes(configAttributesListClassLevel2); - assertEquals(1, configAttributesListClassLevel2.size()); - assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml")); + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareMetaContextConfigWithOverridesInSuperclass() { + assertOneTwo(resolveContextHierarchyAttributes(TestClass2WithBareMetaContextConfigWithOverridesInSuperclass.class)); + } + + @Test + public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareMetaContextConfigWithOverridesInSubclass() { + assertOneTwo(resolveContextHierarchyAttributes(TestClass2WithBareMetaContextConfigWithOverridesInSubclass.class)); } @Test @@ -408,7 +407,7 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextLoad @ContextConfiguration("two.xml") private static class TestClass2WithBareContextConfigurationInSubclass extends - TestClass1WithBareContextConfigurationInSuperclass { + TestClass1WithBareContextConfigurationInSubclass { } @ContextHierarchy({// @@ -569,4 +568,31 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextLoad TestClass2WithSingleLevelContextHierarchyFromMetaAnnotation { } + // ------------------------------------------------------------------------- + + @ContextConfiguration + @Retention(RetentionPolicy.RUNTIME) + private static @interface ContextConfigWithOverrides { + + String[] locations() default "A.xml"; + } + + @ContextConfigWithOverrides(locations = "one.xml") + private static class TestClass1WithBareMetaContextConfigWithOverridesInSuperclass { + } + + @ContextHierarchy(@ContextConfiguration(locations = "two.xml")) + private static class TestClass2WithBareMetaContextConfigWithOverridesInSuperclass extends + TestClass1WithBareMetaContextConfigWithOverridesInSuperclass { + } + + @ContextHierarchy(@ContextConfiguration(locations = "one.xml")) + private static class TestClass1WithBareMetaContextConfigWithOverridesInSubclass { + } + + @ContextConfigWithOverrides(locations = "two.xml") + private static class TestClass2WithBareMetaContextConfigWithOverridesInSubclass extends + TestClass1WithBareMetaContextConfigWithOverridesInSubclass { + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java index 1b5b7c521d..a03b6b1e4c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java @@ -43,7 +43,7 @@ public class MetaAnnotationUtilsTests { Class stereotypeType) { AnnotationDescriptor descriptor = findAnnotationDescriptor(startClass, Component.class); assertNotNull(descriptor); - assertEquals(declaringClass, descriptor.getDeclaringClass()); + assertEquals(declaringClass, descriptor.getRootDeclaringClass()); assertEquals(Component.class, descriptor.getAnnotationType()); assertEquals(name, descriptor.getAnnotation().value()); assertNotNull(descriptor.getStereotype()); @@ -57,7 +57,7 @@ public class MetaAnnotationUtilsTests { UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, Service.class, annotationType, Order.class, Transactional.class); assertNotNull(descriptor); - assertEquals(declaringClass, descriptor.getDeclaringClass()); + assertEquals(declaringClass, descriptor.getRootDeclaringClass()); assertEquals(annotationType, descriptor.getAnnotationType()); assertEquals(name, ((Component) descriptor.getAnnotation()).value()); assertNotNull(descriptor.getStereotype()); @@ -74,16 +74,16 @@ public class MetaAnnotationUtilsTests { public void findAnnotationDescriptorWithInheritedAnnotationOnClass() throws Exception { // Note: @Transactional is inherited assertEquals(InheritedAnnotationClass.class, - findAnnotationDescriptor(InheritedAnnotationClass.class, Transactional.class).getDeclaringClass()); + findAnnotationDescriptor(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()); assertEquals(InheritedAnnotationClass.class, - findAnnotationDescriptor(SubInheritedAnnotationClass.class, Transactional.class).getDeclaringClass()); + findAnnotationDescriptor(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()); } @Test public void findAnnotationDescriptorWithInheritedAnnotationOnInterface() throws Exception { // Note: @Transactional is inherited assertEquals(InheritedAnnotationInterface.class, - findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class).getDeclaringClass()); + findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class).getRootDeclaringClass()); assertNull(findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class)); assertNull(findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class)); } @@ -92,16 +92,16 @@ public class MetaAnnotationUtilsTests { public void findAnnotationDescriptorForNonInheritedAnnotationOnClass() throws Exception { // Note: @Order is not inherited. assertEquals(NonInheritedAnnotationClass.class, - findAnnotationDescriptor(NonInheritedAnnotationClass.class, Order.class).getDeclaringClass()); + findAnnotationDescriptor(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()); assertEquals(NonInheritedAnnotationClass.class, - findAnnotationDescriptor(SubNonInheritedAnnotationClass.class, Order.class).getDeclaringClass()); + findAnnotationDescriptor(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()); } @Test public void findAnnotationDescriptorForNonInheritedAnnotationOnInterface() throws Exception { // Note: @Order is not inherited. assertEquals(NonInheritedAnnotationInterface.class, - findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class).getDeclaringClass()); + findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class).getRootDeclaringClass()); assertNull(findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class)); } @@ -116,7 +116,7 @@ public class MetaAnnotationUtilsTests { Class annotationType = Component.class; AnnotationDescriptor descriptor = findAnnotationDescriptor(HasLocalAndMetaComponentAnnotation.class, annotationType); - assertEquals(HasLocalAndMetaComponentAnnotation.class, descriptor.getDeclaringClass()); + assertEquals(HasLocalAndMetaComponentAnnotation.class, descriptor.getRootDeclaringClass()); assertEquals(annotationType, descriptor.getAnnotationType()); assertNull(descriptor.getStereotype()); assertNull(descriptor.getStereotypeType()); @@ -159,10 +159,10 @@ public class MetaAnnotationUtilsTests { public void findAnnotationDescriptorForTypesWithInheritedAnnotationOnClass() throws Exception { // Note: @Transactional is inherited assertEquals(InheritedAnnotationClass.class, - findAnnotationDescriptorForTypes(InheritedAnnotationClass.class, Transactional.class).getDeclaringClass()); + findAnnotationDescriptorForTypes(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()); assertEquals( InheritedAnnotationClass.class, - findAnnotationDescriptorForTypes(SubInheritedAnnotationClass.class, Transactional.class).getDeclaringClass()); + findAnnotationDescriptorForTypes(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()); } @Test @@ -171,7 +171,7 @@ public class MetaAnnotationUtilsTests { // Note: @Transactional is inherited assertEquals( InheritedAnnotationInterface.class, - findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class).getDeclaringClass()); + findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class).getRootDeclaringClass()); assertNull(findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class)); assertNull(findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class)); } @@ -181,9 +181,9 @@ public class MetaAnnotationUtilsTests { public void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnClass() throws Exception { // Note: @Order is not inherited. assertEquals(NonInheritedAnnotationClass.class, - findAnnotationDescriptorForTypes(NonInheritedAnnotationClass.class, Order.class).getDeclaringClass()); + findAnnotationDescriptorForTypes(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()); assertEquals(NonInheritedAnnotationClass.class, - findAnnotationDescriptorForTypes(SubNonInheritedAnnotationClass.class, Order.class).getDeclaringClass()); + findAnnotationDescriptorForTypes(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()); } @Test @@ -191,7 +191,7 @@ public class MetaAnnotationUtilsTests { public void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnInterface() throws Exception { // Note: @Order is not inherited. assertEquals(NonInheritedAnnotationInterface.class, - findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class).getDeclaringClass()); + findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class).getRootDeclaringClass()); assertNull(findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class)); } @@ -201,7 +201,7 @@ public class MetaAnnotationUtilsTests { Class annotationType = Component.class; UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( HasLocalAndMetaComponentAnnotation.class, Transactional.class, annotationType, Order.class); - assertEquals(HasLocalAndMetaComponentAnnotation.class, descriptor.getDeclaringClass()); + assertEquals(HasLocalAndMetaComponentAnnotation.class, descriptor.getRootDeclaringClass()); assertEquals(annotationType, descriptor.getAnnotationType()); assertNull(descriptor.getStereotype()); assertNull(descriptor.getStereotypeType()); @@ -213,6 +213,44 @@ public class MetaAnnotationUtilsTests { assertComponentOnStereotypeForMultipleCandidateTypes(startClass, startClass, "meta1", Meta1.class); } + @Test + @SuppressWarnings("unchecked") + public void findAnnotationDescriptorForTypesWithMetaAnnotationWithDefaultAttributes() throws Exception { + Class startClass = MetaConfigWithDefaultAttributesTestCase.class; + Class annotationType = ContextConfiguration.class; + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, Service.class, + ContextConfiguration.class, Order.class, Transactional.class); + + assertNotNull(descriptor); + assertEquals(startClass, descriptor.getRootDeclaringClass()); + assertEquals(annotationType, descriptor.getAnnotationType()); + assertArrayEquals(new Class[] {}, ((ContextConfiguration) descriptor.getAnnotation()).value()); + assertArrayEquals(new Class[] { MetaConfig.DevConfig.class, MetaConfig.ProductionConfig.class }, + descriptor.getAnnotationAttributes().getClassArray("classes")); + assertNotNull(descriptor.getStereotype()); + assertEquals(MetaConfig.class, descriptor.getStereotypeType()); + } + + @Test + @SuppressWarnings("unchecked") + public void findAnnotationDescriptorForTypesWithMetaAnnotationWithOverriddenAttributes() throws Exception { + Class startClass = MetaConfigWithOverriddenAttributesTestCase.class; + Class annotationType = ContextConfiguration.class; + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, Service.class, + ContextConfiguration.class, Order.class, Transactional.class); + + assertNotNull(descriptor); + assertEquals(startClass, descriptor.getRootDeclaringClass()); + assertEquals(annotationType, descriptor.getAnnotationType()); + assertArrayEquals(new Class[] {}, ((ContextConfiguration) descriptor.getAnnotation()).value()); + assertArrayEquals(new Class[] { MetaAnnotationUtilsTests.class }, + descriptor.getAnnotationAttributes().getClassArray("classes")); + assertNotNull(descriptor.getStereotype()); + assertEquals(MetaConfig.class, descriptor.getStereotypeType()); + } + @Test public void findAnnotationDescriptorForTypesForInterfaceWithMetaAnnotation() { Class startClass = InterfaceWithMetaAnnotation.class; @@ -279,6 +317,28 @@ public class MetaAnnotationUtilsTests { ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface { } + @ContextConfiguration + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaConfig { + + static class DevConfig { + } + + static class ProductionConfig { + } + + + Class[] classes() default { DevConfig.class, ProductionConfig.class }; + } + + @MetaConfig + public class MetaConfigWithDefaultAttributesTestCase { + } + + @MetaConfig(classes = MetaAnnotationUtilsTests.class) + public class MetaConfigWithOverriddenAttributesTestCase { + } + // ------------------------------------------------------------------------- @Transactional diff --git a/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java new file mode 100644 index 0000000000..e920f14890 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor; + +import static org.junit.Assert.*; +import static org.springframework.test.context.MetaAnnotationUtils.*; + +/** + * Unit tests for {@link MetaAnnotationUtils} that verify support for overridden + * meta-annotation attributes. + * + *

    See SPR-10181. + * + * @author Sam Brannen + * @since 4.0 + */ +public class OverriddenMetaAnnotationAttributesTests { + + @Test + public void contextConfigurationValue() throws Exception { + Class declaringClass = MetaValueConfigTest.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertNotNull(descriptor); + assertEquals(declaringClass, descriptor.getRootDeclaringClass()); + assertEquals(MetaValueConfig.class, descriptor.getStereotypeType()); + assertEquals(ContextConfiguration.class, descriptor.getAnnotationType()); + assertNotNull(descriptor.getStereotype()); + assertEquals(MetaValueConfig.class, descriptor.getStereotypeType()); + + // direct access to annotation value: + assertArrayEquals(new String[] { "foo.xml" }, descriptor.getAnnotation().value()); + } + + @Test + public void overriddenContextConfigurationValue() throws Exception { + Class declaringClass = OverriddenMetaValueConfigTest.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertNotNull(descriptor); + assertEquals(declaringClass, descriptor.getRootDeclaringClass()); + assertEquals(MetaValueConfig.class, descriptor.getStereotypeType()); + assertEquals(ContextConfiguration.class, descriptor.getAnnotationType()); + assertNotNull(descriptor.getStereotype()); + assertEquals(MetaValueConfig.class, descriptor.getStereotypeType()); + + // direct access to annotation value: + assertArrayEquals(new String[] { "foo.xml" }, descriptor.getAnnotation().value()); + + // overridden attribute: + AnnotationAttributes attributes = descriptor.getAnnotationAttributes(); + + // NOTE: we would like to be able to override the 'value' attribute; however, + // Spring currently does not allow overrides for the 'value' attribute. + // TODO Determine if Spring should allow overrides for the 'value' attribute in + // custom annotations. + assertArrayEquals(new String[] { "foo.xml" }, attributes.getStringArray("value")); + } + + @Test + public void contextConfigurationLocationsAndInheritLocations() throws Exception { + Class declaringClass = MetaLocationsConfigTest.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertNotNull(descriptor); + assertEquals(declaringClass, descriptor.getRootDeclaringClass()); + assertEquals(MetaLocationsConfig.class, descriptor.getStereotypeType()); + assertEquals(ContextConfiguration.class, descriptor.getAnnotationType()); + assertNotNull(descriptor.getStereotype()); + assertEquals(MetaLocationsConfig.class, descriptor.getStereotypeType()); + + // direct access to annotation attributes: + assertArrayEquals(new String[] { "foo.xml" }, descriptor.getAnnotation().locations()); + assertFalse(descriptor.getAnnotation().inheritLocations()); + } + + @Test + public void overriddenContextConfigurationLocationsAndInheritLocations() throws Exception { + Class declaringClass = OverriddenMetaLocationsConfigTest.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertNotNull(descriptor); + assertEquals(declaringClass, descriptor.getRootDeclaringClass()); + assertEquals(MetaLocationsConfig.class, descriptor.getStereotypeType()); + assertEquals(ContextConfiguration.class, descriptor.getAnnotationType()); + assertNotNull(descriptor.getStereotype()); + assertEquals(MetaLocationsConfig.class, descriptor.getStereotypeType()); + + // direct access to annotation attributes: + assertArrayEquals(new String[] { "foo.xml" }, descriptor.getAnnotation().locations()); + assertFalse(descriptor.getAnnotation().inheritLocations()); + + // overridden attributes: + AnnotationAttributes attributes = descriptor.getAnnotationAttributes(); + assertArrayEquals(new String[] { "bar.xml" }, attributes.getStringArray("locations")); + assertTrue(attributes.getBoolean("inheritLocations")); + } + + + // ------------------------------------------------------------------------- + + @ContextConfiguration("foo.xml") + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaValueConfig { + + String[] value() default {}; + } + + @MetaValueConfig + public static class MetaValueConfigTest { + } + + @MetaValueConfig("bar.xml") + public static class OverriddenMetaValueConfigTest { + } + + @ContextConfiguration(locations = "foo.xml", inheritLocations = false) + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaLocationsConfig { + + String[] locations() default {}; + + boolean inheritLocations(); + } + + @MetaLocationsConfig(inheritLocations = true) + static class MetaLocationsConfigTest { + } + + @MetaLocationsConfig(locations = "bar.xml", inheritLocations = true) + static class OverriddenMetaLocationsConfigTest { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java index 6fd55d2b8e..32483a078c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java @@ -16,14 +16,14 @@ package org.springframework.test.context; -import static org.junit.Assert.assertEquals; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.junit.Test; import org.springframework.test.context.support.AbstractTestExecutionListener; +import static org.junit.Assert.*; + /** *

    * JUnit 4 based unit test for the {@link TestExecutionListeners @@ -114,6 +114,29 @@ public class TestExecutionListenersTests { testContextManager.getTestExecutionListeners().size()); } + @Test + public void verifyNumListenersRegisteredViaMetaAnnotationWithOverrides() throws Exception { + TestContextManager testContextManager = new TestContextManager(MetaWithOverridesExampleTestCase.class); + assertEquals("Num registered TELs for MetaWithOverridesExampleTestCase.", 3, + testContextManager.getTestExecutionListeners().size()); + } + + @Test + public void verifyNumListenersRegisteredViaMetaAnnotationWithInheritedListenersWithOverrides() throws Exception { + TestContextManager testContextManager = new TestContextManager( + MetaInheritedListenersWithOverridesExampleTestCase.class); + assertEquals("Num registered TELs for MetaInheritedListenersWithOverridesExampleTestCase.", 5, + testContextManager.getTestExecutionListeners().size()); + } + + @Test + public void verifyNumListenersRegisteredViaMetaAnnotationWithNonInheritedListenersWithOverrides() throws Exception { + TestContextManager testContextManager = new TestContextManager( + MetaNonInheritedListenersWithOverridesExampleTestCase.class); + assertEquals("Num registered TELs for MetaNonInheritedListenersWithOverridesExampleTestCase.", 8, + testContextManager.getTestExecutionListeners().size()); + } + @Test(expected = IllegalStateException.class) public void verifyDuplicateListenersConfigThrowsException() throws Exception { new TestContextManager(DuplicateListenersConfigExampleTestCase.class); @@ -174,6 +197,32 @@ public class TestExecutionListenersTests { static @interface MetaNonInheritedListeners { } + @TestExecutionListeners + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaListenersWithOverrides { + + Class[] listeners() default { FooTestExecutionListener.class, + BarTestExecutionListener.class }; + } + + @TestExecutionListeners + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaInheritedListenersWithOverrides { + + Class[] listeners() default QuuxTestExecutionListener.class; + + boolean inheritListeners() default true; + } + + @TestExecutionListeners + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaNonInheritedListenersWithOverrides { + + Class[] listeners() default QuuxTestExecutionListener.class; + + boolean inheritListeners() default false; + } + @MetaListeners static class MetaExampleTestCase { } @@ -186,6 +235,28 @@ public class TestExecutionListenersTests { static class MetaNonInheritedListenersExampleTestCase extends MetaInheritedListenersExampleTestCase { } + @MetaListenersWithOverrides(listeners = {// + FooTestExecutionListener.class,// + BarTestExecutionListener.class,// + BazTestExecutionListener.class // + }) + static class MetaWithOverridesExampleTestCase { + } + + @MetaInheritedListenersWithOverrides(listeners = { FooTestExecutionListener.class, BarTestExecutionListener.class }) + static class MetaInheritedListenersWithOverridesExampleTestCase extends MetaWithOverridesExampleTestCase { + } + + @MetaNonInheritedListenersWithOverrides(listeners = {// + FooTestExecutionListener.class,// + BarTestExecutionListener.class,// + BazTestExecutionListener.class // + },// + inheritListeners = true) + static class MetaNonInheritedListenersWithOverridesExampleTestCase extends + MetaInheritedListenersWithOverridesExampleTestCase { + } + static class FooTestExecutionListener extends AbstractTestExecutionListener { } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java index aedd5ad466..3d73e6b11d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2013 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,13 +16,21 @@ package org.springframework.test.context.junit4; -import org.junit.Test; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.junit.Test; +import org.junit.runners.model.FrameworkMethod; +import org.springframework.test.annotation.Timed; import org.springframework.test.context.TestContextManager; +import static org.junit.Assert.*; + /** - * @author Rick Evans + * Unit tests for {@link SpringJUnit4ClassRunner}. + * * @author Sam Brannen + * @author Rick Evans * @since 2.5 */ public class SpringJUnit4ClassRunnerTests { @@ -37,7 +45,8 @@ public class SpringJUnit4ClassRunnerTests { @Override public void prepareTestInstance(Object testInstance) { - throw new RuntimeException("This RuntimeException should be caught and wrapped in an Exception."); + throw new RuntimeException( + "This RuntimeException should be caught and wrapped in an Exception."); } }; } @@ -45,4 +54,45 @@ public class SpringJUnit4ClassRunnerTests { runner.createTest(); } + @Test + public void getSpringTimeoutViaMetaAnnotation() throws Exception { + SpringJUnit4ClassRunner runner = new SpringJUnit4ClassRunner(getClass()); + long timeout = runner.getSpringTimeout(new FrameworkMethod(getClass().getDeclaredMethod( + "springTimeoutWithMetaAnnotation"))); + assertEquals(10, timeout); + } + + @Test + public void getSpringTimeoutViaMetaAnnotationWithOverride() throws Exception { + SpringJUnit4ClassRunner runner = new SpringJUnit4ClassRunner(getClass()); + long timeout = runner.getSpringTimeout(new FrameworkMethod(getClass().getDeclaredMethod( + "springTimeoutWithMetaAnnotationAndOverride"))); + assertEquals(42, timeout); + } + + // ------------------------------------------------------------------------- + + @MetaTimed + void springTimeoutWithMetaAnnotation() { + /* no-op */ + } + + @MetaTimedWithOverride(millis = 42) + void springTimeoutWithMetaAnnotationAndOverride() { + /* no-op */ + } + + + @Timed(millis = 10) + @Retention(RetentionPolicy.RUNTIME) + private static @interface MetaTimed { + } + + @Timed(millis = 1000) + @Retention(RetentionPolicy.RUNTIME) + private static @interface MetaTimedWithOverride { + + long millis() default 1000; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java index fe3f25b2ab..95851f4035 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java @@ -16,8 +16,6 @@ package org.springframework.test.context.junit4; -import static org.junit.Assert.*; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +29,8 @@ import org.springframework.test.context.TestExecutionListeners; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; +import static org.junit.Assert.*; + /** * Verifies proper handling of the following in conjunction with the * {@link SpringJUnit4ClassRunner}: @@ -54,11 +54,11 @@ public class TimedSpringRunnerTests { notifier.addListener(listener); new SpringJUnit4ClassRunner(testClass).run(notifier); - assertEquals("Verifying number of tests started for test class [" + testClass + "].", 6, + assertEquals("Verifying number of tests started for test class [" + testClass + "].", 7, listener.getTestStartedCount()); - assertEquals("Verifying number of failures for test class [" + testClass + "].", 4, + assertEquals("Verifying number of failures for test class [" + testClass + "].", 5, listener.getTestFailureCount()); - assertEquals("Verifying number of tests finished for test class [" + testClass + "].", 6, + assertEquals("Verifying number of tests finished for test class [" + testClass + "].", 7, listener.getTestFinishedCount()); } @@ -101,6 +101,13 @@ public class TimedSpringRunnerTests { Thread.sleep(20); } + // Should Fail due to timeout. + @Test + @MetaTimedWithOverride(millis = 10) + public void springTimeoutWithSleepAndMetaAnnotationAndOverride() throws Exception { + Thread.sleep(20); + } + // Should Fail due to duplicate configuration. @Test(timeout = 200) @Timed(millis = 200) @@ -114,4 +121,11 @@ public class TimedSpringRunnerTests { private static @interface MetaTimed { } + @Timed(millis = 1000) + @Retention(RetentionPolicy.RUNTIME) + private static @interface MetaTimedWithOverride { + + long millis() default 1000; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfig.java new file mode 100644 index 0000000000..51d2ec70a9 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfig.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.annotation.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; + +/** + * Custom configuration annotation with meta-annotation attribute overrides for + * {@link ContextConfiguration#classes} and {@link ActiveProfiles#profiles}. + * + * @author Sam Brannen + * @since 4.0 + */ +@ContextConfiguration +@ActiveProfiles +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MetaConfig { + + @Configuration + @Profile("dev") + static class DevConfig { + + @Bean + public String foo() { + return "Dev Foo"; + } + } + + @Configuration + @Profile("prod") + static class ProductionConfig { + + @Bean + public String foo() { + return "Production Foo"; + } + } + + + Class[] classes() default { DevConfig.class, ProductionConfig.class }; + + String[] profiles() default "dev"; + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java new file mode 100644 index 0000000000..8349aa846a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigDefaultsTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.annotation.meta; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests for meta-annotation attribute override support, relying on + * default attribute values defined in {@link MetaConfig}. + * + * @author Sam Brannen + * @since 4.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@MetaConfig +public class MetaConfigDefaultsTests { + + @Autowired + private String foo; + + + @Test + public void foo() { + assertEquals("Dev Foo", foo); + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigOverrideTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigOverrideTests.java new file mode 100644 index 0000000000..3567d28bdd --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/MetaConfigOverrideTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.annotation.meta; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.annotation.PojoAndStringConfig; +import org.springframework.tests.sample.beans.Employee; +import org.springframework.tests.sample.beans.Pet; + +import static org.junit.Assert.*; + +/** + * Integration tests for meta-annotation attribute override support, overriding + * default attribute values defined in {@link MetaConfig}. + * + * @author Sam Brannen + * @since 4.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@MetaConfig(classes = { PojoAndStringConfig.class, MetaConfig.ProductionConfig.class }, profiles = "prod") +public class MetaConfigOverrideTests { + + @Autowired + private String foo; + + @Autowired + private Pet pet; + + @Autowired + protected Employee employee; + + + @Test + public void verifyEmployee() { + assertNotNull("The employee should have been autowired.", this.employee); + assertEquals("John Smith", this.employee.getName()); + } + + @Test + public void verifyPet() { + assertNotNull("The pet should have been autowired.", this.pet); + assertEquals("Fido", this.pet.getName()); + } + + @Test + public void verifyFoo() { + assertEquals("Production Foo", this.foo); + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java index 10d0f2cec5..b94049842d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/DirtiesContextTestExecutionListenerTests.java @@ -26,8 +26,8 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.context.TestContext; -import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; +import static org.springframework.test.annotation.DirtiesContext.ClassMode.*; import static org.springframework.test.annotation.DirtiesContext.HierarchyMode.*; /** @@ -84,7 +84,7 @@ public class DirtiesContextTestExecutionListenerTests { Mockito.> when(testContext.getTestClass()).thenReturn(clazz); when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("clean")); listener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + verify(testContext, times(0)).markApplicationContextDirty(EXHAUSTIVE); } @Test @@ -93,7 +93,16 @@ public class DirtiesContextTestExecutionListenerTests { Mockito.> when(testContext.getTestClass()).thenReturn(clazz); when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("clean")); listener.afterTestMethod(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + verify(testContext, times(0)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + public void afterTestMethodForDirtiesContextViaMetaAnnotationWithOverrides() throws Exception { + Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; + Mockito.> when(testContext.getTestClass()).thenReturn(clazz); + when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("clean")); + listener.afterTestMethod(testContext); + verify(testContext, times(1)).markApplicationContextDirty(CURRENT_LEVEL); } // ------------------------------------------------------------------------- @@ -103,7 +112,7 @@ public class DirtiesContextTestExecutionListenerTests { Class clazz = getClass(); Mockito.> when(testContext.getTestClass()).thenReturn(clazz); listener.afterTestClass(testContext); - verify(testContext, times(0)).markApplicationContextDirty(any(HierarchyMode.class)); + verify(testContext, times(0)).markApplicationContextDirty(EXHAUSTIVE); } @Test @@ -127,7 +136,7 @@ public class DirtiesContextTestExecutionListenerTests { Class clazz = DirtiesContextDeclaredLocallyAfterClass.class; Mockito.> when(testContext.getTestClass()).thenReturn(clazz); listener.afterTestClass(testContext); - verify(testContext, times(1)).markApplicationContextDirty(any(HierarchyMode.class)); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } @Test @@ -135,7 +144,23 @@ public class DirtiesContextTestExecutionListenerTests { Class clazz = DirtiesContextDeclaredViaMetaAnnotationAfterClass.class; Mockito.> when(testContext.getTestClass()).thenReturn(clazz); listener.afterTestClass(testContext); - verify(testContext, times(1)).markApplicationContextDirty(any(HierarchyMode.class)); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); + } + + @Test + public void afterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverrides() throws Exception { + Class clazz = DirtiesContextViaMetaAnnotationWithOverrides.class; + Mockito.> when(testContext.getTestClass()).thenReturn(clazz); + listener.afterTestClass(testContext); + verify(testContext, times(1)).markApplicationContextDirty(CURRENT_LEVEL); + } + + @Test + public void afterTestClassForDirtiesContextDeclaredViaMetaAnnotationWithOverridenAttributes() throws Exception { + Class clazz = DirtiesContextViaMetaAnnotationWithOverridenAttributes.class; + Mockito.> when(testContext.getTestClass()).thenReturn(clazz); + listener.afterTestClass(testContext); + verify(testContext, times(1)).markApplicationContextDirty(EXHAUSTIVE); } // ------------------------------------------------------------------------- @@ -156,17 +181,17 @@ public class DirtiesContextTestExecutionListenerTests { static @interface MetaDirty { } - @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) @Retention(RetentionPolicy.RUNTIME) static @interface MetaDirtyAfterEachTestMethod { } - @DirtiesContext(classMode = ClassMode.AFTER_CLASS) + @DirtiesContext(classMode = AFTER_CLASS) @Retention(RetentionPolicy.RUNTIME) static @interface MetaDirtyAfterClass { } - @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) static class DirtiesContextDeclaredLocallyAfterEachTestMethod { void clean() { @@ -174,6 +199,15 @@ public class DirtiesContextTestExecutionListenerTests { } } + @DirtiesContext + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaDirtyWithOverrides { + + ClassMode classMode() default AFTER_EACH_TEST_METHOD; + + HierarchyMode hierarchyMode() default HierarchyMode.CURRENT_LEVEL; + } + @MetaDirtyAfterEachTestMethod static class DirtiesContextDeclaredViaMetaAnnotationAfterEachTestMethod { @@ -182,7 +216,7 @@ public class DirtiesContextTestExecutionListenerTests { } } - @DirtiesContext(classMode = ClassMode.AFTER_CLASS) + @DirtiesContext(classMode = AFTER_CLASS) static class DirtiesContextDeclaredLocallyAfterClass { void clean() { @@ -198,4 +232,20 @@ public class DirtiesContextTestExecutionListenerTests { } } + @MetaDirtyWithOverrides + static class DirtiesContextViaMetaAnnotationWithOverrides { + + void clean() { + /* no-op */ + } + } + + @MetaDirtyWithOverrides(classMode = AFTER_CLASS, hierarchyMode = EXHAUSTIVE) + static class DirtiesContextViaMetaAnnotationWithOverridenAttributes { + + void clean() { + /* no-op */ + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java index 83568850cb..fd6fd9c66c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java @@ -21,14 +21,17 @@ import java.lang.annotation.RetentionPolicy; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.TestContext; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.SimpleTransactionStatus; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import static org.springframework.transaction.annotation.Propagation.*; /** * Unit tests for {@link TransactionalTestExecutionListener}. @@ -56,6 +59,11 @@ public class TransactionalTestExecutionListenerTests { } private void assertBeforeTestMethodWithTransactionalTestMethod(Class clazz) throws Exception { + assertBeforeTestMethodWithTransactionalTestMethod(clazz, true); + } + + private void assertBeforeTestMethodWithTransactionalTestMethod(Class clazz, boolean invokedInTx) + throws Exception { Mockito.> when(testContext.getTestClass()).thenReturn(clazz); Invocable instance = clazz.newInstance(); when(testContext.getTestInstance()).thenReturn(instance); @@ -63,7 +71,7 @@ public class TransactionalTestExecutionListenerTests { assertFalse(instance.invoked); listener.beforeTestMethod(testContext); - assertTrue(instance.invoked); + assertEquals(invokedInTx, instance.invoked); } private void assertBeforeTestMethodWithNonTransactionalTestMethod(Class clazz) @@ -109,6 +117,22 @@ public class TransactionalTestExecutionListenerTests { assertFalse(instance.invoked); } + private void assertTransactionConfigurationAttributes(Class clazz, String transactionManagerName, + boolean defaultRollback) { + Mockito.> when(testContext.getTestClass()).thenReturn(clazz); + + TransactionConfigurationAttributes attributes = listener.retrieveConfigurationAttributes(testContext); + assertNotNull(attributes); + assertEquals(transactionManagerName, attributes.getTransactionManagerName()); + assertEquals(defaultRollback, attributes.isDefaultRollback()); + } + + private void assertIsRollback(Class clazz, boolean rollback) throws NoSuchMethodException, Exception { + Mockito.> when(testContext.getTestClass()).thenReturn(clazz); + when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("test")); + assertEquals(rollback, listener.isRollback(testContext)); + } + @Test public void beforeTestMethodWithTransactionalDeclaredOnClassLocally() throws Exception { assertBeforeTestMethodWithTransactionalTestMethod(TransactionalDeclaredOnClassLocallyTestCase.class); @@ -119,6 +143,23 @@ public class TransactionalTestExecutionListenerTests { assertBeforeTestMethodWithTransactionalTestMethod(TransactionalDeclaredOnClassViaMetaAnnotationTestCase.class); } + @Test + public void beforeTestMethodWithTransactionalDeclaredOnClassViaMetaAnnotationWithOverride() throws Exception { + // Note: not actually invoked within a transaction since the test class is + // annotated with @MetaTxWithOverride(propagation = NOT_SUPPORTED) + assertBeforeTestMethodWithTransactionalTestMethod( + TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase.class, false); + } + + @Test + public void beforeTestMethodWithTransactionalDeclaredOnMethodViaMetaAnnotationWithOverride() throws Exception { + // Note: not actually invoked within a transaction since the method is + // annotated with @MetaTxWithOverride(propagation = NOT_SUPPORTED) + assertBeforeTestMethodWithTransactionalTestMethod( + TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class, false); + assertBeforeTestMethodWithNonTransactionalTestMethod(TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class); + } + @Test public void beforeTestMethodWithTransactionalDeclaredOnMethodLocally() throws Exception { assertBeforeTestMethod(TransactionalDeclaredOnMethodLocallyTestCase.class); @@ -149,6 +190,55 @@ public class TransactionalTestExecutionListenerTests { assertAfterTestMethod(AfterTransactionDeclaredViaMetaAnnotationTestCase.class); } + @Test + public void retrieveConfigurationAttributesWithMissingTransactionConfiguration() throws Exception { + assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "transactionManager", + true); + } + + @Test + public void retrieveConfigurationAttributesWithEmptyTransactionConfiguration() throws Exception { + assertTransactionConfigurationAttributes(EmptyTransactionConfigurationTestCase.class, "transactionManager", + true); + } + + @Test + public void retrieveConfigurationAttributesWithExplicitValues() throws Exception { + assertTransactionConfigurationAttributes(TransactionConfigurationWithExplicitValuesTestCase.class, "tm", false); + } + + @Test + public void retrieveConfigurationAttributesViaMetaAnnotation() throws Exception { + assertTransactionConfigurationAttributes(TransactionConfigurationViaMetaAnnotationTestCase.class, "metaTxMgr", + true); + } + + @Test + public void retrieveConfigurationAttributesViaMetaAnnotationWithOverride() throws Exception { + assertTransactionConfigurationAttributes(TransactionConfigurationViaMetaAnnotationWithOverrideTestCase.class, + "overriddenTxMgr", true); + } + + @Test + public void isRollbackWithMissingRollback() throws Exception { + assertIsRollback(MissingRollbackTestCase.class, true); + } + + @Test + public void isRollbackWithEmptyRollback() throws Exception { + assertIsRollback(EmptyRollbackTestCase.class, true); + } + + @Test + public void isRollbackWithExplicitValue() throws Exception { + assertIsRollback(RollbackWithExplicitValueTestCase.class, false); + } + + @Test + public void isRollbackViaMetaAnnotation() throws Exception { + assertIsRollback(RollbackViaMetaAnnotationTestCase.class, false); + } + // ------------------------------------------------------------------------- @@ -157,6 +247,13 @@ public class TransactionalTestExecutionListenerTests { private static @interface MetaTransactional { } + @Transactional + @Retention(RetentionPolicy.RUNTIME) + private static @interface MetaTxWithOverride { + + Propagation propagation() default REQUIRED; + } + @BeforeTransaction @Retention(RetentionPolicy.RUNTIME) private static @interface MetaBeforeTransaction { @@ -167,6 +264,18 @@ public class TransactionalTestExecutionListenerTests { private static @interface MetaAfterTransaction { } + @TransactionConfiguration + @Retention(RetentionPolicy.RUNTIME) + private static @interface MetaTxConfig { + + String transactionManager() default "metaTxMgr"; + } + + @Rollback(false) + @Retention(RetentionPolicy.RUNTIME) + private static @interface Commit { + } + private static abstract class Invocable { boolean invoked = false; @@ -213,10 +322,6 @@ public class TransactionalTestExecutionListenerTests { public void transactionalTest() { /* no-op */ } - - public void nonTransactionalTest() { - /* no-op */ - } } static class TransactionalDeclaredOnMethodViaMetaAnnotationTestCase extends Invocable { @@ -236,6 +341,36 @@ public class TransactionalTestExecutionListenerTests { } } + @MetaTxWithOverride(propagation = NOT_SUPPORTED) + static class TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase extends Invocable { + + @BeforeTransaction + public void beforeTransaction() { + invoked = true; + } + + public void transactionalTest() { + /* no-op */ + } + } + + static class TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase extends Invocable { + + @BeforeTransaction + public void beforeTransaction() { + invoked = true; + } + + @MetaTxWithOverride(propagation = NOT_SUPPORTED) + public void transactionalTest() { + /* no-op */ + } + + public void nonTransactionalTest() { + /* no-op */ + } + } + static class BeforeTransactionDeclaredLocallyTestCase extends Invocable { @BeforeTransaction @@ -304,4 +439,50 @@ public class TransactionalTestExecutionListenerTests { } } + static class MissingTransactionConfigurationTestCase { + } + + @TransactionConfiguration + static class EmptyTransactionConfigurationTestCase { + } + + @TransactionConfiguration(transactionManager = "tm", defaultRollback = false) + static class TransactionConfigurationWithExplicitValuesTestCase { + } + + @MetaTxConfig + static class TransactionConfigurationViaMetaAnnotationTestCase { + } + + @MetaTxConfig(transactionManager = "overriddenTxMgr") + static class TransactionConfigurationViaMetaAnnotationWithOverrideTestCase { + } + + static class MissingRollbackTestCase { + + public void test() { + } + } + + static class EmptyRollbackTestCase { + + @Rollback + public void test() { + } + } + + static class RollbackWithExplicitValueTestCase { + + @Rollback(false) + public void test() { + } + } + + static class RollbackViaMetaAnnotationTestCase { + + @Commit + public void test() { + } + } + }