Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions documentation/antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ asciidoc:
JRE: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE]'
# Jupiter I/O
TempDir: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir]'
TempDirDeletionStrategy: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.html[TempDirDeletionStrategy]'
TempDirDeletionStrategyIgnoreFailures: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.IgnoreFailures.html[IgnoreFailures]'
TempDirDeletionStrategyStandard: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.Standard.html[Standard]'
TempDirFactory: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirFactory.html[TempDirFactory]'
# Jupiter Params
params-provider-package: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html[org.junit.jupiter.params.provider]'
AfterParameterizedClassInvocation: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/AfterParameterizedClassInvocation.html[@AfterParameterizedClassInvocation]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ xref:running-tests/configuration-parameters.adoc[configuration parameter] to ove
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_cleanup_mode]
----

[[TempDirFactory]]
=== Factories

`@TempDir` supports the programmatic creation of temporary directories via the optional
`factory` attribute. This is typically used to gain control over the temporary directory
creation, like defining the parent directory or the file system that should be used.

Factories can be created by implementing `TempDirFactory`. Implementations must provide a
Factories can be created by implementing `{TempDirFactory}`. Implementations must provide a
no-args constructor and should not make any assumptions regarding when and how many times
they are instantiated, but they can assume that their `createTempDirectory(...)` and
`close()` methods will both be called once per instance, in this order, and from the same
Expand Down Expand Up @@ -118,20 +121,64 @@ parameter of the `createTempDirectory(...)` method.

You can use the `junit.jupiter.tempdir.factory.default`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the
fully qualified class name of the `TempDirFactory` you would like to use by default. Just
fully qualified class name of the `{TempDirFactory}` you would like to use by default. Just
like for factories configured via the `factory` attribute of the `@TempDir` annotation,
the supplied class has to implement the `TempDirFactory` interface. The default factory
the supplied class has to implement the `{TempDirFactory}` interface. The default factory
will be used for all `@TempDir` annotations unless the `factory` attribute of the
annotation specifies a different factory.

In summary, the factory for a temporary directory is determined according to the following
precedence rules:

1. The `factory` attribute of the `@TempDir` annotation, if present
2. The default `TempDirFactory` configured via the configuration
2. The default `{TempDirFactory}` configured via the configuration
parameter, if present
3. Otherwise, `org.junit.jupiter.api.io.TempDirFactory$Standard` will be used.

[[TempDirDeletionStrategy]]
=== Deletion

`@TempDir` supports the programmatic deletion of temporary directories via the optional
`deletionStrategy` attribute. This is typically used to gain control over what happens
when deletion of a file or directory fails.

Deletion strategies can be created by implementing `{TempDirDeletionStrategy}`.
Implementations must provide a no-args constructor.

Jupiter ships with two built-in deletion strategies:

* `{TempDirDeletionStrategyStandard}` (the default): attempts to delete all files and
directories recursively, retrying with permission resets on failure. Paths that still
cannot be deleted are scheduled for deletion on JVM exit, if possible. Additionally,
the test is failed.
* `{TempDirDeletionStrategyIgnoreFailures}`: delegates to `{TempDirDeletionStrategyStandard}`
but suppresses deletion failures by logging a warning instead of failing the test.

The following example uses `{TempDirDeletionStrategyIgnoreFailures}` so that any deletion
failures are only logged.

[source,java,indent=0]
.A test class with a temporary directory that ignores deletion failures
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_deletion_strategy]
----

You can use the `junit.jupiter.tempdir.deletion.strategy.default`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the
fully qualified class name of the `{TempDirDeletionStrategy}` you would like to use by
default. Just like for strategies configured via the `deletionStrategy` attribute of the
`@TempDir` annotation, the supplied class has to implement the `{TempDirDeletionStrategy}`
interface. The default strategy will be used for all `@TempDir` annotations unless the
`deletionStrategy` attribute of the annotation specifies a different strategy.

In summary, the deletion strategy for a temporary directory is determined according to the
following precedence rules:

1. The `deletionStrategy` attribute of the `@TempDir` annotation, if present
2. The default `{TempDirDeletionStrategy}` configured via the configuration parameter,
if present
3. Otherwise, `org.junit.jupiter.api.io.TempDirDeletionStrategy$Standard` will be used.

[[AutoClose]]
== The @AutoClose Extension

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ repository on GitHub.
the xref:writing-tests/built-in-extensions.adoc#system-properties[User Guide]. For
details regarding implementation differences between JUnit Pioneer and Jupiter, see the
corresponding https://github.com/junit-team/junit-framework/pull/5258[pull request].
* `@TempDir` now allows configuring a deletion strategy for temporary directories. This is
typically used to gain control over what happens when deletion of a file or directory
fails (see
xref:writing-tests/built-in-extensions.adoc#TempDirDeletionStrategy[User Guide] for
details).
* `Arguments` may now be created from instances of `Iterable` via the following new
methods which make it easier to dynamically build arguments from collections when using
`@ParameterizedTest`.
Expand Down
13 changes: 13 additions & 0 deletions documentation/src/test/java/example/TempDirectoryDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.junit.jupiter.api.extension.AnnotatedElementContext;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.io.TempDirDeletionStrategy;
import org.junit.jupiter.api.io.TempDirFactory;

@SuppressWarnings("NewClassNamingConvention")
Expand Down Expand Up @@ -151,6 +152,18 @@ public void close() throws IOException {
}
// end::user_guide_factory_jimfs[]

static
// tag::user_guide_deletion_strategy[]
class DeletionStrategyDemo {

@Test
void test(@TempDir(deletionStrategy = TempDirDeletionStrategy.IgnoreFailures.class) Path tempDir) {
// perform test
}

}
// end::user_guide_deletion_strategy[]

// tag::user_guide_composed_annotation[]
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@

package org.junit.jupiter.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.PreInterruptCallback;
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.parallel.Execution;

Expand Down Expand Up @@ -393,13 +395,32 @@ public final class Constants {
public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME;

/**
* Property name used to set the default factory for temporary directories created via
* the {@link TempDir @TempDir} annotation: {@value}
* Property name used to set the default factory for temporary directories
* created via the {@link TempDir @TempDir} annotation: {@value}
*
* @see TempDir#DEFAULT_FACTORY_PROPERTY_NAME
*/
public static final String DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME = TempDir.DEFAULT_FACTORY_PROPERTY_NAME;

/**
* Property name used to configure the default {@link CleanupMode} for
* temporary directories created via the {@link TempDir @TempDir}
* annotation: {@value}
*
* @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME
*/
public static final String DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME = TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME;

/**
* Property name used to set the default deletion strategy class name for
* temporary directories created via the {@link TempDir @TempDir}
* annotation: {@value}
*
* @see TempDir#DEFAULT_DELETION_STRATEGY_PROPERTY_NAME
*/
@API(status = EXPERIMENTAL, since = "6.1")
public static final String DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME = TempDir.DEFAULT_DELETION_STRATEGY_PROPERTY_NAME;

/**
* Property name used to set the default extension context scope for
* extensions that participate in test instantiation: {@value}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.api.io;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionException;
import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionFailure;
import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionResult;
import org.junit.platform.commons.util.Preconditions;

record DefaultDeletionResult(Path rootDir, List<DeletionFailure> failures) implements DeletionResult {

DefaultDeletionResult(Path rootDir, List<DeletionFailure> failures) {
this.rootDir = rootDir;
this.failures = List.copyOf(failures);
}

@Override
public Optional<DeletionException> toException() {
if (isSuccessful()) {
return Optional.empty();
}
var joinedPaths = failures().stream() //
.map(DeletionFailure::path) //
.sorted() //
.distinct() //
.map(path -> relativizeSafely(rootDir(), path).toString()) //
.map(path -> path.isEmpty() ? "<root>" : path) //
.collect(joining(", "));
var exception = new DeletionException("Failed to delete temp directory " + rootDir().toAbsolutePath()
+ ". The following paths could not be deleted (see suppressed exceptions for details): " + joinedPaths);
failures().stream() //
.sorted(comparing(DeletionFailure::path)) //
.map(DeletionFailure::cause) //
.forEach(exception::addSuppressed);
return Optional.of(exception);
}

private static Path relativizeSafely(Path rootDir, Path path) {
try {
return rootDir.relativize(path);
}
catch (IllegalArgumentException e) {
return path;
}
}

static final class Builder implements DeletionResult.Builder {

private final Path rootDir;
private final List<DeletionFailure> failures = new ArrayList<>();

Builder(Path rootDir) {
this.rootDir = rootDir;
}

@Override
public Builder addFailure(Path path, Exception cause) {
Preconditions.notNull(path, "path must not be null");
Preconditions.notNull(cause, "cause must not be null");
failures.add(new DefaultDeletionFailure(path, cause));
return this;
}

@Override
public DefaultDeletionResult build() {
return new DefaultDeletionResult(rootDir, failures);
}

}

record DefaultDeletionFailure(Path path, Exception cause) implements DeletionFailure {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.jupiter.api.io;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.MAINTAINED;
import static org.apiguardian.api.API.Status.STABLE;

Expand Down Expand Up @@ -127,8 +128,10 @@
Class<? extends TempDirFactory> factory() default TempDirFactory.class;

/**
* The name of the configuration parameter that is used to configure the
* default {@link CleanupMode}.
* Property name used to configure the default {@link CleanupMode}: {@value}
*
* <p>Supported values include names of enum constants defined in
* {@link CleanupMode}, ignoring case.
*
* <p>If this configuration parameter is not set, {@link CleanupMode#ALWAYS}
* will be used as the default.
Expand All @@ -139,11 +142,47 @@
String DEFAULT_CLEANUP_MODE_PROPERTY_NAME = "junit.jupiter.tempdir.cleanup.mode.default";

/**
* How the temporary directory gets cleaned up after the test completes.
* In which cases the temporary directory gets cleaned up after the test completes.
*
* @since 5.9
*/
@API(status = STABLE, since = "5.11")
CleanupMode cleanup() default CleanupMode.DEFAULT;

/**
* Property name used to set the default deletion strategy class name:
* {@value}
*
* <h4>Supported Values</h4>
*
* <p>Supported values include fully qualified class names for types that
* implement {@link TempDirDeletionStrategy}.
*
* <p>If not specified, the default is {@link TempDirDeletionStrategy.Standard}.
*
* @since 6.1
*/
@API(status = EXPERIMENTAL, since = "6.1")
String DEFAULT_DELETION_STRATEGY_PROPERTY_NAME = "junit.jupiter.tempdir.deletion.strategy.default";

/**
* Deletion strategy for the temporary directory.
*
* <p>Defaults to {@link TempDirDeletionStrategy.Standard}.
*
* <p>As an alternative to setting this attribute, a global
* {@link TempDirDeletionStrategy} can be configured for the entire test
* suite via the {@value #DEFAULT_DELETION_STRATEGY_PROPERTY_NAME}
* configuration parameter. See the User Guide for details. Note, however,
* that a {@code @TempDir} declaration with a custom
* {@code deletionStrategy} always overrides a global
* {@code TempDirDeletionStrategy}.
*
* @return the type of {@code TempDirDeletionStrategy} to use
* @since 6.1
* @see TempDirDeletionStrategy
*/
@API(status = EXPERIMENTAL, since = "6.1")
Class<? extends TempDirDeletionStrategy> deletionStrategy() default TempDirDeletionStrategy.class;

}
Loading
Loading