Browse Source

Introduce context bootstrap strategy in the TCF

Work done in conjunction with SPR-5243 and SPR-4588 introduced physical
package cycles in the spring-test module. The work performed in
conjunction with SPR-9924 uses reflection to resolve these physical
package cycles; however, prior to this commit the logical package
cycles still remain.

Furthermore, over time it has become apparent that the Spring
TestContext Framework (TCF) could better serve application developers
and especially third-party framework developers by providing a more
flexible mechanism for "bootstrapping" the TCF. For example, prior to
this commit, default TestExecutionListeners could only be registered by
subclassing TestContextManager (and SpringJUnit4ClassRunner if using
JUnit). Similarly, the default ContextLoader could only be set by
subclassing SpringJUnit4ClassRunner for JUnit and by copying and
modifying AbstractTestNGSpringContextTests for TestNG.

This commit addresses the aforementioned issues by introducing a
bootstrap strategy in the TestContext framework that is responsible for
determining default TestExecutionListeners and the default
ContextLoader in an extensible fashion. The new TestContextBootstrapper
SPI also provides a mechanism for supporting various types of
MergedContextConfiguration depending on the feature set of the context
loaders supported by the strategy.

The following provides an overview of the most significant changes in
this commit.

 - Introduced TestContextBootstrapper strategy SPI, BootstrapContext,
   and @BootstrapWith.

 - Introduced AbstractTestContextBootstrapper,
   DefaultTestContextBootstrapper, and WebTestContextBootstrapper
   implementations of the TestContextBootstrapper SPI and extracted
   related reflection code from ContextLoaderUtils & TestContextManager.

 - Introduced BootstrapUtils for retrieving the TestContextBootstrapper
   from @BootstrapWith, falling back to a default if @BootstrapWith is
   not present.

 - @WebAppConfiguration is now annotated with
   @BootstrapWith(WebTestContextBootstrapper.class).

 - CacheAwareContextLoaderDelegate is now an interface with a new
   DefaultCacheAwareContextLoaderDelegate implementation class.

 - Introduced closeContext(MergedContextConfiguration, HierarchyMode) in
   CacheAwareContextLoaderDelegate.

 - DefaultTestContext now uses CacheAwareContextLoaderDelegate instead
   of interacting directly with the ContextCache.

 - DefaultTestContext now delegates to a TestContextBootstrapper for
   building the MergedContextConfiguration.

 - TestContextManager now delegates to TestContextBootstrapper for
   retrieving TestExecutionListeners.

 - Deleted TestContextManager(Class, String) constructor and
   SpringJUnit4ClassRunner.getDefaultContextLoaderClassName(Class)
   method since default ContextLoader support is now implemented by
   TestContextBootstrappers.

 - Extracted ActiveProfilesUtils from ContextLoaderUtils.

 - Extracted ApplicationContextInitializerUtils from ContextLoaderUtils.

 - MetaAnnotationUtils is now a public utility class in the test.util
   package.

 - Removed restriction in @ActiveProfiles that a custom resolver cannot
   be used with the 'value' or 'profiles' attributes.

 - Introduced DefaultActiveProfilesResolver.

Issue: SPR-9955
pull/512/head
Sam Brannen 11 years ago
parent
commit
a281bdbfc5
  1. 16
      spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
  2. 42
      spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java
  3. 92
      spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
  4. 50
      spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java
  5. 118
      spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
  6. 2
      spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
  7. 807
      spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
  8. 78
      spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java
  9. 112
      spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java
  10. 59
      spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java
  11. 105
      spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
  12. 209
      spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
  13. 18
      spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
  14. 53
      spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
  15. 435
      spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
  16. 152
      spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java
  17. 94
      spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java
  18. 311
      spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java
  19. 122
      spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java
  20. 93
      spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java
  21. 5
      spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
  22. 108
      spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
  23. 30
      spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
  24. 41
      spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java
  25. 52
      spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
  26. 45
      spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java
  27. 4
      spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
  28. 36
      spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
  29. 0
      spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml
  30. 33
      spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java
  31. 32
      spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java
  32. 8
      spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java
  33. 9
      spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
  34. 21
      spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java
  35. 47
      spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java
  36. 11
      spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
  37. 9
      spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java

16
spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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
*/

42
spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java

@ -0,0 +1,42 @@ @@ -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();
}

92
spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java

@ -0,0 +1,92 @@ @@ -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);
}
}
}

50
spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java

@ -0,0 +1,50 @@ @@ -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;
}

118
spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 <em>context cache</em> it will simply
* be returned; otherwise, it will be loaded, stored in the cache, and returned.
*
* <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.
* @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);
}

2
spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java

@ -91,6 +91,7 @@ public @interface ContextConfiguration { @@ -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 { @@ -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
*/

807
spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java

@ -1,807 +0,0 @@ @@ -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;
}
}

78
spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java

@ -0,0 +1,78 @@ @@ -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();
}
}

112
spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java

@ -0,0 +1,112 @@ @@ -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);
}
}

59
spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java

@ -1,5 +1,5 @@ @@ -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 @@ -40,8 +40,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
private static final long serialVersionUID = -5827157174866681233L;
private final ContextCache contextCache;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration mergedContextConfiguration;
@ -56,49 +54,31 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext @@ -56,49 +54,31 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
/**
* Delegates to {@link #DefaultTestContext(Class, ContextCache, String)} with a
* value of {@code null} for the default {@code ContextLoader} class name.
* Construct a new test context using the supplied {@link TestContextBootstrapper}.
* @param testContextBootstrapper the {@code TestContextBootstrapper} to use
* to construct the test context (must not be {@code null})
*/
DefaultTestContext(Class<?> testClass, ContextCache contextCache) {
this(testClass, contextCache, null);
DefaultTestContext(TestContextBootstrapper testContextBootstrapper) {
Assert.notNull(testContextBootstrapper, "TestContextBootstrapper must not be null");
BootstrapContext bootstrapContext = testContextBootstrapper.getBootstrapContext();
this.testClass = bootstrapContext.getTestClass();
this.cacheAwareContextLoaderDelegate = bootstrapContext.getCacheAwareContextLoaderDelegate();
this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration();
}
/**
* Construct a new test context for the supplied {@linkplain Class test class}
* and {@linkplain ContextCache context cache} and parse the corresponding
* {@link ContextConfiguration &#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})
* {@inheritDoc}
*/
DefaultTestContext(Class<?> testClass, ContextCache contextCache, String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Test class must not be null");
Assert.notNull(contextCache, "ContextCache must not be null");
this.testClass = testClass;
this.contextCache = contextCache;
this.cacheAwareContextLoaderDelegate = new CacheAwareContextLoaderDelegate(contextCache);
this.mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
defaultContextLoaderClassName, cacheAwareContextLoaderDelegate);
public ApplicationContext getApplicationContext() {
return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
}
/**
* {@inheritDoc}
*/
public ApplicationContext getApplicationContext() {
return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
cacheAwareContextLoaderDelegate.closeContext(mergedContextConfiguration, hierarchyMode);
}
/**
@ -129,13 +109,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext @@ -129,13 +109,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
return testException;
}
/**
* {@inheritDoc}
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
contextCache.remove(mergedContextConfiguration, hierarchyMode);
}
/**
* {@inheritDoc}
*/

105
spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java

@ -0,0 +1,105 @@ @@ -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();
}

209
spring-test/src/main/java/org/springframework/test/context/TestContextManager.java

@ -1,5 +1,5 @@ @@ -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; @@ -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...)
* Get the {@link TestContext} managed by this {@code TestContextManager}.
*/
public TestContextManager(Class<?> testClass, String defaultContextLoaderClassName) {
this.testContext = new DefaultTestContext(testClass, contextCache, defaultContextLoaderClassName);
registerTestExecutionListeners(retrieveTestExecutionListeners(testClass));
protected final TestContext getTestContext() {
return this.testContext;
}
/**
* Returns the {@link TestContext} managed by this
* {@code TestContextManager}.
* Register the supplied list of {@link TestExecutionListener TestExecutionListeners}
* by appending them to the list of listeners used by this {@code TestContextManager}.
* @see #registerTestExecutionListeners(TestExecutionListener...)
*/
protected final TestContext getTestContext() {
return this.testContext;
public void registerTestExecutionListeners(List<TestExecutionListener> testExecutionListeners) {
registerTestExecutionListeners(testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()]));
}
/**
* Register the supplied {@link TestExecutionListener TestExecutionListeners}
* by appending them to the set of listeners used by this {@code TestContextManager}.
* Register the supplied array of {@link TestExecutionListener TestExecutionListeners}
* by appending them to the list of listeners used by this {@code TestContextManager}.
*/
public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
for (TestExecutionListener listener : testExecutionListeners) {
@ -162,110 +154,11 @@ public class TestContextManager { @@ -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 { @@ -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 { @@ -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 { @@ -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

18
spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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>.

53
spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java

@ -50,36 +50,32 @@ import org.springframework.test.context.junit4.statements.SpringRepeat; @@ -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 { @@ -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 { @@ -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

435
spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

@ -0,0 +1,435 @@ @@ -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);
}

152
spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java

@ -0,0 +1,152 @@ @@ -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);
}
}
}

94
spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java

@ -0,0 +1,94 @@ @@ -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;
}
}

311
spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java

@ -0,0 +1,311 @@ @@ -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;
}
}

122
spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java

@ -0,0 +1,122 @@ @@ -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);
}
}

93
spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java

@ -0,0 +1,93 @@ @@ -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);
}
}

5
spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -51,6 +53,7 @@ import java.lang.annotation.Target;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@BootstrapWith(WebTestContextBootstrapper.class)
public @interface WebAppConfiguration {
/**

108
spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java

@ -0,0 +1,108 @@ @@ -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);
}
}

30
spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java → spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java

@ -14,7 +14,7 @@ @@ -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; @@ -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.*; @@ -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 { @@ -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 { @@ -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) {

41
spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java

@ -0,0 +1,41 @@ @@ -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);
}
}

52
spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);

45
spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java

@ -0,0 +1,45 @@ @@ -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);
}
}

4
spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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());
}

36
spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 { @@ -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;
}
}
}

0
spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml → spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml

33
spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java → spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java

@ -14,7 +14,7 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 {
}
}

32
spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java → spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java

@ -14,7 +14,7 @@ @@ -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; @@ -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 @@ -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 @@ -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 {
}

8
spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java → spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java

@ -14,15 +14,19 @@ @@ -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}.

9
spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java → spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java

@ -14,7 +14,7 @@ @@ -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; @@ -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.

21
spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java → spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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);
}

47
spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java → spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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);
}

11
spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java → spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java

@ -14,7 +14,7 @@ @@ -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; @@ -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}.

9
spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java → spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java

@ -14,17 +14,20 @@ @@ -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
Loading…
Cancel
Save