Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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 @@ -53,6 +53,8 @@ to start reporting discovery issues.
`static`)
- Cyclic dependencies between `@Suite` classes

* Introduce feature flag for auto-closing `AutoCloseable` in Jupiter's ExtensionContext.Store


[[release-notes-5.13.0-M3-junit-jupiter]]
=== JUnit Jupiter
Expand Down
22 changes: 22 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,28 @@ include::{testDir}/example/extensions/HttpServerExtension.java[tags=user_guide]
include::{testDir}/example/HttpServerDemo.java[tags=user_guide]
----

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

Starting with JUnit Jupiter 5.13, the framework automatically closes resources stored in the
`ExtensionContext.Store` that implement `AutoCloseable` when auto-close is enabled (which is the default behavior).
Prior to 5.13, 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 @@ -596,7 +598,7 @@ default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
* further details.
*
* <p>If {@code type} implements {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* or {@link AutoCloseable} 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}
Expand All @@ -606,6 +608,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 @@ -626,7 +629,7 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* {@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
* or {@link AutoCloseable} the {@code close()} method will be invoked on the stored object when
* the store is closed.
*
* @param key the key; never {@code null}
Expand All @@ -638,6 +641,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 @@ -653,8 +657,8 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* 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.
* or {@link AutoCloseable} 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 +670,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 @@ -677,22 +682,23 @@ default <V> V getOrComputeIfAbsent(Class<V> type) {
* 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.
* or {@link AutoCloseable} 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 +715,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,9 @@ 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}, 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 AUTOCLOSE_ENABLED_PROPERTY_NAME = JupiterConfiguration.AUTOCLOSE_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,11 @@ public boolean isParallelExecutionEnabled() {
__ -> delegate.isParallelExecutionEnabled());
}

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

@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 isAutoCloseEnabled() {
return configurationParameters.getBoolean(AUTOCLOSE_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 AUTOCLOSE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autoclose.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 isAutoCloseEnabled();

boolean isExtensionAutoDetectionEnabled();

boolean isThreadDumpOnTimeoutEnabled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.junit.jupiter.api.extension.ExecutableInvoker;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
import org.junit.jupiter.api.extension.MediaType;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.junit.jupiter.api.parallel.ExecutionMode;
Expand All @@ -36,6 +35,8 @@
import org.junit.jupiter.engine.extension.ExtensionContextInternal;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.UnrecoverableExceptions;
import org.junit.platform.engine.EngineExecutionListener;
Expand All @@ -51,22 +52,18 @@
*/
abstract class AbstractExtensionContext<T extends TestDescriptor> implements ExtensionContextInternal, AutoCloseable {

private static final NamespacedHierarchicalStore.CloseAction<org.junit.platform.engine.support.store.Namespace> CLOSE_RESOURCES = (
__, ___, value) -> {
if (value instanceof CloseableResource) {
((CloseableResource) value).close();
}
};
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExtensionContext.class);

private final ExtensionContext parent;
private final EngineExecutionListener engineExecutionListener;
private final T testDescriptor;
private final Set<String> tags;
private final JupiterConfiguration configuration;
private final NamespacedHierarchicalStore<org.junit.platform.engine.support.store.Namespace> valuesStore;
private final ExecutableInvoker executableInvoker;
private final ExtensionRegistry extensionRegistry;
private final LauncherStoreFacade launcherStoreFacade;
private final NamespacedHierarchicalStore.CloseAction<org.junit.platform.engine.support.store.Namespace> closeResources;
private final NamespacedHierarchicalStore<org.junit.platform.engine.support.store.Namespace> valuesStore;

AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor,
JupiterConfiguration configuration, ExtensionRegistry extensionRegistry,
Expand All @@ -80,7 +77,6 @@ abstract class AbstractExtensionContext<T extends TestDescriptor> implements Ext
this.engineExecutionListener = engineExecutionListener;
this.testDescriptor = testDescriptor;
this.configuration = configuration;
this.valuesStore = createStore(parent, launcherStoreFacade);
this.extensionRegistry = extensionRegistry;
this.launcherStoreFacade = launcherStoreFacade;

Expand All @@ -89,9 +85,32 @@ abstract class AbstractExtensionContext<T extends TestDescriptor> implements Ext
.map(TestTag::getName)
.collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet));
// @formatter:on

this.closeResources = createCloseResources();
this.valuesStore = createStore(parent, launcherStoreFacade);
}

@SuppressWarnings("deprecation")
private NamespacedHierarchicalStore.CloseAction<org.junit.platform.engine.support.store.Namespace> createCloseResources() {
return (__, ___, value) -> {
boolean isAutoCloseEnabled = this.configuration.isAutoCloseEnabled();

if (value instanceof AutoCloseable && isAutoCloseEnabled) {
((AutoCloseable) value).close();
return;
}

if (value instanceof Store.CloseableResource) {
if (isAutoCloseEnabled) {
LOGGER.warn(
() -> "Type implements CloseableResource but not AutoCloseable: " + value.getClass().getName());
}
((Store.CloseableResource) value).close();
}
};
}

private static NamespacedHierarchicalStore<org.junit.platform.engine.support.store.Namespace> createStore(
private NamespacedHierarchicalStore<org.junit.platform.engine.support.store.Namespace> createStore(
ExtensionContext parent, LauncherStoreFacade launcherStoreFacade) {
NamespacedHierarchicalStore<org.junit.platform.engine.support.store.Namespace> parentStore;
if (parent == null) {
Expand All @@ -100,7 +119,7 @@ private static NamespacedHierarchicalStore<org.junit.platform.engine.support.sto
else {
parentStore = ((AbstractExtensionContext<?>) parent).valuesStore;
}
return new NamespacedHierarchicalStore<>(parentStore, CLOSE_RESOURCES);
return new NamespacedHierarchicalStore<>(parentStore, this.closeResources);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ExtensionContextException;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.support.store.Namespace;
import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
Expand All @@ -29,6 +31,8 @@
@API(status = INTERNAL, since = "5.0")
public class NamespaceAwareStore implements Store {

private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceAwareStore.class);

private final NamespacedHierarchicalStore<Namespace> valuesStore;
private final Namespace namespace;

Expand Down
Loading