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
This commit is contained in:
commit
9e6a5ae4a7
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 {
|
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})
|
* @return the test class (never {@code null})
|
||||||
*/
|
*/
|
||||||
Class<?> getTestClass();
|
Class<?> getTestClass();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
|
* 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();
|
CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.test.context;
|
package org.springframework.test.context;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
|
@ -36,6 +38,10 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
*/
|
*/
|
||||||
abstract class BootstrapUtils {
|
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 String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper";
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
|
private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
|
||||||
|
|
@ -45,6 +51,55 @@ abstract class BootstrapUtils {
|
||||||
/* no-op */
|
/* 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
|
* Resolve the {@link TestContextBootstrapper} type for the test class in the
|
||||||
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference
|
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain
|
||||||
* #loadContext loading} and {@linkplain #closeContext closing} application
|
* #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.
|
* the scenes.
|
||||||
*
|
*
|
||||||
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not extend the
|
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not extend the
|
||||||
|
|
@ -38,8 +38,10 @@ public interface CacheAwareContextLoaderDelegate {
|
||||||
* Load the {@linkplain ApplicationContext application context} for the supplied
|
* Load the {@linkplain ApplicationContext application context} for the supplied
|
||||||
* {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
|
* {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
|
||||||
* configured in the given {@code MergedContextConfiguration}.
|
* 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.
|
* 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
|
* @param mergedContextConfiguration the merged context configuration to use
|
||||||
* to load the application context; never {@code null}
|
* to load the application context; never {@code null}
|
||||||
* @return the application context
|
* @return the application context
|
||||||
|
|
@ -50,7 +52,7 @@ public interface CacheAwareContextLoaderDelegate {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the {@linkplain ApplicationContext application context} for the
|
* 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
|
* and {@linkplain ConfigurableApplicationContext#close() close} it if it is
|
||||||
* an instance of {@link ConfigurableApplicationContext}.
|
* an instance of {@link ConfigurableApplicationContext}.
|
||||||
* <p>The semantics of the supplied {@code HierarchyMode} must be honored when
|
* <p>The semantics of the supplied {@code HierarchyMode} must be honored when
|
||||||
|
|
|
||||||
|
|
@ -16,145 +16,55 @@
|
||||||
|
|
||||||
package org.springframework.test.context;
|
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.ApplicationContext;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.core.style.ToStringCreator;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
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
|
* {@code ContextCache} defines the public API for caching Spring
|
||||||
* environment.
|
* {@link ApplicationContext ApplicationContexts} within the <em>Spring
|
||||||
|
* TestContext Framework</em>.
|
||||||
|
*
|
||||||
|
* <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts}
|
||||||
|
* keyed by {@link MergedContextConfiguration} instances.
|
||||||
*
|
*
|
||||||
* <h3>Rationale</h3>
|
* <h3>Rationale</h3>
|
||||||
* <p>Caching has significant performance benefits if initializing the context
|
* <p>Context caching can have significant performance benefits if context
|
||||||
* takes a considerable about of time. Although initializing a Spring context
|
* initialization is complex. So, although initializing a Spring context itself
|
||||||
* itself is very quick, some beans in a context, such as a
|
* is typically very quick, some beans in a context — for example, an
|
||||||
* {@code LocalSessionFactoryBean} for working with Hibernate, may take some
|
* in-memory database or a {@code LocalSessionFactoryBean} for working with
|
||||||
* time to initialize. Hence it often makes sense to perform that initialization
|
* Hibernate — may take several seconds to initialize. Hence it often
|
||||||
* only once per test suite.
|
* makes sense to perform that initialization only once per test suite.
|
||||||
*
|
|
||||||
* <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.
|
|
||||||
*
|
*
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 2.5
|
* @since 4.2
|
||||||
* @see ConcurrentReferenceHashMap
|
|
||||||
*/
|
*/
|
||||||
class ContextCache {
|
public interface ContextCache {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of context keys to Spring {@code ApplicationContext} instances.
|
* The name of the logging category used for reporting {@code ContextCache}
|
||||||
|
* statistics.
|
||||||
*/
|
*/
|
||||||
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
|
public static final String CONTEXT_CACHE_LOGGING_CATEGORY = "org.springframework.test.context.cache";
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all contexts from the cache and clear context hierarchy information as well.
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
synchronized (contextMap) {
|
|
||||||
this.contextMap.clear();
|
|
||||||
this.hierarchyMap.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Determine whether there is a cached context for the given key.
|
||||||
* @param key the context key (never {@code null})
|
* @param key the context key (never {@code null})
|
||||||
* @return {@code true} if the cache contains a context with the given key
|
* @return {@code true} if the cache contains a context with the given key
|
||||||
*/
|
*/
|
||||||
public boolean contains(MergedContextConfiguration key) {
|
boolean contains(MergedContextConfiguration key);
|
||||||
Assert.notNull(key, "Key must not be null");
|
|
||||||
return this.contextMap.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a cached {@code ApplicationContext} for the given key.
|
* Obtain a cached {@code ApplicationContext} for the given key.
|
||||||
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
|
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts
|
||||||
* be updated accordingly.
|
* must be updated accordingly.
|
||||||
* @param key the context key (never {@code null})
|
* @param key the context key (never {@code null})
|
||||||
* @return the corresponding {@code ApplicationContext} instance, or {@code null}
|
* @return the corresponding {@code ApplicationContext} instance, or {@code null}
|
||||||
* if not found in the cache
|
* if not found in the cache
|
||||||
* @see #remove
|
* @see #remove
|
||||||
*/
|
*/
|
||||||
public ApplicationContext get(MergedContextConfiguration key) {
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Explicitly add an {@code ApplicationContext} instance to the cache
|
* Explicitly add an {@code ApplicationContext} instance to the cache
|
||||||
|
|
@ -162,122 +72,79 @@ class ContextCache {
|
||||||
* @param key the context key (never {@code null})
|
* @param key the context key (never {@code null})
|
||||||
* @param context the {@code ApplicationContext} instance (never {@code null})
|
* @param context the {@code ApplicationContext} instance (never {@code null})
|
||||||
*/
|
*/
|
||||||
public void put(MergedContextConfiguration key, ApplicationContext context) {
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the context with the given key from the cache and explicitly
|
* Remove the context with the given key from the cache and explicitly
|
||||||
* {@linkplain ConfigurableApplicationContext#close() close} it if it is an
|
* {@linkplain org.springframework.context.ConfigurableApplicationContext#close() close}
|
||||||
* instance of {@link ConfigurableApplicationContext}.
|
* it if it is an instance of {@code ConfigurableApplicationContext}.
|
||||||
* <p>Generally speaking, you would only call this method if you change the
|
* <p>Generally speaking, this method should be called if the state of
|
||||||
* state of a singleton bean, potentially affecting future interaction with
|
* a singleton bean has been modified, potentially affecting future
|
||||||
* the context.
|
* interaction with the context.
|
||||||
* <p>In addition, the semantics of the supplied {@code HierarchyMode} will
|
* <p>In addition, the semantics of the supplied {@code HierarchyMode} must
|
||||||
* be honored. See the Javadoc for {@link HierarchyMode} for details.
|
* be honored. See the Javadoc for {@link HierarchyMode} for details.
|
||||||
* @param key the context key; never {@code null}
|
* @param key the context key; never {@code null}
|
||||||
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
|
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
|
||||||
* is not part of a hierarchy
|
* is not part of a hierarchy
|
||||||
*/
|
*/
|
||||||
public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the number of contexts currently stored in the cache.
|
* Determine the number of contexts currently stored in the cache.
|
||||||
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
|
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
|
||||||
* this method returns {@code Integer.MAX_VALUE}.
|
* this method must return {@code Integer.MAX_VALUE}.
|
||||||
*/
|
*/
|
||||||
public int size() {
|
int size();
|
||||||
return this.contextMap.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the number of parent contexts currently tracked within the cache.
|
* Determine the number of parent contexts currently tracked within the cache.
|
||||||
*/
|
*/
|
||||||
public int getParentContextCount() {
|
int getParentContextCount();
|
||||||
return this.hierarchyMap.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a text string containing the statistics for this cache.
|
* Get the overall hit count for this cache.
|
||||||
* <p>Specifically, the returned string contains the {@linkplain #size},
|
* <p>A <em>hit</em> is any access to the cache that returns a non-null
|
||||||
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount() miss count},
|
* context for the queried key.
|
||||||
* and {@linkplain #getParentContextCount() parent context count}.
|
|
||||||
* @return the statistics for this cache
|
|
||||||
*/
|
*/
|
||||||
@Override
|
int getHitCount();
|
||||||
public String toString() {
|
|
||||||
return new ToStringCreator(this)
|
/**
|
||||||
.append("size", size())
|
* Get the overall miss count for this cache.
|
||||||
.append("hitCount", getHitCount())
|
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
|
||||||
.append("missCount", getMissCount())
|
* context for the queried key.
|
||||||
.append("parentContextCount", getParentContextCount())
|
*/
|
||||||
.toString();
|
int getMissCount();
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Reset all state maintained by this cache including statistics.
|
||||||
|
* @see #clear()
|
||||||
|
* @see #clearStatistics()
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all contexts from the cache, clearing context hierarchy information as well.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
|
||||||
|
*/
|
||||||
|
void clearStatistics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
*/
|
||||||
|
void logStatistics();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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>.
|
* <em>Spring TestContext Framework</em>.
|
||||||
*
|
*
|
||||||
* <p>A custom bootstrapping strategy can be configured for a test class via
|
* <p>A {@code TestContextBootstrapper} is used by the {@link TestContextManager} to
|
||||||
* {@link BootstrapWith @BootstrapWith}, either directly or as a meta-annotation.
|
* {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the
|
||||||
* See {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
|
* 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.
|
* for an example.
|
||||||
*
|
*
|
||||||
* <p>The {@link TestContextManager} uses a {@code TestContextBootstrapper} to
|
* <p>If a bootstrapper is not explicitly configured via {@code @BootstrapWith}, the
|
||||||
* {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the
|
* {@link org.springframework.test.context.support.DefaultTestContextBootstrapper DefaultTestContextBootstrapper}
|
||||||
* current test and to {@linkplain #buildMergedContextConfiguration build the
|
* will be used.
|
||||||
* merged context configuration} necessary to create the {@link TestContext} that
|
*
|
||||||
* it manages.
|
* <h3>Implementation Notes</h3>
|
||||||
*
|
*
|
||||||
* <p>Concrete implementations must provide a {@code public} no-args constructor.
|
* <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
|
* 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
|
* {@link org.springframework.test.context.support.AbstractTestContextBootstrapper
|
||||||
* AbstractTestContextBootstrapper} or one of its concrete subclasses instead.
|
* AbstractTestContextBootstrapper} or one of its concrete subclasses instead.
|
||||||
*
|
*
|
||||||
|
|
@ -58,6 +66,46 @@ public interface TestContextBootstrapper {
|
||||||
*/
|
*/
|
||||||
BootstrapContext getBootstrapContext();
|
BootstrapContext getBootstrapContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
*/
|
||||||
|
TestContext buildTestContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <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>
|
||||||
|
* <li>If a {@code ContextLoader} class has been explicitly declared via
|
||||||
|
* {@link ContextConfiguration#loader}, use it.</li>
|
||||||
|
* <li>Otherwise, concrete implementations are free to determine which
|
||||||
|
* {@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}
|
* Get a list of newly instantiated {@link TestExecutionListener TestExecutionListeners}
|
||||||
* for the test class in the {@link BootstrapContext} associated with this bootstrapper.
|
* for the test class in the {@link BootstrapContext} associated with this bootstrapper.
|
||||||
|
|
@ -82,31 +130,4 @@ public interface TestContextBootstrapper {
|
||||||
*/
|
*/
|
||||||
List<TestExecutionListener> getTestExecutionListeners();
|
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>
|
|
||||||
* </ul>
|
|
||||||
* <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, concrete implementations are free to determine which
|
|
||||||
* {@code ContextLoader} class to use as as default.</li>
|
|
||||||
* </ol>
|
|
||||||
* @return the merged context configuration, never {@code null}
|
|
||||||
*/
|
|
||||||
MergedContextConfiguration buildMergedContextConfiguration();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* {@code TestContextManager} is the main entry point into the <em>Spring
|
||||||
* TestContext Framework</em>, which provides support for loading and accessing
|
* TestContext Framework</em>.
|
||||||
* {@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
|
* <p>Specifically, a {@code TestContextManager} is responsible for managing a
|
||||||
* single {@link TestContext} and signaling events to all registered
|
* 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:
|
* execution points:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
|
|
@ -56,6 +52,21 @@ import org.springframework.util.Assert;
|
||||||
* 4's {@link org.junit.AfterClass @AfterClass})</li>
|
* 4's {@link org.junit.AfterClass @AfterClass})</li>
|
||||||
* </ul>
|
* </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 Sam Brannen
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
|
|
@ -73,37 +84,42 @@ public class TestContextManager {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(TestContextManager.class);
|
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 TestContext testContext;
|
||||||
|
|
||||||
private final TestContextBootstrapper testContextBootstrapper;
|
|
||||||
|
|
||||||
private final List<TestExecutionListener> testExecutionListeners = new ArrayList<TestExecutionListener>();
|
private final List<TestExecutionListener> testExecutionListeners = new ArrayList<TestExecutionListener>();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new {@code TestContextManager} for the specified {@linkplain Class test class}
|
* Construct a new {@code TestContextManager} for the specified {@linkplain Class test class},
|
||||||
* and automatically {@link #registerTestExecutionListeners register} the
|
* automatically {@linkplain #registerTestExecutionListeners registering} the necessary
|
||||||
* {@link TestExecutionListener TestExecutionListeners} configured for the test class
|
* {@link TestExecutionListener TestExecutionListeners}.
|
||||||
* via the {@link TestExecutionListeners @TestExecutionListeners} annotation.
|
* <p>Delegates to a {@link TestContextBootstrapper} for building the {@code TestContext}
|
||||||
|
* and retrieving the {@code TestExecutionListeners}.
|
||||||
* @param testClass the test class to be managed
|
* @param testClass the test class to be managed
|
||||||
|
* @see TestContextBootstrapper#buildTestContext
|
||||||
|
* @see TestContextBootstrapper#getTestExecutionListeners
|
||||||
* @see #registerTestExecutionListeners
|
* @see #registerTestExecutionListeners
|
||||||
*/
|
*/
|
||||||
public TestContextManager(Class<?> testClass) {
|
public TestContextManager(Class<?> testClass) {
|
||||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate(contextCache);
|
BootstrapContext bootstrapContext = createBootstrapContext(testClass);
|
||||||
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
|
TestContextBootstrapper bootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
|
||||||
this.testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
|
this.testContext = bootstrapper.buildTestContext();
|
||||||
this.testContext = new DefaultTestContext(this.testContextBootstrapper);
|
registerTestExecutionListeners(bootstrapper.getTestExecutionListeners());
|
||||||
registerTestExecutionListeners(this.testContextBootstrapper.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}.
|
* Get the {@link TestContext} managed by this {@code TestContextManager}.
|
||||||
|
|
@ -201,7 +217,7 @@ public class TestContextManager {
|
||||||
* @see #getTestExecutionListeners()
|
* @see #getTestExecutionListeners()
|
||||||
*/
|
*/
|
||||||
public void prepareTestInstance(Object testInstance) throws Exception {
|
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()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
|
logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +298,7 @@ public class TestContextManager {
|
||||||
* @see #getTestExecutionListeners()
|
* @see #getTestExecutionListeners()
|
||||||
*/
|
*/
|
||||||
public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception {
|
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()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod +
|
logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod +
|
||||||
"], exception [" + exception + "]");
|
"], exception [" + exception + "]");
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,14 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||||
import org.springframework.test.context.BootstrapContext;
|
import org.springframework.test.context.BootstrapContext;
|
||||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
||||||
|
import org.springframework.test.context.ContextCache;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||||
import org.springframework.test.context.ContextHierarchy;
|
import org.springframework.test.context.ContextHierarchy;
|
||||||
import org.springframework.test.context.ContextLoader;
|
import org.springframework.test.context.ContextLoader;
|
||||||
import org.springframework.test.context.MergedContextConfiguration;
|
import org.springframework.test.context.MergedContextConfiguration;
|
||||||
import org.springframework.test.context.SmartContextLoader;
|
import org.springframework.test.context.SmartContextLoader;
|
||||||
|
import org.springframework.test.context.TestContext;
|
||||||
import org.springframework.test.context.TestContextBootstrapper;
|
import org.springframework.test.context.TestContextBootstrapper;
|
||||||
import org.springframework.test.context.TestExecutionListener;
|
import org.springframework.test.context.TestExecutionListener;
|
||||||
import org.springframework.test.context.TestExecutionListeners;
|
import org.springframework.test.context.TestExecutionListeners;
|
||||||
|
|
@ -66,6 +68,9 @@ import org.springframework.util.StringUtils;
|
||||||
* <li>{@link #processMergedContextConfiguration}
|
* <li>{@link #processMergedContextConfiguration}
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
|
* <p>To plug in custom {@link ContextCache} support, override
|
||||||
|
* {@link #getCacheAwareContextLoaderDelegate()}.
|
||||||
|
*
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
|
|
@ -93,6 +98,21 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
return this.bootstrapContext;
|
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}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|
@ -266,7 +286,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
@Override
|
@Override
|
||||||
public final MergedContextConfiguration buildMergedContextConfiguration() {
|
public final MergedContextConfiguration buildMergedContextConfiguration() {
|
||||||
Class<?> testClass = getBootstrapContext().getTestClass();
|
Class<?> testClass = getBootstrapContext().getTestClass();
|
||||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate();
|
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate();
|
||||||
|
|
||||||
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class,
|
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class,
|
||||||
ContextHierarchy.class) == null) {
|
ContextHierarchy.class) == null) {
|
||||||
|
|
@ -455,6 +475,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
return null;
|
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}
|
* Determine the default {@link ContextLoader} {@linkplain Class class}
|
||||||
* to use for the supplied test class.
|
* to use for the supplied test class.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -14,9 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context;
|
package org.springframework.test.context.support;
|
||||||
|
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
|
import org.springframework.test.context.BootstrapContext;
|
||||||
|
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,13 +27,19 @@ import org.springframework.util.Assert;
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
class DefaultBootstrapContext implements BootstrapContext {
|
public class DefaultBootstrapContext implements BootstrapContext {
|
||||||
|
|
||||||
private final Class<?> testClass;
|
private final Class<?> testClass;
|
||||||
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
|
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(testClass, "Test class must not be null");
|
||||||
Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
|
Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
|
||||||
this.testClass = testClass;
|
this.testClass = testClass;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -14,47 +14,78 @@
|
||||||
* limitations under the License.
|
* 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.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
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;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of the {@link CacheAwareContextLoaderDelegate} interface.
|
* Default implementation of the {@link CacheAwareContextLoaderDelegate} interface.
|
||||||
*
|
*
|
||||||
* <p>Although {@code DefaultCacheAwareContextLoaderDelegate} was first introduced
|
* <p>To use a static {@code DefaultContextCache}, invoke the
|
||||||
* in Spring Framework 4.1, the initial implementation of this class was extracted
|
* {@link #DefaultCacheAwareContextLoaderDelegate()} constructor; otherwise,
|
||||||
* from the existing code base for {@code CacheAwareContextLoaderDelegate} when
|
* invoke the {@link #DefaultCacheAwareContextLoaderDelegate(ContextCache)}
|
||||||
* {@code CacheAwareContextLoaderDelegate} was converted into an interface.
|
* and provide a custom {@link ContextCache} implementation.
|
||||||
*
|
*
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @since 4.1
|
* @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 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;
|
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");
|
Assert.notNull(contextCache, "ContextCache must not be null");
|
||||||
this.contextCache = contextCache;
|
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.
|
* Load the {@code ApplicationContext} for the supplied merged context configuration.
|
||||||
* <p>Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
|
* <p>Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
|
||||||
* @throws Exception if an error occurs while loading the application context
|
* @throws Exception if an error occurs while loading the application context
|
||||||
*/
|
*/
|
||||||
private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
|
protected ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
|
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
|
||||||
|
|
@ -101,9 +132,7 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statsLogger.isDebugEnabled()) {
|
this.contextCache.logStatistics();
|
||||||
statsLogger.debug("Spring test ApplicationContext cache statistics: " + this.contextCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context;
|
package org.springframework.test.context.support;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
|
@ -22,21 +22,19 @@ import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.AttributeAccessorSupport;
|
import org.springframework.core.AttributeAccessorSupport;
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
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;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of the {@link TestContext} interface.
|
* 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 Sam Brannen
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
class DefaultTestContext extends AttributeAccessorSupport implements TestContext {
|
public class DefaultTestContext extends AttributeAccessorSupport implements TestContext {
|
||||||
|
|
||||||
private static final long serialVersionUID = -5827157174866681233L;
|
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}.
|
* Construct a new {@code DefaultTestContext} from the supplied arguments.
|
||||||
* @param testContextBootstrapper the {@code TestContextBootstrapper} to use
|
* @param testClass the test class for this test context; never {@code null}
|
||||||
* to construct the test context (must not be {@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) {
|
public DefaultTestContext(Class<?> testClass, MergedContextConfiguration mergedContextConfiguration,
|
||||||
Assert.notNull(testContextBootstrapper, "TestContextBootstrapper must not be null");
|
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
|
||||||
|
Assert.notNull(testClass, "testClass must not be null");
|
||||||
BootstrapContext bootstrapContext = testContextBootstrapper.getBootstrapContext();
|
Assert.notNull(mergedContextConfiguration, "MergedContextConfiguration must not be null");
|
||||||
this.testClass = bootstrapContext.getTestClass();
|
Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
|
||||||
this.cacheAwareContextLoaderDelegate = bootstrapContext.getCacheAwareContextLoaderDelegate();
|
this.testClass = testClass;
|
||||||
this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration();
|
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() {
|
public ApplicationContext getApplicationContext() {
|
||||||
return this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
|
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) {
|
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
|
||||||
this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode);
|
this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode);
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.test.context;
|
package org.springframework.test.context;
|
||||||
|
|
||||||
|
import org.springframework.test.context.support.DefaultBootstrapContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of test-related utility methods for working with {@link BootstrapContext
|
* Collection of test-related utility methods for working with {@link BootstrapContext
|
||||||
* BootstrapContexts} and {@link TestContextBootstrapper TestContextBootstrappers}.
|
* BootstrapContexts} and {@link TestContextBootstrapper TestContextBootstrappers}.
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import org.springframework.test.context.testng.TrackingTestNGTestListener;
|
||||||
import org.testng.TestNG;
|
import org.testng.TestNG;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
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
|
* JUnit 4 based integration test which verifies correct {@linkplain ContextCache
|
||||||
|
|
@ -79,8 +79,9 @@ public class ClassLevelDirtiesContextTestNGTests {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void verifyInitialCacheState() {
|
public static void verifyInitialCacheState() {
|
||||||
ContextCache contextCache = TestContextManager.contextCache;
|
resetContextCache();
|
||||||
contextCache.reset();
|
// Reset static counters in case tests are run multiple times in a test suite --
|
||||||
|
// for example, via JUnit's @Suite.
|
||||||
cacheHits.set(0);
|
cacheHits.set(0);
|
||||||
cacheMisses.set(0);
|
cacheMisses.set(0);
|
||||||
assertContextCacheStatistics("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
|
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 org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
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
|
* JUnit 4 based integration test which verifies correct {@linkplain ContextCache
|
||||||
|
|
@ -74,8 +74,9 @@ public class ClassLevelDirtiesContextTests {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void verifyInitialCacheState() {
|
public static void verifyInitialCacheState() {
|
||||||
ContextCache contextCache = TestContextManager.contextCache;
|
resetContextCache();
|
||||||
contextCache.reset();
|
// Reset static counters in case tests are run multiple times in a test suite --
|
||||||
|
// for example, via JUnit's @Suite.
|
||||||
cacheHits.set(0);
|
cacheHits.set(0);
|
||||||
cacheMisses.set(0);
|
cacheMisses.set(0);
|
||||||
assertContextCacheStatistics("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
|
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.context.annotation.Configuration;
|
||||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||||
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||||
|
import org.springframework.test.context.support.DefaultContextCache;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
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
|
* 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 {
|
public class ContextCacheTests {
|
||||||
|
|
||||||
private ContextCache contextCache = new ContextCache();
|
private ContextCache contextCache = new DefaultContextCache();
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
|
||||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
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
|
* JUnit 4 based unit test which verifies correct {@link ContextCache
|
||||||
|
|
@ -58,8 +58,7 @@ public class SpringRunnerContextCacheTests {
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void verifyInitialCacheState() {
|
public static void verifyInitialCacheState() {
|
||||||
dirtiedApplicationContext = null;
|
dirtiedApplicationContext = null;
|
||||||
ContextCache contextCache = TestContextManager.contextCache;
|
resetContextCache();
|
||||||
contextCache.reset();
|
|
||||||
assertContextCacheStatistics("BeforeClass", 0, 0, 0);
|
assertContextCacheStatistics("BeforeClass", 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.test.context;
|
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}.
|
* 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));
|
return buildTestContext(testClass, new DefaultCacheAwareContextLoaderDelegate(contextCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TestContext buildTestContext(
|
public static TestContext buildTestContext(Class<?> testClass,
|
||||||
Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
|
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
|
||||||
|
|
||||||
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
|
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
|
||||||
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
|
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
|
||||||
return new DefaultTestContext(testContextBootstrapper);
|
return testContextBootstrapper.buildTestContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@
|
||||||
* limitations under the License.
|
* 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.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
|
@ -28,7 +30,14 @@ import static org.junit.Assert.*;
|
||||||
public class ContextCacheTestUtils {
|
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 usageScenario the scenario in which the statistics are used
|
||||||
* @param expectedSize the expected number of contexts in the cache
|
* @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,
|
public static final void assertContextCacheStatistics(String usageScenario, int expectedSize, int expectedHitCount,
|
||||||
int expectedMissCount) {
|
int expectedMissCount) {
|
||||||
assertContextCacheStatistics(TestContextManager.contextCache, usageScenario, expectedSize, expectedHitCount,
|
assertContextCacheStatistics(DefaultCacheAwareContextLoaderDelegate.defaultContextCache, usageScenario,
|
||||||
expectedMissCount);
|
expectedSize, expectedHitCount, expectedMissCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Loading…
Reference in New Issue