Close contexts when clearing test context cache - #36825
Conversation
|
Thanks for the PR, @Will-thom. 👍
Please note that this change would only serve as a building block for #26196, since it would not address the broader topic raised in that issue. In any case, it makes sense for Also, please make sure you comply with the DCO requirement by signing your commits. Otherwise, we cannot process this PR. |
This comment was marked as outdated.
This comment was marked as outdated.
Signed-off-by: Will-thom <116388885+Will-thom@users.noreply.github.com>
9272ec3 to
0923445
Compare
|
Thanks for the reminder. The commit now includes the required Signed-off-by line, and the DCO check is passing on the latest PR head. Could you please take another look when you have a chance? |
sbrannen
left a comment
There was a problem hiding this comment.
Thanks for the update, @Will-thom. The DCO check looks good now.
I went through the clear()/reset() change in detail, and the core approach is correct: reusing remove(key, HierarchyMode.CURRENT_LEVEL) over a snapshot of the cache's keys closes every cached context exactly once, in the correct bottom-up order, regardless of iteration order.
I've requested a minor change and improvements to the tests, after which I think this PR will be good to go.
|
|
||
| verify(fooContext, times(1)).close(); | ||
| verify(barContext, times(1)).close(); | ||
| } |
There was a problem hiding this comment.
There are a couple of things I'd like addressed on the test side before merging:
-
Hierarchy coverage. These two new tests only cover flat, non-hierarchical configs (
config()always passes anullparent). Since this change goes through the hierarchy-awareremove()path, could you please add a test that builds an actual multi-level@ContextHierarchy(similar to theremoveContextHierarchyCacheLevel*()tests inContextCacheTests) and asserts thatclear()/reset()closes every level correctly? -
Invariant coverage. Neither of the new tests verifies that
hierarchyMapandcontextUsageMapactually end up empty — onlycontextMapis checked, viaassertCacheContents(). Since those two structures are exactly what this change touches insideremove(), please also assertgetParentContextCount()andgetContextUsageCount()return to0afterclear()/reset()– for example, using the existingassertContextCacheStatistics()/assertParentContextCount()helpers already used inContextCacheTests. ForresetClosesContexts(), it'd also be good to assertgetMissCount()is0for symmetry with the existinggetHitCount()check.
This would give us a real regression guard on these new code paths, rather than only checking that the mock contexts were closed.
As side note, I think using Mockito.inOrder() for the verification would also serve us well to ensure the contexts are closed in the expected order within a context hierarchy.
However, inOrder should only assert order along a single ancestor → descendant chain (e.g. child → parent). It shouldn't assert anything about the relative order of sibling contexts, since children of the same parent are stored in a HashSet and closed in unspecified order. In other words, asserting sibling order would result in a flaky test.
| for (MergedContextConfiguration key : new ArrayList<>(this.contextMap.keySet())) { | ||
| remove(key, HierarchyMode.CURRENT_LEVEL); | ||
| } | ||
| this.contextMap.clear(); |
There was a problem hiding this comment.
After this new loop, the four subsequent calls to this.contextMap.clear(), this.hierarchyMap.clear(), this.contextUsageMap.clear(), and this.unusedContexts.clear() are now redundant: the loop's calls to remove() should already empty all four as an invariant, since every key in the pre-loop snapshot ends up removed either directly or as a descendant.
Could you please remove those clear() calls and ensure the tests cover those invariants (see other comment regarding tests)?
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().
Closes gh-26196
Tests:
./gradlew.bat --no-daemon --max-workers=1 :spring-test:test --tests org.springframework.test.context.cache.LruContextCacheTests./gradlew.bat --no-daemon --max-workers=1 :spring-test:test --tests org.springframework.test.context.cache.ContextCacheTests./gradlew.bat --no-daemon --max-workers=1 :spring-test:check