Skip to content

Introduce feature flag for auto-closing AutoCloseable in Jupiter's ExtensionContext.Store #4452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5cf0a08
Deprecate `CloseableResource`
YongGoose Apr 6, 2025
a524c83
Introduce configuration parameter
YongGoose Apr 6, 2025
5c7b99e
Log warning for CloseableResource without AutoCloseable
YongGoose Apr 6, 2025
44dfb8a
Add tests
YongGoose Apr 6, 2025
0e22d7b
Update jupiter-tests/src/test/java/org/junit/jupiter/engine/descripto…
YongGoose Apr 9, 2025
f52d0e0
Merge branch 'main' into feature/4434
YongGoose Apr 9, 2025
35f25db
Apply comments
YongGoose Apr 9, 2025
8c6dbb3
Merge branch 'main' into feature/4434
YongGoose Apr 9, 2025
708af7d
Add documents
YongGoose Apr 9, 2025
849492d
Replace warning-suppressed code with AutoCloseable usage
YongGoose Apr 9, 2025
f0af9bb
Merge branch 'main' into feature/4434
YongGoose Apr 11, 2025
a2746b3
Merge branch 'main' into feature/4434
YongGoose Apr 11, 2025
c555910
Merge branch 'main' into feature/4434
YongGoose Apr 12, 2025
c82c098
Apply comment
YongGoose Apr 12, 2025
8a5b7fe
Test warning logs for `CloseableResource` without `AutoCloseable`
YongGoose Apr 12, 2025
e8cb1d3
Merge branch 'main' into feature/4434
YongGoose Apr 13, 2025
46a8df0
Merge branch 'main' into feature/4434
YongGoose Apr 15, 2025
7fab7eb
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/de…
YongGoose Apr 15, 2025
8057fe9
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/de…
YongGoose Apr 15, 2025
ed2df61
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/de…
YongGoose Apr 15, 2025
a255a23
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/de…
YongGoose Apr 15, 2025
46a606d
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/de…
YongGoose Apr 15, 2025
d7a985e
Apply comment
YongGoose Apr 15, 2025
c05235e
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/co…
YongGoose Apr 15, 2025
49cb3f5
Update junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/co…
YongGoose Apr 15, 2025
d498200
Update javadoc
YongGoose Apr 15, 2025
056d060
polishing
YongGoose Apr 15, 2025
d25f1b0
Update document
YongGoose Apr 16, 2025
30a4b01
Merge branch 'main' into feature/4434
YongGoose Apr 18, 2025
96c46b0
Merge branch 'main' into feature/4434
YongGoose Apr 26, 2025
bcf642b
Polishing
marcphilipp Apr 28, 2025
f2f8ade
Merge branch 'main' into feature/4434
marcphilipp Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ to start reporting discovery issues.
- Blank `@SentenceFragment` declarations
- `@BeforeParameterizedClassInvocation` and `@AfterParameterizedClassInvocation`
methods declared in non-parameterized test classes
* By default, `AutoCloseable` objects put into `ExtensionContext.Store` are now treated
like instances of `CloseableResource` (which has been deprecated) and are closed
automatically when the store is closed at the end of the test lifecycle. It's possible
to <<../user-guide/index.adoc#extensions-keeping-state-autocloseable-support, revert to the old behavior>>
via a configuration parameter. Please also see the
<<../user-guide/index.adoc#extensions-keeping-state-autocloseable-migration, migration note>>
for third-party extensions wanting to support both JUnit 5.13 and earlier versions.

* `java.util.Locale` arguments are now converted according to the IETF BCP 47 language tag
format. See the
<<../user-guide/index.adoc#writing-tests-parameterized-tests-argument-conversion-implicit, User Guide>>
Expand Down
46 changes: 39 additions & 7 deletions documentation/src/docs/asciidoc/user-guide/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -863,17 +863,22 @@ surrounding `ExtensionContext`. Since `ExtensionContexts` may be nested, the sco
inner contexts may also be limited. Consult the corresponding Javadoc for details on the
methods available for storing and retrieving values via the `{ExtensionContext_Store}`.

.`ExtensionContext.Store.CloseableResource`
[[extensions-keeping-state-autocloseable-support]]
.Resource management via `_AutoCloseable_`
NOTE: An extension context store is bound to its extension context lifecycle. When an
extension context lifecycle ends it closes its associated store. All stored values
that are instances of `CloseableResource` are notified by an invocation of their `close()`
method in the inverse order they were added in.

An example implementation of `CloseableResource` is shown below, using an `HttpServer`
extension context lifecycle ends it closes its associated store. As of JUnit 5.13,
all stored values that are instances of `AutoCloseable` are notified by an invocation of
their `close()` method in the inverse order they were added in (unless the
`junit.jupiter.extensions.store.close.autocloseable.enabled`
<<running-tests-config-params, configuration parameter>> is set to `false`). Older
versions only supported `CloseableResource`, which is deprecated but still available for
backward compatibility.

An example implementation of `AutoCloseable` is shown below, using an `HttpServer`
resource.

[source,java,indent=0]
.`HttpServer` resource implementing `CloseableResource`
.`HttpServer` resource implementing `AutoCloseable`
----
include::{testDir}/example/extensions/HttpServerResource.java[tags=user_guide]
----
Expand All @@ -896,6 +901,33 @@ include::{testDir}/example/extensions/HttpServerExtension.java[tags=user_guide]
include::{testDir}/example/HttpServerDemo.java[tags=user_guide]
----

[[extensions-keeping-state-autocloseable-migration]]
[TIP]
.Migration Note for Resource Cleanup
====

Starting with JUnit Jupiter 5.13, the framework automatically closes resources stored in
the `ExtensionContext.Store` that implement `AutoCloseable`. In earlier versions, only
resources implementing `Store.CloseableResource` were automatically closed.

If you're developing an extension that needs to support both JUnit Jupiter 5.13+ and
earlier versions and your extension stores resources that need to be cleaned up, you
should implement both interfaces:

[source,java,indent=0]
----
public class MyResource implements Store.CloseableResource, AutoCloseable {
@Override
public void close() throws Exception {
// Resource cleanup code
}
}
----

This ensures that your resource will be properly closed regardless of which JUnit Jupiter
version is being used.
====

[[extensions-supported-utilities]]
=== Supported Utilities in Extensions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@

import com.sun.net.httpserver.HttpServer;

import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;

/**
* Demonstrates an implementation of {@link CloseableResource} using an {@link HttpServer}.
* Demonstrates an implementation of {@link AutoCloseable} using an {@link HttpServer}.
*/
// tag::user_guide[]
class HttpServerResource implements CloseableResource {
class HttpServerResource implements AutoCloseable {

private final HttpServer httpServer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,9 @@ interface Store {
* inverse order they were added in.
*
* @since 5.1
* @deprecated Please extend {@code AutoCloseable} directly.
*/
@Deprecated
@API(status = STABLE, since = "5.1")
interface CloseableResource {

Expand Down Expand Up @@ -595,9 +597,11 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* <p>See {@link #getOrComputeIfAbsent(Object, Function, Class)} for
* further details.
*
* <p>If {@code type} implements {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
* <p>If {@code type} implements {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param type the type of object to retrieve; never {@code null}
* @param <V> the key and value type
Expand All @@ -606,6 +610,7 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* @see #getOrComputeIfAbsent(Object, Function)
* @see #getOrComputeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*/
@API(status = STABLE, since = "5.1")
default <V> V getOrComputeIfAbsent(Class<V> type) {
Expand All @@ -625,9 +630,11 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* <p>For greater type safety, consider using
* {@link #getOrComputeIfAbsent(Object, Function, Class)} instead.
*
* <p>If the created value is an instance of {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
* <p>If the created value is an instance of {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param key the key; never {@code null}
* @param defaultCreator the function called with the supplied {@code key}
Expand All @@ -638,6 +645,7 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* @see #getOrComputeIfAbsent(Class)
* @see #getOrComputeIfAbsent(Object, Function, Class)
* @see CloseableResource
* @see AutoCloseable
*/
<K, V> Object getOrComputeIfAbsent(K key, Function<K, V> defaultCreator);

Expand All @@ -652,9 +660,11 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* a new value will be computed by the {@code defaultCreator} (given
* the {@code key} as input), stored, and returned.
*
* <p>If {@code requiredType} implements {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
* <p>If {@code requiredType} implements {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param key the key; never {@code null}
* @param defaultCreator the function called with the supplied {@code key}
Expand All @@ -666,6 +676,7 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* @see #getOrComputeIfAbsent(Class)
* @see #getOrComputeIfAbsent(Object, Function)
* @see CloseableResource
* @see AutoCloseable
*/
<K, V> V getOrComputeIfAbsent(K key, Function<K, V> defaultCreator, Class<V> requiredType);

Expand All @@ -676,23 +687,26 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* ExtensionContexts} for the store's {@code Namespace} unless they
* overwrite it.
*
* <p>If the {@code value} is an instance of {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
* <p>If the {@code value} is an instance of {@link CloseableResource} or
* {@link AutoCloseable} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then the {@code close()}
* method will be invoked on the stored object when the store is closed.
*
* @param key the key under which the value should be stored; never
* {@code null}
* @param value the value to store; may be {@code null}
* @see CloseableResource
* @see AutoCloseable
*/
void put(Object key, Object value);

/**
* Remove the value that was previously stored under the supplied {@code key}.
*
* <p>The value will only be removed in the current {@link ExtensionContext},
* not in ancestors. In addition, the {@link CloseableResource} API will not
* be honored for values that are manually removed via this method.
* not in ancestors. In addition, the {@link CloseableResource} and {@link AutoCloseable}
* API will not be honored for values that are manually removed via this method.
*
* <p>For greater type safety, consider using {@link #remove(Object, Class)}
* instead.
Expand All @@ -709,8 +723,8 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* under the supplied {@code key}.
*
* <p>The value will only be removed in the current {@link ExtensionContext},
* not in ancestors. In addition, the {@link CloseableResource} API will not
* be honored for values that are manually removed via this method.
* not in ancestors. In addition, the {@link CloseableResource} and {@link AutoCloseable}
* API will not be honored for values that are manually removed via this method.
*
* @param key the key; never {@code null}
* @param requiredType the required type of the value; never {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;

/**
* Interface for {@link Extension Extensions} that are aware and can influence
Expand Down Expand Up @@ -65,9 +64,11 @@ public interface TestInstantiationAwareExtension extends Extension {
* <li>{@link ExtensionContext#getTestMethod() getTestMethod()} is no longer
* empty, unless the {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS}
* lifecycle is used.</li>
* <li>If the callback adds a new {@link CloseableResource} to the
* {@link Store Store}, the resource is closed just after the instance is
* destroyed.</li>
* <li>If the callback adds a new {@link Store.CloseableResource} or
* {@link AutoCloseable} to the {@link Store Store} (unless the
* {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
* configuration parameter is set to {@code false}), then
* the resource is closed just after the instance is destroyed.</li>
* <li>The callbacks can now access data previously stored by
* {@link TestTemplateInvocationContext}, unless the
* {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used.</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,16 @@ public final class Constants {
@API(status = STABLE, since = "5.10")
public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;

/**
* Property name used to enable auto-closing of {@link AutoCloseable} instances
*
* <p>By default, auto-closing is enabled.
*
* @since 5.13
*/
@API(status = EXPERIMENTAL, since = "5.13")
public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = JupiterConfiguration.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME;

/**
* Property name used to set the default test execution mode: {@value}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public boolean isParallelExecutionEnabled() {
__ -> delegate.isParallelExecutionEnabled());
}

@Override
public boolean isClosingStoredAutoCloseablesEnabled() {
return (boolean) cache.computeIfAbsent(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME,
__ -> delegate.isClosingStoredAutoCloseablesEnabled());
}

@Override
public boolean isExtensionAutoDetectionEnabled() {
return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public boolean isParallelExecutionEnabled() {
return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false);
}

@Override
public boolean isClosingStoredAutoCloseablesEnabled() {
return configurationParameters.getBoolean(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME).orElse(true);
}

@Override
public boolean isExtensionAutoDetectionEnabled() {
return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ public interface JupiterConfiguration {
String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude";
String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate";
String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled";
String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled";
String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME;
String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME;
String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled";
String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME;
String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME;
String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME;
String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME;
String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME;;
String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME;
String DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;

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

boolean isParallelExecutionEnabled();

boolean isClosingStoredAutoCloseablesEnabled();

boolean isExtensionAutoDetectionEnabled();

boolean isThreadDumpOnTimeoutEnabled();
Expand Down
Loading