提交 9e6a5ae4 编写于 作者: S Sam Brannen

Merge pull request #780 from sbrannen/SPR-12683

Improve extensibility of TestContext bootstrapping & context caching

These commits include numerous refactorings and enhancements to the
bootstrapping and context caching mechanisms in the Spring TestContext
Framework.

Issue: SPR-12683
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -28,14 +28,15 @@ package org.springframework.test.context;
public interface BootstrapContext {
/**
* Get the {@link Class test class} for this bootstrap context.
* Get the {@linkplain 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>.
* interaction with the {@code ContextCache}.
* @return the context loader delegate (never {@code null})
*/
CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate();
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,6 +16,8 @@
package org.springframework.test.context;
import java.lang.reflect.Constructor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
......@@ -36,6 +38,10 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
*/
abstract class BootstrapUtils {
private static final String DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME = "org.springframework.test.context.support.DefaultBootstrapContext";
private static final String DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME = "org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate";
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);
......@@ -45,6 +51,55 @@ abstract class BootstrapUtils {
/* no-op */
}
/**
* Create the {@code BootstrapContext} for the specified {@linkplain Class test class}.
*
* <p>Uses reflection to create a {@link org.springframework.test.context.support.DefaultBootstrapContext}
* that uses a {@link org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate}.
*
* @param testClass the test class for which the bootstrap context should be created
* @return a new {@code BootstrapContext}; never {@code null}
*/
@SuppressWarnings("unchecked")
static BootstrapContext createBootstrapContext(Class<?> testClass) {
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = createCacheAwareContextLoaderDelegate();
Class<? extends BootstrapContext> clazz = null;
try {
clazz = (Class<? extends BootstrapContext>) ClassUtils.forName(DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME,
BootstrapUtils.class.getClassLoader());
Constructor<? extends BootstrapContext> constructor = clazz.getConstructor(Class.class,
CacheAwareContextLoaderDelegate.class);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Instantiating BootstrapContext using constructor [%s]", constructor));
}
return instantiateClass(constructor, testClass, cacheAwareContextLoaderDelegate);
}
catch (Throwable t) {
throw new IllegalStateException("Could not load BootstrapContext [" + clazz + "]", t);
}
}
@SuppressWarnings("unchecked")
private static CacheAwareContextLoaderDelegate createCacheAwareContextLoaderDelegate() {
Class<? extends CacheAwareContextLoaderDelegate> clazz = null;
try {
clazz = (Class<? extends CacheAwareContextLoaderDelegate>) ClassUtils.forName(
DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME, BootstrapUtils.class.getClassLoader());
if (logger.isDebugEnabled()) {
logger.debug(String.format("Instantiating CacheAwareContextLoaderDelegate from class [%s]",
clazz.getName()));
}
return instantiateClass(clazz, CacheAwareContextLoaderDelegate.class);
}
catch (Throwable t) {
throw new IllegalStateException("Could not load CacheAwareContextLoaderDelegate [" + clazz + "]", t);
}
}
/**
* Resolve the {@link TestContextBootstrapper} type for the test class in the
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,7 +23,7 @@ import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
/**
* A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain
* #loadContext loading} and {@linkplain #closeContext closing} application
* contexts, interacting transparently with a <em>context cache</em> behind
* contexts, interacting transparently with a {@link ContextCache} behind
* the scenes.
*
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not extend the
......@@ -38,8 +38,10 @@ public interface CacheAwareContextLoaderDelegate {
* 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
* <p>If the context is present in the {@code ContextCache} it will simply
* be returned; otherwise, it will be loaded, stored in the cache, and returned.
* <p>The cache statistics should be logged by invoking
* {@link ContextCache#logStatistics()}.
* @param mergedContextConfiguration the merged context configuration to use
* to load the application context; never {@code null}
* @return the application context
......@@ -50,7 +52,7 @@ public interface CacheAwareContextLoaderDelegate {
/**
* Remove the {@linkplain ApplicationContext application context} for the
* supplied {@link MergedContextConfiguration} from the <em>context cache</em>
* supplied {@link MergedContextConfiguration} from the {@code ContextCache}
* 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
......
......@@ -16,145 +16,55 @@
package org.springframework.test.context;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Cache for Spring {@link ApplicationContext ApplicationContexts} in a test
* environment.
* {@code ContextCache} defines the public API for caching Spring
* {@link ApplicationContext ApplicationContexts} within the <em>Spring
* TestContext Framework</em>.
*
* <h3>Rationale</h3>
* <p>Caching has significant performance benefits if initializing the context
* takes a considerable about of time. Although initializing a Spring context
* itself is very quick, some beans in a context, such as a
* {@code LocalSessionFactoryBean} for working with Hibernate, may take some
* time to initialize. Hence it often makes sense to perform that initialization
* only once per test suite.
* <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts}
* keyed by {@link MergedContextConfiguration} instances.
*
* <h3>Implementation Details</h3>
* <p>{@code ContextCache} maintains a cache of {@code ApplicationContexts}
* keyed by {@link MergedContextConfiguration} instances. Behind the scenes,
* Spring's {@link ConcurrentReferenceHashMap} is used to store
* {@linkplain java.lang.ref.SoftReference soft references} to cached contexts
* and {@code MergedContextConfiguration} instances.
* <h3>Rationale</h3>
* <p>Context caching can have significant performance benefits if context
* initialization is complex. So, although initializing a Spring context itself
* is typically very quick, some beans in a context &mdash; for example, an
* in-memory database or a {@code LocalSessionFactoryBean} for working with
* Hibernate &mdash; may take several seconds to initialize. Hence it often
* makes sense to perform that initialization only once per test suite.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see ConcurrentReferenceHashMap
* @since 4.2
*/
class ContextCache {
/**
* Map of context keys to Spring {@code ApplicationContext} instances.
*/
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
new ConcurrentReferenceHashMap<MergedContextConfiguration, ApplicationContext>(64);
/**
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
* of context hierarchies. This information is used for determining which subtrees
* need to be recursively removed and closed when removing a context that is a parent
* of other contexts.
*/
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
new ConcurrentReferenceHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
private final AtomicInteger hitCount = new AtomicInteger();
private final AtomicInteger missCount = new AtomicInteger();
/**
* Reset all state maintained by this cache.
* @see #clear()
* @see #clearStatistics()
*/
public void reset() {
synchronized (contextMap) {
clear();
clearStatistics();
}
}
public interface ContextCache {
/**
* Clear all contexts from the cache and clear context hierarchy information as well.
* The name of the logging category used for reporting {@code ContextCache}
* statistics.
*/
public void clear() {
synchronized (contextMap) {
this.contextMap.clear();
this.hierarchyMap.clear();
}
}
public static final String CONTEXT_CACHE_LOGGING_CATEGORY = "org.springframework.test.context.cache";
/**
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
*/
public void clearStatistics() {
synchronized (contextMap) {
this.hitCount.set(0);
this.missCount.set(0);
}
}
/**
* Determine whether there is a cached context for the given key.
* @param key the context key (never {@code null})
* @return {@code true} if the cache contains a context with the given key
*/
public boolean contains(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
return this.contextMap.containsKey(key);
}
boolean contains(MergedContextConfiguration key);
/**
* Obtain a cached {@code ApplicationContext} for the given key.
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
* be updated accordingly.
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts
* must be updated accordingly.
* @param key the context key (never {@code null})
* @return the corresponding {@code ApplicationContext} instance, or {@code null}
* if not found in the cache
* @see #remove
*/
public ApplicationContext get(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
ApplicationContext context = this.contextMap.get(key);
if (context == null) {
this.missCount.incrementAndGet();
}
else {
this.hitCount.incrementAndGet();
}
return context;
}
/**
* Get the overall hit count for this cache.
* <p>A <em>hit</em> is any access to the cache that returns a non-null
* context for the queried key.
*/
public int getHitCount() {
return this.hitCount.get();
}
/**
* Get the overall miss count for this cache.
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
* context for the queried key.
*/
public int getMissCount() {
return this.missCount.get();
}
ApplicationContext get(MergedContextConfiguration key);
/**
* Explicitly add an {@code ApplicationContext} instance to the cache
......@@ -162,122 +72,79 @@ class ContextCache {
* @param key the context key (never {@code null})
* @param context the {@code ApplicationContext} instance (never {@code null})
*/
public void put(MergedContextConfiguration key, ApplicationContext context) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(context, "ApplicationContext must not be null");
this.contextMap.put(key, context);
MergedContextConfiguration child = key;
MergedContextConfiguration parent = child.getParent();
while (parent != null) {
Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
if (list == null) {
list = new HashSet<MergedContextConfiguration>();
this.hierarchyMap.put(parent, list);
}
list.add(child);
child = parent;
parent = child.getParent();
}
}
void put(MergedContextConfiguration key, ApplicationContext context);
/**
* Remove the context with the given key from the cache and explicitly
* {@linkplain ConfigurableApplicationContext#close() close} it if it is an
* instance of {@link ConfigurableApplicationContext}.
* <p>Generally speaking, you would only call this method if you change the
* state of a singleton bean, potentially affecting future interaction with
* the context.
* <p>In addition, the semantics of the supplied {@code HierarchyMode} will
* {@linkplain org.springframework.context.ConfigurableApplicationContext#close() close}
* it if it is an instance of {@code ConfigurableApplicationContext}.
* <p>Generally speaking, this method should be called if the state of
* a singleton bean has been modified, potentially affecting future
* interaction with the context.
* <p>In addition, the semantics of the supplied {@code HierarchyMode} must
* be honored. See the Javadoc for {@link HierarchyMode} for details.
* @param key the context key; never {@code null}
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
* is not part of a hierarchy
*/
public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
Assert.notNull(key, "Key must not be null");
// startKey is the level at which to begin clearing the cache, depending
// on the configured hierarchy mode.
MergedContextConfiguration startKey = key;
if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
while (startKey.getParent() != null) {
startKey = startKey.getParent();
}
}
List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
remove(removedContexts, startKey);
void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode);
// Remove all remaining references to any removed contexts from the
// hierarchy map.
for (MergedContextConfiguration currentKey : removedContexts) {
for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
children.remove(currentKey);
}
}
/**
* Determine the number of contexts currently stored in the cache.
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
* this method must return {@code Integer.MAX_VALUE}.
*/
int size();
// Remove empty entries from the hierarchy map.
for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
if (this.hierarchyMap.get(currentKey).isEmpty()) {
this.hierarchyMap.remove(currentKey);
}
}
}
/**
* Determine the number of parent contexts currently tracked within the cache.
*/
int getParentContextCount();
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
/**
* Get the overall hit count for this cache.
* <p>A <em>hit</em> is any access to the cache that returns a non-null
* context for the queried key.
*/
int getHitCount();
Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
if (children != null) {
for (MergedContextConfiguration child : children) {
// Recurse through lower levels
remove(removedContexts, child);
}
// Remove the set of children for the current context from the hierarchy map.
this.hierarchyMap.remove(key);
}
/**
* Get the overall miss count for this cache.
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
* context for the queried key.
*/
int getMissCount();
// Physically remove and close leaf nodes first (i.e., on the way back up the
// stack as opposed to prior to the recursive call).
ApplicationContext context = this.contextMap.remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
removedContexts.add(key);
}
/**
* Reset all state maintained by this cache including statistics.
* @see #clear()
* @see #clearStatistics()
*/
void reset();
/**
* Determine the number of contexts currently stored in the cache.
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
* this method returns {@code Integer.MAX_VALUE}.
* Clear all contexts from the cache, clearing context hierarchy information as well.
*/
public int size() {
return this.contextMap.size();
}
void clear();
/**
* Determine the number of parent contexts currently tracked within the cache.
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
*/
public int getParentContextCount() {
return this.hierarchyMap.size();
}
void clearStatistics();
/**
* Generate a text string containing the statistics for this cache.
* <p>Specifically, the returned string contains the {@linkplain #size},
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount() miss count},
* and {@linkplain #getParentContextCount() parent context count}.
* @return the statistics for this cache
* Log the statistics for this {@code ContextCache} at {@code DEBUG} level
* using the {@value #CONTEXT_CACHE_LOGGING_CATEGORY} logging category.
* <p>The following information should be logged.
* <ul>
* <li>name of the concrete {@code ContextCache} implementation</li>
* <li>{@linkplain #size}</li>
* <li>{@linkplain #getParentContextCount() parent context count}</li>
* <li>{@linkplain #getHitCount() hit count}</li>
* <li>{@linkplain #getMissCount() miss count}</li>
* <li>any other information useful for monitoring the state of this cache</li>
* </ul>
*/
@Override
public String toString() {
return new ToStringCreator(this)
.append("size", size())
.append("hitCount", getHitCount())
.append("missCount", getMissCount())
.append("parentContextCount", getParentContextCount())
.toString();
}
void logStatistics();
}
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -19,25 +19,33 @@ package org.springframework.test.context;
import java.util.List;
/**
* {@code TestContextBootstrapper} defines a strategy SPI for bootstrapping the
* {@code TestContextBootstrapper} defines the 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
* <p>A {@code TestContextBootstrapper} is used by the {@link TestContextManager} 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
* current test and to {@linkplain #buildTestContext build the TestContext} that
* it manages.
*
* <h3>Configuration</h3>
*
* <p>A custom bootstrapping strategy can be configured for a test class (or
* test class hierarchy) via {@link BootstrapWith @BootstrapWith}, either
* directly or as a meta-annotation. See
* {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
* for an example.
*
* <p>If a bootstrapper is not explicitly configured via {@code @BootstrapWith}, the
* {@link org.springframework.test.context.support.DefaultTestContextBootstrapper DefaultTestContextBootstrapper}
* will be used.
*
* <h3>Implementation Notes</h3>
*
* <p>Concrete implementations must provide a {@code public} no-args constructor.
*
* <p><strong>Note</strong>: this SPI might potentially change in the future in
* <p><strong>WARNING</strong>: this SPI will likely 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>
* <strong>not</strong> 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.
*
......@@ -59,28 +67,13 @@ public interface TestContextBootstrapper {
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. Furthermore, default
* listeners must be sorted using
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
* AnnotationAwareOrderComparator}.
* <p>Concrete implementations are free to determine what comprises the
* set of default listeners. However, by default, the Spring TestContext
* Framework will use the
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
* mechanism to look up all {@code TestExecutionListener} class names
* configured in all {@code META-INF/spring.factories} files on the classpath.
* <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
* Build the {@link TestContext} for the {@link BootstrapContext}
* associated with this bootstrapper.
* @return a new {@link TestContext}, never {@code null}
* @since 4.2
* @see #buildMergedContextConfiguration()
*/
List<TestExecutionListener> getTestExecutionListeners();
TestContext buildTestContext();
/**
* Build the {@linkplain MergedContextConfiguration merged context configuration}
......@@ -94,9 +87,12 @@ public interface TestContextBootstrapper {
* <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>
* <li>Test property sources declared via {@link TestPropertySource @TestPropertySource}</li>
* </ul>
* <p>Consult the Javadoc for the aforementioned annotations for details on
* the required semantics.
* <p>Note that the implementation of {@link #buildTestContext()} should
* typically delegate to this method when constructing the {@code TestContext}.
* <p>When determining which {@link ContextLoader} to use for a given test
* class, the following algorithm should be used:
* <ol>
......@@ -106,7 +102,32 @@ public interface TestContextBootstrapper {
* {@code ContextLoader} class to use as as default.</li>
* </ol>
* @return the merged context configuration, never {@code null}
* @see #buildTestContext()
*/
MergedContextConfiguration buildMergedContextConfiguration();
/**
* 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. Furthermore, default
* listeners must be sorted using
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
* AnnotationAwareOrderComparator}.
* <p>Concrete implementations are free to determine what comprises the
* set of default listeners. However, by default, the Spring TestContext
* Framework will use the
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
* mechanism to look up all {@code TestExecutionListener} class names
* configured in all {@code META-INF/spring.factories} files on the classpath.
* <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();
}
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -28,15 +28,11 @@ import org.springframework.util.Assert;
/**
* {@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.
* TestContext Framework</em>.
*
* <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
* {@link TestExecutionListener TestExecutionListeners} at the following test
* execution points:
*
* <ul>
......@@ -56,6 +52,21 @@ import org.springframework.util.Assert;
* 4's {@link org.junit.AfterClass @AfterClass})</li>
* </ul>
*
* <p>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. is provided by
* {@link SmartContextLoader ContextLoaders} and {@link TestExecutionListener
* TestExecutionListeners}, which are configured via
* {@link ContextConfiguration @ContextConfiguration} and
* {@link TestExecutionListeners @TestExecutionListeners}.
*
* <p>Bootstrapping of the {@code TestContext}, the default {@code ContextLoader},
* default {@code TestExecutionListeners}, and their collaborators is performed
* by a {@link TestContextBootstrapper}, which is configured via
* {@link BootstrapWith @BootstrapWith}.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
......@@ -73,37 +84,42 @@ public class TestContextManager {
private static final Log logger = LogFactory.getLog(TestContextManager.class);
/**
* 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>();
/**
* 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.
* Construct a new {@code TestContextManager} for the specified {@linkplain Class test class},
* automatically {@linkplain #registerTestExecutionListeners registering} the necessary
* {@link TestExecutionListener TestExecutionListeners}.
* <p>Delegates to a {@link TestContextBootstrapper} for building the {@code TestContext}
* and retrieving the {@code TestExecutionListeners}.
* @param testClass the test class to be managed
* @see TestContextBootstrapper#buildTestContext
* @see TestContextBootstrapper#getTestExecutionListeners
* @see #registerTestExecutionListeners
*/
public TestContextManager(Class<?> testClass) {
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate(contextCache);
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
this.testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
this.testContext = new DefaultTestContext(this.testContextBootstrapper);
registerTestExecutionListeners(this.testContextBootstrapper.getTestExecutionListeners());
BootstrapContext bootstrapContext = createBootstrapContext(testClass);
TestContextBootstrapper bootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
this.testContext = bootstrapper.buildTestContext();
registerTestExecutionListeners(bootstrapper.getTestExecutionListeners());
}
/**
* Create the {@code BootstrapContext} for the specified {@linkplain Class test class}.
* <p>The default implementation creates a
* {@link org.springframework.test.context.support.DefaultBootstrapContext DefaultBootstrapContext}
* that uses a
* {@link org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate DefaultCacheAwareContextLoaderDelegate}.
* <p>Can be overridden by subclasses as necessary.
* @param testClass the test class for which the bootstrap context should be created
* @return a new {@code BootstrapContext}; never {@code null}
*/
protected BootstrapContext createBootstrapContext(Class<?> testClass) {
return BootstrapUtils.createBootstrapContext(testClass);
}
/**
* Get the {@link TestContext} managed by this {@code TestContextManager}.
......@@ -201,7 +217,7 @@ public class TestContextManager {
* @see #getTestExecutionListeners()
*/
public void prepareTestInstance(Object testInstance) throws Exception {
Assert.notNull(testInstance, "testInstance must not be null");
Assert.notNull(testInstance, "Test instance must not be null");
if (logger.isTraceEnabled()) {
logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
}
......@@ -282,7 +298,7 @@ public class TestContextManager {
* @see #getTestExecutionListeners()
*/
public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception {
Assert.notNull(testInstance, "testInstance must not be null");
Assert.notNull(testInstance, "Test instance must not be null");
if (logger.isTraceEnabled()) {
logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod +
"], exception [" + exception + "]");
......
......@@ -38,12 +38,14 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextCache;
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.TestContext;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
......@@ -66,6 +68,9 @@ import org.springframework.util.StringUtils;
* <li>{@link #processMergedContextConfiguration}
* </ul>
*
* <p>To plug in custom {@link ContextCache} support, override
* {@link #getCacheAwareContextLoaderDelegate()}.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 4.1
......@@ -93,6 +98,21 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return this.bootstrapContext;
}
/**
* Build a new {@link DefaultTestContext} using the {@linkplain Class test class}
* in the {@link BootstrapContext} associated with this bootstrapper and
* by delegating to {@link #buildMergedContextConfiguration()} and
* {@link #getCacheAwareContextLoaderDelegate()}.
* <p>Concrete subclasses may choose to override this method to return a
* custom {@link TestContext} implementation.
* @since 4.2
*/
@Override
public TestContext buildTestContext() {
return new DefaultTestContext(getBootstrapContext().getTestClass(), buildMergedContextConfiguration(),
getCacheAwareContextLoaderDelegate());
}
/**
* {@inheritDoc}
*/
......@@ -266,7 +286,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
@Override
public final MergedContextConfiguration buildMergedContextConfiguration() {
Class<?> testClass = getBootstrapContext().getTestClass();
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate();
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate();
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class,
ContextHierarchy.class) == null) {
......@@ -455,6 +475,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return null;
}
/**
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
* interaction with the {@code ContextCache}.
* <p>The default implementation simply delegates to
* {@code getBootstrapContext().getCacheAwareContextLoaderDelegate()}.
* <p>Concrete subclasses may choose to override this method to return a
* custom {@code CacheAwareContextLoaderDelegate} implementation with
* custom {@link ContextCache} support.
* @return the context loader delegate (never {@code null})
*/
protected CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() {
return getBootstrapContext().getCacheAwareContextLoaderDelegate();
}
/**
* Determine the default {@link ContextLoader} {@linkplain Class class}
* to use for the supplied test class.
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,9 +14,11 @@
* limitations under the License.
*/
package org.springframework.test.context;
package org.springframework.test.context.support;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.util.Assert;
/**
......@@ -25,13 +27,19 @@ import org.springframework.util.Assert;
* @author Sam Brannen
* @since 4.1
*/
class DefaultBootstrapContext implements BootstrapContext {
public class DefaultBootstrapContext implements BootstrapContext {
private final Class<?> testClass;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
DefaultBootstrapContext(Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
/**
* Construct a new {@code DefaultBootstrapContext} from the supplied arguments.
* @param testClass the test class for this bootstrap context; never {@code null}
* @param cacheAwareContextLoaderDelegate the context loader delegate to use for
* transparent interaction with the {@code ContextCache}; never {@code null}
*/
public 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;
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,47 +14,78 @@
* limitations under the License.
*/
package org.springframework.test.context;
package org.springframework.test.context.support;
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.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextCache;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
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.
* <p>To use a static {@code DefaultContextCache}, invoke the
* {@link #DefaultCacheAwareContextLoaderDelegate()} constructor; otherwise,
* invoke the {@link #DefaultCacheAwareContextLoaderDelegate(ContextCache)}
* and provide a custom {@link ContextCache} implementation.
*
* @author Sam Brannen
* @since 4.1
*/
class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate {
public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate {
private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class);
private static final Log statsLogger = LogFactory.getLog("org.springframework.test.context.cache");
/**
* Default static cache of Spring application contexts.
*/
static final ContextCache defaultContextCache = new DefaultContextCache();
private final ContextCache contextCache;
DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) {
/**
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
* a static {@link DefaultContextCache}.
* <p>This default cache is static so that each context can be cached
* and reused for all subsequent tests that declare the same unique
* context configuration within the same JVM process.
* @see #DefaultCacheAwareContextLoaderDelegate(ContextCache)
*/
public DefaultCacheAwareContextLoaderDelegate() {
this(defaultContextCache);
}
/**
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
* the supplied {@link ContextCache}.
* @see #DefaultCacheAwareContextLoaderDelegate()
*/
public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) {
Assert.notNull(contextCache, "ContextCache must not be null");
this.contextCache = contextCache;
}
/**
* Get the {@link ContextCache} used by this context loader delegate.
*/
protected ContextCache getContextCache() {
return this.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)
protected ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
throws Exception {
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
......@@ -101,9 +132,7 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
}
}
if (statsLogger.isDebugEnabled()) {
statsLogger.debug("Spring test ApplicationContext cache statistics: " + this.contextCache);
}
this.contextCache.logStatistics();
return context;
}
......
/*
* Copyright 2002-2015 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.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.ContextCache;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Default implementation of the {@link ContextCache} API.
*
* <p>Uses Spring's {@link ConcurrentReferenceHashMap} to store
* {@linkplain java.lang.ref.SoftReference soft references} to cached
* contexts and {@code MergedContextConfiguration} instances.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see ConcurrentReferenceHashMap
*/
public class DefaultContextCache implements ContextCache {
private static final Log statsLogger = LogFactory.getLog(CONTEXT_CACHE_LOGGING_CATEGORY);
/**
* Map of context keys to Spring {@code ApplicationContext} instances.
*/
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
new ConcurrentReferenceHashMap<MergedContextConfiguration, ApplicationContext>(64);
/**
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
* of context hierarchies. This information is used for determining which subtrees
* need to be recursively removed and closed when removing a context that is a parent
* of other contexts.
*/
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
new ConcurrentReferenceHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
private final AtomicInteger hitCount = new AtomicInteger();
private final AtomicInteger missCount = new AtomicInteger();
/**
* {@inheritDoc}
*/
@Override
public boolean contains(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
return this.contextMap.containsKey(key);
}
/**
* {@inheritDoc}
*/
@Override
public ApplicationContext get(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
ApplicationContext context = this.contextMap.get(key);
if (context == null) {
this.missCount.incrementAndGet();
}
else {
this.hitCount.incrementAndGet();
}
return context;
}
/**
* {@inheritDoc}
*/
@Override
public void put(MergedContextConfiguration key, ApplicationContext context) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(context, "ApplicationContext must not be null");
this.contextMap.put(key, context);
MergedContextConfiguration child = key;
MergedContextConfiguration parent = child.getParent();
while (parent != null) {
Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
if (list == null) {
list = new HashSet<MergedContextConfiguration>();
this.hierarchyMap.put(parent, list);
}
list.add(child);
child = parent;
parent = child.getParent();
}
}
/**
* {@inheritDoc}
*/
@Override
public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
Assert.notNull(key, "Key must not be null");
// startKey is the level at which to begin clearing the cache, depending
// on the configured hierarchy mode.
MergedContextConfiguration startKey = key;
if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
while (startKey.getParent() != null) {
startKey = startKey.getParent();
}
}
List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
remove(removedContexts, startKey);
// Remove all remaining references to any removed contexts from the
// hierarchy map.
for (MergedContextConfiguration currentKey : removedContexts) {
for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
children.remove(currentKey);
}
}
// Remove empty entries from the hierarchy map.
for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
if (this.hierarchyMap.get(currentKey).isEmpty()) {
this.hierarchyMap.remove(currentKey);
}
}
}
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
if (children != null) {
for (MergedContextConfiguration child : children) {
// Recurse through lower levels
remove(removedContexts, child);
}
// Remove the set of children for the current context from the hierarchy map.
this.hierarchyMap.remove(key);
}
// Physically remove and close leaf nodes first (i.e., on the way back up the
// stack as opposed to prior to the recursive call).
ApplicationContext context = this.contextMap.remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
removedContexts.add(key);
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return this.contextMap.size();
}
/**
* {@inheritDoc}
*/
@Override
public int getParentContextCount() {
return this.hierarchyMap.size();
}
/**
* {@inheritDoc}
*/
@Override
public int getHitCount() {
return this.hitCount.get();
}
/**
* {@inheritDoc}
*/
@Override
public int getMissCount() {
return this.missCount.get();
}
/**
* {@inheritDoc}
*/
@Override
public void reset() {
synchronized (contextMap) {
clear();
clearStatistics();
}
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
synchronized (contextMap) {
this.contextMap.clear();
this.hierarchyMap.clear();
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearStatistics() {
synchronized (contextMap) {
this.hitCount.set(0);
this.missCount.set(0);
}
}
/**
* {@inheritDoc}
*/
@Override
public void logStatistics() {
if (statsLogger.isDebugEnabled()) {
statsLogger.debug("Spring test ApplicationContext cache statistics: " + this);
}
}
/**
* Generate a text string containing the implementation type of this
* cache and its statistics.
* <p>The string returned by this method contains all information
* required for compliance with the contract for {@link #logStatistics()}.
* @return a string representation of this cache, including statistics
*/
@Override
public String toString() {
return new ToStringCreator(this)
.append("size", size())
.append("parentContextCount", getParentContextCount())
.append("hitCount", getHitCount())
.append("missCount", getMissCount())
.toString();
}
}
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.test.context;
package org.springframework.test.context.support;
import java.lang.reflect.Method;
......@@ -22,21 +22,19 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.AttributeAccessorSupport;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.util.Assert;
/**
* Default implementation of the {@link TestContext} interface.
*
* <p>Although {@code DefaultTestContext} was first introduced in Spring Framework
* 4.0, the initial implementation of this class was extracted from the existing
* code base for {@code TestContext} when {@code TestContext} was converted into
* an interface.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 4.0
*/
class DefaultTestContext extends AttributeAccessorSupport implements TestContext {
public class DefaultTestContext extends AttributeAccessorSupport implements TestContext {
private static final long serialVersionUID = -5827157174866681233L;
......@@ -54,24 +52,42 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
/**
* 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})
* Construct a new {@code DefaultTestContext} from the supplied arguments.
* @param testClass the test class for this test context; never {@code null}
* @param mergedContextConfiguration the merged application context
* configuration for this test context; never {@code null}
* @param cacheAwareContextLoaderDelegate the delegate to use for loading
* and closing the application context for this test context; never {@code 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();
public DefaultTestContext(Class<?> testClass, MergedContextConfiguration mergedContextConfiguration,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
Assert.notNull(testClass, "testClass must not be null");
Assert.notNull(mergedContextConfiguration, "MergedContextConfiguration must not be null");
Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
this.testClass = testClass;
this.mergedContextConfiguration = mergedContextConfiguration;
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
}
/**
* Get the {@linkplain ApplicationContext application context} for this
* test context.
* <p>The default implementation delegates to the {@link CacheAwareContextLoaderDelegate}
* that was supplied when this {@code TestContext} was constructed.
* @see CacheAwareContextLoaderDelegate#loadContext
*/
public ApplicationContext getApplicationContext() {
return this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
}
/**
* Mark the {@linkplain ApplicationContext application context} associated
* with this test context as <em>dirty</em> (i.e., by removing it from the
* context cache and closing it).
* <p>The default implementation delegates to the {@link CacheAwareContextLoaderDelegate}
* that was supplied when this {@code TestContext} was constructed.
* @see CacheAwareContextLoaderDelegate#closeContext
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode);
}
......
......@@ -16,6 +16,8 @@
package org.springframework.test.context;
import org.springframework.test.context.support.DefaultBootstrapContext;
/**
* Collection of test-related utility methods for working with {@link BootstrapContext
* BootstrapContexts} and {@link TestContextBootstrapper TestContextBootstrappers}.
......
......@@ -37,7 +37,7 @@ import org.springframework.test.context.testng.TrackingTestNGTestListener;
import org.testng.TestNG;
import static org.junit.Assert.*;
import static org.springframework.test.context.ContextCacheTestUtils.*;
import static org.springframework.test.context.support.ContextCacheTestUtils.*;
/**
* JUnit 4 based integration test which verifies correct {@linkplain ContextCache
......@@ -79,8 +79,9 @@ public class ClassLevelDirtiesContextTestNGTests {
@BeforeClass
public static void verifyInitialCacheState() {
ContextCache contextCache = TestContextManager.contextCache;
contextCache.reset();
resetContextCache();
// Reset static counters in case tests are run multiple times in a test suite --
// for example, via JUnit's @Suite.
cacheHits.set(0);
cacheMisses.set(0);
assertContextCacheStatistics("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
......
......@@ -36,7 +36,7 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import static org.junit.Assert.*;
import static org.springframework.test.context.ContextCacheTestUtils.*;
import static org.springframework.test.context.support.ContextCacheTestUtils.*;
/**
* JUnit 4 based integration test which verifies correct {@linkplain ContextCache
......@@ -74,8 +74,9 @@ public class ClassLevelDirtiesContextTests {
@BeforeClass
public static void verifyInitialCacheState() {
ContextCache contextCache = TestContextManager.contextCache;
contextCache.reset();
resetContextCache();
// Reset static counters in case tests are run multiple times in a test suite --
// for example, via JUnit's @Suite.
cacheHits.set(0);
cacheMisses.set(0);
assertContextCacheStatistics("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
......
......@@ -23,10 +23,11 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.support.DefaultContextCache;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.*;
import static org.springframework.test.context.ContextCacheTestUtils.*;
import static org.springframework.test.context.support.ContextCacheTestUtils.*;
/**
* Integration tests for verifying proper behavior of the {@link ContextCache} in
......@@ -39,7 +40,7 @@ import static org.springframework.test.context.ContextCacheTestUtils.*;
*/
public class ContextCacheTests {
private ContextCache contextCache = new ContextCache();
private ContextCache contextCache = new DefaultContextCache();
@Before
......
......@@ -31,7 +31,7 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import static org.junit.Assert.*;
import static org.springframework.test.context.ContextCacheTestUtils.*;
import static org.springframework.test.context.support.ContextCacheTestUtils.*;
/**
* JUnit 4 based unit test which verifies correct {@link ContextCache
......@@ -58,8 +58,7 @@ public class SpringRunnerContextCacheTests {
@BeforeClass
public static void verifyInitialCacheState() {
dirtiedApplicationContext = null;
ContextCache contextCache = TestContextManager.contextCache;
contextCache.reset();
resetContextCache();
assertContextCacheStatistics("BeforeClass", 0, 0, 0);
}
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,6 +16,9 @@
package org.springframework.test.context;
import org.springframework.test.context.support.DefaultBootstrapContext;
import org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate;
/**
* Collection of test-related utility methods for working with {@link TestContext TestContexts}.
*
......@@ -28,12 +31,12 @@ public abstract class TestContextTestUtils {
return buildTestContext(testClass, new DefaultCacheAwareContextLoaderDelegate(contextCache));
}
public static TestContext buildTestContext(
Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
public static TestContext buildTestContext(Class<?> testClass,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
return new DefaultTestContext(testContextBootstrapper);
return testContextBootstrapper.buildTestContext();
}
}
......@@ -14,7 +14,9 @@
* limitations under the License.
*/
package org.springframework.test.context;
package org.springframework.test.context.support;
import org.springframework.test.context.ContextCache;
import static org.junit.Assert.*;
......@@ -28,7 +30,14 @@ import static org.junit.Assert.*;
public class ContextCacheTestUtils {
/**
* Assert the statistics of the context cache in {@link TestContextManager}.
* Reset the state of the static context cache in {@link DefaultCacheAwareContextLoaderDelegate}.
*/
public static final void resetContextCache() {
DefaultCacheAwareContextLoaderDelegate.defaultContextCache.reset();
}
/**
* Assert the statistics of the static context cache in {@link DefaultCacheAwareContextLoaderDelegate}.
*
* @param usageScenario the scenario in which the statistics are used
* @param expectedSize the expected number of contexts in the cache
......@@ -37,8 +46,8 @@ public class ContextCacheTestUtils {
*/
public static final void assertContextCacheStatistics(String usageScenario, int expectedSize, int expectedHitCount,
int expectedMissCount) {
assertContextCacheStatistics(TestContextManager.contextCache, usageScenario, expectedSize, expectedHitCount,
expectedMissCount);
assertContextCacheStatistics(DefaultCacheAwareContextLoaderDelegate.defaultContextCache, usageScenario,
expectedSize, expectedHitCount, expectedMissCount);
}
/**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册