[SPR-8386] SmartContextLoader enhancements:

- introduced processContextConfigurationAttributes() method in SmartContextLoader SPI
- refactored AnnotationConfigContextLoader, AbstractContextLoader, AbstractGenericContextLoader, ContextLoaderUtils, and TestContext implementations to take advantage of the SmartContextLoader SPI, MergedContextConfiguration, and ContextConfigurationAttributes
- deleted ResourceTypeAwareContextLoader
- deleted ContextLoaderUtils.LocationsResolver and implementations
- moved context key generation from TestContext to MergedContextConfiguration
This commit is contained in:
Sam Brannen 2011-06-17 21:49:06 +00:00
parent 8e35734856
commit 9a56deb283
12 changed files with 317 additions and 452 deletions

View File

@ -34,13 +34,13 @@ public class ContextConfigurationAttributes {
private final Class<?> declaringClass;
private final String[] locations;
private String[] locations;
private final Class<?>[] classes;
private Class<?>[] classes;
private final boolean inheritLocations;
private final Class<? extends ContextLoader> contextLoader;
private final Class<? extends ContextLoader> contextLoaderClass;
/**
@ -72,9 +72,6 @@ public class ContextConfigurationAttributes {
/**
* TODO Document ContextConfigurationAttributes constructor.
*
* @param declaringClass
* @param contextConfiguration
*/
public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
@ -83,20 +80,14 @@ public class ContextConfigurationAttributes {
/**
* TODO Document ContextConfigurationAttributes constructor.
*
* @param declaringClass
* @param locations
* @param classes
* @param inheritLocations
* @param contextLoader
*/
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
boolean inheritLocations, Class<? extends ContextLoader> contextLoader) {
boolean inheritLocations, Class<? extends ContextLoader> contextLoaderClass) {
this.declaringClass = declaringClass;
this.locations = locations;
this.classes = classes;
this.inheritLocations = inheritLocations;
this.contextLoader = contextLoader;
this.contextLoaderClass = contextLoaderClass;
}
/**
@ -113,6 +104,13 @@ public class ContextConfigurationAttributes {
return this.locations;
}
/**
* TODO Document setLocations().
*/
public void setLocations(String[] locations) {
this.locations = locations;
}
/**
* TODO Document getClasses().
*/
@ -120,6 +118,13 @@ public class ContextConfigurationAttributes {
return this.classes;
}
/**
* TODO Document setClasses().
*/
public void setClasses(Class<?>[] classes) {
this.classes = classes;
}
/**
* TODO Document isInheritLocations().
*/
@ -128,10 +133,10 @@ public class ContextConfigurationAttributes {
}
/**
* TODO Document getContextLoader().
* TODO Document getContextLoaderClass().
*/
public Class<? extends ContextLoader> getContextLoader() {
return this.contextLoader;
public Class<? extends ContextLoader> getContextLoaderClass() {
return this.contextLoaderClass;
}
/**
@ -144,7 +149,7 @@ public class ContextConfigurationAttributes {
.append("locations", ObjectUtils.nullSafeToString(this.locations))//
.append("classes", ObjectUtils.nullSafeToString(this.classes))//
.append("inheritLocations", this.inheritLocations)//
.append("contextLoader", this.contextLoader)//
.append("contextLoaderClass", this.contextLoaderClass)//
.toString();
}

View File

@ -25,10 +25,9 @@ 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.AnnotationUtils;
import org.springframework.test.context.ResourceTypeAwareContextLoader.ResourceType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@ -40,21 +39,53 @@ import org.springframework.util.StringUtils;
* @since 3.1
* @see ContextLoader
* @see ContextConfiguration
* @see ContextConfigurationAttributes
* @see ActiveProfiles
* @see MergedContextConfiguration
*/
abstract class ContextLoaderUtils {
// TODO Consider refactoring ContextLoaderUtils into a stateful
// ContextLoaderResolver.
private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
private static final String STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.GenericXmlContextLoader";
private static final ResourcePathLocationsResolver resourcePathLocationsResolver = new ResourcePathLocationsResolver();
private static final ClassNameLocationsResolver classNameLocationsResolver = new ClassNameLocationsResolver();
/**
* TODO Document resolveContextConfigurationAttributes().
*/
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType,
clazz));
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
contextConfiguration, declaringClass));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
contextConfiguration);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(0, attributes);
declaringClass = contextConfiguration.inheritLocations() ? AnnotationUtils.findAnnotationDeclaringClass(
annotationType, declaringClass.getSuperclass()) : null;
}
return attributesList;
}
/**
* Resolves the {@link ContextLoader} {@link Class} to use for the
@ -66,22 +97,27 @@ abstract class ContextLoaderUtils {
* default context loader class name ({@value #STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME})
* will be used. For details on the class resolution process, see
* {@link #resolveContextLoaderClass(Class, String)}.
*
* @param testClass the test class for which the <code>ContextLoader</code>
* should be resolved (must not be <code>null</code>)
* @param configAttributesList TODO Document parameter
* @param defaultContextLoaderClassName the name of the default
* <code>ContextLoader</code> class to use (may be <code>null</code>)
*
* @return the resolved <code>ContextLoader</code> for the supplied
* <code>testClass</code> (never <code>null</code>)
* @see #resolveContextLoaderClass(Class, String)
*/
static ContextLoader resolveContextLoader(Class<?> testClass, String defaultContextLoaderClassName) {
static ContextLoader resolveContextLoader(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Test class must not be null");
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be null or empty");
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
defaultContextLoaderClassName = STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME;
}
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass,
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass, configAttributesList,
defaultContextLoaderClassName);
return (ContextLoader) BeanUtils.instantiateClass(contextLoaderClass);
@ -103,52 +139,46 @@ abstract class ContextLoaderUtils {
* with the supplied <code>defaultContextLoaderClassName</code>.</li>
* </ol>
*
* @param clazz the class for which to resolve the <code>ContextLoader</code>
* @param testClass the class for which to resolve the <code>ContextLoader</code>
* class; must not be <code>null</code>
* @param configAttributesList TODO Document parameter
* @param defaultContextLoaderClassName the name of the default
* <code>ContextLoader</code> class to use; must not be <code>null</code> or empty
*
* @return the <code>ContextLoader</code> class to use for the specified class
* (never <code>null</code>)
* @throws IllegalArgumentException if {@link ContextConfiguration
* &#064;ContextConfiguration} is not <em>present</em> on the supplied class
*/
@SuppressWarnings("unchecked")
static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> clazz, String defaultContextLoaderClassName) {
Assert.notNull(clazz, "Class must not be null");
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 null or empty");
Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty");
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType,
clazz));
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
if (logger.isTraceEnabled()) {
logger.trace("Processing ContextLoader for @ContextConfiguration [" + contextConfiguration
+ "] and declaring class [" + declaringClass + "]");
logger.trace(String.format(
"Processing ContextLoader for context configuration attributes [%s] and test class [%s]",
configAttributes, testClass));
}
Class<? extends ContextLoader> contextLoaderClass = contextConfiguration.loader();
Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
if (!ContextLoader.class.equals(contextLoaderClass)) {
if (logger.isDebugEnabled()) {
logger.debug("Found explicit ContextLoader [" + contextLoaderClass
+ "] for @ContextConfiguration [" + contextConfiguration + "] and declaring class ["
+ declaringClass + "]");
logger.debug(String.format(
"Found explicit ContextLoader class [%s] for context configuration attributes [%s] and test class [%s]",
contextLoaderClass, configAttributes, testClass));
}
return contextLoaderClass;
}
declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType,
declaringClass.getSuperclass());
}
try {
if (logger.isTraceEnabled()) {
ContextConfiguration contextConfiguration = clazz.getAnnotation(annotationType);
logger.trace("Using default ContextLoader class [" + defaultContextLoaderClassName
+ "] for @ContextConfiguration [" + contextConfiguration + "] and class [" + clazz + "]");
logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]",
defaultContextLoaderClassName, testClass));
}
return (Class<? extends ContextLoader>) ContextLoaderUtils.class.getClassLoader().loadClass(
defaultContextLoaderClassName);
@ -160,65 +190,6 @@ abstract class ContextLoaderUtils {
}
}
/**
* Resolves {@link ApplicationContext} resource locations for the supplied
* {@link Class class}, using the supplied {@link ContextLoader} to
* {@link ContextLoader#processLocations(Class, String...) process} the
* locations.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations()
* inheritLocations} flag of {@link ContextConfiguration
* &#064;ContextConfiguration} will be taken into consideration.
* Specifically, if the <code>inheritLocations</code> flag is set to
* <code>true</code>, locations defined in the annotated class will be
* appended to the locations defined in superclasses.
*
* @param contextLoader the ContextLoader to use for processing the
* locations (must not be <code>null</code>)
* @param clazz the class for which to resolve the resource locations (must
* not be <code>null</code>)
* @return the list of ApplicationContext resource locations for the
* specified class, including locations from superclasses if appropriate
* (never <code>null</code>)
* @throws IllegalArgumentException if {@link ContextConfiguration
* &#064;ContextConfiguration} is not <em>present</em> on the supplied class
*/
static String[] resolveContextLocations(ContextLoader contextLoader, Class<?> clazz) {
Assert.notNull(contextLoader, "ContextLoader must not be null");
Assert.notNull(clazz, "Class must not be null");
boolean processConfigurationClasses = (contextLoader instanceof ResourceTypeAwareContextLoader)
&& ResourceType.CLASSES == ((ResourceTypeAwareContextLoader) contextLoader).getResourceType();
LocationsResolver locationsResolver = processConfigurationClasses ? classNameLocationsResolver
: resourcePathLocationsResolver;
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType,
clazz));
final List<String> locationsList = new ArrayList<String>();
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
contextConfiguration, declaringClass));
}
String[] resolvedLocations = locationsResolver.resolveLocations(contextConfiguration, declaringClass);
String[] processedLocations = contextLoader.processLocations(declaringClass, resolvedLocations);
locationsList.addAll(0, Arrays.asList(processedLocations));
declaringClass = contextConfiguration.inheritLocations() ? AnnotationUtils.findAnnotationDeclaringClass(
annotationType, declaringClass.getSuperclass()) : null;
}
return StringUtils.toStringArray(locationsList);
}
/**
* Resolves <em>active bean definition profiles</em> for the supplied
* {@link Class class}.
@ -231,6 +202,7 @@ abstract class ContextLoaderUtils {
*
* @param clazz the class for which to resolve the active profiles (must
* not be <code>null</code>)
*
* @return the set of active profiles for the specified class, including
* active profiles from superclasses if appropriate (never <code>null</code>)
*/
@ -284,136 +256,77 @@ abstract class ContextLoaderUtils {
return StringUtils.toStringArray(activeProfiles);
}
/**
* TODO Document resolveContextConfigurationAttributes().
*
* @param clazz
* @return
/*
* Resolves {@link ApplicationContext} resource locations for the supplied
* {@link Class class}, using the supplied {@link ContextLoader} to {@link
* ContextLoader#processLocations(Class, String...) process} the locations.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations()
* inheritLocations} flag of {@link ContextConfiguration
* &#064;ContextConfiguration} will be taken into consideration.
* Specifically, if the <code>inheritLocations</code> flag is set to
* <code>true</code>, locations defined in the annotated class will be
* appended to the locations defined in superclasses.
*
* @param contextLoader the ContextLoader to use for processing the
* locations (must not be <code>null</code>)
*
* @param clazz the class for which to resolve the resource locations (must
* not be <code>null</code>)
*
* @return the list of ApplicationContext resource locations for the
* specified class, including locations from superclasses if appropriate
* (never <code>null</code>)
*
* @throws IllegalArgumentException if {@link ContextConfiguration
* &#064;ContextConfiguration} is not <em>present</em> on the supplied class
*/
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType,
clazz));
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
contextConfiguration, declaringClass));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
contextConfiguration);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(0, attributes);
declaringClass = contextConfiguration.inheritLocations() ? AnnotationUtils.findAnnotationDeclaringClass(
annotationType, declaringClass.getSuperclass()) : null;
}
return attributesList;
}
/**
* TODO Document buildMergedContextConfiguration().
*
* @param testClass
* @param defaultContextLoaderClassName
* @return
*/
static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
String defaultContextLoaderClassName) {
ContextLoader contextLoader = resolveContextLoader(testClass, defaultContextLoaderClassName);
List<ContextConfigurationAttributes> configAttributesList = resolveContextConfigurationAttributes(testClass);
// TODO Merge locations from List<ContextConfigurationAttributes>
String[] locations = resolveContextLocations(contextLoader, testClass);
ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
defaultContextLoaderClassName);
// TODO Merge classes from List<ContextConfigurationAttributes>
Class<?>[] classes = {};
// Algorithm:
// - iterate over config attributes
// -- let loader process locations
// -- let loader process classes, if it's a SmartContextLoader
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;
// TODO Decide on mutability of locations and classes properties
smartContextLoader.processContextConfigurationAttributes(configAttributes);
locationsList.addAll(Arrays.asList(configAttributes.getLocations()));
classesList.addAll(Arrays.asList(configAttributes.getClasses()));
}
else {
String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
configAttributes.getLocations());
locationsList.addAll(Arrays.asList(processedLocations));
// Legacy ContextLoaders don't know how to process classes
}
}
String[] locations = StringUtils.toStringArray(locationsList);
Class<?>[] classes = ClassUtils.toClassArray(classesList);
String[] activeProfiles = resolveActiveProfiles(testClass);
return new MergedContextConfiguration(testClass, locations, classes, activeProfiles, contextLoader);
}
/**
* Strategy interface for resolving application context resource locations.
*
* <p>The semantics of the resolved locations are implementation-dependent.
*/
private static interface LocationsResolver {
/**
* Resolves application context resource locations for the supplied
* {@link ContextConfiguration} annotation and the class which declared it.
* @param contextConfiguration the <code>ContextConfiguration</code>
* for which to resolve resource locations
* @param declaringClass the class that declared <code>ContextConfiguration</code>
* @return an array of application context resource locations
* (can be <code>null</code> or empty)
*/
String[] resolveLocations(ContextConfiguration contextConfiguration, Class<?> declaringClass);
}
/**
* <code>LocationsResolver</code> that resolves locations as Strings,
* which are assumed to be path-based resources.
*/
private static final class ResourcePathLocationsResolver implements LocationsResolver {
/**
* Resolves path-based resources from the {@link ContextConfiguration#locations() locations}
* and {@link ContextConfiguration#value() value} attributes of the supplied
* {@link ContextConfiguration} annotation.
*
* <p>Ignores the {@link ContextConfiguration#classes() classes} attribute.
* @throws IllegalStateException if both the locations and value
* attributes have been declared
*/
public String[] resolveLocations(ContextConfiguration contextConfiguration, Class<?> declaringClass) {
return ContextConfigurationAttributes.resolveLocations(declaringClass, contextConfiguration);
}
}
/**
* <code>LocationsResolver</code> that converts classes to fully qualified class names.
*/
private static final class ClassNameLocationsResolver implements LocationsResolver {
/**
* Resolves class names from the {@link ContextConfiguration#classes() classes}
* attribute of the supplied {@link ContextConfiguration} annotation.
*
* <p>Ignores the {@link ContextConfiguration#locations() locations}
* and {@link ContextConfiguration#value() value} attributes.
*/
public String[] resolveLocations(ContextConfiguration contextConfiguration, Class<?> declaringClass) {
String[] classNames = null;
Class<?>[] configClasses = contextConfiguration.classes();
if (!ObjectUtils.isEmpty(configClasses)) {
classNames = new String[configClasses.length];
for (int i = 0; i < configClasses.length; i++) {
classNames[i] = configClasses[i].getName();
}
}
return classNames;
}
}
}

View File

@ -44,6 +44,8 @@ public class MergedContextConfiguration {
private final ContextLoader contextLoader;
private final String contextKey;
private static String[] processLocations(String[] locations) {
return locations == null ? new String[] {} : locations;
@ -65,14 +67,23 @@ public class MergedContextConfiguration {
return StringUtils.toStringArray(sortedProfilesSet);
}
/**
* Generates a context <code>key</code> from the supplied values.
*/
private static String generateContextKey(String[] locations, Class<?>[] classes, String[] activeProfiles,
ContextLoader contextLoader) {
String locationsKey = ObjectUtils.nullSafeToString(locations);
String classesKey = ObjectUtils.nullSafeToString(classes);
String activeProfilesKey = ObjectUtils.nullSafeToString(activeProfiles);
String contextLoaderKey = contextLoader == null ? "null" : contextLoader.getClass().getName();
return String.format("locations = [%s], classes = [%s], activeProfiles = [%s], contextLoader = [%s]",
locationsKey, classesKey, activeProfilesKey, contextLoaderKey);
}
/**
* TODO Document MergedContextConfiguration constructor.
*
* @param testClass
* @param locations
* @param classes
* @param activeProfiles
* @param contextLoader
*/
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
String[] activeProfiles, ContextLoader contextLoader) {
@ -81,6 +92,7 @@ public class MergedContextConfiguration {
this.classes = processClasses(classes);
this.activeProfiles = processActiveProfiles(activeProfiles);
this.contextLoader = contextLoader;
this.contextKey = generateContextKey(this.locations, this.classes, this.activeProfiles, this.contextLoader);
}
/**
@ -118,6 +130,13 @@ public class MergedContextConfiguration {
return this.contextLoader;
}
/**
* TODO Document getContextKey().
*/
public String getContextKey() {
return this.contextKey;
}
/**
* TODO Document overridden toString().
*/
@ -129,6 +148,7 @@ public class MergedContextConfiguration {
.append("classes", ObjectUtils.nullSafeToString(this.classes))//
.append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles))//
.append("contextLoader", this.contextLoader)//
.append("contextKey", this.contextKey)//
.toString();
}

View File

@ -1,69 +0,0 @@
/*
* Copyright 2002-2011 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;
/**
* Extension of the {@link ContextLoader} API for context loaders that
* are aware of the type of context configuration resources that they
* support.
*
* <p>Prior to Spring 3.1, context loaders supported only String-based
* resource locations; as of Spring 3.1 context loaders may choose to
* support either String-based or Class-based resources (but not both).
*
* <p>TODO Document how classes are converted into String[] locations
* and passed into the corresponding methods in the ContextLoader API.
*
* <p>If a context loader does not implement this interface it is assumed
* that the loader supports String-based resource locations.
*
* @author Sam Brannen
* @since 3.1
*/
public interface ResourceTypeAwareContextLoader extends ContextLoader {
/**
* Enumeration of context configuration resource types that a given
* <code>ContextLoader</code> can support.
*
* <p>The enum constants have a one-to-one correlation to attributes
* of the {@link ContextConfiguration} annotation.
*/
public static enum ResourceType {
/**
* String-based resource locations.
* @see ContextConfiguration#locations
* @see ContextConfiguration#value
*/
LOCATIONS,
/**
* Configuration class resources.
* @see ContextConfiguration#classes
*/
CLASSES;
};
/**
* @return the context configuration resource type supported by this ContextLoader
*/
ResourceType getResourceType();
}

View File

@ -26,12 +26,13 @@ import org.springframework.context.ApplicationContext;
*/
public interface SmartContextLoader extends ContextLoader {
/**
* TODO Document processContextConfigurationAttributes().
*/
void processContextConfigurationAttributes(ContextConfigurationAttributes configAttributes);
/**
* TODO Document loadContext().
*
* @param mergedContextConfiguration
* @return
* @throws Exception
*/
ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) throws Exception;

View File

@ -24,11 +24,10 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.AttributeAccessorSupport;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* TestContext encapsulates the context in which a test is executed, agnostic of
* the actual testing framework in use.
* <code>TestContext</code> encapsulates the context in which a test is executed,
* agnostic of the actual testing framework in use.
*
* @author Sam Brannen
* @author Juergen Hoeller
@ -42,8 +41,6 @@ public class TestContext extends AttributeAccessorSupport {
private final ContextCache contextCache;
private final String contextKey;
private final MergedContextConfiguration mergedContextConfiguration;
private final Class<?> testClass;
@ -69,8 +66,8 @@ public class TestContext extends AttributeAccessorSupport {
* and {@link ContextCache context cache} and parse the corresponding
* {@link ContextConfiguration &#064;ContextConfiguration} annotation, if
* present.
* <p>If the supplied class name for the default ContextLoader is
* <code>null</code> or <em>empty</em> and no <code>ContextLoader</code>
* <p>If the supplied class name for the default <code>ContextLoader</code>
* is <code>null</code> or <em>empty</em> and no <code>ContextLoader</code>
* class is explicitly supplied via the
* <code>&#064;ContextConfiguration</code> annotation, a
* {@link org.springframework.test.context.support.GenericXmlContextLoader
@ -106,14 +103,13 @@ public class TestContext extends AttributeAccessorSupport {
}
this.contextCache = contextCache;
this.contextKey = generateContextKey(mergedContextConfiguration);
this.mergedContextConfiguration = mergedContextConfiguration;
this.testClass = testClass;
}
/**
* Load an <code>ApplicationContext</code> for this test context using the
* configured <code>ContextLoader</code> and resource locations.
* configured <code>ContextLoader</code> and configuration attributes.
* @throws Exception if an error occurs while loading the application context
*/
private ApplicationContext loadApplicationContext() throws Exception {
@ -137,26 +133,6 @@ public class TestContext extends AttributeAccessorSupport {
return applicationContext;
}
/**
* Generates a context <code>key</code> from information stored in the
* {@link MergedContextConfiguration} for this <code>TestContext</code>.
*/
private String generateContextKey(MergedContextConfiguration mergedContextConfiguration) {
String[] locations = mergedContextConfiguration.getLocations();
Class<?>[] classes = mergedContextConfiguration.getClasses();
String[] activeProfiles = mergedContextConfiguration.getActiveProfiles();
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
String locationsKey = ObjectUtils.nullSafeToString(locations);
String classesKey = ObjectUtils.nullSafeToString(classes);
String activeProfilesKey = ObjectUtils.nullSafeToString(activeProfiles);
String contextLoaderKey = contextLoader == null ? "null" : contextLoader.getClass().getName();
return String.format("locations = [%s], classes = [%s], activeProfiles = [%s], contextLoader = [%s]",
locationsKey, classesKey, activeProfilesKey, contextLoaderKey);
}
/**
* Get the {@link ApplicationContext application context} for this test
* context, possibly cached.
@ -165,6 +141,7 @@ public class TestContext extends AttributeAccessorSupport {
* application context
*/
public ApplicationContext getApplicationContext() {
String contextKey = mergedContextConfiguration.getContextKey();
synchronized (contextCache) {
ApplicationContext context = contextCache.get(contextKey);
if (context == null) {
@ -239,7 +216,9 @@ public class TestContext extends AttributeAccessorSupport {
* replacing a bean definition).
*/
public void markApplicationContextDirty() {
contextCache.setDirty(contextKey);
synchronized (contextCache) {
contextCache.setDirty(mergedContextConfiguration.getContextKey());
}
}
/**

View File

@ -18,8 +18,8 @@ package org.springframework.test.context.support;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.ResourceTypeAwareContextLoader;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -39,7 +39,17 @@ import org.springframework.util.StringUtils;
* @see #generateDefaultLocations
* @see #modifyLocations
*/
public abstract class AbstractContextLoader implements SmartContextLoader, ResourceTypeAwareContextLoader {
public abstract class AbstractContextLoader implements SmartContextLoader {
/**
* TODO Document processContextConfigurationAttributes().
*/
public void processContextConfigurationAttributes(ContextConfigurationAttributes configAttributes) {
String[] processedLocations = processLocations(configAttributes.getDeclaringClass(),
configAttributes.getLocations());
configAttributes.setLocations(processedLocations);
}
/**
* If the supplied <code>locations</code> are <code>null</code> or
@ -142,13 +152,4 @@ public abstract class AbstractContextLoader implements SmartContextLoader, Resou
*/
protected abstract String getResourceSuffix();
/**
* The default implementation returns {@link ResourceType#LOCATIONS}.
* <p>Can be overridden by subclasses.
* @since 3.1
*/
public ResourceType getResourceType() {
return ResourceType.LOCATIONS;
}
}

View File

@ -20,12 +20,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.StringUtils;
/**
@ -49,25 +48,21 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
/**
* TODO Document loadContext().
* TODO Document loadContext(MergedContextConfiguration).
*
* @see org.springframework.test.context.SmartContextLoader#loadContext(org.springframework.test.context.MergedContextConfiguration)
* @see SmartContextLoader#loadContext(MergedContextConfiguration)
*/
public final ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) throws Exception {
public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration)
throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
mergedContextConfiguration));
}
String[] locations = mergedContextConfiguration.getLocations();
Assert.notNull(locations, "Can not load an ApplicationContext with a NULL 'locations' array. "
+ "Consider annotating your test class with @ContextConfiguration.");
GenericApplicationContext context = new GenericApplicationContext();
context.getEnvironment().setActiveProfiles(mergedContextConfiguration.getActiveProfiles());
prepareContext(context);
customizeBeanFactory(context.getDefaultListableBeanFactory());
loadBeanDefinitions(context, locations);
loadBeanDefinitions(context, mergedContextConfiguration);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context);
context.refresh();
@ -84,7 +79,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* prepare the context.</li>
* <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to
* allow for customizing the context's <code>DefaultListableBeanFactory</code>.</li>
* <li>Delegates to {@link #loadBeanDefinitions(GenericApplicationContext, String...)}
* <li>TODO Update/revert documentation... Delegates to {@link #loadBeanDefinitions(GenericApplicationContext, String...)}
* to populate the context from the specified config locations.</li>
* <li>Delegates to {@link AnnotationConfigUtils} for
* {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
@ -106,7 +101,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
GenericApplicationContext context = new GenericApplicationContext();
prepareContext(context);
customizeBeanFactory(context.getDefaultListableBeanFactory());
loadBeanDefinitions(context, locations);
createBeanDefinitionReader(context).loadBeanDefinitions(locations);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context);
context.refresh();
@ -154,12 +149,13 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* and override this method to provide a custom strategy for loading or
* registering bean definitions.
* @param context the context into which the bean definitions should be loaded
* @param locations the resource locations from which to load the bean definitions
* @param mergedContextConfiguration TODO Document parameters.
* @since 3.1
* @see #loadContext
*/
protected void loadBeanDefinitions(GenericApplicationContext context, String... locations) {
createBeanDefinitionReader(context).loadBeanDefinitions(locations);
protected void loadBeanDefinitions(GenericApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
createBeanDefinitionReader(context).loadBeanDefinitions(mergedContextConfiguration.getLocations());
}
/**
@ -167,7 +163,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* loading bean definitions into the supplied {@link GenericApplicationContext context}.
* @param context the context for which the BeanDefinitionReader should be created
* @return a BeanDefinitionReader for the supplied context
* @see #loadBeanDefinitions
* @see #loadContext(String...)
* @see BeanDefinitionReader
*/
protected abstract BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context);

View File

@ -16,12 +16,16 @@
package org.springframework.test.context.support;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@ -30,13 +34,6 @@ import org.springframework.util.ObjectUtils;
* registers bean definitions from
* {@link org.springframework.context.annotation.Configuration configuration classes}.
*
* <p>This <code>ContextLoader</code> supports class-based context configuration
* {@link #getResourceType() resources} as opposed to string-based resources.
* Consequently, <em>locations</em> (as discussed in the {@link ContextLoader}
* API and superclasses) are interpreted as fully qualified class names
* in the context of this class. The documentation and method parameters
* reflect this.
*
* @author Sam Brannen
* @since 3.1
*/
@ -46,6 +43,21 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
/**
* TODO Document overridden processContextConfigurationAttributes().
*
* @see org.springframework.test.context.SmartContextLoader#processContextConfigurationAttributes
*/
public void processContextConfigurationAttributes(ContextConfigurationAttributes configAttributes) {
if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultClasses()) {
Class<?>[] defaultConfigurationClasses = generateDefaultConfigurationClasses(configAttributes.getDeclaringClass());
configAttributes.setClasses(defaultConfigurationClasses);
}
}
/**
* TODO Update documentation regarding SmartContextLoader SPI.
*
* <p>
* Registers {@link org.springframework.context.annotation.Configuration configuration classes}
* in the supplied {@link GenericApplicationContext context} from the specified
* class names.
@ -64,21 +76,9 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
* @throws IllegalArgumentException if a supplied class name does not represent a class
*/
@Override
protected void loadBeanDefinitions(GenericApplicationContext context, String... classNames) {
Class<?>[] configClasses = new Class<?>[classNames.length];
for (int i = 0; i < classNames.length; i++) {
String className = classNames[i];
try {
configClasses[i] = (Class<?>) context.getClassLoader().loadClass(className);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException(String.format(
"The supplied class name [%s] does not represent a class.", className), e);
}
}
protected void loadBeanDefinitions(GenericApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
Class<?>[] configClasses = mergedContextConfiguration.getClasses();
if (logger.isDebugEnabled()) {
logger.debug("Registering configuration classes: " + ObjectUtils.nullSafeToString(configClasses));
}
@ -86,40 +86,10 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
}
/**
* Returns <code>null</code>; intended as a <em>no-op</em> operation.
* TODO Document isGenerateDefaultClasses().
*/
@Override
protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
return null;
}
/**
* Generates the default {@link org.springframework.context.annotation.Configuration configuration class}
* names array based on the supplied class.
*
* <p>For example, if the supplied class is <code>com.example.MyTest</code>,
* the generated array will contain a single string with a value of
* &quot;com.example.MyTest<code>&lt;suffix&gt;</code>&quot;,
* where <code>&lt;suffix&gt;</code> is the value of the
* {@link #getResourceSuffix() resource suffix} string.
* @param clazz the class for which the default configuration class names are to be generated
* @return an array of default configuration class names
* @see #getResourceSuffix()
*/
@Override
protected String[] generateDefaultLocations(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
String suffix = getResourceSuffix();
Assert.hasText(suffix, "Resource suffix must not be empty");
return new String[] { clazz.getName() + suffix };
}
/**
* Returns the supplied class names unmodified.
*/
@Override
protected String[] modifyLocations(Class<?> clazz, String... classNames) {
return classNames;
protected boolean isGenerateDefaultClasses() {
return true;
}
/**
@ -133,17 +103,64 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
*
* @see #generateDefaultLocations(Class)
*/
@Override
protected String getResourceSuffix() {
protected String getConfigurationClassNameSuffix() {
return "$ContextConfiguration";
}
/**
* Returns {@link ResourceType#CLASSES}.
* TODO Document generateDefaultConfigurationClasses().
*/
protected Class<?>[] generateDefaultConfigurationClasses(Class<?> declaringClass) {
Assert.notNull(declaringClass, "Declaring class must not be null");
String suffix = getConfigurationClassNameSuffix();
Assert.hasText(suffix, "Configuration class name suffix must not be empty");
String className = declaringClass.getName() + suffix;
List<Class<?>> configClasses = new ArrayList<Class<?>>();
try {
configClasses.add((Class<?>) getClass().getClassLoader().loadClass(className));
}
catch (ClassNotFoundException e) {
logger.warn(String.format("Cannot load @Configuration class with generated class name [%s].", className), e);
}
return configClasses.toArray(new Class<?>[configClasses.size()]);
}
/**
* TODO Document overridden createBeanDefinitionReader().
*/
@Override
public final ResourceType getResourceType() {
return ResourceType.CLASSES;
protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the createBeanDefinitionReader(GenericApplicationContext) method");
}
/**
* TODO Document overridden generateDefaultLocations().
*/
@Override
protected String[] generateDefaultLocations(Class<?> clazz) {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the generateDefaultLocations(Class) method");
}
/**
* TODO Document overridden modifyLocations().
*/
@Override
protected String[] modifyLocations(Class<?> clazz, String... locations) {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the modifyLocations(Class, String...) method");
}
/**
* TODO Document overridden getResourceSuffix().
*/
@Override
protected String getResourceSuffix() {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the getResourceSuffix() method");
}
}

View File

@ -38,6 +38,7 @@ import org.springframework.test.context.support.GenericXmlContextLoader;
*/
public class ContextLoaderUtilsTests {
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[] {};
private static final String[] EMPTY_STRING_ARRAY = new String[] {};
@ -48,7 +49,7 @@ public class ContextLoaderUtilsTests {
assertArrayEquals(expectedLocations, attributes.getLocations());
assertArrayEquals(expectedClasses, attributes.getClasses());
assertEquals(expectedInheritLocations, attributes.isInheritLocations());
assertEquals(expectedContextLoaderClass, attributes.getContextLoader());
assertEquals(expectedContextLoaderClass, attributes.getContextLoaderClass());
}
private void assertFooAttributes(ContextConfigurationAttributes attributes) {
@ -62,12 +63,14 @@ public class ContextLoaderUtilsTests {
}
private void assertMergedContextConfiguration(MergedContextConfiguration mergedConfig, Class<?> expectedTestClass,
String[] expectedLocations, Class<? extends ContextLoader> expectedContextLoaderClass) {
String[] expectedLocations, Class<?>[] expectedClasses,
Class<? extends ContextLoader> expectedContextLoaderClass) {
assertNotNull(mergedConfig);
assertEquals(expectedTestClass, mergedConfig.getTestClass());
assertNotNull(mergedConfig.getLocations());
assertArrayEquals(expectedLocations, mergedConfig.getLocations());
assertNotNull(mergedConfig.getClasses());
assertArrayEquals(expectedClasses, mergedConfig.getClasses());
assertNotNull(mergedConfig.getActiveProfiles());
assertEquals(expectedContextLoaderClass, mergedConfig.getContextLoader().getClass());
}
@ -82,7 +85,7 @@ public class ContextLoaderUtilsTests {
List<ContextConfigurationAttributes> attributesList = ContextLoaderUtils.resolveContextConfigurationAttributes(BareAnnotations.class);
assertNotNull(attributesList);
assertEquals(1, attributesList.size());
assertAttributes(attributesList.get(0), BareAnnotations.class, EMPTY_STRING_ARRAY, new Class<?>[] {},
assertAttributes(attributesList.get(0), BareAnnotations.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY,
ContextLoader.class, true);
}
@ -117,7 +120,7 @@ public class ContextLoaderUtilsTests {
mergedConfig,
testClass,
new String[] { "classpath:/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml" },
GenericXmlContextLoader.class);
EMPTY_CLASS_ARRAY, GenericXmlContextLoader.class);
}
@Test
@ -126,31 +129,28 @@ public class ContextLoaderUtilsTests {
MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass, null);
assertMergedContextConfiguration(mergedConfig, testClass, new String[] { "classpath:/foo.xml" },
GenericXmlContextLoader.class);
new Class<?>[] { FooConfig.class }, GenericXmlContextLoader.class);
}
@Test
public void buildMergedContextConfigurationWithLocalAnnotationAndOverriddenContexLoader() {
public void buildMergedContextConfigurationWithLocalAnnotationAndOverriddenContextLoader() {
Class<?> testClass = Foo.class;
Class<? extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
expectedContextLoaderClass.getName());
assertMergedContextConfiguration(mergedConfig, testClass, new String[] { "classpath:/foo.xml" },
expectedContextLoaderClass);
new Class<?>[] { FooConfig.class }, expectedContextLoaderClass);
}
@Test
public void buildMergedContextConfigurationWithLocalAndInheritedAnnotations() {
Class<?> testClass = Bar.class;
String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" };
Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class, BarConfig.class };
MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass, null);
// TODO Assert @Configuration classes instead of locations
String[] expectedLocations = new String[] {
"org.springframework.test.context.ContextLoaderUtilsTests$FooConfig",
"org.springframework.test.context.ContextLoaderUtilsTests$BarConfig" };
assertMergedContextConfiguration(mergedConfig, testClass, expectedLocations,
assertMergedContextConfiguration(mergedConfig, testClass, expectedLocations, expectedClasses,
AnnotationConfigContextLoader.class);
}

View File

@ -27,7 +27,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader;
/**
* Unit tests for verifying proper behavior of the {@link ContextCache} in
* conjunction with cache keys generated in {@link TestContext}.
* conjunction with cache keys used in {@link TestContext}.
*
* @author Sam Brannen
* @since 3.1

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 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.
@ -22,8 +22,11 @@ import org.junit.runners.Suite.SuiteClasses;
import org.springframework.test.context.ClassLevelDirtiesContextTests;
import org.springframework.test.context.SpringRunnerContextCacheTests;
import org.springframework.test.context.junit4.annotation.AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests;
import org.springframework.test.context.junit4.annotation.BeanOverridingDefaultConfigClassesInheritedTests;
import org.springframework.test.context.junit4.annotation.BeanOverridingExplicitConfigClassesInheritedTests;
import org.springframework.test.context.junit4.annotation.DefaultConfigClassesBaseTests;
import org.springframework.test.context.junit4.annotation.DefaultConfigClassesInheritedTests;
import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesInheritedTests;
import org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests;
import org.springframework.test.context.junit4.profile.annotation.DefaultProfileAnnotationConfigTests;
import org.springframework.test.context.junit4.profile.annotation.DevProfileAnnotationConfigTests;
@ -31,20 +34,16 @@ import org.springframework.test.context.junit4.profile.xml.DefaultProfileXmlConf
import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTests;
/**
* <p>
* JUnit 4 based test suite for tests involving {@link SpringJUnit4ClassRunner}
* and the <em>Spring TestContext Framework</em>.
* </p>
* <p>
* This test suite serves a dual purpose of verifying that tests run with
* {@link SpringJUnit4ClassRunner} can be used in conjunction with JUnit 4's
* JUnit test suite for tests involving {@link SpringJUnit4ClassRunner} and the
* <em>Spring TestContext Framework</em>.
*
* <p>This test suite serves a dual purpose of verifying that tests run with
* {@link SpringJUnit4ClassRunner} can be used in conjunction with JUnit's
* {@link Suite} runner.
* </p>
* <p>
* Note that tests included in this suite will be executed at least twice if run
* from an automated build process, test runner, etc. that is configured to run
* tests based on a &quot;*Tests.class&quot; pattern match.
* </p>
*
* <p>Note that tests included in this suite will be executed at least twice if
* run from an automated build process, test runner, etc. that is configured to
* run tests based on a &quot;*Tests.class&quot; pattern match.
*
* @author Sam Brannen
* @since 2.5
@ -58,6 +57,9 @@ StandardJUnit4FeaturesTests.class,//
AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.class,//
DefaultConfigClassesBaseTests.class,//
DefaultConfigClassesInheritedTests.class,//
BeanOverridingDefaultConfigClassesInheritedTests.class,//
ExplicitConfigClassesInheritedTests.class,//
BeanOverridingExplicitConfigClassesInheritedTests.class,//
DefaultProfileAnnotationConfigTests.class,//
DevProfileAnnotationConfigTests.class, //
DefaultProfileXmlConfigTests.class,//