From 9711db787e0db5e7d569119491e723c720b684a2 Mon Sep 17 00:00:00 2001
From: Sam Brannen <104798+sbrannen@users.noreply.github.com>
Date: Fri, 9 Jan 2026 12:01:02 +0100
Subject: [PATCH] Introduce Spring property to disable context pausing for
tests
Spring Framework 7.0 introduced support for pausing inactive
application contexts between test classes and restarting them once they
are needed again. If pausing and restarting are fast, this feature does
not have a negative impact on test suites.
However, if the pausing or restarting of certain Lifecycle components
in the application context is slow, that can have a negative impact on
the duration of the overall test suite.
In gh-36044, we hope to find a way to avoid unnecessarily pausing an
application context after a test class if the same context is used by
the next test class that is run. That should help reduce the risk of a
negative impact caused by the pause/restart feature; however, for
certain scenarios that may not be enough. In light of that, this commit
introduces a mechanism for completely disabling the pausing feature via
a Spring property or JVM system property, as follows.
-Dspring.test.context.cache.pause=never
See gh-35168
See gh-36044
Closes gh-36117
---
.../modules/ROOT/pages/appendix.adoc | 4 +
.../ctx-management/caching.adoc | 16 ++
.../core/SpringProperties.java | 1 +
.../test/context/cache/ContextCache.java | 74 ++++++-
.../test/context/cache/ContextCacheUtils.java | 24 +++
.../context/cache/DefaultContextCache.java | 36 +++-
.../cache/ContextCachePauseModeTests.java | 201 ++++++++++++++++++
.../test/context/cache/ContextCacheTests.java | 2 +
.../context/cache/ContextCacheUtilsTests.java | 174 +++++++++++----
9 files changed, 487 insertions(+), 45 deletions(-)
create mode 100644 spring-test/src/test/java/org/springframework/test/context/cache/ContextCachePauseModeTests.java
diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc
index 6e7e5cecd0e..f6101064014 100644
--- a/framework-docs/modules/ROOT/pages/appendix.adoc
+++ b/framework-docs/modules/ROOT/pages/appendix.adoc
@@ -124,6 +124,10 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
| The maximum size of the context cache in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
+| `spring.test.context.cache.pause`
+| The pause mode for the context cache in the _Spring TestContext Framework_. See
+xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
+
| `spring.test.context.failure.threshold`
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
in the _Spring TestContext Framework_. See
diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc
index be0eda85172..19b4d66f2df 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc
@@ -73,6 +73,22 @@ will be in a "stopped" state until the context is used again by a test. Note, ho
that `SmartLifecycle` components can opt out of pausing by returning `false` from
`SmartLifecycle#isPauseable()`.
+[TIP]
+====
+If you encounter issues with `Lifecycle` components that cannot or should not opt out of
+pausing, or if you discover that your test suite runs more slowly due to the pausing and
+restarting of application contexts, you can disable the pausing feature from the command
+line or a build script by setting a JVM system property named
+`spring.test.context.cache.pause` to `never`. For example:
+
+```shell
+-Dspring.test.context.cache.pause=never
+```
+
+As an alternative, you can set the same property via the
+xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
+====
+
Since having a large number of application contexts loaded within a given test suite can
cause the suite to take an unnecessarily long time to run, it is often beneficial to
know exactly how many contexts have been loaded and cached. To view the statistics for
diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
index f2c255f77c5..e4ed3c6e8b7 100644
--- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java
+++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
@@ -50,6 +50,7 @@ import org.jspecify.annotations.Nullable;
* @see org.springframework.test.context.NestedTestConfiguration#ENCLOSING_CONFIGURATION_PROPERTY_NAME
* @see org.springframework.test.context.TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME
* @see org.springframework.test.context.cache.ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
+ * @see org.springframework.test.context.cache.ContextCache#CONTEXT_CACHE_PAUSE_PROPERTY_NAME
*/
public final class SpringProperties {
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
index 260e8b60b17..9669af1ac1b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
@@ -16,6 +16,10 @@
package org.springframework.test.context.cache;
+import java.util.Locale;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
@@ -83,6 +87,24 @@ public interface ContextCache {
*/
String MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME = "spring.test.context.cache.maxSize";
+ /**
+ * System property used to configure whether inactive application contexts
+ * stored in the {@link ContextCache} should be paused: {@value}.
+ *
Defaults to {@code always}. Set this property to {@code never} to
+ * disable pausing of inactive application contexts — for example:
+ *
{@code -Dspring.test.context.cache.pause=never}
+ *
May alternatively be configured via the
+ * {@link org.springframework.core.SpringProperties} mechanism.
+ *
Note that implementations of {@code ContextCache} are not required to
+ * support context pausing. Consult the documentation of the corresponding
+ * implementation for details.
+ * @since 7.0.3
+ * @see PauseMode
+ * @see org.springframework.context.ConfigurableApplicationContext#pause()
+ * @see #unregisterContextUsage(MergedContextConfiguration, Class)
+ */
+ String CONTEXT_CACHE_PAUSE_PROPERTY_NAME = "spring.test.context.cache.pause";
+
/**
* Determine whether there is a cached context for the given key.
@@ -221,7 +243,8 @@ public interface ContextCache {
* {@link MergedContextConfiguration} and any of its parents.
*
If no other test classes are actively using the same application
* context(s), the application context(s) should be
- * {@linkplain org.springframework.context.ConfigurableApplicationContext#pause() paused}.
+ * {@linkplain org.springframework.context.ConfigurableApplicationContext#pause()
+ * paused} according to the configured {@link PauseMode}.
*
The default implementation of this method does nothing. Concrete
* implementations are therefore highly encouraged to override this
* method, {@link #registerContextUsage(MergedContextConfiguration, Class)},
@@ -336,4 +359,53 @@ public interface ContextCache {
}
+ /**
+ * Enumeration of modes that dictate whether inactive application contexts
+ * stored in the {@link ContextCache} should be
+ * {@linkplain org.springframework.context.ConfigurableApplicationContext#pause() paused}.
+ *
+ * @since 7.0.3
+ * @see #ALWAYS
+ * @see #NEVER
+ * @see ContextCache#CONTEXT_CACHE_PAUSE_PROPERTY_NAME
+ */
+ enum PauseMode {
+
+ /**
+ * Always pause inactive application contexts.
+ */
+ ALWAYS,
+
+ /**
+ * Never pause inactive application contexts, effectively disabling the
+ * pausing feature of the {@link ContextCache}.
+ */
+ NEVER;
+
+
+ /**
+ * Get the {@code PauseMode} enum constant with the supplied name,
+ * {@linkplain String#strip() stripped} and ignoring case.
+ * @param name the name of the enum constant to retrieve
+ * @return the corresponding enum constant or {@code null} if not found
+ * @see PauseMode#valueOf(String)
+ */
+ public static @Nullable PauseMode from(@Nullable String name) {
+ if (name == null) {
+ return null;
+ }
+ try {
+ return PauseMode.valueOf(name.strip().toUpperCase(Locale.ROOT));
+ }
+ catch (IllegalArgumentException ex) {
+ Log logger = LogFactory.getLog(PauseMode.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to parse PauseMode from '%s': %s"
+ .formatted(name, ex.getMessage()));
+ }
+ return null;
+ }
+ }
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
index 9ea50dfdf8f..66dae634ad5 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
@@ -18,6 +18,7 @@ package org.springframework.test.context.cache;
import org.springframework.core.SpringProperties;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
+import org.springframework.test.context.cache.ContextCache.PauseMode;
import org.springframework.util.StringUtils;
/**
@@ -60,6 +61,29 @@ public abstract class ContextCacheUtils {
return retrieveProperty(propertyName, defaultValue);
}
+ /**
+ * Retrieve the {@link PauseMode} for the {@link ContextCache}.
+ *
Uses {@link SpringProperties} to retrieve a system property or Spring
+ * property named {@value ContextCache#CONTEXT_CACHE_PAUSE_PROPERTY_NAME}.
+ *
Defaults to {@link PauseMode#ALWAYS} if no such property has been set.
+ * @return the configured or default {@code PauseMode}
+ * @since 7.0.3
+ * @see ContextCache#CONTEXT_CACHE_PAUSE_PROPERTY_NAME
+ * @see PauseMode#from(String)
+ */
+ public static PauseMode retrievePauseMode() {
+ String value = SpringProperties.getProperty(ContextCache.CONTEXT_CACHE_PAUSE_PROPERTY_NAME);
+ if (StringUtils.hasText(value)) {
+ PauseMode pauseMode = PauseMode.from(value);
+ if (pauseMode == null) {
+ throw new IllegalArgumentException("Unsupported value '%s' for property '%s'"
+ .formatted(value, ContextCache.CONTEXT_CACHE_PAUSE_PROPERTY_NAME));
+ }
+ return pauseMode;
+ }
+ return PauseMode.ALWAYS;
+ }
+
private static int retrieveProperty(String key, int defaultValue) {
try {
String value = SpringProperties.getProperty(key);
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
index 24098cf3d7e..8956273342d 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
@@ -49,10 +49,15 @@ import org.springframework.util.Assert;
* constructor argument} or set via a system property or Spring property named
* {@value ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME}.
*
+ *
The {@link PauseMode} may be supplied as a {@linkplain #DefaultContextCache(int, PauseMode)
+ * constructor argument} or set via a system property or Spring property named
+ * {@value ContextCache#CONTEXT_CACHE_PAUSE_PROPERTY_NAME}.
+ *
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see ContextCacheUtils#retrieveMaxCacheSize()
+ * @see ContextCacheUtils#retrievePauseMode()
*/
public class DefaultContextCache implements ContextCache {
@@ -91,6 +96,8 @@ public class DefaultContextCache implements ContextCache {
private final int maxSize;
+ private final PauseMode pauseMode;
+
private final AtomicInteger hitCount = new AtomicInteger();
private final AtomicInteger missCount = new AtomicInteger();
@@ -98,10 +105,13 @@ public class DefaultContextCache implements ContextCache {
/**
* Create a new {@code DefaultContextCache} using the maximum cache size
- * obtained via {@link ContextCacheUtils#retrieveMaxCacheSize()}.
+ * obtained via {@link ContextCacheUtils#retrieveMaxCacheSize()} and the
+ * {@link PauseMode} obtained via {@link ContextCacheUtils#retrievePauseMode()}.
* @since 4.3
* @see #DefaultContextCache(int)
+ * @see #DefaultContextCache(int, PauseMode)
* @see ContextCacheUtils#retrieveMaxCacheSize()
+ * @see ContextCacheUtils#retrievePauseMode()
*/
public DefaultContextCache() {
this(ContextCacheUtils.retrieveMaxCacheSize());
@@ -109,16 +119,35 @@ public class DefaultContextCache implements ContextCache {
/**
* Create a new {@code DefaultContextCache} using the supplied maximum
- * cache size.
+ * cache size and the {@link PauseMode} obtained via
+ * {@link ContextCacheUtils#retrievePauseMode()}.
* @param maxSize the maximum cache size
* @throws IllegalArgumentException if the supplied {@code maxSize} value
* is not positive
* @since 4.3
* @see #DefaultContextCache()
+ * @see #DefaultContextCache(int, PauseMode)
+ * @see ContextCacheUtils#retrievePauseMode()
*/
public DefaultContextCache(int maxSize) {
+ this(maxSize, ContextCacheUtils.retrievePauseMode());
+ }
+
+ /**
+ * Create a new {@code DefaultContextCache} using the supplied maximum
+ * cache size and {@link PauseMode}.
+ * @param maxSize the maximum cache size
+ * @param pauseMode the {@code PauseMode} to use
+ * @throws IllegalArgumentException if the supplied {@code maxSize} value
+ * is not positive or if the supplied {@code PauseMode} is {@code null}
+ * @since 7.0.3
+ * @see #DefaultContextCache()
+ */
+ public DefaultContextCache(int maxSize, PauseMode pauseMode) {
Assert.isTrue(maxSize > 0, "'maxSize' must be positive");
+ Assert.notNull(pauseMode, "'pauseMode' must not be null");
this.maxSize = maxSize;
+ this.pauseMode = pauseMode;
}
@@ -222,7 +251,8 @@ public class DefaultContextCache implements ContextCache {
Set> activeTestClasses = getActiveTestClasses(mergedConfig);
activeTestClasses.remove(testClass);
if (activeTestClasses.isEmpty()) {
- if (context instanceof ConfigurableApplicationContext cac && cac.isRunning()) {
+ if ((this.pauseMode == PauseMode.ALWAYS) &&
+ (context instanceof ConfigurableApplicationContext cac && cac.isRunning())) {
cac.pause();
}
this.contextUsageMap.remove(mergedConfig);
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCachePauseModeTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCachePauseModeTests.java
new file mode 100644
index 00000000000..0da74caab72
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCachePauseModeTests.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * https://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.cache;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextCustomizerFactories;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestContextTestUtils;
+import org.springframework.test.context.cache.ContextCache.PauseMode;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.test.context.cache.ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE;
+import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics;
+
+/**
+ * Integration tests for verifying proper behavior of the {@link ContextCache} in
+ * conjunction with {@link PauseMode}.
+ *
+ * @author Sam Brannen
+ * @since 7.0.3
+ * @see ContextCacheTests
+ * @see UnusedContextsIntegrationTests
+ */
+class ContextCachePauseModeTests {
+
+ private ContextCache contextCache;
+
+
+ @BeforeEach
+ @AfterEach
+ void clearApplicationEvents() {
+ EventTracker.events.clear();
+ }
+
+ @Test
+ void topLevelTestClassesWithPauseModeAlways() {
+ this.contextCache = new DefaultContextCache(DEFAULT_MAX_CONTEXT_CACHE_SIZE, PauseMode.ALWAYS);
+
+ loadCtxAndAssertStats(TestCase1A.class, 1, 1, 0, 1);
+ assertThat(EventTracker.events).containsExactly("ContextRefreshed:TestCase1A", "ContextPaused:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1A.class, 1, 1, 1, 1);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase1A", "ContextPaused:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1B.class, 1, 1, 2, 1);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase1A", "ContextPaused:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1A.class, 1, 1, 3, 1);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase1A", "ContextPaused:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 3, 2);
+ assertThat(EventTracker.events).containsExactly("ContextRefreshed:TestCase2", "ContextPaused:TestCase2");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1B.class, 2, 1, 4, 2);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase1A", "ContextPaused:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1A.class, 2, 1, 5, 2);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase1A", "ContextPaused:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 6, 2);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase2", "ContextPaused:TestCase2");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 7, 2);
+ assertThat(EventTracker.events).containsExactly("ContextRestarted:TestCase2", "ContextPaused:TestCase2");
+ clearApplicationEvents();
+
+ markContextDirty(TestCase2.class);
+ assertThat(EventTracker.events).containsExactly("ContextClosed:TestCase2");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 7, 3);
+ assertThat(EventTracker.events).containsExactly("ContextRefreshed:TestCase2", "ContextPaused:TestCase2");
+ clearApplicationEvents();
+ }
+
+ @Test
+ void topLevelTestClassesWithPauseModeNever() {
+ this.contextCache = new DefaultContextCache(DEFAULT_MAX_CONTEXT_CACHE_SIZE, PauseMode.NEVER);
+
+ loadCtxAndAssertStats(TestCase1A.class, 1, 1, 0, 1);
+ assertThat(EventTracker.events).containsExactly("ContextRefreshed:TestCase1A");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1A.class, 1, 1, 1, 1);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1B.class, 1, 1, 2, 1);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1A.class, 1, 1, 3, 1);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 3, 2);
+ assertThat(EventTracker.events).containsExactly("ContextRefreshed:TestCase2");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1B.class, 2, 1, 4, 2);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase1A.class, 2, 1, 5, 2);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 6, 2);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 7, 2);
+ assertThat(EventTracker.events).isEmpty();
+ clearApplicationEvents();
+
+ markContextDirty(TestCase2.class);
+ assertThat(EventTracker.events).containsExactly("ContextClosed:TestCase2");
+ clearApplicationEvents();
+
+ loadCtxAndAssertStats(TestCase2.class, 2, 1, 7, 3);
+ assertThat(EventTracker.events).containsExactly("ContextRefreshed:TestCase2");
+ clearApplicationEvents();
+ }
+
+ private void loadCtxAndAssertStats(Class> testClass, int expectedSize, int expectedActiveContextsCount,
+ int expectedHitCount, int expectedMissCount) {
+
+ TestContext testContext = TestContextTestUtils.buildTestContext(testClass, contextCache);
+
+ ApplicationContext context = testContext.getApplicationContext();
+ assertThat(context).isNotNull();
+ assertContextCacheStatistics(contextCache, testClass.getName(), expectedSize, expectedActiveContextsCount,
+ expectedHitCount, expectedMissCount);
+ testContext.markApplicationContextUnused();
+ assertThat(contextCache.getContextUsageCount())
+ .as("active contexts in cache (%s)", testClass.getSimpleName()).isZero();
+ }
+
+ private void markContextDirty(Class> testClass) {
+ TestContext testContext = TestContextTestUtils.buildTestContext(testClass, contextCache);
+ testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
+ }
+
+
+ @Configuration
+ @Import(EventTracker.class)
+ static class Config1 {
+ }
+
+ @Configuration
+ @Import(EventTracker.class)
+ static class Config2 {
+ }
+
+ @ContextConfiguration(classes = Config1.class)
+ @ContextCustomizerFactories(DisplayNameCustomizerFactory.class)
+ private abstract static class AbstractTestCase1 {
+ }
+
+ private static class TestCase1A extends AbstractTestCase1 {
+ }
+
+ private static class TestCase1B extends AbstractTestCase1 {
+ }
+
+ @ContextConfiguration(classes = Config2.class)
+ @ContextCustomizerFactories(DisplayNameCustomizerFactory.class)
+ private static class TestCase2 {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
index 98c9332b812..dd8c82166b6 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
@@ -71,6 +71,8 @@ class ContextCacheTests {
assertContextCacheStatistics(contextCache, testClass.getName(), expectedSize, expectedActiveContextsCount,
expectedHitCount, expectedMissCount);
testContext.markApplicationContextUnused();
+ assertThat(contextCache.getContextUsageCount())
+ .as("active contexts in cache (%s)", testClass.getSimpleName()).isZero();
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
index acef7a99ac5..2b8744ea2ca 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
@@ -18,14 +18,21 @@ package org.springframework.test.context.cache;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.FieldSource;
import org.springframework.core.SpringProperties;
+import org.springframework.test.context.cache.ContextCache.PauseMode;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.springframework.test.context.cache.ContextCache.CONTEXT_CACHE_PAUSE_PROPERTY_NAME;
import static org.springframework.test.context.cache.ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE;
import static org.springframework.test.context.cache.ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME;
import static org.springframework.test.context.cache.ContextCacheUtils.retrieveMaxCacheSize;
+import static org.springframework.test.context.cache.ContextCacheUtils.retrievePauseMode;
/**
* Tests for {@link ContextCacheUtils}.
@@ -35,56 +42,141 @@ import static org.springframework.test.context.cache.ContextCacheUtils.retrieveM
*/
class ContextCacheUtilsTests {
- @BeforeEach
- @AfterEach
- void clearProperties() {
- System.clearProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME);
- SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, null);
- }
+ @Nested
+ class MaxCacheSizeTests {
- @Test
- void retrieveMaxCacheSizeFromDefault() {
- assertDefaultValue();
- }
+ @BeforeEach
+ @AfterEach
+ void clearProperties() {
+ System.clearProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME);
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, null);
+ }
- @Test
- void retrieveMaxCacheSizeFromBogusSystemProperty() {
- System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
- assertDefaultValue();
- }
+ @Test
+ void retrieveMaxCacheSizeFromDefault() {
+ assertDefaultValue();
+ }
- @Test
- void retrieveMaxCacheSizeFromBogusSpringProperty() {
- SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
- assertDefaultValue();
- }
+ @Test
+ void retrieveMaxCacheSizeFromBogusSystemProperty() {
+ System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
+ assertDefaultValue();
+ }
- @Test
- void retrieveMaxCacheSizeFromDecimalSpringProperty() {
- SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "3.14");
- assertDefaultValue();
- }
+ @Test
+ void retrieveMaxCacheSizeFromBogusSpringProperty() {
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
+ assertDefaultValue();
+ }
- @Test
- void retrieveMaxCacheSizeFromSystemProperty() {
- System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42");
- assertThat(retrieveMaxCacheSize()).isEqualTo(42);
- }
+ @Test
+ void retrieveMaxCacheSizeFromDecimalSpringProperty() {
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "3.14");
+ assertDefaultValue();
+ }
- @Test
- void retrieveMaxCacheSizeFromSystemPropertyContainingWhitespace() {
- System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42\t");
- assertThat(retrieveMaxCacheSize()).isEqualTo(42);
- }
+ @Test
+ void retrieveMaxCacheSizeFromSystemProperty() {
+ System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42");
+ assertThat(retrieveMaxCacheSize()).isEqualTo(42);
+ }
+
+ @Test
+ void retrieveMaxCacheSizeFromSystemPropertyContainingWhitespace() {
+ System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42\t");
+ assertThat(retrieveMaxCacheSize()).isEqualTo(42);
+ }
+
+ @Test
+ void retrieveMaxCacheSizeFromSpringProperty() {
+ SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "99");
+ assertThat(retrieveMaxCacheSize()).isEqualTo(99);
+ }
- @Test
- void retrieveMaxCacheSizeFromSpringProperty() {
- SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "99");
- assertThat(retrieveMaxCacheSize()).isEqualTo(99);
+ private static void assertDefaultValue() {
+ assertThat(retrieveMaxCacheSize()).isEqualTo(DEFAULT_MAX_CONTEXT_CACHE_SIZE);
+ }
}
- private static void assertDefaultValue() {
- assertThat(retrieveMaxCacheSize()).isEqualTo(DEFAULT_MAX_CONTEXT_CACHE_SIZE);
+ /**
+ * Tests for {@link PauseMode} support.
+ * @since 7.0.3
+ */
+ @Nested
+ class PauseModeTests {
+
+ static final String[] ALWAYS_VALUES = {
+ "always",
+ "Always",
+ "ALWAYS",
+ "\talways\u000B"
+ };
+
+ static final String[] NEVER_VALUES = {
+ "never",
+ "Never",
+ "NEVER",
+ "\tnever\u000B"
+ };
+
+
+ @BeforeEach
+ @AfterEach
+ void clearProperties() {
+ System.clearProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME);
+ SpringProperties.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, null);
+ }
+
+ @Test
+ void retrievePauseModeFromDefault() {
+ assertThat(retrievePauseMode()).isEqualTo(PauseMode.ALWAYS);
+ }
+
+ @Test
+ void retrievePauseModeFromBogusSystemProperty() {
+ System.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, "bogus");
+ asssertUnsupportedValue();
+ }
+
+ @Test
+ void retrievePauseModeFromBogusSpringProperty() {
+ SpringProperties.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, "bogus");
+ asssertUnsupportedValue();
+ }
+
+ @ParameterizedTest
+ @FieldSource("ALWAYS_VALUES")
+ void retrievePauseModeFromSystemPropertyWithValueAlways(String value) {
+ System.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, value);
+ assertThat(retrievePauseMode()).isEqualTo(PauseMode.ALWAYS);
+ }
+
+ @ParameterizedTest
+ @FieldSource("NEVER_VALUES")
+ void retrievePauseModeFromSystemPropertyWithValueNever(String value) {
+ System.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, value);
+ assertThat(retrievePauseMode()).isEqualTo(PauseMode.NEVER);
+ }
+
+ @ParameterizedTest
+ @FieldSource("ALWAYS_VALUES")
+ void retrievePauseModeFromSpringPropertyWithValueAlways(String value) {
+ SpringProperties.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, value);
+ assertThat(retrievePauseMode()).isEqualTo(PauseMode.ALWAYS);
+ }
+
+ @ParameterizedTest
+ @FieldSource("NEVER_VALUES")
+ void retrievePauseModeFromSpringPropertyWithValueNever(String value) {
+ SpringProperties.setProperty(CONTEXT_CACHE_PAUSE_PROPERTY_NAME, value);
+ assertThat(retrievePauseMode()).isEqualTo(PauseMode.NEVER);
+ }
+
+ private void asssertUnsupportedValue() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(ContextCacheUtils::retrievePauseMode)
+ .withMessage("Unsupported value 'bogus' for property 'spring.test.context.cache.pause'");
+ }
}
}