diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java index c54e7c2d68b..c0f73c3fe5d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java +++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -50,18 +50,16 @@ public @interface ActiveProfiles { /** * Alias for {@link #profiles}. * - *

This attribute may not be used in conjunction - * with {@link #profiles} or {@link #resolver}, but it may be used - * instead of them. + *

This attribute may not be used in conjunction with + * {@link #profiles}, but it may be used instead of {@link #profiles}. */ String[] value() default {}; /** * The bean definition profiles to activate. * - *

This attribute may not be used in conjunction - * with {@link #value} or {@link #resolver}, but it may be used - * instead of them. + *

This attribute may not be used in conjunction with + * {@link #value}, but it may be used instead of {@link #value}. */ String[] profiles() default {}; @@ -69,10 +67,6 @@ public @interface ActiveProfiles { * The type of {@link ActiveProfilesResolver} to use for resolving the active * bean definition profiles programmatically. * - *

This attribute may not be used in conjunction - * with {@link #profiles} or {@link #value}, but it may be used instead - * of them in order to resolve the active profiles programmatically. - * * @since 4.0 * @see ActiveProfilesResolver */ diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java new file mode 100644 index 00000000000..fa12a797e34 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2014 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; + +/** + * {@code BootstrapContext} encapsulates the context in which the Spring + * TestContext Framework is bootstrapped. + * + * @author Sam Brannen + * @since 4.1 + * @see BootstrapWith + * @see TestContextBootstrapper + */ +public interface BootstrapContext { + + /** + * Get the {@link Class test class} for this bootstrap context. + * @return the test class (never {@code null}) + */ + Class getTestClass(); + + /** + * Get the {@link CacheAwareContextLoaderDelegate} to use for transparent + * interaction with the context cache. + */ + CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate(); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java new file mode 100644 index 00000000000..6ef59cbcf44 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2014 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.ClassUtils; + +import static org.springframework.beans.BeanUtils.*; +import static org.springframework.core.annotation.AnnotationUtils.*; + +/** + * {@code BootstrapUtils} is a collection of utility methods to assist with + * bootstrapping the Spring TestContext Framework. + * + * @author Sam Brannen + * @since 4.1 + * @see BootstrapWith + * @see BootstrapContext + * @see TestContextBootstrapper + */ +abstract class BootstrapUtils { + + private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper"; + + private static final Log logger = LogFactory.getLog(BootstrapUtils.class); + + + private BootstrapUtils() { + /* no-op */ + } + + /** + * Resolve the {@link TestContextBootstrapper} type for the test class in the + * supplied {@link BootstrapContext}, instantiate it, and provide it a reference + * to the {@link BootstrapContext}. + * + *

If the {@link BootstrapWith @BootstrapWith} annotation is present on + * the test class, either directly or as a meta-annotation, then its + * {@link BootstrapWith#value value} will be used as the bootstrapper type. + * Otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper + * DefaultTestContextBootstrapper} will be used. + * + * @param bootstrapContext the bootstrap context to use + * @return a fully configured {@code TestContextBootstrapper} + */ + @SuppressWarnings("unchecked") + static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) { + Class testClass = bootstrapContext.getTestClass(); + + Class clazz = null; + try { + BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class); + if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) { + clazz = bootstrapWith.value(); + } + else { + clazz = (Class) ClassUtils.forName( + DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, BootstrapUtils.class.getClassLoader()); + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Instantiating TestContextBootstrapper from class [%s]", clazz.getName())); + } + + TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class); + testContextBootstrapper.setBootstrapContext(bootstrapContext); + + return testContextBootstrapper; + } + catch (Throwable t) { + throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz + + "]. Specify @BootstrapWith's 'value' attribute " + + "or make the default bootstrapper class available.", t); + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java new file mode 100644 index 00000000000..bfd32c89316 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2014 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code @BootstrapWith} defines class-level metadata that is used to determine + * how to bootstrap the Spring TestContext Framework. + * + *

This annotation may also be used as a meta-annotation to create + * custom composed annotations. + * + * @author Sam Brannen + * @since 4.1 + * @see BootstrapContext + * @see TestContextBootstrapper + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface BootstrapWith { + + /** + * The {@link TestContextBootstrapper} to use to bootstrap the Spring + * TestContext Framework. + */ + Class value() default TestContextBootstrapper.class; + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java index f7375def035..958b210820a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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,97 +16,61 @@ package org.springframework.test.context; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; -import org.springframework.util.Assert; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.annotation.DirtiesContext.HierarchyMode; /** - * {@code CacheAwareContextLoaderDelegate} loads application contexts from - * {@link MergedContextConfiguration} by delegating to the - * {@link ContextLoader} configured in the {@code MergedContextConfiguration} - * and interacting transparently with the {@link ContextCache} behind the scenes. + * A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain + * #loadContext loading} and {@linkplain #closeContext closing} application + * contexts, interacting transparently with a context cache behind + * the scenes. * - *

Note: {@code CacheAwareContextLoaderDelegate} does not implement the + *

Note: {@code CacheAwareContextLoaderDelegate} does not extend the * {@link ContextLoader} or {@link SmartContextLoader} interface. * * @author Sam Brannen * @since 3.2.2 */ -public class CacheAwareContextLoaderDelegate { - - private static final Log logger = LogFactory.getLog(CacheAwareContextLoaderDelegate.class); - - private final ContextCache contextCache; - - - CacheAwareContextLoaderDelegate(ContextCache contextCache) { - Assert.notNull(contextCache, "ContextCache must not be null"); - this.contextCache = contextCache; - } +public interface CacheAwareContextLoaderDelegate { /** - * Load the {@code ApplicationContext} for the supplied merged context - * configuration. Supports both the {@link SmartContextLoader} and - * {@link ContextLoader} SPIs. - * @throws Exception if an error occurs while loading the application context - */ - private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration) - throws Exception { - ContextLoader contextLoader = mergedContextConfiguration.getContextLoader(); - Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " - + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); - - ApplicationContext applicationContext; - - if (contextLoader instanceof SmartContextLoader) { - SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; - applicationContext = smartContextLoader.loadContext(mergedContextConfiguration); - } - else { - String[] locations = mergedContextConfiguration.getLocations(); - Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " - + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); - applicationContext = contextLoader.loadContext(locations); - } - - return applicationContext; - } - - /** - * Load the {@link ApplicationContext application context} for the supplied - * merged context configuration. + * Load the {@linkplain ApplicationContext application context} for the supplied + * {@link MergedContextConfiguration} by delegating to the {@link ContextLoader} + * configured in the given {@code MergedContextConfiguration}. + * + *

If the context is present in the context cache it will simply + * be returned; otherwise, it will be loaded, stored in the cache, and returned. * - *

If the context is present in the cache it will simply be returned; - * otherwise, it will be loaded, stored in the cache, and returned. + * @param mergedContextConfiguration the merged context configuration to use + * to load the application context; never {@code null} * @return the application context * @throws IllegalStateException if an error occurs while retrieving or * loading the application context */ - public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) { - synchronized (contextCache) { - ApplicationContext context = contextCache.get(mergedContextConfiguration); - if (context == null) { - try { - context = loadContextInternal(mergedContextConfiguration); - if (logger.isDebugEnabled()) { - logger.debug(String.format("Storing ApplicationContext in cache under key [%s].", - mergedContextConfiguration)); - } - contextCache.put(mergedContextConfiguration, context); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to load ApplicationContext", ex); - } - } - else { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].", - mergedContextConfiguration)); - } - } - return context; - } - } + ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration); + + /** + * Remove the {@linkplain ApplicationContext application context} for the + * supplied {@link MergedContextConfiguration} from the context cache + * and {@linkplain ConfigurableApplicationContext#close() close} it if it is + * an instance of {@link ConfigurableApplicationContext}. + * + *

The semantics of the supplied {@code HierarchyMode} must be honored when + * removing the context from the cache. See the Javadoc for {@link HierarchyMode} + * for details. + * + *

Generally speaking, this method should only be called if the state of + * a singleton bean has been changed (potentially affecting future interaction + * with the context) or if the context needs to be prematurely removed from + * the cache. + * + * @param mergedContextConfiguration the merged context configuration for the + * application context to close; never {@code null} + * @param hierarchyMode the hierarchy mode; may be {@code null} if the context + * is not part of a hierarchy + * @since 4.1 + */ + void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode); } diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java index 1b3ea9f2741..a716284a90a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java @@ -91,6 +91,7 @@ public @interface ContextConfiguration { * *

This attribute may not be used in conjunction with * {@link #locations}, but it may be used instead of {@link #locations}. + * * @since 3.0 * @see #inheritLocations */ @@ -120,6 +121,7 @@ public @interface ContextConfiguration { * *

This attribute may not be used in conjunction with * {@link #value}, but it may be used instead of {@link #value}. + * * @since 2.5 * @see #inheritLocations */ 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 deleted file mode 100644 index a3bd700d35d..00000000000 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright 2002-2014 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.Annotation; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -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; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -import static org.springframework.beans.BeanUtils.*; -import static org.springframework.core.annotation.AnnotationUtils.*; -import static org.springframework.test.context.MetaAnnotationUtils.*; - -/** - * Utility methods for working with {@link ContextLoader ContextLoaders} and - * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations, - * annotated classes, active bean definition profiles, and application context - * initializers. - * - * @author Sam Brannen - * @author Michail Nikolaev - * @since 3.1 - * @see ContextLoader - * @see SmartContextLoader - * @see ContextConfiguration - * @see ContextConfigurationAttributes - * @see ActiveProfiles - * @see ActiveProfilesResolver - * @see ApplicationContextInitializer - * @see ContextHierarchy - * @see MergedContextConfiguration - */ -abstract class ContextLoaderUtils { - - static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#"; - - private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class); - - private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader"; - private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.web.WebDelegatingSmartContextLoader"; - - private static final String WEB_APP_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration"; - private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebMergedContextConfiguration"; - - - private ContextLoaderUtils() { - /* no-op */ - } - - /** - * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied - * list of {@link ContextConfigurationAttributes} and then instantiate and return that - * {@code ContextLoader}. - * - *

If the supplied {@code defaultContextLoaderClassName} is {@code null} or - * empty, depending on the absence or presence of - * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} either - * {@code "org.springframework.test.context.support.DelegatingSmartContextLoader"} or - * {@code "org.springframework.test.context.web.WebDelegatingSmartContextLoader"} will - * be used as the default context loader class name. For details on the class - * resolution process, see {@link #resolveContextLoaderClass}. - * - * @param testClass the test class for which the {@code ContextLoader} should be - * resolved; must not be {@code null} - * @param configAttributesList the list of configuration attributes to process; must - * not be {@code null} or empty; must be ordered bottom-up - * (i.e., as if we were traversing up the class hierarchy) - * @param defaultContextLoaderClassName the name of the default {@code ContextLoader} - * class to use; may be {@code null} or empty - * @return the resolved {@code ContextLoader} for the supplied {@code testClass} - * (never {@code null}) - * @see #resolveContextLoaderClass - */ - static ContextLoader resolveContextLoader(Class testClass, - List configAttributesList, String defaultContextLoaderClassName) { - Assert.notNull(testClass, "Class must not be null"); - Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); - - if (!StringUtils.hasText(defaultContextLoaderClassName)) { - Class webAppConfigClass = loadWebAppConfigurationClass(); - defaultContextLoaderClassName = webAppConfigClass != null - && findAnnotation(testClass, webAppConfigClass) != null ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME - : DEFAULT_CONTEXT_LOADER_CLASS_NAME; - } - - Class contextLoaderClass = resolveContextLoaderClass(testClass, configAttributesList, - defaultContextLoaderClassName); - - return instantiateClass(contextLoaderClass, ContextLoader.class); - } - - /** - * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied - * list of {@link ContextConfigurationAttributes}. - * - *

Beginning with the first level in the context configuration attributes hierarchy: - * - *

    - *
  1. If the {@link ContextConfigurationAttributes#getContextLoaderClass() - * contextLoaderClass} property of {@link ContextConfigurationAttributes} is - * configured with an explicit class, that class will be returned.
  2. - *
  3. If an explicit {@code ContextLoader} class is not specified at the current - * level in the hierarchy, traverse to the next level in the hierarchy and return to - * step #1.
  4. - *
  5. If no explicit {@code ContextLoader} class is found after traversing the - * hierarchy, an attempt will be made to load and return the class with the supplied - * {@code defaultContextLoaderClassName}.
  6. - *
- * - * @param testClass the class for which to resolve the {@code ContextLoader} class; - * must not be {@code null}; only used for logging purposes - * @param configAttributesList the list of configuration attributes to process; must - * not be {@code null} or empty; must be ordered bottom-up - * (i.e., as if we were traversing up the class hierarchy) - * @param defaultContextLoaderClassName the name of the default {@code ContextLoader} - * class to use; must not be {@code null} or empty - * @return the {@code ContextLoader} class to use for the supplied test class - * @throws IllegalArgumentException if {@code @ContextConfiguration} is not - * present on the supplied test class - * @throws IllegalStateException if the default {@code ContextLoader} class could not - * be loaded - */ - @SuppressWarnings("unchecked") - static Class resolveContextLoaderClass(Class testClass, - List configAttributesList, String defaultContextLoaderClassName) { - Assert.notNull(testClass, "Class must not be null"); - Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); - Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty"); - - for (ContextConfigurationAttributes configAttributes : configAttributesList) { - if (logger.isTraceEnabled()) { - logger.trace(String.format("Processing ContextLoader for context configuration attributes %s", - configAttributes)); - } - - Class contextLoaderClass = configAttributes.getContextLoaderClass(); - if (!ContextLoader.class.equals(contextLoaderClass)) { - if (logger.isDebugEnabled()) { - logger.debug(String.format( - "Found explicit ContextLoader class [%s] for context configuration attributes %s", - contextLoaderClass.getName(), configAttributes)); - } - return contextLoaderClass; - } - } - - try { - if (logger.isTraceEnabled()) { - logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]", - defaultContextLoaderClassName, testClass.getName())); - } - return (Class) ClassUtils.forName(defaultContextLoaderClassName, - ContextLoaderUtils.class.getClassLoader()); - } - catch (Throwable t) { - throw new IllegalStateException("Could not load default ContextLoader class [" - + defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' " - + "attribute or make the default loader class available.", t); - } - } - - /** - * 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) { - if (logger.isTraceEnabled()) { - logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", - contextConfiguration, declaringClass.getName())); - } - - ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, - contextConfiguration); - if (logger.isTraceEnabled()) { - logger.trace("Resolved context configuration attributes: " + attributes); - } - 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 - * superclasses, taking into account context hierarchies declared via - * {@link ContextHierarchy @ContextHierarchy} and - * {@link ContextConfiguration @ContextConfiguration}. - * - *

The outer list represents a top-down ordering of context configuration - * attributes, where each element in the list represents the context configuration - * declared on a given test class in the class hierarchy. Each nested list - * contains the context configuration attributes declared either via a single - * instance of {@code @ContextConfiguration} on the particular class or via - * multiple instances of {@code @ContextConfiguration} declared within a - * single {@code @ContextHierarchy} instance on the particular class. - * Furthermore, each nested list maintains the order in which - * {@code @ContextConfiguration} instances are declared. - * - *

Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and - * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of - * {@link ContextConfiguration @ContextConfiguration} will not - * be taken into consideration. If these flags need to be honored, that must be - * handled manually when traversing the nested lists returned by this method. - * - * @param testClass the class for which to resolve the context hierarchy attributes - * (must not be {@code null}) - * @return the list of lists of configuration attributes for the specified class; - * never {@code null} - * @throws IllegalArgumentException if the supplied class is {@code null}; if - * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is - * present on the supplied class; or if a test class or composed annotation - * 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) - * @see #resolveContextConfigurationAttributes(Class) - */ - @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; - final List> hierarchyAttributes = new ArrayList>(); - - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(testClass, contextConfigType, - contextHierarchyType); - Assert.notNull(descriptor, String.format( - "Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]", - contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName())); - - while (descriptor != null) { - 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("Class [%s] has been configured with both @ContextConfiguration " - + "and @ContextHierarchy. Only one of these annotations may be declared on a test class " - + "or composed annotation.", declaringClass.getName()); - logger.error(msg); - throw new IllegalStateException(msg); - } - - final List configAttributesList = new ArrayList(); - - if (contextConfigDeclaredLocally) { - convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), - rootDeclaringClass, configAttributesList); - } - else if (contextHierarchyDeclaredLocally) { - ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); - for (ContextConfiguration contextConfiguration : contextHierarchy.value()) { - convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, rootDeclaringClass, - configAttributesList); - } - } - else { - // 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); - throw new IllegalStateException(msg); - } - - hierarchyAttributes.add(0, configAttributesList); - - descriptor = findAnnotationDescriptorForTypes(rootDeclaringClass.getSuperclass(), contextConfigType, - contextHierarchyType); - } - - return hierarchyAttributes; - } - - /** - * Build a context hierarchy map for the supplied {@linkplain Class - * test class} and its superclasses, taking into account context hierarchies - * declared via {@link ContextHierarchy @ContextHierarchy} and - * {@link ContextConfiguration @ContextConfiguration}. - * - *

Each value in the map represents the consolidated list of {@linkplain - * ContextConfigurationAttributes context configuration attributes} for a - * given level in the context hierarchy (potentially across the test class - * hierarchy), keyed by the {@link ContextConfiguration#name() name} of the - * context hierarchy level. - * - *

If a given level in the context hierarchy does not have an explicit - * name (i.e., configured via {@link ContextConfiguration#name}), a name will - * be generated for that hierarchy level by appending the numerical level to - * the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}. - * - * @param testClass the class for which to resolve the context hierarchy map - * (must not be {@code null}) - * @return a map of context configuration attributes for the context hierarchy, - * keyed by context hierarchy level name; never {@code null} - * @throws IllegalArgumentException if the lists of context configuration - * attributes for each level in the {@code @ContextHierarchy} do not define - * unique context configuration within the overall hierarchy. - * - * @since 3.2.2 - * @see #resolveContextHierarchyAttributes(Class) - */ - static Map> buildContextHierarchyMap(Class testClass) { - final Map> map = new LinkedHashMap>(); - int hierarchyLevel = 1; - - for (List configAttributesList : resolveContextHierarchyAttributes(testClass)) { - for (ContextConfigurationAttributes configAttributes : configAttributesList) { - String name = configAttributes.getName(); - - // Assign a generated name? - if (!StringUtils.hasText(name)) { - name = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + hierarchyLevel; - } - - // Encountered a new context hierarchy level? - if (!map.containsKey(name)) { - hierarchyLevel++; - map.put(name, new ArrayList()); - } - - map.get(name).add(configAttributes); - } - } - - // Check for uniqueness - Set> set = new HashSet>(map.values()); - if (set.size() != map.size()) { - String msg = String.format("The @ContextConfiguration elements configured via " - + "@ContextHierarchy in test class [%s] and its superclasses must " - + "define unique contexts per hierarchy level.", testClass.getName()); - logger.error(msg); - throw new IllegalStateException(msg); - } - - return map; - } - - /** - * Resolve the list of {@linkplain ContextConfigurationAttributes context - * configuration attributes} for the supplied {@linkplain Class test class} and its - * superclasses. - * - *

Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and - * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of - * {@link ContextConfiguration @ContextConfiguration} will not - * be taken into consideration. If these flags need to be honored, that must be - * handled manually when traversing the list returned by this method. - * - * @param testClass the class for which to resolve the configuration attributes (must - * not be {@code null}) - * @return the list of configuration attributes for the specified class, ordered - * bottom-up (i.e., as if we were traversing up the class hierarchy); - * never {@code null} - * @throws IllegalArgumentException if the supplied class is {@code null} or if - * {@code @ContextConfiguration} is not present on the supplied class - */ - static List resolveContextConfigurationAttributes(Class testClass) { - Assert.notNull(testClass, "Class must not be null"); - - final List attributesList = new ArrayList(); - - Class annotationType = ContextConfiguration.class; - - AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); - Assert.notNull(descriptor, String.format( - "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", - annotationType.getName(), testClass.getName())); - - while (descriptor != null) { - convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), - descriptor.getRootDeclaringClass(), attributesList); - descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); - } - - return attributesList; - } - - /** - * Resolve the list of merged {@code ApplicationContextInitializer} classes for the - * supplied list of {@code ContextConfigurationAttributes}. - * - *

Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers} - * flag of {@link ContextConfiguration @ContextConfiguration} will be taken into - * consideration. Specifically, if the {@code inheritInitializers} flag is set to - * {@code true} for a given level in the class hierarchy represented by the provided - * configuration attributes, context initializer classes defined at the given level - * will be merged with those defined in higher levels of the class hierarchy. - * - * @param configAttributesList the list of configuration attributes to process; must - * not be {@code null} or empty; must be ordered bottom-up - * (i.e., as if we were traversing up the class hierarchy) - * @return the set of merged context initializer classes, including those from - * superclasses if appropriate (never {@code null}) - * @since 3.2 - */ - static Set>> resolveInitializerClasses( - List configAttributesList) { - Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); - - final Set>> initializerClasses = // - new HashSet>>(); - - for (ContextConfigurationAttributes configAttributes : configAttributesList) { - if (logger.isTraceEnabled()) { - logger.trace(String.format("Processing context initializers for context configuration attributes %s", - configAttributes)); - } - - initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers())); - - if (!configAttributes.isInheritInitializers()) { - break; - } - } - - return initializerClasses; - } - - /** - * Resolve active bean definition profiles for the supplied {@link Class}. - * - *

Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles} flag of - * {@link ActiveProfiles @ActiveProfiles} will be taken into consideration. - * Specifically, if the {@code inheritProfiles} flag is set to {@code true}, profiles - * defined in the test class will be merged with those defined in superclasses. - * - * @param testClass the class for which to resolve the active profiles (must not be - * {@code null}) - * @return the set of active profiles for the specified class, including active - * profiles from superclasses if appropriate (never {@code null}) - * @see ActiveProfiles - * @see org.springframework.context.annotation.Profile - */ - static String[] resolveActiveProfiles(Class testClass) { - Assert.notNull(testClass, "Class must not be null"); - - Class annotationType = ActiveProfiles.class; - - AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); - - if (descriptor == null && logger.isDebugEnabled()) { - logger.debug(String.format( - "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", - annotationType.getName(), testClass.getName())); - } - - final Set activeProfiles = new HashSet(); - - while (descriptor != null) { - Class rootDeclaringClass = descriptor.getRootDeclaringClass(); - Class declaringClass = descriptor.getDeclaringClass(); - - AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); - if (logger.isTraceEnabled()) { - logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", - annAttrs, declaringClass.getName())); - } - validateActiveProfilesConfiguration(declaringClass, annAttrs); - - 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); - - if (resolverDeclared) { - ActiveProfilesResolver resolver = null; - try { - resolver = instantiateClass(resolverClass, ActiveProfilesResolver.class); - } - catch (Exception e) { - String msg = String.format("Could not instantiate ActiveProfilesResolver of " - + "type [%s] for test class [%s].", resolverClass.getName(), rootDeclaringClass.getName()); - logger.error(msg); - throw new IllegalStateException(msg, e); - } - - profiles = resolver.resolve(rootDeclaringClass); - if (profiles == null) { - String msg = String.format( - "ActiveProfilesResolver [%s] returned a null array of bean definition profiles.", - resolverClass.getName()); - logger.error(msg); - throw new IllegalStateException(msg); - } - } - else if (valueDeclared) { - profiles = valueProfiles; - } - - for (String profile : profiles) { - if (StringUtils.hasText(profile)) { - activeProfiles.add(profile.trim()); - } - } - - descriptor = annAttrs.getBoolean("inheritProfiles") ? findAnnotationDescriptor( - rootDeclaringClass.getSuperclass(), annotationType) : null; - } - - return StringUtils.toStringArray(activeProfiles); - } - - 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); - - String msg = null; - - if (valueDeclared && profilesDeclared) { - msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " - + "and 'profiles' [%s] attributes. Only one declaration of active bean " - + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), - ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); - } - else if (valueDeclared && resolverDeclared) { - msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " - + "and 'resolver' [%s] attributes. Only one source of active bean " - + "definition profiles is permitted per @ActiveProfiles annotation, " - + "either declaritively or programmatically.", declaringClass.getName(), - ObjectUtils.nullSafeToString(valueProfiles), resolverClass.getName()); - } - else if (profilesDeclared && resolverDeclared) { - msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'profiles' [%s] " - + "and 'resolver' [%s] attributes. Only one source of active bean " - + "definition profiles is permitted per @ActiveProfiles annotation, " - + "either declaritively or programmatically.", declaringClass.getName(), - ObjectUtils.nullSafeToString(profiles), resolverClass.getName()); - } - - if (msg != null) { - logger.error(msg); - throw new IllegalStateException(msg); - } - } - - /** - * Build the {@link MergedContextConfiguration merged context configuration} for - * the supplied {@link Class testClass} and {@code defaultContextLoaderClassName}, - * taking into account context hierarchies declared via - * {@link ContextHierarchy @ContextHierarchy} and - * {@link ContextConfiguration @ContextConfiguration}. - * @param testClass the test class for which the {@code MergedContextConfiguration} - * should be built (must not be {@code null}) - * @param defaultContextLoaderClassName the name of the default {@code ContextLoader} - * class to use (may be {@code null}) - * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to - * be passed to the {@code MergedContextConfiguration} constructor - * @return the merged context configuration - * @see #buildContextHierarchyMap(Class) - * @see #buildMergedContextConfiguration(Class, List, String, MergedContextConfiguration, CacheAwareContextLoaderDelegate) - */ - @SuppressWarnings({ "unchecked" }) - static MergedContextConfiguration buildMergedContextConfiguration(Class testClass, - String defaultContextLoaderClassName, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { - - if (findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, ContextHierarchy.class) == null) { - if (logger.isInfoEnabled()) { - logger.info(String.format( - "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", - testClass.getName())); - } - return new MergedContextConfiguration(testClass, null, null, null, null); - } - - if (findAnnotation(testClass, ContextHierarchy.class) != null) { - Map> hierarchyMap = buildContextHierarchyMap(testClass); - - MergedContextConfiguration parentConfig = null; - MergedContextConfiguration mergedConfig = null; - - for (List list : hierarchyMap.values()) { - List reversedList = new ArrayList(list); - Collections.reverse(reversedList); - - // Don't use the supplied testClass; instead ensure that we are - // building the MCC for the actual test class that declared the - // configuration for the current level in the context hierarchy. - Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty"); - Class declaringClass = reversedList.get(0).getDeclaringClass(); - - mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList, - defaultContextLoaderClassName, parentConfig, cacheAwareContextLoaderDelegate); - parentConfig = mergedConfig; - } - - // Return the last level in the context hierarchy - return mergedConfig; - } - else { - return buildMergedContextConfiguration(testClass, resolveContextConfigurationAttributes(testClass), - defaultContextLoaderClassName, null, cacheAwareContextLoaderDelegate); - } - } - - /** - * Build the {@link MergedContextConfiguration merged context configuration} for the - * supplied {@link Class testClass}, context configuration attributes, - * {@code defaultContextLoaderClassName}, and parent context configuration. - * - * @param testClass the test class for which the {@code MergedContextConfiguration} - * should be built (must not be {@code null}) - * @param configAttributesList the list of context configuration attributes for the - * specified test class, ordered bottom-up (i.e., as if we were - * traversing up the class hierarchy); never {@code null} or empty - * @param defaultContextLoaderClassName the name of the default {@code ContextLoader} - * class to use (may be {@code null}) - * @param parentConfig the merged context configuration for the parent application - * context in a context hierarchy, or {@code null} if there is no parent - * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to - * be passed to the {@code MergedContextConfiguration} constructor - * @return the merged context configuration - * @see #resolveContextLoader - * @see #resolveContextConfigurationAttributes - * @see SmartContextLoader#processContextConfiguration - * @see ContextLoader#processLocations - * @see #resolveActiveProfiles - * @see MergedContextConfiguration - */ - private static MergedContextConfiguration buildMergedContextConfiguration(final Class testClass, - final List configAttributesList, - final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig, - CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { - - final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList, - defaultContextLoaderClassName); - final List locationsList = new ArrayList(); - final List> classesList = new ArrayList>(); - - for (ContextConfigurationAttributes configAttributes : configAttributesList) { - if (logger.isTraceEnabled()) { - logger.trace(String.format("Processing locations and classes for context configuration attributes %s", - configAttributes)); - } - - if (contextLoader instanceof SmartContextLoader) { - SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; - smartContextLoader.processContextConfiguration(configAttributes); - locationsList.addAll(0, Arrays.asList(configAttributes.getLocations())); - classesList.addAll(0, Arrays.asList(configAttributes.getClasses())); - } - else { - String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), - configAttributes.getLocations()); - locationsList.addAll(0, Arrays.asList(processedLocations)); - // Legacy ContextLoaders don't know how to process classes - } - - if (!configAttributes.isInheritLocations()) { - break; - } - } - - String[] locations = StringUtils.toStringArray(locationsList); - Class[] classes = ClassUtils.toClassArray(classesList); - Set>> initializerClasses = resolveInitializerClasses(configAttributesList); - String[] activeProfiles = resolveActiveProfiles(testClass); - - MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes, - initializerClasses, activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); - - if (mergedConfig == null) { - mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses, - activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); - } - - return mergedConfig; - } - - /** - * Load the {@link org.springframework.test.context.web.WebAppConfiguration} - * class, using reflection in order to avoid package cycles. - * - * @return the {@code @WebAppConfiguration} class or {@code null} if it cannot be loaded - * @since 3.2 - */ - @SuppressWarnings("unchecked") - private static Class loadWebAppConfigurationClass() { - Class webAppConfigClass = null; - try { - webAppConfigClass = (Class) ClassUtils.forName(WEB_APP_CONFIGURATION_CLASS_NAME, - ContextLoaderUtils.class.getClassLoader()); - } - catch (Throwable t) { - if (logger.isDebugEnabled()) { - logger.debug("Could not load @WebAppConfiguration class [" + WEB_APP_CONFIGURATION_CLASS_NAME + "].", t); - } - } - return webAppConfigClass; - } - - /** - * Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration} - * from the supplied arguments, using reflection in order to avoid package cycles. - * - * @return the {@code WebMergedContextConfiguration} or {@code null} if it could not be built - * @since 3.2 - */ - @SuppressWarnings("unchecked") - private static MergedContextConfiguration buildWebMergedContextConfiguration( - Class testClass, - String[] locations, - Class[] classes, - Set>> initializerClasses, - String[] activeProfiles, ContextLoader contextLoader, - CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { - - Class webAppConfigClass = loadWebAppConfigurationClass(); - if (webAppConfigClass != null) { - - Annotation annotation = findAnnotation(testClass, webAppConfigClass); - if (annotation != null) { - - String resourceBasePath = (String) AnnotationUtils.getValue(annotation); - - try { - Class webMergedConfigClass = (Class) ClassUtils.forName( - WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader()); - - Constructor constructor = ClassUtils.getConstructorIfAvailable( - webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class, - String.class, ContextLoader.class, CacheAwareContextLoaderDelegate.class, - MergedContextConfiguration.class); - - if (constructor != null) { - return instantiateClass(constructor, testClass, locations, classes, initializerClasses, - activeProfiles, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, - parentConfig); - } - } - catch (Throwable t) { - if (logger.isDebugEnabled()) { - logger.debug("Could not instantiate [" + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME + "].", t); - } - } - } - } - - return null; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java b/spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java new file mode 100644 index 00000000000..c0f43571584 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2014 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 org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; + +/** + * Default implementation of the {@link BootstrapContext} interface. + * + * @author Sam Brannen + * @since 4.1 + */ +class DefaultBootstrapContext implements BootstrapContext { + + private final Class testClass; + private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; + + + DefaultBootstrapContext(Class testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { + Assert.notNull(testClass, "Test class must not be null"); + Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null"); + this.testClass = testClass; + this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getTestClass() { + return this.testClass; + } + + /** + * {@inheritDoc} + */ + @Override + public CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() { + return this.cacheAwareContextLoaderDelegate; + } + + /** + * Provide a String representation of this bootstrap context's state. + */ + @Override + public String toString() { + return new ToStringCreator(this)// + .append("testClass", testClass)// + .append("cacheAwareContextLoaderDelegate", nullSafeToString(cacheAwareContextLoaderDelegate))// + .toString(); + } + + /** + * Generate a null-safe {@link String} representation of the supplied + * {@link CacheAwareContextLoaderDelegate} based solely on the fully qualified + * name of the delegate or "null" if the supplied delegate is + * {@code null}. + */ + private static String nullSafeToString(CacheAwareContextLoaderDelegate delegate) { + return delegate == null ? "null" : delegate.getClass().getName(); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java new file mode 100644 index 00000000000..2d1c2e0d9a6 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2014 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.test.annotation.DirtiesContext.HierarchyMode; +import org.springframework.util.Assert; + +/** + * Default implementation of the {@link CacheAwareContextLoaderDelegate} interface. + * + *

Although {@code DefaultCacheAwareContextLoaderDelegate} was first introduced + * in Spring Framework 4.1, the initial implementation of this class was extracted + * from the existing code base for {@code CacheAwareContextLoaderDelegate} when + * {@code CacheAwareContextLoaderDelegate} was converted into an interface. + * + * @author Sam Brannen + * @since 4.1 + */ +class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate { + + private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class); + + private final ContextCache contextCache; + + + DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { + Assert.notNull(contextCache, "ContextCache must not be null"); + this.contextCache = contextCache; + } + + /** + * Load the {@code ApplicationContext} for the supplied merged context configuration. + *

Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs. + * @throws Exception if an error occurs while loading the application context + */ + private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration) + throws Exception { + ContextLoader contextLoader = mergedContextConfiguration.getContextLoader(); + Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " + + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); + + ApplicationContext applicationContext; + + if (contextLoader instanceof SmartContextLoader) { + SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; + applicationContext = smartContextLoader.loadContext(mergedContextConfiguration); + } + else { + String[] locations = mergedContextConfiguration.getLocations(); + Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " + + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); + applicationContext = contextLoader.loadContext(locations); + } + + return applicationContext; + } + + /** + * {@inheritDoc} + */ + public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) { + synchronized (contextCache) { + ApplicationContext context = contextCache.get(mergedContextConfiguration); + if (context == null) { + try { + context = loadContextInternal(mergedContextConfiguration); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Storing ApplicationContext in cache under key [%s].", + mergedContextConfiguration)); + } + contextCache.put(mergedContextConfiguration, context); + } + catch (Exception ex) { + throw new IllegalStateException("Failed to load ApplicationContext", ex); + } + } + else { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].", + mergedContextConfiguration)); + } + } + return context; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode) { + contextCache.remove(mergedContextConfiguration, hierarchyMode); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java b/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java index 22d6580a3df..b3fcfc13826 100644 --- a/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java +++ b/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -40,8 +40,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext private static final long serialVersionUID = -5827157174866681233L; - private final ContextCache contextCache; - private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; private final MergedContextConfiguration mergedContextConfiguration; @@ -56,49 +54,31 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext /** - * Delegates to {@link #DefaultTestContext(Class, ContextCache, String)} with a - * value of {@code null} for the default {@code ContextLoader} class name. + * Construct a new test context using the supplied {@link TestContextBootstrapper}. + * @param testContextBootstrapper the {@code TestContextBootstrapper} to use + * to construct the test context (must not be {@code null}) */ - DefaultTestContext(Class testClass, ContextCache contextCache) { - this(testClass, contextCache, null); + DefaultTestContext(TestContextBootstrapper testContextBootstrapper) { + Assert.notNull(testContextBootstrapper, "TestContextBootstrapper must not be null"); + + BootstrapContext bootstrapContext = testContextBootstrapper.getBootstrapContext(); + this.testClass = bootstrapContext.getTestClass(); + this.cacheAwareContextLoaderDelegate = bootstrapContext.getCacheAwareContextLoaderDelegate(); + this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration(); } /** - * Construct a new test context for the supplied {@linkplain Class test class} - * and {@linkplain ContextCache context cache} and parse the corresponding - * {@link ContextConfiguration @ContextConfiguration} or - * {@link ContextHierarchy @ContextHierarchy} annotation, if present. - *

If the supplied class name for the default {@code ContextLoader} - * is {@code null} or empty and no concrete {@code ContextLoader} - * class is explicitly supplied via {@code @ContextConfiguration}, a - * {@link org.springframework.test.context.support.DelegatingSmartContextLoader - * DelegatingSmartContextLoader} or - * {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader - * WebDelegatingSmartContextLoader} will be used instead. - * @param testClass the test class for which the test context should be - * constructed (must not be {@code null}) - * @param contextCache the context cache from which the constructed test - * context should retrieve application contexts (must not be - * {@code null}) - * @param defaultContextLoaderClassName the name of the default - * {@code ContextLoader} class to use (may be {@code null}) + * {@inheritDoc} */ - DefaultTestContext(Class testClass, ContextCache contextCache, String defaultContextLoaderClassName) { - Assert.notNull(testClass, "Test class must not be null"); - Assert.notNull(contextCache, "ContextCache must not be null"); - - this.testClass = testClass; - this.contextCache = contextCache; - this.cacheAwareContextLoaderDelegate = new CacheAwareContextLoaderDelegate(contextCache); - this.mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass, - defaultContextLoaderClassName, cacheAwareContextLoaderDelegate); + public ApplicationContext getApplicationContext() { + return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration); } /** * {@inheritDoc} */ - public ApplicationContext getApplicationContext() { - return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration); + public void markApplicationContextDirty(HierarchyMode hierarchyMode) { + cacheAwareContextLoaderDelegate.closeContext(mergedContextConfiguration, hierarchyMode); } /** @@ -129,13 +109,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext return testException; } - /** - * {@inheritDoc} - */ - public void markApplicationContextDirty(HierarchyMode hierarchyMode) { - contextCache.remove(mergedContextConfiguration, hierarchyMode); - } - /** * {@inheritDoc} */ diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java new file mode 100644 index 00000000000..5e22e497e2a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2014 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.util.List; + +/** + * {@code TestContextBootstrapper} defines a strategy SPI for bootstrapping the + * Spring TestContext Framework. + * + *

A custom bootstrapping strategy can be configured for a test class via + * {@link BootstrapWith @BootstrapWith}, either directly or as a meta-annotation. + * See {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} + * for an example. + * + *

The {@link TestContextManager} uses a {@code TestContextBootstrapper} to + * {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the + * current test and to {@linkplain #buildMergedContextConfiguration build the + * merged context configuration} necessary to create the {@link TestContext} that + * it manages. + * + *

Concrete implementations must provide a {@code public} no-args constructor. + * + *

Note: this SPI might potentially change in the future in + * order to accommodate new requirements. Implementers are therefore strongly encouraged + * not to implement this interface directly but rather to extend + * {@link org.springframework.test.context.support.AbstractTestContextBootstrapper + * AbstractTestContextBootstrapper} or one of its concrete subclasses instead. + * + * @author Sam Brannen + * @since 4.1 + * @see BootstrapWith + * @see BootstrapContext + */ +public interface TestContextBootstrapper { + + /** + * Set the {@link BootstrapContext} to be used by this bootstrapper. + */ + void setBootstrapContext(BootstrapContext bootstrapContext); + + /** + * Get the {@link BootstrapContext} associated with this bootstrapper. + */ + BootstrapContext getBootstrapContext(); + + /** + * Get a list of newly instantiated {@link TestExecutionListener TestExecutionListeners} + * for the test class in the {@link BootstrapContext} associated with this bootstrapper. + *

If {@link TestExecutionListeners @TestExecutionListeners} is not + * present on the test class in the {@code BootstrapContext}, + * default listeners should be returned. Concrete implementations + * are free to determine what comprises the set of default listeners. + *

The {@link TestExecutionListeners#inheritListeners() inheritListeners} + * flag of {@link TestExecutionListeners @TestExecutionListeners} must be + * taken into consideration. Specifically, if the {@code inheritListeners} + * flag is set to {@code true}, listeners declared for a given test class must + * be appended to the end of the list of listeners declared in superclasses. + * @return a list of {@code TestExecutionListener} instances + */ + List getTestExecutionListeners(); + + /** + * Build the {@linkplain MergedContextConfiguration merged context configuration} + * for the test class in the {@link BootstrapContext} associated with this + * bootstrapper. + *

Implementations must take the following into account when building the + * merged configuration: + *

    + *
  • Context hierarchies declared via {@link ContextHierarchy @ContextHierarchy} + * and {@link ContextConfiguration @ContextConfiguration}
  • + *
  • Active bean definition profiles declared via {@link ActiveProfiles @ActiveProfiles}
  • + *
  • {@linkplain org.springframework.context.ApplicationContextInitializer + * Context initializers} declared via {@link ContextConfiguration#initializers}
  • + *

    Consult the Javadoc for the aforementioned annotations for details on + * the required semantics. + *

    When determining which {@link ContextLoader} to use for a given test + * class, the following algorithm should be used: + *

      + *
    1. If a {@code ContextLoader} class has been explicitly declared via + * {@link ContextConfiguration#loader}, use it.
    2. + *
    3. Otherwise, if the name of a {@linkplain BootstrapContext#getCustomDefaultContextLoaderClassName + * custom default ContextLoader class} has been provided in the {@link BootstrapContext}, + * use it.
    4. + *
    5. Otherwise, concrete implementations are free to determine which + * {@code ContextLoader} class to use as as default.
    6. + * @return the merged context configuration, never {@code null} + */ + MergedContextConfiguration buildMergedContextConfiguration(); + +} 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 8255db3b27e..75e22ab16c9 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -18,119 +18,111 @@ package org.springframework.test.context; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; 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; - -import static org.springframework.test.context.MetaAnnotationUtils.*; /** - *

      - * {@code TestContextManager} is the main entry point into the - * Spring TestContext Framework, which provides support for loading and - * accessing {@link ApplicationContext application contexts}, dependency - * injection of test instances, - * {@link org.springframework.transaction.annotation.Transactional - * transactional} execution of test methods, etc. - *

      - *

      - * Specifically, a {@code TestContextManager} is responsible for managing a + * {@code TestContextManager} is the main entry point into the Spring + * TestContext Framework, which provides support for loading and accessing + * {@link org.springframework.context.ApplicationContext application contexts}, + * dependency injection of test instances, + * {@link org.springframework.transaction.annotation.Transactional transactional} + * execution of test methods, etc. + * + *

      Specifically, a {@code TestContextManager} is responsible for managing a * single {@link TestContext} and signaling events to all registered * {@link TestExecutionListener TestExecutionListeners} at well defined test * execution points: - *

      + * *
        *
      • {@link #beforeTestClass() before test class execution}: prior to any * before class methods of a particular testing framework (e.g., JUnit - * 4's {@link org.junit.BeforeClass @BeforeClass})
      • + * 4's {@link org.junit.BeforeClass @BeforeClass}) *
      • {@link #prepareTestInstance(Object) test instance preparation}: * immediately following instantiation of the test instance
      • *
      • {@link #beforeTestMethod(Object, Method) before test method execution}: * prior to any before methods of a particular testing framework (e.g., - * JUnit 4's {@link org.junit.Before @Before})
      • + * JUnit 4's {@link org.junit.Before @Before}) *
      • {@link #afterTestMethod(Object, Method, Throwable) after test method * execution}: after any after methods of a particular testing - * framework (e.g., JUnit 4's {@link org.junit.After @After})
      • + * framework (e.g., JUnit 4's {@link org.junit.After @After}) *
      • {@link #afterTestClass() after test class execution}: after any * after class methods of a particular testing framework (e.g., JUnit - * 4's {@link org.junit.AfterClass @AfterClass})
      • + * 4's {@link org.junit.AfterClass @AfterClass}) *
      * * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 + * @see BootstrapWith + * @see BootstrapContext + * @see TestContextBootstrapper * @see TestContext + * @see TestExecutionListener * @see TestExecutionListeners * @see ContextConfiguration + * @see ContextHierarchy * @see org.springframework.test.context.transaction.TransactionConfiguration */ public class TestContextManager { - private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] { - "org.springframework.test.context.web.ServletTestExecutionListener", - "org.springframework.test.context.support.DependencyInjectionTestExecutionListener", - "org.springframework.test.context.support.DirtiesContextTestExecutionListener", - "org.springframework.test.context.transaction.TransactionalTestExecutionListener" }; - private static final Log logger = LogFactory.getLog(TestContextManager.class); /** - * Cache of Spring application contexts. This needs to be static, as tests - * may be destroyed and recreated between running individual test methods, - * for example with JUnit. + * Cache of Spring application contexts. + *

      This needs to be static, since test instances may be destroyed and + * recreated between invocations of individual test methods, as is the case + * with JUnit. */ static final ContextCache contextCache = new ContextCache(); private final TestContext testContext; + private final TestContextBootstrapper testContextBootstrapper; + private final List testExecutionListeners = new ArrayList(); /** - * Delegates to {@link #TestContextManager(Class, String)} with a value of - * {@code null} for the default {@code ContextLoader} class name. + * Construct a new {@code TestContextManager} for the specified {@linkplain Class + * test class} and automatically {@link #registerTestExecutionListeners register} the + * {@link TestExecutionListener TestExecutionListeners} configured for the test class + * via the {@link TestExecutionListeners @TestExecutionListeners} annotation. + * @param testClass the test class to be managed + * @see #registerTestExecutionListeners(List) */ public TestContextManager(Class testClass) { - this(testClass, null); + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate( + contextCache); + BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); + this.testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); + this.testContext = new DefaultTestContext(testContextBootstrapper); + registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners()); } /** - * Constructs a new {@code TestContextManager} for the specified {@linkplain Class - * test class} and automatically {@link #registerTestExecutionListeners registers} the - * {@link TestExecutionListener TestExecutionListeners} configured for the test class - * via the {@link TestExecutionListeners @TestExecutionListeners} annotation. - * @param testClass the test class to be managed - * @param defaultContextLoaderClassName the name of the default {@code ContextLoader} - * class to use (may be {@code null}) - * @see #registerTestExecutionListeners(TestExecutionListener...) + * Get the {@link TestContext} managed by this {@code TestContextManager}. */ - public TestContextManager(Class testClass, String defaultContextLoaderClassName) { - this.testContext = new DefaultTestContext(testClass, contextCache, defaultContextLoaderClassName); - registerTestExecutionListeners(retrieveTestExecutionListeners(testClass)); + protected final TestContext getTestContext() { + return this.testContext; } /** - * Returns the {@link TestContext} managed by this - * {@code TestContextManager}. + * Register the supplied list of {@link TestExecutionListener TestExecutionListeners} + * by appending them to the list of listeners used by this {@code TestContextManager}. + * @see #registerTestExecutionListeners(TestExecutionListener...) */ - protected final TestContext getTestContext() { - return this.testContext; + public void registerTestExecutionListeners(List testExecutionListeners) { + registerTestExecutionListeners(testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()])); } /** - * Register the supplied {@link TestExecutionListener TestExecutionListeners} - * by appending them to the set of listeners used by this {@code TestContextManager}. + * Register the supplied array of {@link TestExecutionListener TestExecutionListeners} + * by appending them to the list of listeners used by this {@code TestContextManager}. */ public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) { for (TestExecutionListener listener : testExecutionListeners) { @@ -162,110 +154,11 @@ public class TestContextManager { return listenersReversed; } - /** - * Retrieve an array of newly instantiated {@link TestExecutionListener TestExecutionListeners} - * for the specified {@link Class class}. If {@link TestExecutionListeners @TestExecutionListeners} - * is not present on the supplied class, the default listeners will be returned. - *

      Note that the {@link TestExecutionListeners#inheritListeners() inheritListeners} flag of - * {@link TestExecutionListeners @TestExecutionListeners} will be taken into consideration. - * Specifically, if the {@code inheritListeners} flag is set to {@code true}, listeners - * defined in the annotated class will be appended to the listeners defined in superclasses. - * @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); - - // Use defaults? - if (descriptor == null) { - if (logger.isDebugEnabled()) { - logger.debug("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults."); - } - classesList.addAll(getDefaultTestExecutionListenerClasses()); - } - else { - // Traverse the class hierarchy... - while (descriptor != null) { - Class declaringClass = descriptor.getDeclaringClass(); - - AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); - if (logger.isTraceEnabled()) { - logger.trace(String.format( - "Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, - declaringClass)); - } - - Class[] valueListenerClasses = (Class[]) annAttrs.getClassArray("value"); - Class[] listenerClasses = (Class[]) annAttrs.getClassArray("listeners"); - if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) { - String msg = String.format( - "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)); - logger.error(msg); - throw new IllegalStateException(msg); - } - else if (!ObjectUtils.isEmpty(valueListenerClasses)) { - listenerClasses = valueListenerClasses; - } - - if (listenerClasses != null) { - classesList.addAll(0, Arrays.> asList(listenerClasses)); - } - - descriptor = (annAttrs.getBoolean("inheritListeners") ? findAnnotationDescriptor( - descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null); - } - } - - List listeners = new ArrayList(classesList.size()); - for (Class listenerClass : classesList) { - try { - listeners.add(BeanUtils.instantiateClass(listenerClass)); - } - catch (NoClassDefFoundError err) { - if (logger.isInfoEnabled()) { - logger.info(String.format("Could not instantiate TestExecutionListener class [%s]. " - + "Specify custom listener classes or make the default listener classes " - + "(and their dependencies) available.", listenerClass.getName())); - } - } - } - return listeners.toArray(new TestExecutionListener[listeners.size()]); - } - - /** - * Determine the default {@link TestExecutionListener} classes. - */ - @SuppressWarnings("unchecked") - protected Set> getDefaultTestExecutionListenerClasses() { - Set> defaultListenerClasses = new LinkedHashSet>(); - for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) { - try { - defaultListenerClasses.add((Class) getClass().getClassLoader().loadClass( - className)); - } - catch (Throwable t) { - if (logger.isDebugEnabled()) { - logger.debug("Could not load default TestExecutionListener class [" + className - + "]. Specify custom listener classes or make the default listener classes available.", t); - } - } - } - return defaultListenerClasses; - } - /** * Hook for pre-processing a test class before execution of any * tests within the class. Should be called prior to any framework-specific * before class methods (e.g., methods annotated with JUnit's - * {@link org.junit.BeforeClass @BeforeClass}). + * {@link org.junit.BeforeClass @BeforeClass}). *

      An attempt will be made to give each registered * {@link TestExecutionListener} a chance to pre-process the test class * execution. If a listener throws an exception, however, the remaining @@ -331,7 +224,7 @@ public class TestContextManager { * {@link Method test method}, for example for setting up test fixtures, * starting a transaction, etc. Should be called prior to any * framework-specific before methods (e.g., methods annotated with - * JUnit's {@link org.junit.Before @Before}). + * JUnit's {@link org.junit.Before @Before}). *

      The managed {@link TestContext} will be updated with the supplied * {@code testInstance} and {@code testMethod}. *

      An attempt will be made to give each registered @@ -369,7 +262,7 @@ public class TestContextManager { * {@link Method test method}, for example for tearing down test fixtures, * ending a transaction, etc. Should be called after any framework-specific * after methods (e.g., methods annotated with JUnit's - * {@link org.junit.After @After}). + * {@link org.junit.After @After}). *

      The managed {@link TestContext} will be updated with the supplied * {@code testInstance}, {@code testMethod}, and * {@code exception}. @@ -421,7 +314,7 @@ public class TestContextManager { * Hook for post-processing a test class after execution of all * tests within the class. Should be called after any framework-specific * after class methods (e.g., methods annotated with JUnit's - * {@link org.junit.AfterClass @AfterClass}). + * {@link org.junit.AfterClass @AfterClass}). *

      Each registered {@link TestExecutionListener} will be given a chance to * post-process the test class. If a listener throws an exception, the * remaining registered listeners will still be called, but the first diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java index 30d8f43d30f..74464aa8d80 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -46,10 +46,21 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) public @interface TestExecutionListeners { + /** + * Alias for {@link #listeners() listeners}. + * + *

      This attribute may not be used in conjunction with + * {@link #listeners}, but it may be used instead of {@link #listeners}. + */ + Class[] value() default {}; + /** * The {@link TestExecutionListener TestExecutionListeners} to register with * a {@link TestContextManager}. * + *

      This attribute may not be used in conjunction with + * {@link #value}, but it may be used instead of {@link #value}. + * * @see org.springframework.test.context.web.ServletTestExecutionListener * @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener * @see org.springframework.test.context.support.DirtiesContextTestExecutionListener @@ -57,11 +68,6 @@ public @interface TestExecutionListeners { */ Class[] listeners() default {}; - /** - * Alias for {@link #listeners() listeners}. - */ - Class[] value() default {}; - /** * Whether or not {@link #value() TestExecutionListeners} from superclasses * should be inherited. 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 4ec6101e422..d8633716c4f 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 @@ -50,36 +50,32 @@ import org.springframework.test.context.junit4.statements.SpringRepeat; import org.springframework.util.ReflectionUtils; /** - *

      - * {@code SpringJUnit4ClassRunner} is a custom extension of + *

      {@code SpringJUnit4ClassRunner} is a custom extension of JUnit's * {@link BlockJUnit4ClassRunner} which provides functionality of the * Spring TestContext Framework to standard JUnit 4.5+ tests by means * of the {@link TestContextManager} and associated support classes and * annotations. - *

      - *

      - * The following list constitutes all annotations currently supported directly + * + *

      The following list constitutes all annotations currently supported directly * by {@code SpringJUnit4ClassRunner}. - * (Note that additional annotations may be supported by various - * {@link org.springframework.test.context.TestExecutionListener - * TestExecutionListeners}) - *

      + * (Note that additional annotations may be supported by various {@link + * org.springframework.test.context.TestExecutionListener TestExecutionListeners} + * or {@link org.springframework.test.context.TestContextBootstrapper + * TestContextBootstrapper} implementations) + * *
        *
      • {@link Test#expected() @Test(expected=...)}
      • *
      • {@link Test#timeout() @Test(timeout=...)}
      • *
      • {@link Timed @Timed}
      • *
      • {@link Repeat @Repeat}
      • *
      • {@link Ignore @Ignore}
      • - *
      • - * {@link org.springframework.test.annotation.ProfileValueSourceConfiguration + *
      • {@link org.springframework.test.annotation.ProfileValueSourceConfiguration * @ProfileValueSourceConfiguration}
      • - *
      • {@link org.springframework.test.annotation.IfProfileValue - * @IfProfileValue}
      • + *
      • {@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}
      • *
      - *

      - * NOTE: As of Spring 3.0, {@code SpringJUnit4ClassRunner} requires - * JUnit 4.5+. - *

      + * + *

      NOTE: As of Spring 3.0, {@code SpringJUnit4ClassRunner} + * requires JUnit 4.5 or higher. * * @author Sam Brannen * @author Juergen Hoeller @@ -110,14 +106,12 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { } /** - * Creates a new {@link TestContextManager} for the supplied test class and - * the configured default {@code ContextLoader} class name. - * Can be overridden by subclasses. + * Creates a new {@link TestContextManager} for the supplied test class. + *

      Can be overridden by subclasses. * @param clazz the test class to be managed - * @see #getDefaultContextLoaderClassName(Class) */ protected TestContextManager createTestContextManager(Class clazz) { - return new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz)); + return new TestContextManager(clazz); } /** @@ -127,21 +121,6 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { return this.testContextManager; } - /** - * Get the name of the default {@code ContextLoader} class to use for - * the supplied test class. The named class will be used if the test class - * does not explicitly declare a {@code ContextLoader} class via the - * {@code @ContextConfiguration} annotation. - *

      The default implementation returns {@code null}, thus implying use - * of the standard default {@code ContextLoader} class name. - * Can be overridden by subclasses. - * @param clazz the test class - * @return {@code null} - */ - protected String getDefaultContextLoaderClassName(Class clazz) { - return null; - } - /** * Returns a description suitable for an ignored test class if the test is * disabled via {@code @IfProfileValue} at the class-level, and diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java new file mode 100644 index 00000000000..0a6f472fec6 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -0,0 +1,435 @@ +/* + * Copyright 2002-2014 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.support; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanUtils; +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.BootstrapContext; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.SmartContextLoader; +import org.springframework.test.context.TestContextBootstrapper; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Abstract implementation of the {@link TestContextBootstrapper} interface which + * provides most of the behavior required by a bootstrapper. + * + *

      Concrete subclasses typically will only need to provide implementations for + * the following {@code abstract} methods: + *

        + *
      • {@link #getDefaultTestExecutionListenerClassNames()} + *
      • {@link #getDefaultContextLoaderClass(Class)} + *
      • {@link #buildMergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} + * + * @author Sam Brannen + * @since 4.1 + */ +public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper { + + private static final Log logger = LogFactory.getLog(AbstractTestContextBootstrapper.class); + + private BootstrapContext bootstrapContext; + + + /** + * {@inheritDoc} + */ + @Override + public void setBootstrapContext(BootstrapContext bootstrapContext) { + this.bootstrapContext = bootstrapContext; + } + + /** + * {@inheritDoc} + */ + @Override + public BootstrapContext getBootstrapContext() { + return this.bootstrapContext; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public final List getTestExecutionListeners() { + Class clazz = getBootstrapContext().getTestClass(); + Class annotationType = TestExecutionListeners.class; + List> classesList = new ArrayList>(); + + AnnotationDescriptor descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz, + annotationType); + + // Use defaults? + if (descriptor == null) { + if (logger.isDebugEnabled()) { + logger.debug("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults."); + } + classesList.addAll(getDefaultTestExecutionListenerClasses()); + } + else { + // Traverse the class hierarchy... + while (descriptor != null) { + Class declaringClass = descriptor.getDeclaringClass(); + + AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); + if (logger.isTraceEnabled()) { + logger.trace(String.format( + "Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, + declaringClass)); + } + + Class[] valueListenerClasses = (Class[]) annAttrs.getClassArray("value"); + Class[] listenerClasses = (Class[]) annAttrs.getClassArray("listeners"); + if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) { + String msg = String.format( + "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)); + logger.error(msg); + throw new IllegalStateException(msg); + } + else if (!ObjectUtils.isEmpty(valueListenerClasses)) { + listenerClasses = valueListenerClasses; + } + + if (listenerClasses != null) { + classesList.addAll(0, Arrays.> asList(listenerClasses)); + } + + descriptor = (annAttrs.getBoolean("inheritListeners") ? MetaAnnotationUtils.findAnnotationDescriptor( + descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null); + } + } + + List listeners = new ArrayList(classesList.size()); + for (Class listenerClass : classesList) { + try { + listeners.add(BeanUtils.instantiateClass(listenerClass)); + } + catch (NoClassDefFoundError err) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " + + "Specify custom listener classes or make the default listener classes " + + "(and their dependencies) available.", listenerClass.getName())); + } + } + } + return listeners; + } + + /** + * Get the default {@link TestExecutionListener} classes for this bootstrapper. + *

        This method is invoked by {@link #getTestExecutionListeners()} and + * delegates to {@link #getDefaultTestExecutionListenerClassNames()} to + * retrieve the class names. + *

        If a particular class cannot be loaded, a {@code DEBUG} message will + * be logged, but the associated exception will not be rethrown. + */ + @SuppressWarnings("unchecked") + protected Set> getDefaultTestExecutionListenerClasses() { + Set> defaultListenerClasses = new LinkedHashSet>(); + for (String className : getDefaultTestExecutionListenerClassNames()) { + try { + defaultListenerClasses.add((Class) getClass().getClassLoader().loadClass( + className)); + } + catch (Throwable t) { + if (logger.isDebugEnabled()) { + logger.debug("Could not load default TestExecutionListener class [" + className + + "]. Specify custom listener classes or make the default listener classes available.", t); + } + } + } + return defaultListenerClasses; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings({ "unchecked" }) + @Override + public final MergedContextConfiguration buildMergedContextConfiguration() { + Class testClass = getBootstrapContext().getTestClass(); + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate(); + + if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, + ContextHierarchy.class) == null) { + if (logger.isInfoEnabled()) { + logger.info(String.format( + "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", + testClass.getName())); + } + return new MergedContextConfiguration(testClass, null, null, null, null); + } + + if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) { + Map> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass); + + MergedContextConfiguration parentConfig = null; + MergedContextConfiguration mergedConfig = null; + + for (List list : hierarchyMap.values()) { + List reversedList = new ArrayList(list); + Collections.reverse(reversedList); + + // Don't use the supplied testClass; instead ensure that we are + // building the MCC for the actual test class that declared the + // configuration for the current level in the context hierarchy. + Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty"); + Class declaringClass = reversedList.get(0).getDeclaringClass(); + + mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList, parentConfig, + cacheAwareContextLoaderDelegate); + parentConfig = mergedConfig; + } + + // Return the last level in the context hierarchy + return mergedConfig; + } + else { + return buildMergedContextConfiguration(testClass, + ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null, + cacheAwareContextLoaderDelegate); + } + } + + /** + * Build the {@link MergedContextConfiguration merged context configuration} + * for the supplied {@link Class testClass}, context configuration attributes, + * and parent context configuration. + * + * @param testClass the test class for which the {@code MergedContextConfiguration} + * should be built (must not be {@code null}) + * @param configAttributesList the list of context configuration attributes for the + * specified test class, ordered bottom-up (i.e., as if we were + * traversing up the class hierarchy); never {@code null} or empty + * @param parentConfig the merged context configuration for the parent application + * context in a context hierarchy, or {@code null} if there is no parent + * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to + * be passed to the {@code MergedContextConfiguration} constructor + * @return the merged context configuration + * @see #resolveContextLoader + * @see ContextLoaderUtils#resolveContextConfigurationAttributes + * @see SmartContextLoader#processContextConfiguration + * @see ContextLoader#processLocations + * @see ActiveProfilesUtils#resolveActiveProfiles + * @see ApplicationContextInitializerUtils#resolveInitializerClasses + * @see MergedContextConfiguration + */ + private MergedContextConfiguration buildMergedContextConfiguration(final Class testClass, + final List configAttributesList, MergedContextConfiguration parentConfig, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { + + final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList); + final List locationsList = new ArrayList(); + final List> classesList = new ArrayList>(); + + for (ContextConfigurationAttributes configAttributes : configAttributesList) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Processing locations and classes for context configuration attributes %s", + configAttributes)); + } + + if (contextLoader instanceof SmartContextLoader) { + SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; + smartContextLoader.processContextConfiguration(configAttributes); + locationsList.addAll(0, Arrays.asList(configAttributes.getLocations())); + classesList.addAll(0, Arrays.asList(configAttributes.getClasses())); + } + else { + String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), + configAttributes.getLocations()); + locationsList.addAll(0, Arrays.asList(processedLocations)); + // Legacy ContextLoaders don't know how to process classes + } + + if (!configAttributes.isInheritLocations()) { + break; + } + } + + String[] locations = StringUtils.toStringArray(locationsList); + Class[] classes = ClassUtils.toClassArray(classesList); + Set>> initializerClasses = // + ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList); + String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass); + + return buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, + contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + } + + /** + * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the + * supplied list of {@link ContextConfigurationAttributes} and then instantiate + * and return that {@code ContextLoader}. + * + *

        If the user has not explicitly declared which loader to use, the value + * returned from {@link #getDefaultContextLoaderClass} will be used as the + * default context loader class. For details on the class resolution process, + * see {@link #resolveExplicitContextLoaderClass} and + * {@link #getDefaultContextLoaderClass}. + * + * @param testClass the test class for which the {@code ContextLoader} should be + * resolved; must not be {@code null} + * @param configAttributesList the list of configuration attributes to process; must + * not be {@code null} or empty; must be ordered bottom-up + * (i.e., as if we were traversing up the class hierarchy) + * @return the resolved {@code ContextLoader} for the supplied {@code testClass} + * (never {@code null}) + */ + private ContextLoader resolveContextLoader(Class testClass, + List configAttributesList) { + Assert.notNull(testClass, "Class must not be null"); + Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); + + Class contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList); + if (contextLoaderClass == null) { + contextLoaderClass = getDefaultContextLoaderClass(testClass); + } + + if (logger.isTraceEnabled()) { + logger.trace(String.format("Using ContextLoader class [%s] for test class [%s]", + contextLoaderClass.getName(), testClass.getName())); + } + + return BeanUtils.instantiateClass(contextLoaderClass, ContextLoader.class); + } + + /** + * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied + * list of {@link ContextConfigurationAttributes}. + * + *

        Beginning with the first level in the context configuration attributes hierarchy: + * + *

          + *
        1. If the {@link ContextConfigurationAttributes#getContextLoaderClass() + * contextLoaderClass} property of {@link ContextConfigurationAttributes} is + * configured with an explicit class, that class will be returned.
        2. + *
        3. If an explicit {@code ContextLoader} class is not specified at the current + * level in the hierarchy, traverse to the next level in the hierarchy and return to + * step #1.
        4. + *
        + * + * @param configAttributesList the list of configuration attributes to process; + * must not be {@code null} or empty; must be ordered bottom-up + * (i.e., as if we were traversing up the class hierarchy) + * @return the {@code ContextLoader} class to use for the supplied configuration + * attributes, or {@code null} if no explicit loader is found + * @throws IllegalArgumentException if supplied configuration attributes are + * {@code null} or empty + */ + private Class resolveExplicitContextLoaderClass( + List configAttributesList) { + Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); + + for (ContextConfigurationAttributes configAttributes : configAttributesList) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s", + configAttributes)); + } + + Class contextLoaderClass = configAttributes.getContextLoaderClass(); + if (!ContextLoader.class.equals(contextLoaderClass)) { + if (logger.isDebugEnabled()) { + logger.debug(String.format( + "Found explicit ContextLoader class [%s] for context configuration attributes %s", + contextLoaderClass.getName(), configAttributes)); + } + return contextLoaderClass; + } + } + + return null; + } + + /** + * Get the names of the default {@link TestExecutionListener} classes for + * this bootstrapper. + *

        This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}. + * @return an unmodifiable list of names of default {@code + * TestExecutionListener} classes + */ + protected abstract List getDefaultTestExecutionListenerClassNames(); + + /** + * Determine the default {@link ContextLoader} class to use for the supplied + * test class. + *

        The class returned by this method will only be used if a {@code ContextLoader} + * class has not been explicitly declared via {@link ContextConfiguration#loader}. + * @param testClass the test class for which to retrieve the default + * {@code ContextLoader} class + */ + protected abstract Class getDefaultContextLoaderClass(Class testClass); + + /** + * Build a {@link MergedContextConfiguration} instance from the supplied, + * merged values. + * + *

        Concrete subclasses typically will only need to instantiate + * {@link MergedContextConfiguration} (or a specialized subclass thereof) + * from the provided values; further processing and merging of values is likely + * unnecessary. + * + * @param testClass the test class for which the {@code MergedContextConfiguration} + * should be built (must not be {@code null}) + * @param locations the merged resource locations + * @param classes the merged annotated classes + * @param initializerClasses the merged context initializer classes + * @param activeProfiles the merged active bean definition profiles + * @param contextLoader the resolved {@code ContextLoader} + * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to + * be provided to the instantiated {@code MergedContextConfiguration} + * @param parentConfig the merged context configuration for the parent application + * context in a context hierarchy, or {@code null} if there is no parent + * @return the fully initialized {@code MergedContextConfiguration} + */ + protected abstract MergedContextConfiguration buildMergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> initializerClasses, + String[] activeProfiles, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java new file mode 100644 index 00000000000..22b8cfdb5d8 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2014 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.support; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; +import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Utility methods for working with {@link ActiveProfiles @ActiveProfiles} and + * {@link ActiveProfilesResolver ActiveProfilesResolvers}. + * + *

        Although {@code ActiveProfilesUtils} was first introduced in Spring Framework + * 4.1, the initial implementations of methods in this class were based on the + * existing code base in {@code ContextLoaderUtils}. + * + * @author Sam Brannen + * @author Michail Nikolaev + * @since 4.1 + * @see ActiveProfiles + * @see ActiveProfilesResolver + */ +abstract class ActiveProfilesUtils { + + private static final Log logger = LogFactory.getLog(ActiveProfilesUtils.class); + + + private ActiveProfilesUtils() { + /* no-op */ + } + + /** + * Resolve active bean definition profiles for the supplied {@link Class}. + * + *

        Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles} flag of + * {@link ActiveProfiles @ActiveProfiles} will be taken into consideration. + * Specifically, if the {@code inheritProfiles} flag is set to {@code true}, profiles + * defined in the test class will be merged with those defined in superclasses. + * + * @param testClass the class for which to resolve the active profiles (must not be + * {@code null}) + * @return the set of active profiles for the specified class, including active + * profiles from superclasses if appropriate (never {@code null}) + * @see ActiveProfiles + * @see ActiveProfilesResolver + * @see org.springframework.context.annotation.Profile + */ + static String[] resolveActiveProfiles(Class testClass) { + Assert.notNull(testClass, "Class must not be null"); + + final Set activeProfiles = new HashSet(); + + Class annotationType = ActiveProfiles.class; + AnnotationDescriptor descriptor = MetaAnnotationUtils.findAnnotationDescriptor(testClass, + annotationType); + if (descriptor == null && logger.isDebugEnabled()) { + logger.debug(String.format( + "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", + annotationType.getName(), testClass.getName())); + } + + while (descriptor != null) { + Class rootDeclaringClass = descriptor.getRootDeclaringClass(); + Class declaringClass = descriptor.getDeclaringClass(); + + AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", + annAttrs, declaringClass.getName())); + } + validateActiveProfilesConfiguration(declaringClass, annAttrs); + + Class resolverClass = annAttrs.getClass("resolver"); + if (ActiveProfilesResolver.class.equals(resolverClass)) { + resolverClass = DefaultActiveProfilesResolver.class; + } + + ActiveProfilesResolver resolver = null; + try { + resolver = BeanUtils.instantiateClass(resolverClass, ActiveProfilesResolver.class); + } + catch (Exception e) { + String msg = String.format("Could not instantiate ActiveProfilesResolver of " + + "type [%s] for test class [%s].", resolverClass.getName(), rootDeclaringClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg, e); + } + + String[] profiles = resolver.resolve(rootDeclaringClass); + if (profiles == null) { + String msg = String.format( + "ActiveProfilesResolver [%s] returned a null array of bean definition profiles.", + resolverClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } + + for (String profile : profiles) { + if (StringUtils.hasText(profile)) { + activeProfiles.add(profile.trim()); + } + } + + descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor( + rootDeclaringClass.getSuperclass(), annotationType) : null; + } + + return StringUtils.toStringArray(activeProfiles); + } + + private static void validateActiveProfilesConfiguration(Class declaringClass, AnnotationAttributes annAttrs) { + String[] valueProfiles = annAttrs.getStringArray("value"); + String[] profiles = annAttrs.getStringArray("profiles"); + boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); + boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); + + if (valueDeclared && profilesDeclared) { + String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] " + + "and 'profiles' [%s] attributes. Only one declaration of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), + ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); + logger.error(msg); + throw new IllegalStateException(msg); + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java new file mode 100644 index 00000000000..f9b196b4e8d --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2014 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.support; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.util.Assert; + +/** + * Utility methods for working with {@link ApplicationContextInitializer + * ApplicationContextInitializers}. + * + *

        Although {@code ApplicationContextInitializerUtils} was first introduced + * in Spring Framework 4.1, the initial implementations of methods in this class + * were based on the existing code base in {@code ContextLoaderUtils}. + * + * @author Sam Brannen + * @since 4.1 + * @see ContextConfiguration#initializers + */ +abstract class ApplicationContextInitializerUtils { + + private static final Log logger = LogFactory.getLog(ApplicationContextInitializerUtils.class); + + + private ApplicationContextInitializerUtils() { + /* no-op */ + } + + /** + * Resolve the set of merged {@code ApplicationContextInitializer} classes for the + * supplied list of {@code ContextConfigurationAttributes}. + * + *

        Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers} + * flag of {@link ContextConfiguration @ContextConfiguration} will be taken into + * consideration. Specifically, if the {@code inheritInitializers} flag is set to + * {@code true} for a given level in the class hierarchy represented by the provided + * configuration attributes, context initializer classes defined at the given level + * will be merged with those defined in higher levels of the class hierarchy. + * + * @param configAttributesList the list of configuration attributes to process; must + * not be {@code null} or empty; must be ordered bottom-up + * (i.e., as if we were traversing up the class hierarchy) + * @return the set of merged context initializer classes, including those from + * superclasses if appropriate (never {@code null}) + * @since 3.2 + */ + static Set>> resolveInitializerClasses( + List configAttributesList) { + Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); + + final Set>> initializerClasses = // + new HashSet>>(); + + for (ContextConfigurationAttributes configAttributes : configAttributesList) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Processing context initializers for context configuration attributes %s", + configAttributes)); + } + + initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers())); + + if (!configAttributes.isInheritInitializers()) { + break; + } + } + + return initializerClasses; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java new file mode 100644 index 00000000000..edca68e314c --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java @@ -0,0 +1,311 @@ +/* + * Copyright 2002-2014 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.support; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.SmartContextLoader; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import static org.springframework.core.annotation.AnnotationUtils.*; +import static org.springframework.test.util.MetaAnnotationUtils.*; + +/** + * Utility methods for working with {@link ContextLoader ContextLoaders} and + * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations, + * annotated classes, and application context initializers. + * + * @author Sam Brannen + * @since 3.1 + * @see ContextLoader + * @see SmartContextLoader + * @see ContextConfiguration + * @see ContextConfigurationAttributes + * @see ApplicationContextInitializer + * @see ContextHierarchy + */ +abstract class ContextLoaderUtils { + + static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#"; + + private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class); + + + private ContextLoaderUtils() { + /* no-op */ + } + + /** + * 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) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", + contextConfiguration, declaringClass.getName())); + } + + ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, + contextConfiguration); + if (logger.isTraceEnabled()) { + logger.trace("Resolved context configuration attributes: " + attributes); + } + 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 + * superclasses, taking into account context hierarchies declared via + * {@link ContextHierarchy @ContextHierarchy} and + * {@link ContextConfiguration @ContextConfiguration}. + * + *

        The outer list represents a top-down ordering of context configuration + * attributes, where each element in the list represents the context configuration + * declared on a given test class in the class hierarchy. Each nested list + * contains the context configuration attributes declared either via a single + * instance of {@code @ContextConfiguration} on the particular class or via + * multiple instances of {@code @ContextConfiguration} declared within a + * single {@code @ContextHierarchy} instance on the particular class. + * Furthermore, each nested list maintains the order in which + * {@code @ContextConfiguration} instances are declared. + * + *

        Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and + * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of + * {@link ContextConfiguration @ContextConfiguration} will not + * be taken into consideration. If these flags need to be honored, that must be + * handled manually when traversing the nested lists returned by this method. + * + * @param testClass the class for which to resolve the context hierarchy attributes + * (must not be {@code null}) + * @return the list of lists of configuration attributes for the specified class; + * never {@code null} + * @throws IllegalArgumentException if the supplied class is {@code null}; if + * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is + * present on the supplied class; or if a test class or composed annotation + * 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) + * @see #resolveContextConfigurationAttributes(Class) + */ + @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; + final List> hierarchyAttributes = new ArrayList>(); + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(testClass, contextConfigType, + contextHierarchyType); + Assert.notNull(descriptor, String.format( + "Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]", + contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName())); + + while (descriptor != null) { + 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("Class [%s] has been configured with both @ContextConfiguration " + + "and @ContextHierarchy. Only one of these annotations may be declared on a test class " + + "or composed annotation.", declaringClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } + + final List configAttributesList = new ArrayList(); + + if (contextConfigDeclaredLocally) { + convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), + rootDeclaringClass, configAttributesList); + } + else if (contextHierarchyDeclaredLocally) { + ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); + for (ContextConfiguration contextConfiguration : contextHierarchy.value()) { + convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, rootDeclaringClass, + configAttributesList); + } + } + else { + // 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); + throw new IllegalStateException(msg); + } + + hierarchyAttributes.add(0, configAttributesList); + + descriptor = findAnnotationDescriptorForTypes(rootDeclaringClass.getSuperclass(), contextConfigType, + contextHierarchyType); + } + + return hierarchyAttributes; + } + + /** + * Build a context hierarchy map for the supplied {@linkplain Class + * test class} and its superclasses, taking into account context hierarchies + * declared via {@link ContextHierarchy @ContextHierarchy} and + * {@link ContextConfiguration @ContextConfiguration}. + * + *

        Each value in the map represents the consolidated list of {@linkplain + * ContextConfigurationAttributes context configuration attributes} for a + * given level in the context hierarchy (potentially across the test class + * hierarchy), keyed by the {@link ContextConfiguration#name() name} of the + * context hierarchy level. + * + *

        If a given level in the context hierarchy does not have an explicit + * name (i.e., configured via {@link ContextConfiguration#name}), a name will + * be generated for that hierarchy level by appending the numerical level to + * the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}. + * + * @param testClass the class for which to resolve the context hierarchy map + * (must not be {@code null}) + * @return a map of context configuration attributes for the context hierarchy, + * keyed by context hierarchy level name; never {@code null} + * @throws IllegalArgumentException if the lists of context configuration + * attributes for each level in the {@code @ContextHierarchy} do not define + * unique context configuration within the overall hierarchy. + * + * @since 3.2.2 + * @see #resolveContextHierarchyAttributes(Class) + */ + static Map> buildContextHierarchyMap(Class testClass) { + final Map> map = new LinkedHashMap>(); + int hierarchyLevel = 1; + + for (List configAttributesList : resolveContextHierarchyAttributes(testClass)) { + for (ContextConfigurationAttributes configAttributes : configAttributesList) { + String name = configAttributes.getName(); + + // Assign a generated name? + if (!StringUtils.hasText(name)) { + name = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + hierarchyLevel; + } + + // Encountered a new context hierarchy level? + if (!map.containsKey(name)) { + hierarchyLevel++; + map.put(name, new ArrayList()); + } + + map.get(name).add(configAttributes); + } + } + + // Check for uniqueness + Set> set = new HashSet>(map.values()); + if (set.size() != map.size()) { + String msg = String.format("The @ContextConfiguration elements configured via " + + "@ContextHierarchy in test class [%s] and its superclasses must " + + "define unique contexts per hierarchy level.", testClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } + + return map; + } + + /** + * Resolve the list of {@linkplain ContextConfigurationAttributes context + * configuration attributes} for the supplied {@linkplain Class test class} and its + * superclasses. + * + *

        Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and + * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of + * {@link ContextConfiguration @ContextConfiguration} will not + * be taken into consideration. If these flags need to be honored, that must be + * handled manually when traversing the list returned by this method. + * + * @param testClass the class for which to resolve the configuration attributes (must + * not be {@code null}) + * @return the list of configuration attributes for the specified class, ordered + * bottom-up (i.e., as if we were traversing up the class hierarchy); + * never {@code null} + * @throws IllegalArgumentException if the supplied class is {@code null} or if + * {@code @ContextConfiguration} is not present on the supplied class + */ + static List resolveContextConfigurationAttributes(Class testClass) { + Assert.notNull(testClass, "Class must not be null"); + + final List attributesList = new ArrayList(); + + Class annotationType = ContextConfiguration.class; + + AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); + Assert.notNull(descriptor, String.format( + "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", + annotationType.getName(), testClass.getName())); + + while (descriptor != null) { + convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), + descriptor.getRootDeclaringClass(), attributesList); + descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); + } + + return attributesList; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java new file mode 100644 index 00000000000..46b1cb46a4f --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2014 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.support; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import static org.springframework.test.util.MetaAnnotationUtils.*; + +/** + * Default implementation of the {@link ActiveProfilesResolver} strategy that + * resolves active bean definition profiles based solely on profiles + * configured declaratively via {@link ActiveProfiles#profiles} or + * {@link ActiveProfiles#value}. + * + * @author Sam Brannen + * @since 4.1 + * @see ActiveProfiles + * @see ActiveProfilesResolver + */ +public class DefaultActiveProfilesResolver implements ActiveProfilesResolver { + + private static final Log logger = LogFactory.getLog(DefaultActiveProfilesResolver.class); + + + /** + * Resolve the bean definition profiles for the given {@linkplain + * Class test class} based on profiles configured declaratively via + * {@link ActiveProfiles#profiles} or {@link ActiveProfiles#value}. + * @param testClass the test class for which the profiles should be resolved; + * never {@code null} + * @return the list of bean definition profiles to use when loading the + * {@code ApplicationContext}; never {@code null} + */ + @Override + public String[] resolve(Class testClass) { + Assert.notNull(testClass, "Class must not be null"); + + final Set activeProfiles = new HashSet(); + + Class annotationType = ActiveProfiles.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); + + if (descriptor == null) { + if (logger.isDebugEnabled()) { + logger.debug(String.format( + "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", + annotationType.getName(), testClass.getName())); + } + } + else { + Class rootDeclaringClass = descriptor.getRootDeclaringClass(); + Class declaringClass = descriptor.getDeclaringClass(); + + AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", + annAttrs, declaringClass.getName())); + } + + Class resolverClass = annAttrs.getClass("resolver"); + if (!ActiveProfilesResolver.class.equals(resolverClass)) { + String msg = String.format("Configuration error for test class [%s]: %s cannot be used " + + "in conjunction with custom resolver [%s].", rootDeclaringClass.getName(), + getClass().getSimpleName(), resolverClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } + + String[] profiles = annAttrs.getStringArray("profiles"); + String[] valueProfiles = annAttrs.getStringArray("value"); + boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); + boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); + + if (valueDeclared && profilesDeclared) { + String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] " + + "and 'profiles' [%s] attributes. Only one declaration of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), + ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); + logger.error(msg); + throw new IllegalStateException(msg); + } + + if (valueDeclared) { + profiles = valueProfiles; + } + + for (String profile : profiles) { + if (StringUtils.hasText(profile)) { + activeProfiles.add(profile.trim()); + } + } + } + + return StringUtils.toStringArray(activeProfiles); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java new file mode 100644 index 00000000000..d3cf3f87762 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2014 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.support; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestContextBootstrapper; +import org.springframework.test.context.TestExecutionListener; + +/** + * Default implementation of the {@link TestContextBootstrapper} SPI. + * + *

          + *
        • Uses the following default {@link TestExecutionListener TestExecutionListeners}: + *
            + *
          1. {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener} + *
          2. {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener} + *
          3. {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener} + *
          + *
        • Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}. + *
        • Builds a standard {@link MergedContextConfiguration}. + *
        + * + * @author Sam Brannen + * @since 4.1 + */ +public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrapper { + + private static final List DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = Collections.unmodifiableList(Arrays.asList( + "org.springframework.test.context.support.DependencyInjectionTestExecutionListener", + "org.springframework.test.context.support.DirtiesContextTestExecutionListener", + "org.springframework.test.context.transaction.TransactionalTestExecutionListener")); + + + /** + * Returns an unmodifiable list of fully qualified class names for the following + * default {@link TestExecutionListener TestExecutionListeners}: + *
          + *
        1. {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener} + *
        2. {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener} + *
        3. {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener} + *
        + */ + protected List getDefaultTestExecutionListenerClassNames() { + return DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES; + } + + /** + * Returns {@link DelegatingSmartContextLoader}. + */ + @Override + protected Class getDefaultContextLoaderClass(Class testClass) { + return DelegatingSmartContextLoader.class; + } + + /** + * Builds a standard {@link MergedContextConfiguration}. + */ + protected MergedContextConfiguration buildMergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> initializerClasses, + String[] activeProfiles, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { + + return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, + contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java index 07746c1e142..b2434c5879e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -23,6 +23,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.test.context.BootstrapWith; + /** * {@code @WebAppConfiguration} is a class-level annotation that is used to * declare that the {@code ApplicationContext} loaded for an integration test @@ -51,6 +53,7 @@ import java.lang.annotation.Target; @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@BootstrapWith(WebTestContextBootstrapper.class) public @interface WebAppConfiguration { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java new file mode 100644 index 00000000000..46b0e4ad292 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2014 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.web; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestContextBootstrapper; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.support.DefaultTestContextBootstrapper; + +/** + * Web-specific implementation of the {@link TestContextBootstrapper} SPI. + * + *
          + *
        • Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener} + * to the list of default {@link TestExecutionListener TestExecutionListeners} supported by + * the superclass. + *
        • Uses {@link WebDelegatingSmartContextLoader} as the default {@link ContextLoader} + * if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration} + * and otherwise delegates to the superclass. + *
        • Builds a {@link WebMergedContextConfiguration} if the test class is annotated + * with {@link WebAppConfiguration @WebAppConfiguration} and otherwise delegates to + * the superclass. + *
        + * + * @author Sam Brannen + * @since 4.1 + */ +public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { + + /** + * Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener} + * to the list of default {@link TestExecutionListener TestExecutionListeners} + * supported by the superclass and returns an unmodifiable, updated list. + */ + @Override + protected List getDefaultTestExecutionListenerClassNames() { + List classNames = new ArrayList(super.getDefaultTestExecutionListenerClassNames()); + classNames.add(0, "org.springframework.test.context.web.ServletTestExecutionListener"); + return Collections.unmodifiableList(classNames); + } + + /** + * Returns {@link WebDelegatingSmartContextLoader} if the supplied class is + * annotated with {@link WebAppConfiguration @WebAppConfiguration} and + * otherwise delegates to the superclass. + */ + @Override + protected Class getDefaultContextLoaderClass(Class testClass) { + if (AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class) != null) { + return WebDelegatingSmartContextLoader.class; + } + + // else... + return super.getDefaultContextLoaderClass(testClass); + } + + /** + * Builds a {@link WebMergedContextConfiguration} if the supplied class is + * annotated with {@link WebAppConfiguration @WebAppConfiguration} and + * otherwise delegates to the superclass. + */ + @Override + protected MergedContextConfiguration buildMergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> initializerClasses, + String[] activeProfiles, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { + + WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class); + if (webAppConfiguration != null) { + String resourceBasePath = webAppConfiguration.value(); + + return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, + resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + } + + // else... + return super.buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, + contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java similarity index 92% rename from spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java rename to spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java index d926568d91b..6f556909811 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.util; import java.lang.annotation.Annotation; import java.util.HashSet; @@ -27,8 +27,6 @@ import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; -import static org.springframework.core.annotation.AnnotationUtils.*; - /** * {@code MetaAnnotationUtils} is a collection of utility methods that complements * the standard support already available in {@link AnnotationUtils}. @@ -36,24 +34,26 @@ import static org.springframework.core.annotation.AnnotationUtils.*; *

        Whereas {@code AnnotationUtils} provides utilities for getting or * finding an annotation, {@code MetaAnnotationUtils} goes a step further * by providing support for determining the root class on which an - * annotation is declared, either directly or via a composed annotation. - * This additional information is encapsulated in an {@link AnnotationDescriptor}. + * annotation is declared, either directly or indirectly via a composed + * annotation. This additional information is encapsulated in an + * {@link AnnotationDescriptor}. * *

        The additional information provided by an {@code AnnotationDescriptor} is * required by the Spring TestContext Framework in order to be able to * support class hierarchy traversals for annotations such as - * {@link ContextConfiguration @ContextConfiguration}, - * {@link TestExecutionListeners @TestExecutionListeners}, and - * {@link ActiveProfiles @ActiveProfiles} which offer support for merging and - * overriding various inherited annotation attributes (e.g., {@link - * ContextConfiguration#inheritLocations}). + * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}, + * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}, + * and {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles} + * which offer support for merging and overriding various inherited + * annotation attributes (e.g., {@link + * org.springframework.test.context.ContextConfiguration#inheritLocations}). * * @author Sam Brannen * @since 4.0 * @see AnnotationUtils * @see AnnotationDescriptor */ -abstract class MetaAnnotationUtils { +public abstract class MetaAnnotationUtils { private MetaAnnotationUtils() { /* no-op */ @@ -117,13 +117,13 @@ abstract class MetaAnnotationUtils { } // Declared locally? - if (isAnnotationDeclaredLocally(annotationType, clazz)) { + if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { return new AnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); } // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { - if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { AnnotationDescriptor descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited, annotationType); if (descriptor != null) { @@ -203,14 +203,14 @@ abstract class MetaAnnotationUtils { // Declared locally? for (Class annotationType : annotationTypes) { - if (isAnnotationDeclaredLocally(annotationType, clazz)) { + if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); } } // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { - if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( composedAnnotation.annotationType(), visited, annotationTypes); if (descriptor != null) { diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java new file mode 100644 index 00000000000..7af0039f022 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2014 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; + +/** + * Collection of test-related utility methods for working with {@link BootstrapContext + * BootstrapContexts} and {@link TestContextBootstrapper TestContextBootstrappers}. + * + * @author Sam Brannen + * @since 4.1 + */ +public abstract class BootstrapTestUtils { + + private BootstrapTestUtils() { + /* no-op */ + } + + public static BootstrapContext buildBootstrapContext(Class testClass, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { + return new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); + } + + public static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) { + return BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java index d593ddb8e11..8818df46272 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -56,7 +56,7 @@ public class ContextCacheTests { } private ApplicationContext loadContext(Class testClass) { - TestContext testContext = new DefaultTestContext(testClass, contextCache); + TestContext testContext = TestContextTestUtils.buildTestContext(testClass, contextCache); return testContext.getApplicationContext(); } @@ -119,15 +119,15 @@ public class ContextCacheTests { public void removeContextHierarchyCacheLevel1() { // Load Level 3-A - TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, - contextCache); + TestContext testContext3a = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache); testContext3a.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3); assertParentContextCount(2); // Load Level 3-B - TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, - contextCache); + TestContext testContext3b = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache); testContext3b.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4); assertParentContextCount(2); @@ -144,15 +144,15 @@ public class ContextCacheTests { public void removeContextHierarchyCacheLevel1WithExhaustiveMode() { // Load Level 3-A - TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, - contextCache); + TestContext testContext3a = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache); testContext3a.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3); assertParentContextCount(2); // Load Level 3-B - TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, - contextCache); + TestContext testContext3b = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache); testContext3b.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4); assertParentContextCount(2); @@ -169,15 +169,15 @@ public class ContextCacheTests { public void removeContextHierarchyCacheLevel2() { // Load Level 3-A - TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, - contextCache); + TestContext testContext3a = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache); testContext3a.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3); assertParentContextCount(2); // Load Level 3-B - TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, - contextCache); + TestContext testContext3b = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache); testContext3b.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4); assertParentContextCount(2); @@ -195,15 +195,15 @@ public class ContextCacheTests { public void removeContextHierarchyCacheLevel2WithExhaustiveMode() { // Load Level 3-A - TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, - contextCache); + TestContext testContext3a = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache); testContext3a.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3); assertParentContextCount(2); // Load Level 3-B - TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, - contextCache); + TestContext testContext3b = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache); testContext3b.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4); assertParentContextCount(2); @@ -219,15 +219,15 @@ public class ContextCacheTests { public void removeContextHierarchyCacheLevel3Then2() { // Load Level 3-A - TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, - contextCache); + TestContext testContext3a = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache); testContext3a.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3); assertParentContextCount(2); // Load Level 3-B - TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, - contextCache); + TestContext testContext3b = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache); testContext3b.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4); assertParentContextCount(2); @@ -248,15 +248,15 @@ public class ContextCacheTests { public void removeContextHierarchyCacheLevel3Then2WithExhaustiveMode() { // Load Level 3-A - TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, - contextCache); + TestContext testContext3a = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache); testContext3a.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3); assertParentContextCount(2); // Load Level 3-B - TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, - contextCache); + TestContext testContext3b = TestContextTestUtils.buildTestContext( + ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache); testContext3b.getApplicationContext(); assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4); assertParentContextCount(2); diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java b/spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java new file mode 100644 index 00000000000..94d7dbaba9a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2014 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; + +/** + * Collection of test-related utility methods for working with {@link TestContext TestContexts}. + * + * @author Sam Brannen + * @since 4.1 + */ +public abstract class TestContextTestUtils { + + private TestContextTestUtils() { + /* no-op */ + } + + public static TestContext buildTestContext(Class testClass, ContextCache contextCache) { + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate( + contextCache); + return buildTestContext(testClass, null, cacheAwareContextLoaderDelegate); + } + + public static TestContext buildTestContext(Class testClass, String customDefaultContextLoaderClassName, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { + BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); + TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); + + return new DefaultTestContext(testContextBootstrapper); + } + +} 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 32483a078cd..5142ee5a3f1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -45,7 +45,7 @@ public class TestExecutionListenersTests { @Test public void verifyNumDefaultListenersRegistered() throws Exception { TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class); - assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 4, + assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 3, testContextManager.getTestExecutionListeners().size()); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java index 3ee77974a0e..74d5e465d9e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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,27 +16,29 @@ package org.springframework.test.context.junit4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.model.InitializationError; -import org.springframework.tests.sample.beans.Pet; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.support.DefaultTestContextBootstrapper; import org.springframework.test.context.support.GenericPropertiesContextLoader; +import org.springframework.tests.sample.beans.Pet; + +import static org.junit.Assert.*; /** - * Integration tests which verify that a subclass of {@link SpringJUnit4ClassRunner} - * can specify a custom default ContextLoader class name that overrides - * the standard default class name. + * Integration tests which verify that a subclass of {@link DefaultTestContextBootstrapper} + * can specify a custom default ContextLoader class that overrides the standard + * default class name. * * @author Sam Brannen * @since 3.0 */ -@RunWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedSpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = "PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties") +@RunWith(SpringJUnit4ClassRunner.class) +@BootstrapWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedTestContextBootstrapper.class) +@ContextConfiguration("PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties") public class CustomDefaultContextLoaderClassSpringRunnerTests { @Autowired @@ -56,16 +58,12 @@ public class CustomDefaultContextLoaderClassSpringRunnerTests { } - public static final class PropertiesBasedSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner { - - public PropertiesBasedSpringJUnit4ClassRunner(Class clazz) throws InitializationError { - super(clazz); - } + public static class PropertiesBasedTestContextBootstrapper extends DefaultTestContextBootstrapper { @Override - protected String getDefaultContextLoaderClassName(Class clazz) { - return GenericPropertiesContextLoader.class.getName(); + protected Class getDefaultContextLoaderClass(Class testClass) { + return GenericPropertiesContextLoader.class; } - } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml similarity index 100% rename from spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml rename to spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml diff --git a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java similarity index 78% rename from spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java rename to spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java index 06515d42695..eae38bc594c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.context.support; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -23,10 +23,19 @@ import java.lang.annotation.Target; import java.util.Collections; import java.util.Set; +import org.mockito.Mockito; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.BootstrapContext; +import org.springframework.test.context.BootstrapTestUtils; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestContextBootstrapper; import static org.junit.Assert.*; @@ -44,6 +53,14 @@ abstract class AbstractContextLoaderUtilsTests { Collections.>> emptySet(); + MergedContextConfiguration buildMergedContextConfiguration(Class testClass) { + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = Mockito.mock(CacheAwareContextLoaderDelegate.class); + BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass, + cacheAwareContextLoaderDelegate); + TestContextBootstrapper bootstrapper = BootstrapTestUtils.resolveTestContextBootstrapper(bootstrapContext); + return bootstrapper.buildMergedContextConfiguration(); + } + void assertAttributes(ContextConfigurationAttributes attributes, Class expectedDeclaringClass, String[] expectedLocations, Class[] expectedClasses, Class expectedContextLoaderClass, boolean expectedInheritLocations) { @@ -173,4 +190,16 @@ abstract class AbstractContextLoaderUtilsTests { static class OverriddenClassesBar extends ClassesFoo { } + @ContextConfiguration(locations = "/foo.properties", loader = GenericPropertiesContextLoader.class) + @ActiveProfiles(profiles = "foo") + static class PropertiesLocationsFoo { + } + + // Combining @Configuration classes with a Properties based loader doesn't really make + // sense, but that's OK for unit testing purposes. + @ContextConfiguration(classes = FooConfig.class, loader = GenericPropertiesContextLoader.class) + @ActiveProfiles(profiles = "foo") + static class PropertiesClassesFoo { + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java similarity index 90% rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java rename to spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java index 76e4e7b7bb0..f9f98b7c73c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.context.support; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -25,19 +25,21 @@ import java.util.HashSet; import java.util.Set; import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; import static org.junit.Assert.*; -import static org.springframework.test.context.ContextLoaderUtils.*; +import static org.springframework.test.context.support.ActiveProfilesUtils.*; /** - * Unit tests for {@link ContextLoaderUtils} involving resolution of active bean + * Unit tests for {@link ActiveProfilesUtils} involving resolution of active bean * definition profiles. * * @author Sam Brannen * @author Michail Nikolaev * @since 3.1 */ -public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoaderUtilsTests { +public class ActiveProfilesUtilsTests extends AbstractContextLoaderUtilsTests { private void assertResolvedProfiles(Class testClass, String... expected) { assertNotNull(testClass); @@ -168,17 +170,17 @@ public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoader /** * @since 4.0 */ - @Test(expected = IllegalStateException.class) - public void resolveActiveProfilesWithConflictingResolverAndProfiles() { - resolveActiveProfiles(ConflictingResolverAndProfilesTestCase.class); + @Test + public void resolveActiveProfilesWithResolverAndProfiles() { + assertResolvedProfiles(ResolverAndProfilesTestCase.class, "bar"); } /** * @since 4.0 */ - @Test(expected = IllegalStateException.class) - public void resolveActiveProfilesWithConflictingResolverAndValue() { - resolveActiveProfiles(ConflictingResolverAndValueTestCase.class); + @Test + public void resolveActiveProfilesWithResolverAndValue() { + assertResolvedProfiles(ResolverAndValueTestCase.class, "bar"); } /** @@ -279,19 +281,19 @@ public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoader InheritedFooActiveProfilesResolverTestCase { } - @ActiveProfiles(resolver = BarActiveProfilesResolver.class, profiles = "conflict") - private static class ConflictingResolverAndProfilesTestCase { + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, profiles = "ignored by custom resolver") + private static class ResolverAndProfilesTestCase { } - @ActiveProfiles(resolver = BarActiveProfilesResolver.class, value = "conflict") - private static class ConflictingResolverAndValueTestCase { + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, value = "ignored by custom resolver") + private static class ResolverAndValueTestCase { } @MetaResolverConfig private static class TestClassVerifyingActiveProfilesResolverTestCase { } - @ActiveProfiles(profiles = "conflict", value = "conflict") + @ActiveProfiles(profiles = "conflict 1", value = "conflict 2") private static class ConflictingProfilesAndValueTestCase { } diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java index 6025c2d47fd..a7f9a08ffa3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java @@ -14,15 +14,19 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.context.support; import java.util.List; import org.junit.Test; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextLoader; import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.support.ContextLoaderUtils; +import static org.springframework.test.context.support.ContextLoaderUtils.*; import static org.junit.Assert.*; -import static org.springframework.test.context.ContextLoaderUtils.*; /** * Unit tests for {@link ContextLoaderUtils} involving {@link ContextConfigurationAttributes}. diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java similarity index 98% rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java index b1d07c02d14..052128f3931 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.context.support; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -25,11 +25,16 @@ import java.util.Map; import org.junit.Test; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.ContextLoader; import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.support.ContextLoaderUtils; +import static org.springframework.test.context.support.ContextLoaderUtils.*; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.springframework.test.context.ContextLoaderUtils.*; /** * Unit tests for {@link ContextLoaderUtils} involving context hierarchies. diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java similarity index 93% rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java index 654efb2bd6e..03be7bd2567 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.context.support; import java.util.HashSet; import java.util.Set; @@ -23,11 +23,10 @@ import org.junit.Test; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.test.context.support.DelegatingSmartContextLoader; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.MergedContextConfiguration; import org.springframework.web.context.support.GenericWebApplicationContext; -import static org.springframework.test.context.ContextLoaderUtils.*; - /** * Unit tests for {@link ContextLoaderUtils} involving {@link ApplicationContextInitializer}s. * @@ -44,7 +43,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo = new HashSet>>(); expectedInitializerClasses.add(FooInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, DelegatingSmartContextLoader.class); } @@ -58,7 +58,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo expectedInitializerClasses.add(FooInitializer.class); expectedInitializerClasses.add(BarInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, DelegatingSmartContextLoader.class); } @@ -71,7 +72,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo = new HashSet>>(); expectedInitializerClasses.add(BarInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, DelegatingSmartContextLoader.class); } @@ -84,7 +86,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo = new HashSet>>(); expectedInitializerClasses.add(BarInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, DelegatingSmartContextLoader.class); } diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java similarity index 79% rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java index f9988392a4c..2d84d022f2c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -14,14 +14,11 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.context.support; import org.junit.Test; -import org.springframework.test.context.support.AnnotationConfigContextLoader; -import org.springframework.test.context.support.DelegatingSmartContextLoader; -import org.springframework.test.context.support.GenericPropertiesContextLoader; - -import static org.springframework.test.context.ContextLoaderUtils.*; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; /** * Unit tests for {@link ContextLoaderUtils} involving {@link MergedContextConfiguration}. @@ -33,28 +30,28 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt @Test public void buildMergedConfigWithoutAnnotation() { - Class testClass = Enigma.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + Class testClass = Enigma.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null); } @Test public void buildMergedConfigWithBareAnnotations() { - Class testClass = BareAnnotations.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + Class testClass = BareAnnotations.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); assertMergedConfig( mergedConfig, testClass, - new String[] { "classpath:/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" }, + new String[] { "classpath:/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" }, EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class); } @Test public void buildMergedConfigWithLocalAnnotationAndLocations() { Class testClass = LocationsFoo.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class); @@ -63,7 +60,7 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt @Test public void buildMergedConfigWithMetaAnnotationAndLocations() { Class testClass = MetaLocationsFoo.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class); @@ -72,7 +69,7 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt @Test public void buildMergedConfigWithLocalAnnotationAndClasses() { Class testClass = ClassesFoo.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, DelegatingSmartContextLoader.class); @@ -80,21 +77,19 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt @Test public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndLocations() { - Class testClass = LocationsFoo.class; + Class testClass = PropertiesLocationsFoo.class; Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, - expectedContextLoaderClass.getName(), null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY, + assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.properties" }, EMPTY_CLASS_ARRAY, expectedContextLoaderClass); } @Test public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndClasses() { - Class testClass = ClassesFoo.class; + Class testClass = PropertiesClassesFoo.class; Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, - expectedContextLoaderClass.getName(), null); + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class[] { FooConfig.class }, expectedContextLoaderClass); @@ -104,8 +99,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt public void buildMergedConfigWithLocalAndInheritedAnnotationsAndLocations() { Class testClass = LocationsBar.class; String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" }; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY, AnnotationConfigContextLoader.class); } @@ -114,8 +109,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt public void buildMergedConfigWithLocalAndInheritedAnnotationsAndClasses() { Class testClass = ClassesBar.class; Class[] expectedClasses = new Class[] { FooConfig.class, BarConfig.class }; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, AnnotationConfigContextLoader.class); } @@ -124,8 +119,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt public void buildMergedConfigWithAnnotationsAndOverriddenLocations() { Class testClass = OverriddenLocationsBar.class; String[] expectedLocations = new String[] { "/bar.xml" }; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY, AnnotationConfigContextLoader.class); } @@ -134,8 +129,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt public void buildMergedConfigWithAnnotationsAndOverriddenClasses() { Class testClass = OverriddenClassesBar.class; Class[] expectedClasses = new Class[] { BarConfig.class }; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null); assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, AnnotationConfigContextLoader.class); } diff --git a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java similarity index 98% rename from spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java rename to spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java index 01a81d6456d..93cd0cecc91 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.util; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; @@ -27,12 +27,15 @@ import org.junit.Test; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; -import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor; -import org.springframework.test.context.MetaAnnotationUtils.UntypedAnnotationDescriptor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.transaction.annotation.Transactional; +import static org.springframework.test.util.MetaAnnotationUtils.*; + import static org.junit.Assert.*; -import static org.springframework.test.context.MetaAnnotationUtils.*; /** * Unit tests for {@link MetaAnnotationUtils}. diff --git a/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java rename to spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java index d2e1abee9f9..4b6e43eb75d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java +++ b/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java @@ -14,17 +14,20 @@ * limitations under the License. */ -package org.springframework.test.context; +package org.springframework.test.util; 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 org.springframework.test.context.ContextConfiguration; +import org.springframework.test.util.MetaAnnotationUtils; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; + +import static org.springframework.test.util.MetaAnnotationUtils.*; import static org.junit.Assert.*; -import static org.springframework.test.context.MetaAnnotationUtils.*; /** * Unit tests for {@link MetaAnnotationUtils} that verify support for overridden