Skip to content

Commit 1d81839

Browse files
Close AutoCloseable objects when closing ExtensionContext.Store
By default, instances of `AutoCloseable` are now closed when the `Store` is closed in addition to instances of `CloseableResource`. The new `junit.jupiter.extensions.store.close.autocloseable.enabled` config param allows reverting to the old behavior. All core extensions implement both interfaces so they still work when the new behavior has been disabled. Extensions implemented in test code only implement `AutoCloseable` since they don't need to support that mode. Resolves #4434. --------- Co-authored-by: Marc Philipp <[email protected]>
1 parent 85d2553 commit 1d81839

File tree

23 files changed

+317
-80
lines changed

23 files changed

+317
-80
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M3.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ to start reporting discovery issues.
9898
- Blank `@SentenceFragment` declarations
9999
- `@BeforeParameterizedClassInvocation` and `@AfterParameterizedClassInvocation`
100100
methods declared in non-parameterized test classes
101+
* By default, `AutoCloseable` objects put into `ExtensionContext.Store` are now treated
102+
like instances of `CloseableResource` (which has been deprecated) and are closed
103+
automatically when the store is closed at the end of the test lifecycle. It's possible
104+
to <<../user-guide/index.adoc#extensions-keeping-state-autocloseable-support, revert to the old behavior>>
105+
via a configuration parameter. Please also see the
106+
<<../user-guide/index.adoc#extensions-keeping-state-autocloseable-migration, migration note>>
107+
for third-party extensions wanting to support both JUnit 5.13 and earlier versions.
108+
101109
* `java.util.Locale` arguments are now converted according to the IETF BCP 47 language tag
102110
format. See the
103111
<<../user-guide/index.adoc#writing-tests-parameterized-tests-argument-conversion-implicit, User Guide>>

documentation/src/docs/asciidoc/user-guide/extensions.adoc

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -863,17 +863,22 @@ surrounding `ExtensionContext`. Since `ExtensionContexts` may be nested, the sco
863863
inner contexts may also be limited. Consult the corresponding Javadoc for details on the
864864
methods available for storing and retrieving values via the `{ExtensionContext_Store}`.
865865

866-
.`ExtensionContext.Store.CloseableResource`
866+
[[extensions-keeping-state-autocloseable-support]]
867+
.Resource management via `_AutoCloseable_`
867868
NOTE: An extension context store is bound to its extension context lifecycle. When an
868-
extension context lifecycle ends it closes its associated store. All stored values
869-
that are instances of `CloseableResource` are notified by an invocation of their `close()`
870-
method in the inverse order they were added in.
871-
872-
An example implementation of `CloseableResource` is shown below, using an `HttpServer`
869+
extension context lifecycle ends it closes its associated store. As of JUnit 5.13,
870+
all stored values that are instances of `AutoCloseable` are notified by an invocation of
871+
their `close()` method in the inverse order they were added in (unless the
872+
`junit.jupiter.extensions.store.close.autocloseable.enabled`
873+
<<running-tests-config-params, configuration parameter>> is set to `false`). Older
874+
versions only supported `CloseableResource`, which is deprecated but still available for
875+
backward compatibility.
876+
877+
An example implementation of `AutoCloseable` is shown below, using an `HttpServer`
873878
resource.
874879

875880
[source,java,indent=0]
876-
.`HttpServer` resource implementing `CloseableResource`
881+
.`HttpServer` resource implementing `AutoCloseable`
877882
----
878883
include::{testDir}/example/extensions/HttpServerResource.java[tags=user_guide]
879884
----
@@ -896,6 +901,33 @@ include::{testDir}/example/extensions/HttpServerExtension.java[tags=user_guide]
896901
include::{testDir}/example/HttpServerDemo.java[tags=user_guide]
897902
----
898903

904+
[[extensions-keeping-state-autocloseable-migration]]
905+
[TIP]
906+
.Migration Note for Resource Cleanup
907+
====
908+
909+
Starting with JUnit Jupiter 5.13, the framework automatically closes resources stored in
910+
the `ExtensionContext.Store` that implement `AutoCloseable`. In earlier versions, only
911+
resources implementing `Store.CloseableResource` were automatically closed.
912+
913+
If you're developing an extension that needs to support both JUnit Jupiter 5.13+ and
914+
earlier versions and your extension stores resources that need to be cleaned up, you
915+
should implement both interfaces:
916+
917+
[source,java,indent=0]
918+
----
919+
public class MyResource implements Store.CloseableResource, AutoCloseable {
920+
@Override
921+
public void close() throws Exception {
922+
// Resource cleanup code
923+
}
924+
}
925+
----
926+
927+
This ensures that your resource will be properly closed regardless of which JUnit Jupiter
928+
version is being used.
929+
====
930+
899931
[[extensions-supported-utilities]]
900932
=== Supported Utilities in Extensions
901933

documentation/src/test/java/example/extensions/HttpServerResource.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@
1919

2020
import com.sun.net.httpserver.HttpServer;
2121

22-
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
23-
2422
/**
25-
* Demonstrates an implementation of {@link CloseableResource} using an {@link HttpServer}.
23+
* Demonstrates an implementation of {@link AutoCloseable} using an {@link HttpServer}.
2624
*/
2725
// tag::user_guide[]
28-
class HttpServerResource implements CloseableResource {
26+
class HttpServerResource implements AutoCloseable {
2927

3028
private final HttpServer httpServer;
3129

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,9 @@ interface Store {
503503
* inverse order they were added in.
504504
*
505505
* @since 5.1
506+
* @deprecated Please extend {@code AutoCloseable} directly.
506507
*/
508+
@Deprecated
507509
@API(status = STABLE, since = "5.1")
508510
interface CloseableResource {
509511

@@ -595,9 +597,11 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
595597
* <p>See {@link #getOrComputeIfAbsent(Object, Function, Class)} for
596598
* further details.
597599
*
598-
* <p>If {@code type} implements {@link ExtensionContext.Store.CloseableResource}
599-
* the {@code close()} method will be invoked on the stored object when
600-
* the store is closed.
600+
* <p>If {@code type} implements {@link CloseableResource} or
601+
* {@link AutoCloseable} (unless the
602+
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
603+
* configuration parameter is set to {@code false}), then the {@code close()}
604+
* method will be invoked on the stored object when the store is closed.
601605
*
602606
* @param type the type of object to retrieve; never {@code null}
603607
* @param <V> the key and value type
@@ -606,6 +610,7 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
606610
* @see #getOrComputeIfAbsent(Object, Function)
607611
* @see #getOrComputeIfAbsent(Object, Function, Class)
608612
* @see CloseableResource
613+
* @see AutoCloseable
609614
*/
610615
@API(status = STABLE, since = "5.1")
611616
default <V> V getOrComputeIfAbsent(Class<V> type) {
@@ -625,9 +630,11 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
625630
* <p>For greater type safety, consider using
626631
* {@link #getOrComputeIfAbsent(Object, Function, Class)} instead.
627632
*
628-
* <p>If the created value is an instance of {@link ExtensionContext.Store.CloseableResource}
629-
* the {@code close()} method will be invoked on the stored object when
630-
* the store is closed.
633+
* <p>If the created value is an instance of {@link CloseableResource} or
634+
* {@link AutoCloseable} (unless the
635+
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
636+
* configuration parameter is set to {@code false}), then the {@code close()}
637+
* method will be invoked on the stored object when the store is closed.
631638
*
632639
* @param key the key; never {@code null}
633640
* @param defaultCreator the function called with the supplied {@code key}
@@ -638,6 +645,7 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
638645
* @see #getOrComputeIfAbsent(Class)
639646
* @see #getOrComputeIfAbsent(Object, Function, Class)
640647
* @see CloseableResource
648+
* @see AutoCloseable
641649
*/
642650
<K, V> Object getOrComputeIfAbsent(K key, Function<K, V> defaultCreator);
643651

@@ -652,9 +660,11 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
652660
* a new value will be computed by the {@code defaultCreator} (given
653661
* the {@code key} as input), stored, and returned.
654662
*
655-
* <p>If {@code requiredType} implements {@link ExtensionContext.Store.CloseableResource}
656-
* the {@code close()} method will be invoked on the stored object when
657-
* the store is closed.
663+
* <p>If {@code requiredType} implements {@link CloseableResource} or
664+
* {@link AutoCloseable} (unless the
665+
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
666+
* configuration parameter is set to {@code false}), then the {@code close()}
667+
* method will be invoked on the stored object when the store is closed.
658668
*
659669
* @param key the key; never {@code null}
660670
* @param defaultCreator the function called with the supplied {@code key}
@@ -666,6 +676,7 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
666676
* @see #getOrComputeIfAbsent(Class)
667677
* @see #getOrComputeIfAbsent(Object, Function)
668678
* @see CloseableResource
679+
* @see AutoCloseable
669680
*/
670681
<K, V> V getOrComputeIfAbsent(K key, Function<K, V> defaultCreator, Class<V> requiredType);
671682

@@ -676,23 +687,26 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
676687
* ExtensionContexts} for the store's {@code Namespace} unless they
677688
* overwrite it.
678689
*
679-
* <p>If the {@code value} is an instance of {@link ExtensionContext.Store.CloseableResource}
680-
* the {@code close()} method will be invoked on the stored object when
681-
* the store is closed.
690+
* <p>If the {@code value} is an instance of {@link CloseableResource} or
691+
* {@link AutoCloseable} (unless the
692+
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
693+
* configuration parameter is set to {@code false}), then the {@code close()}
694+
* method will be invoked on the stored object when the store is closed.
682695
*
683696
* @param key the key under which the value should be stored; never
684697
* {@code null}
685698
* @param value the value to store; may be {@code null}
686699
* @see CloseableResource
700+
* @see AutoCloseable
687701
*/
688702
void put(Object key, Object value);
689703

690704
/**
691705
* Remove the value that was previously stored under the supplied {@code key}.
692706
*
693707
* <p>The value will only be removed in the current {@link ExtensionContext},
694-
* not in ancestors. In addition, the {@link CloseableResource} API will not
695-
* be honored for values that are manually removed via this method.
708+
* not in ancestors. In addition, the {@link CloseableResource} and {@link AutoCloseable}
709+
* API will not be honored for values that are manually removed via this method.
696710
*
697711
* <p>For greater type safety, consider using {@link #remove(Object, Class)}
698712
* instead.
@@ -709,8 +723,8 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
709723
* under the supplied {@code key}.
710724
*
711725
* <p>The value will only be removed in the current {@link ExtensionContext},
712-
* not in ancestors. In addition, the {@link CloseableResource} API will not
713-
* be honored for values that are manually removed via this method.
726+
* not in ancestors. In addition, the {@link CloseableResource} and {@link AutoCloseable}
727+
* API will not be honored for values that are manually removed via this method.
714728
*
715729
* @param key the key; never {@code null}
716730
* @param requiredType the required type of the value; never {@code null}

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.apiguardian.api.API;
1717
import org.junit.jupiter.api.TestInstance;
1818
import org.junit.jupiter.api.extension.ExtensionContext.Store;
19-
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
2019

2120
/**
2221
* Interface for {@link Extension Extensions} that are aware and can influence
@@ -65,9 +64,11 @@ public interface TestInstantiationAwareExtension extends Extension {
6564
* <li>{@link ExtensionContext#getTestMethod() getTestMethod()} is no longer
6665
* empty, unless the {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS}
6766
* lifecycle is used.</li>
68-
* <li>If the callback adds a new {@link CloseableResource} to the
69-
* {@link Store Store}, the resource is closed just after the instance is
70-
* destroyed.</li>
67+
* <li>If the callback adds a new {@link Store.CloseableResource} or
68+
* {@link AutoCloseable} to the {@link Store Store} (unless the
69+
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
70+
* configuration parameter is set to {@code false}), then
71+
* the resource is closed just after the instance is destroyed.</li>
7172
* <li>The callbacks can now access data previously stored by
7273
* {@link TestTemplateInvocationContext}, unless the
7374
* {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used.</li>

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,16 @@ public final class Constants {
210210
@API(status = STABLE, since = "5.10")
211211
public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;
212212

213+
/**
214+
* Property name used to enable auto-closing of {@link AutoCloseable} instances
215+
*
216+
* <p>By default, auto-closing is enabled.
217+
*
218+
* @since 5.13
219+
*/
220+
@API(status = EXPERIMENTAL, since = "5.13")
221+
public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = JupiterConfiguration.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME;
222+
213223
/**
214224
* Property name used to set the default test execution mode: {@value}
215225
*

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ public boolean isParallelExecutionEnabled() {
6969
__ -> delegate.isParallelExecutionEnabled());
7070
}
7171

72+
@Override
73+
public boolean isClosingStoredAutoCloseablesEnabled() {
74+
return (boolean) cache.computeIfAbsent(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME,
75+
__ -> delegate.isClosingStoredAutoCloseablesEnabled());
76+
}
77+
7278
@Override
7379
public boolean isExtensionAutoDetectionEnabled() {
7480
return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME,

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ public boolean isParallelExecutionEnabled() {
112112
return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false);
113113
}
114114

115+
@Override
116+
public boolean isClosingStoredAutoCloseablesEnabled() {
117+
return configurationParameters.getBoolean(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME).orElse(true);
118+
}
119+
115120
@Override
116121
public boolean isExtensionAutoDetectionEnabled() {
117122
return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false);

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,15 @@ public interface JupiterConfiguration {
4242
String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude";
4343
String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate";
4444
String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled";
45+
String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled";
4546
String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME;
4647
String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME;
4748
String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled";
4849
String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME;
4950
String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME;
5051
String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME;
5152
String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME;
52-
String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME;;
53+
String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME;
5354
String DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
5455

5556
Predicate<Class<? extends Extension>> getFilterForAutoDetectedExtensions();
@@ -60,6 +61,8 @@ public interface JupiterConfiguration {
6061

6162
boolean isParallelExecutionEnabled();
6263

64+
boolean isClosingStoredAutoCloseablesEnabled();
65+
6366
boolean isExtensionAutoDetectionEnabled();
6467

6568
boolean isThreadDumpOnTimeoutEnabled();

0 commit comments

Comments
 (0)