Skip to content

Support ignoring errors during @TempDir cleanup #4567

@strangelookingnerd

Description

@strangelookingnerd
Contributor

Overview

There are various circumstances that can cause the cleanup of a temporary directory to fail. Some of these errors may indicate open or leaking file handles, issues in the implementation being tested, or problems in the test itself. Others may occur only occasionally on slow filesystems, race-conditions in asynchronous process and so on.

Currently, a test encountering such a problem will fail with something like:

org.junit.platform.commons.JUnitException: Failed to close extension context
org.junit.platform.commons.JUnitException: Failed to close extension context
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.io.IOException: Failed to delete temp directory C:\Windows\TEMP\junit-6301891027881835454. The following paths could not be deleted (see suppressed exceptions for details): <root>, ws
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:395)
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	... 2 more
	Suppressed: java.nio.file.DirectoryNotEmptyException: C:\Windows\TEMP\junit-6301891027881835454
		at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:272)
		at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
		at java.base/java.nio.file.Files.delete(Files.java:1152)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2828)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2882)
		... 13 more
	Suppressed: java.nio.file.DirectoryNotEmptyException: C:\Windows\TEMP\junit-6301891027881835454\ws
		at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:272)
		at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
		at java.base/java.nio.file.Files.delete(Files.java:1152)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2828)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2882)
		... 13 more

In my opinion, this is unfortunate and could also be considered a regression compared to JUnit 4. In JUnit 4, it was possible to work around these kinds of issues using TemporaryFolder#assureDeletion, which allowed ignoring failed deletion of the temporary directory.

As of now, there is no way to bypass these issues in JUnit 5 unless you define @TempDir(cleanup = CleanupMode.NEVER) or avoid using @TempDir altogether - both of which seem undesirable.

See #4549 for a working proposal.

Activity

ascopes

ascopes commented on May 24, 2025

@ascopes

I can reproduce the same issue(s). It seems in my case to be related to the fact Windows does not appear to always immediately close file handles after Java requests them to be closed. I cannot reproduce on my Linux or MacOS builds, just Windows, and this is very spurious.

[INFO] Stack trace
[INFO] org.junit.platform.commons.JUnitException: Failed to close extension context
	at java.base/java.util.Optional.ifPresent(Optional.java:183)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
	at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:274)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
	at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:274)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1[541](https://github.com/ascopes/protobuf-maven-plugin/actions/runs/15225268219/job/42826508202?pr=674#step:6:542))
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
Caused by: java.io.IOException: Failed to delete temp directory C:\Users\RUNNER~1\AppData\Local\Temp\junit-1260862847092294509. The following paths could not be deleted (see suppressed exceptions for details): <root>, inputs, inputs\archive.jar
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:395)
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
	... 27 more
	Suppressed: java.nio.file.DirectoryNotEmptyException: C:\Users\RUNNER~1\AppData\Local\Temp\junit-1260862847092294509
		at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:271)
		at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
		at java.base/java.nio.file.Files.delete(Files.java:1142)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2743)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2797)
		... 38 more
	Suppressed: java.nio.file.DirectoryNotEmptyException: C:\Users\RUNNER~1\AppData\Local\Temp\junit-1260862847092294509\inputs
		at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:271)
		at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
		at java.base/java.nio.file.Files.delete(Files.java:1142)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2743)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2797)
		... 38 more
	Suppressed: java.nio.file.FileSystemException: C:\Users\RUNNER~1\AppData\Local\Temp\junit-1260862847092294509\inputs\archive.jar: The process cannot access the file because it is being used by another process.

		at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
		at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
		at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
		at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:274)
		at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
		at java.base/java.nio.file.Files.delete(Files.java:1142)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2725)
		at java.base/java.nio.file.Files.walkFileTree(Files.java:2797)
		... 38 more
		Suppressed: java.nio.file.FileSystemException: C:\Users\RUNNER~1\AppData\Local\Temp\junit-1260862847092294509\inputs\archive.jar: The process cannot access the file because it is being used by another process.

			... 46 more

[ERROR] Errors: 
[ERROR]   UriResourceFetcherTest.nestedFileUrisAreNotResolvedWhenTheyDoNotExist(String, Path)[1] � JUnit Failed to close extension context
[ERROR]   UriResourceFetcherTest.nestedFileUrisAreNotResolvedWhenTheyDoNotExist(String, Path)[2] � JUnit Failed to close extension context
[ERROR]   UriResourceFetcherTest.nestedFileUrisAreResolvedWhenTheyExist(String, boolean, Path)[1] � JUnit Failed to close extension context
[ERROR]   UriResourceFetcherTest.nestedFileUrisAreResolvedWhenTheyExist(String, boolean, Path)[2] � JUnit Failed to close extension context
[ERROR]   UriResourceFetcherTest.nestedFileUrisAreResolvedWhenTheyExist(String, boolean, Path)[3] � JUnit Failed to close extension context
[ERROR]   UriResourceFetcherTest.nestedFileUrisAreResolvedWhenTheyExist(String, boolean, Path)[4] � JUnit Failed to close extension context

Pipeline reproducing this: https://github.com/ascopes/protobuf-maven-plugin/actions/runs/15225268219/job/42826508202?pr=674

Test case reproducing this: https://github.com/ascopes/protobuf-maven-plugin/blob/43d19dbe0c6cb98bfb4477d5672b5f159a211565/protobuf-maven-plugin/src/test/java/io/github/ascopes/protobufmavenplugin/fs/UriResourceFetcherTest.java#L135

In my case, I have observed that backing off for a few dozen milliseconds when this occurs and retrying often fixes the issue, so perhaps retry handling is needed in this case with a backoff in the case that the resource is still in use? The example above was from using @ParameterizedTest but I have seen it on regular @Test cases as well... and it is always on Windows.

For now, ignoring the errors would be much appreciated, as it is resulting in my builds being unstable and I'm having to repeatedly rerun builds that take several minutes to complete.

ascopes

ascopes commented on May 24, 2025

@ascopes

Is it possible to label this as a bug in the meantime?

sormuras

sormuras commented on May 24, 2025

@sormuras
Member

Is it possible to label this as a bug in the meantime?

No, it's not a bug of JUnit per-se.

Non-deletable files (on Windows) usually hint at a resource leak or a non-closed file descriptor in the code under test or the test code itself. Make sure that every file, directory stream, ..., any IO-related resource is closed after usage. IIRC, JUnit tries to release already closed file handles by calling System,.gc() at some point in the cleanup process, in order to help with some cases on Windows.

22 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sbrannen@marcphilipp@sormuras@strangelookingnerd@ascopes

        Issue actions

          Support ignoring errors during `@TempDir` cleanup · Issue #4567 · junit-team/junit-framework