Merge from sbrannen/SPR-9955

* SPR-9955:
  Introduce context bootstrap strategy in the TCF
This commit is contained in:
Sam Brannen
2014-04-10 00:36:02 +02:00
37 changed files with 2197 additions and 1268 deletions

View File

@@ -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}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction
* with {@link #profiles} or {@link #resolver}, but it may be used
* <em>instead</em> of them.
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #profiles}, but it may be used <em>instead</em> of {@link #profiles}.
*/
String[] value() default {};
/**
* The bean definition profiles to activate.
*
* <p>This attribute may <strong>not</strong> be used in conjunction
* with {@link #value} or {@link #resolver}, but it may be used
* <em>instead</em> of them.
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value}, but it may be used <em>instead</em> 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.
*
* <p>This attribute may <strong>not</strong> be used in conjunction
* with {@link #profiles} or {@link #value}, but it may be used <em>instead</em>
* of them in order to resolve the active profiles programmatically.
*
* @since 4.0
* @see ActiveProfilesResolver
*/

View File

@@ -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 <em>Spring
* TestContext Framework</em> 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 <em>context cache</em>.
*/
CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate();
}

View File

@@ -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 <em>Spring TestContext Framework</em>.
*
* @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}.
*
* <p>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<? extends TestContextBootstrapper> clazz = null;
try {
BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class);
if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) {
clazz = bootstrapWith.value();
}
else {
clazz = (Class<? extends TestContextBootstrapper>) 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);
}
}
}

View File

@@ -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 <em>Spring TestContext Framework</em>.
*
* <p>This annotation may also be used as a <em>meta-annotation</em> to create
* custom <em>composed annotations</em>.
*
* @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 <em>Spring
* TestContext Framework</em>.
*/
Class<? extends TestContextBootstrapper> value() default TestContextBootstrapper.class;
}

View File

@@ -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 <em>context cache</em> behind
* the scenes.
*
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not implement the
* <p>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}.
*
* <p>If the context is present in the cache it will simply be returned;
* otherwise, it will be loaded, stored in the cache, and returned.
* <p>If the context is present in the <em>context cache</em> 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 <em>context cache</em>
* and {@linkplain ConfigurableApplicationContext#close() close} it if it is
* an instance of {@link ConfigurableApplicationContext}.
*
* <p>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.
*
* <p>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);
}

View File

@@ -91,6 +91,7 @@ public @interface ContextConfiguration {
*
* <p>This attribute may <strong>not</strong> 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 {
*
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value}, but it may be used instead of {@link #value}.
*
* @since 2.5
* @see #inheritLocations
*/

View File

@@ -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}.
*
* <p>If the supplied {@code defaultContextLoaderClassName} is {@code null} or
* <em>empty</em>, 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 <em>empty</em>; must be ordered <em>bottom-up</em>
* (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 <em>empty</em>
* @return the resolved {@code ContextLoader} for the supplied {@code testClass}
* (never {@code null})
* @see #resolveContextLoaderClass
*/
static ContextLoader resolveContextLoader(Class<?> testClass,
List<ContextConfigurationAttributes> 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<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
defaultContextLoaderClassName = webAppConfigClass != null
&& findAnnotation(testClass, webAppConfigClass) != null ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
: DEFAULT_CONTEXT_LOADER_CLASS_NAME;
}
Class<? extends ContextLoader> 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}.
*
* <p>Beginning with the first level in the context configuration attributes hierarchy:
*
* <ol>
* <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
* contextLoaderClass} property of {@link ContextConfigurationAttributes} is
* configured with an explicit class, that class will be returned.</li>
* <li>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.</li>
* <li>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}.</li>
* </ol>
*
* @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 <em>empty</em>; must be ordered <em>bottom-up</em>
* (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
* <em>present</em> on the supplied test class
* @throws IllegalStateException if the default {@code ContextLoader} class could not
* be loaded
*/
@SuppressWarnings("unchecked")
static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testClass,
List<ContextConfigurationAttributes> 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<? extends ContextLoader> 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<? extends ContextLoader>) 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<ContextConfigurationAttributes> 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<ContextConfigurationAttributes> attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
annAttrs, declaringClass.getName()));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
}
/**
* Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
* configuration attributes} for the supplied {@linkplain Class test class} and its
* superclasses, taking into account context hierarchies declared via
* {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* <p>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.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
* 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
* <em>present</em> 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<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");
final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>();
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<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
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 <em>context hierarchy map</em> for the supplied {@linkplain Class
* test class} and its superclasses, taking into account context hierarchies
* declared via {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* <p>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.
*
* <p>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<String, List<ContextConfigurationAttributes>> buildContextHierarchyMap(Class<?> testClass) {
final Map<String, List<ContextConfigurationAttributes>> map = new LinkedHashMap<String, List<ContextConfigurationAttributes>>();
int hierarchyLevel = 1;
for (List<ContextConfigurationAttributes> 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<ContextConfigurationAttributes>());
}
map.get(name).add(configAttributes);
}
}
// Check for uniqueness
Set<List<ContextConfigurationAttributes>> set = new HashSet<List<ContextConfigurationAttributes>>(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.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
* 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
* <em>bottom-up</em> (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 <em>present</em> on the supplied class
*/
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
AnnotationDescriptor<ContextConfiguration> 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}.
*
* <p>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 <em>empty</em>; must be ordered <em>bottom-up</em>
* (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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses(
List<ContextConfigurationAttributes> configAttributesList) {
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
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 <em>active bean definition profiles</em> for the supplied {@link Class}.
*
* <p>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<ActiveProfiles> annotationType = ActiveProfiles.class;
AnnotationDescriptor<ActiveProfiles> 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<String> activeProfiles = new HashSet<String>();
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<? extends ActiveProfilesResolver> 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<? extends ActiveProfilesResolver> 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<String, List<ContextConfigurationAttributes>> hierarchyMap = buildContextHierarchyMap(testClass);
MergedContextConfiguration parentConfig = null;
MergedContextConfiguration mergedConfig = null;
for (List<ContextConfigurationAttributes> list : hierarchyMap.values()) {
List<ContextConfigurationAttributes> reversedList = new ArrayList<ContextConfigurationAttributes>(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 <em>bottom-up</em> (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<ContextConfigurationAttributes> configAttributesList,
final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
defaultContextLoaderClassName);
final List<String> locationsList = new ArrayList<String>();
final List<Class<?>> classesList = new ArrayList<Class<?>>();
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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> 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<? extends Annotation> loadWebAppConfigurationClass() {
Class<? extends Annotation> webAppConfigClass = null;
try {
webAppConfigClass = (Class<? extends Annotation>) 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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
if (webAppConfigClass != null) {
Annotation annotation = findAnnotation(testClass, webAppConfigClass);
if (annotation != null) {
String resourceBasePath = (String) AnnotationUtils.getValue(annotation);
try {
Class<? extends MergedContextConfiguration> webMergedConfigClass = (Class<? extends MergedContextConfiguration>) ClassUtils.forName(
WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader());
Constructor<? extends MergedContextConfiguration> 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;
}
}

View File

@@ -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 &quot;null&quot; if the supplied delegate is
* {@code null}.
*/
private static String nullSafeToString(CacheAwareContextLoaderDelegate delegate) {
return delegate == null ? "null" : delegate.getClass().getName();
}
}

View File

@@ -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.
*
* <p>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.
* <p>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);
}
}

View File

@@ -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,42 +54,17 @@ 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");
/**
* Construct a new test context for the supplied {@linkplain Class test class}
* and {@linkplain ContextCache context cache} and parse the corresponding
* {@link ContextConfiguration &#064;ContextConfiguration} or
* {@link ContextHierarchy &#064;ContextHierarchy} annotation, if present.
* <p>If the supplied class name for the default {@code ContextLoader}
* is {@code null} or <em>empty</em> 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})
*/
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);
BootstrapContext bootstrapContext = testContextBootstrapper.getBootstrapContext();
this.testClass = bootstrapContext.getTestClass();
this.cacheAwareContextLoaderDelegate = bootstrapContext.getCacheAwareContextLoaderDelegate();
this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration();
}
/**
@@ -101,6 +74,13 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
}
/**
* {@inheritDoc}
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
cacheAwareContextLoaderDelegate.closeContext(mergedContextConfiguration, hierarchyMode);
}
/**
* {@inheritDoc}
*/
@@ -129,13 +109,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
return testException;
}
/**
* {@inheritDoc}
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
contextCache.remove(mergedContextConfiguration, hierarchyMode);
}
/**
* {@inheritDoc}
*/

View File

@@ -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
* <em>Spring TestContext Framework</em>.
*
* <p>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.
*
* <p>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.
*
* <p>Concrete implementations must provide a {@code public} no-args constructor.
*
* <p><strong>Note</strong>: this SPI might potentially change in the future in
* order to accommodate new requirements. Implementers are therefore strongly encouraged
* <em>not</em> to implement this interface directly but rather to <em>extend</em>
* {@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.
* <p>If {@link TestExecutionListeners @TestExecutionListeners} is not
* <em>present</em> on the test class in the {@code BootstrapContext},
* <em>default</em> listeners should be returned. Concrete implementations
* are free to determine what comprises the set of default listeners.
* <p>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<TestExecutionListener> getTestExecutionListeners();
/**
* Build the {@linkplain MergedContextConfiguration merged context configuration}
* for the test class in the {@link BootstrapContext} associated with this
* bootstrapper.
* <p>Implementations must take the following into account when building the
* merged configuration:
* <ul>
* <li>Context hierarchies declared via {@link ContextHierarchy @ContextHierarchy}
* and {@link ContextConfiguration @ContextConfiguration}</li>
* <li>Active bean definition profiles declared via {@link ActiveProfiles @ActiveProfiles}</li>
* <li>{@linkplain org.springframework.context.ApplicationContextInitializer
* Context initializers} declared via {@link ContextConfiguration#initializers}</li>
* <p>Consult the Javadoc for the aforementioned annotations for details on
* the required semantics.
* <p>When determining which {@link ContextLoader} to use for a given test
* class, the following algorithm should be used:
* <ol>
* <li>If a {@code ContextLoader} class has been explicitly declared via
* {@link ContextConfiguration#loader}, use it.</li>
* <li>Otherwise, if the name of a {@linkplain BootstrapContext#getCustomDefaultContextLoaderClassName
* custom default ContextLoader class} has been provided in the {@link BootstrapContext},
* use it.</li>
* <li>Otherwise, concrete implementations are free to determine which
* {@code ContextLoader} class to use as as default.</li>
* @return the merged context configuration, never {@code null}
*/
MergedContextConfiguration buildMergedContextConfiguration();
}

View File

@@ -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.*;
/**
* <p>
* {@code TestContextManager} is the main entry point into the
* <em>Spring TestContext Framework</em>, 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.
* </p>
* <p>
* Specifically, a {@code TestContextManager} is responsible for managing a
* {@code TestContextManager} is the main entry point into the <em>Spring
* TestContext Framework</em>, 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.
*
* <p>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:
* </p>
*
* <ul>
* <li>{@link #beforeTestClass() before test class execution}: prior to any
* <em>before class methods</em> of a particular testing framework (e.g., JUnit
* 4's {@link org.junit.BeforeClass &#064;BeforeClass})</li>
* 4's {@link org.junit.BeforeClass @BeforeClass})</li>
* <li>{@link #prepareTestInstance(Object) test instance preparation}:
* immediately following instantiation of the test instance</li>
* <li>{@link #beforeTestMethod(Object, Method) before test method execution}:
* prior to any <em>before methods</em> of a particular testing framework (e.g.,
* JUnit 4's {@link org.junit.Before &#064;Before})</li>
* JUnit 4's {@link org.junit.Before @Before})</li>
* <li>{@link #afterTestMethod(Object, Method, Throwable) after test method
* execution}: after any <em>after methods</em> of a particular testing
* framework (e.g., JUnit 4's {@link org.junit.After &#064;After})</li>
* framework (e.g., JUnit 4's {@link org.junit.After @After})</li>
* <li>{@link #afterTestClass() after test class execution}: after any
* <em>after class methods</em> of a particular testing framework (e.g., JUnit
* 4's {@link org.junit.AfterClass &#064;AfterClass})</li>
* 4's {@link org.junit.AfterClass @AfterClass})</li>
* </ul>
*
* @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.
* <p>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<TestExecutionListener> testExecutionListeners = new ArrayList<TestExecutionListener>();
/**
* 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 &#064;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...)
*/
public TestContextManager(Class<?> testClass, String defaultContextLoaderClassName) {
this.testContext = new DefaultTestContext(testClass, contextCache, defaultContextLoaderClassName);
registerTestExecutionListeners(retrieveTestExecutionListeners(testClass));
}
/**
* Returns the {@link TestContext} managed by this
* {@code TestContextManager}.
* Get the {@link TestContext} managed by this {@code TestContextManager}.
*/
protected final TestContext getTestContext() {
return this.testContext;
}
/**
* Register the supplied {@link TestExecutionListener TestExecutionListeners}
* by appending them to the set of listeners used 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...)
*/
public void registerTestExecutionListeners(List<TestExecutionListener> testExecutionListeners) {
registerTestExecutionListeners(testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()]));
}
/**
* 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 &#064;TestExecutionListeners}
* is not <em>present</em> on the supplied class, the default listeners will be returned.
* <p>Note that the {@link TestExecutionListeners#inheritListeners() inheritListeners} flag of
* {@link TestExecutionListeners &#064;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<TestExecutionListeners> annotationType = TestExecutionListeners.class;
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
AnnotationDescriptor<TestExecutionListeners> 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<? extends TestExecutionListener>[] valueListenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("value");
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
String msg = String.format(
"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.<Class<? extends TestExecutionListener>> asList(listenerClasses));
}
descriptor = (annAttrs.getBoolean("inheritListeners") ? findAnnotationDescriptor(
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null);
}
}
List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
for (Class<? extends TestExecutionListener> 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<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>();
for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) {
try {
defaultListenerClasses.add((Class<? extends TestExecutionListener>) 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 <em>before</em> execution of any
* tests within the class. Should be called prior to any framework-specific
* <em>before class methods</em> (e.g., methods annotated with JUnit's
* {@link org.junit.BeforeClass &#064;BeforeClass}).
* {@link org.junit.BeforeClass @BeforeClass}).
* <p>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 <em>before methods</em> (e.g., methods annotated with
* JUnit's {@link org.junit.Before &#064;Before}).
* JUnit's {@link org.junit.Before @Before}).
* <p>The managed {@link TestContext} will be updated with the supplied
* {@code testInstance} and {@code testMethod}.
* <p>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
* <em>after methods</em> (e.g., methods annotated with JUnit's
* {@link org.junit.After &#064;After}).
* {@link org.junit.After @After}).
* <p>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 <em>after</em> execution of all
* tests within the class. Should be called after any framework-specific
* <em>after class methods</em> (e.g., methods annotated with JUnit's
* {@link org.junit.AfterClass &#064;AfterClass}).
* {@link org.junit.AfterClass @AfterClass}).
* <p>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

View File

@@ -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}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #listeners}, but it may be used instead of {@link #listeners}.
*/
Class<? extends TestExecutionListener>[] value() default {};
/**
* The {@link TestExecutionListener TestExecutionListeners} to register with
* a {@link TestContextManager}.
*
* <p>This attribute may <strong>not</strong> 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<? extends TestExecutionListener>[] listeners() default {};
/**
* Alias for {@link #listeners() listeners}.
*/
Class<? extends TestExecutionListener>[] value() default {};
/**
* Whether or not {@link #value() TestExecutionListeners} from superclasses
* should be <em>inherited</em>.

View File

@@ -50,36 +50,32 @@ import org.springframework.test.context.junit4.statements.SpringRepeat;
import org.springframework.util.ReflectionUtils;
/**
* <p>
* {@code SpringJUnit4ClassRunner} is a custom extension of
* <p>{@code SpringJUnit4ClassRunner} is a custom extension of JUnit's
* {@link BlockJUnit4ClassRunner} which provides functionality of the
* <em>Spring TestContext Framework</em> to standard JUnit 4.5+ tests by means
* of the {@link TestContextManager} and associated support classes and
* annotations.
* </p>
* <p>
* The following list constitutes all annotations currently supported directly
*
* <p>The following list constitutes all annotations currently supported directly
* by {@code SpringJUnit4ClassRunner}.
* <em>(Note that additional annotations may be supported by various
* {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListeners})</em>
* </p>
* <em>(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)</em>
*
* <ul>
* <li>{@link Test#expected() &#064;Test(expected=...)}</li>
* <li>{@link Test#timeout() &#064;Test(timeout=...)}</li>
* <li>{@link Timed &#064;Timed}</li>
* <li>{@link Repeat &#064;Repeat}</li>
* <li>{@link Ignore &#064;Ignore}</li>
* <li>
* {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
* <li>{@link org.springframework.test.annotation.ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration}</li>
* <li>{@link org.springframework.test.annotation.IfProfileValue
* &#064;IfProfileValue}</li>
* <li>{@link org.springframework.test.annotation.IfProfileValue &#064;IfProfileValue}</li>
* </ul>
* <p>
* <b>NOTE:</b> As of Spring 3.0, {@code SpringJUnit4ClassRunner} requires
* JUnit 4.5+.
* </p>
*
* <p><strong>NOTE:</strong> 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 <em>default {@code ContextLoader} class name</em>.
* Can be overridden by subclasses.
* Creates a new {@link TestContextManager} for the supplied test class.
* <p>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.
* <p>The default implementation returns {@code null}, thus implying use
* of the <em>standard</em> 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

View File

@@ -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.
*
* <p>Concrete subclasses typically will only need to provide implementations for
* the following {@code abstract} methods:
* <ul>
* <li>{@link #getDefaultTestExecutionListenerClassNames()}
* <li>{@link #getDefaultContextLoaderClass(Class)}
* <li>{@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<TestExecutionListener> getTestExecutionListeners() {
Class<?> clazz = getBootstrapContext().getTestClass();
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
AnnotationDescriptor<TestExecutionListeners> 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<? extends TestExecutionListener>[] valueListenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("value");
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
String msg = String.format(
"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.<Class<? extends TestExecutionListener>> asList(listenerClasses));
}
descriptor = (annAttrs.getBoolean("inheritListeners") ? MetaAnnotationUtils.findAnnotationDescriptor(
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null);
}
}
List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
for (Class<? extends TestExecutionListener> 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.
* <p>This method is invoked by {@link #getTestExecutionListeners()} and
* delegates to {@link #getDefaultTestExecutionListenerClassNames()} to
* retrieve the class names.
* <p>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<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>();
for (String className : getDefaultTestExecutionListenerClassNames()) {
try {
defaultListenerClasses.add((Class<? extends TestExecutionListener>) 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<String, List<ContextConfigurationAttributes>> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass);
MergedContextConfiguration parentConfig = null;
MergedContextConfiguration mergedConfig = null;
for (List<ContextConfigurationAttributes> list : hierarchyMap.values()) {
List<ContextConfigurationAttributes> reversedList = new ArrayList<ContextConfigurationAttributes>(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 <em>bottom-up</em> (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<ContextConfigurationAttributes> configAttributesList, MergedContextConfiguration parentConfig,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList);
final List<String> locationsList = new ArrayList<String>();
final List<Class<?>> classesList = new ArrayList<Class<?>>();
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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> 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}.
*
* <p>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 <em>empty</em>; must be ordered <em>bottom-up</em>
* (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<ContextConfigurationAttributes> configAttributesList) {
Assert.notNull(testClass, "Class must not be null");
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
Class<? extends ContextLoader> 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}.
*
* <p>Beginning with the first level in the context configuration attributes hierarchy:
*
* <ol>
* <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
* contextLoaderClass} property of {@link ContextConfigurationAttributes} is
* configured with an explicit class, that class will be returned.</li>
* <li>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.</li>
* </ol>
*
* @param configAttributesList the list of configuration attributes to process;
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* (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 <em>empty</em>
*/
private Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
List<ContextConfigurationAttributes> 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<? extends ContextLoader> 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.
* <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
* @return an <em>unmodifiable</em> list of names of default {@code
* TestExecutionListener} classes
*/
protected abstract List<String> getDefaultTestExecutionListenerClassNames();
/**
* Determine the default {@link ContextLoader} class to use for the supplied
* test class.
* <p>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<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass);
/**
* Build a {@link MergedContextConfiguration} instance from the supplied,
* merged values.
*
* <p>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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig);
}

View File

@@ -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}.
*
* <p>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 <em>active bean definition profiles</em> for the supplied {@link Class}.
*
* <p>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<String> activeProfiles = new HashSet<String>();
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
AnnotationDescriptor<ActiveProfiles> 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<? extends ActiveProfilesResolver> 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);
}
}
}

View File

@@ -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}.
*
* <p>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}.
*
* <p>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 <em>empty</em>; must be ordered <em>bottom-up</em>
* (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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses(
List<ContextConfigurationAttributes> configAttributesList) {
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
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;
}
}

View File

@@ -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<ContextConfigurationAttributes> 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<ContextConfigurationAttributes> attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
annAttrs, declaringClass.getName()));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
}
/**
* Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
* configuration attributes} for the supplied {@linkplain Class test class} and its
* superclasses, taking into account context hierarchies declared via
* {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* <p>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.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
* 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
* <em>present</em> 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<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");
final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>();
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<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
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 <em>context hierarchy map</em> for the supplied {@linkplain Class
* test class} and its superclasses, taking into account context hierarchies
* declared via {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* <p>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.
*
* <p>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<String, List<ContextConfigurationAttributes>> buildContextHierarchyMap(Class<?> testClass) {
final Map<String, List<ContextConfigurationAttributes>> map = new LinkedHashMap<String, List<ContextConfigurationAttributes>>();
int hierarchyLevel = 1;
for (List<ContextConfigurationAttributes> 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<ContextConfigurationAttributes>());
}
map.get(name).add(configAttributes);
}
}
// Check for uniqueness
Set<List<ContextConfigurationAttributes>> set = new HashSet<List<ContextConfigurationAttributes>>(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.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
* 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
* <em>bottom-up</em> (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 <em>present</em> on the supplied class
*/
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
AnnotationDescriptor<ContextConfiguration> 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;
}
}

View File

@@ -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 <em>active bean definition profiles</em> 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 <em>bean definition profiles</em> 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<String> activeProfiles = new HashSet<String>();
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
AnnotationDescriptor<ActiveProfiles> 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<? extends ActiveProfilesResolver> 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);
}
}

View File

@@ -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.
*
* <ul>
* <li>Uses the following default {@link TestExecutionListener TestExecutionListeners}:
* <ol>
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
* </ol>
* <li>Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}.
* <li>Builds a standard {@link MergedContextConfiguration}.
* </ul>
*
* @author Sam Brannen
* @since 4.1
*/
public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrapper {
private static final List<String> 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}:
* <ol>
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
* </ol>
*/
protected List<String> getDefaultTestExecutionListenerClassNames() {
return DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES;
}
/**
* Returns {@link DelegatingSmartContextLoader}.
*/
@Override
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) {
return DelegatingSmartContextLoader.class;
}
/**
* Builds a standard {@link MergedContextConfiguration}.
*/
protected MergedContextConfiguration buildMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
}
}

View File

@@ -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 {
/**

View File

@@ -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.
*
* <ul>
* <li>Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener}
* to the list of default {@link TestExecutionListener TestExecutionListeners} supported by
* the superclass.
* <li>Uses {@link WebDelegatingSmartContextLoader} as the default {@link ContextLoader}
* if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration}
* and otherwise delegates to the superclass.
* <li>Builds a {@link WebMergedContextConfiguration} if the test class is annotated
* with {@link WebAppConfiguration @WebAppConfiguration} and otherwise delegates to
* the superclass.
* </ul>
*
* @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<String> getDefaultTestExecutionListenerClassNames() {
List<String> classNames = new ArrayList<String>(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<? extends ContextLoader> 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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> 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);
}
}

View File

@@ -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.*;
* <p>Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or
* <em>finding</em> an annotation, {@code MetaAnnotationUtils} goes a step further
* by providing support for determining the <em>root class</em> on which an
* annotation is declared, either directly or via a <em>composed annotation</em>.
* This additional information is encapsulated in an {@link AnnotationDescriptor}.
* annotation is declared, either directly or indirectly via a <em>composed
* annotation</em>. This additional information is encapsulated in an
* {@link AnnotationDescriptor}.
*
* <p>The additional information provided by an {@code AnnotationDescriptor} is
* required by the <em>Spring TestContext Framework</em> 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 <em>inherited</em> 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 <em>inherited</em>
* 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<T>(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<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(),
visited, annotationType);
if (descriptor != null) {
@@ -203,14 +203,14 @@ abstract class MetaAnnotationUtils {
// Declared locally?
for (Class<? extends Annotation> 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) {

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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());
}

View File

@@ -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 <em>default ContextLoader class name</em> that overrides
* the standard default class name.
* Integration tests which verify that a subclass of {@link DefaultTestContextBootstrapper}
* can specify a custom <em>default ContextLoader class</em> 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<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) {
return GenericPropertiesContextLoader.class;
}
}
}

View File

@@ -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.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> 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<? extends ContextLoader> 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 {
}
}

View File

@@ -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 {
}

View File

@@ -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}.

View File

@@ -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.

View File

@@ -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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
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<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
expectedInitializerClasses.add(BarInitializer.class);
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}

View File

@@ -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<Enigma> 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<BareAnnotations> 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<? extends ContextLoader> 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<? extends ContextLoader> 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);
}

View File

@@ -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}.

View File

@@ -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