From 97067f9c9cfbcbdf0e7dfd07295a061f53ccb6dd Mon Sep 17 00:00:00 2001 From: Will-thom <116388885+Will-thom@users.noreply.github.com> Date: Thu, 21 May 2026 21:18:22 +0000 Subject: [PATCH] Close contexts when clearing test context cache This updates the TestContext Framework cache so that clearing the cache also closes cached ConfigurableApplicationContext instances instead of only dropping the internal references. The implementation reuses the existing removal path, preserving the hierarchy-aware close behavior already used by cache eviction/removal. The ContextCache contract now documents the close behavior for clear(), and LruContextCacheTests covers both clear() and reset(), since reset() delegates to clear(). See gh-26196 Closes gh-36825 Signed-off-by: Will-thom <116388885+Will-thom@users.noreply.github.com> --- .../test/context/cache/ContextCache.java | 5 ++- .../context/cache/DefaultContextCache.java | 3 ++ .../context/cache/LruContextCacheTests.java | 36 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) 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 cfbc6c44345..a350678774e 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 @@ -314,7 +314,10 @@ public interface ContextCache { void reset(); /** - * Clear all contexts from the cache, clearing context hierarchy information as well. + * Clear all contexts from the cache, explicitly + * {@linkplain org.springframework.context.ConfigurableApplicationContext#close() closing} + * each context that is an instance of {@code ConfigurableApplicationContext} + * and clearing context hierarchy information as well. */ void clear(); 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 b6d38af1818..e15347189ce 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 @@ -426,6 +426,9 @@ public class DefaultContextCache implements ContextCache { @Override public void clear() { synchronized (this.contextMap) { + for (MergedContextConfiguration key : new ArrayList<>(this.contextMap.keySet())) { + remove(key, HierarchyMode.CURRENT_LEVEL); + } this.contextMap.clear(); this.hierarchyMap.clear(); this.contextUsageMap.clear(); diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java index 186addab6b5..c45d4b4f299 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java @@ -81,6 +81,42 @@ class LruContextCacheTests { assertThatIllegalArgumentException().isThrownBy(() -> new DefaultContextCache(0)); } + @Test + void clearClosesContexts() { + DefaultContextCache cache = new DefaultContextCache(4); + + cache.put(fooConfig, key -> fooContext); + cache.put(barConfig, key -> barContext); + cache.put(bazConfig, key -> bazContext); + assertCacheContents(cache, "Foo", "Bar", "Baz"); + + cache.clear(); + assertCacheContents(cache); + + verify(fooContext, times(1)).close(); + verify(barContext, times(1)).close(); + verify(bazContext, times(1)).close(); + verify(abcContext, never()).close(); + } + + @Test + void resetClosesContexts() { + DefaultContextCache cache = new DefaultContextCache(4); + + cache.put(fooConfig, key -> fooContext); + cache.put(barConfig, key -> barContext); + cache.get(fooConfig); + assertThat(cache.getHitCount()).isEqualTo(1); + assertCacheContents(cache, "Bar", "Foo"); + + cache.reset(); + assertCacheContents(cache); + assertThat(cache.getHitCount()).isZero(); + + verify(fooContext, times(1)).close(); + verify(barContext, times(1)).close(); + } + @Nested @SuppressWarnings("deprecation")