-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
I'm using JUnit 5.9.3 with Java 17 on Windows 10 with @TempDir. There are several ironies in this ticket. 😄
It appears that @TempDir knows how to clean up files that are set to read-only, but doesn't know how to clean up directories that are read-only.
I'm working on my own ticket, JAVA-312: Option to force deletion of read-only files for file subtrees. This is primarily to clean up Git temporary repositories; see JGit Bug 582051. I of course need to test my library to make sure it cleans things up with a "force" parameter.
These JUnit tests work:
/** @see {@link Files#deleteIfExists(Path, boolean)} */
@Test
@EnabledOnOs(value = OS.WINDOWS, disabledReason = "Working with DOS read-only files should only be done on Windows.")
void verifyDeleteIfExistsNotForceFailsOnReadOnlyFile(@TempDir final Path tempDir) throws IOException {
assumeTrue(readAttributes(tempDir, BasicFileAttributes.class) instanceof DosFileAttributes,
"We assume that on Windows the file system uses DOS attributes; otherwise this test will not work.");
final Path file = createFile(tempDir.resolve("foo.bar"));
setAttribute(file, ATTRIBUTE_DOS_READONLY, true);
assertThat(exists(file), is(true));
assertThrows(AccessDeniedException.class, () -> Files.deleteIfExists(file, false));
}
/** @see {@link Files#deleteIfExists(Path, boolean)} */
@Test
@EnabledOnOs(value = OS.WINDOWS, disabledReason = "Working with DOS read-only files should only be done on Windows.")
void verifyDeleteIfExistsForceDeletesReadOnlyFile(@TempDir final Path tempDir) throws IOException {
assumeTrue(readAttributes(tempDir, BasicFileAttributes.class) instanceof DosFileAttributes,
"We assume that on Windows the file system uses DOS attributes; otherwise this test will not work.");
final Path file = createFile(tempDir.resolve("foo.bar"));
setAttribute(file, ATTRIBUTE_DOS_READONLY, true);
assertThat(exists(file), is(true));
assertThat(Files.deleteIfExists(file, true), is(true));
assertThat(exists(file), is(false));
}Basically the first test verifies that without the "force" parameter, my deletion method will fail if a file is set to read-only. Note also that this results in a file being set to read-only at the end of the test, but JUnit cleans it up just fine.
Now I do the same test for a read-only directory:
/** @see {@link Files#deleteIfExists(Path, boolean)} */
@Test
@EnabledOnOs(value = OS.WINDOWS, disabledReason = "Working with DOS read-only files should only be done on Windows.")
void verifyDeleteIfExistsNotForceFailsOnReadOnlyDirectory(@TempDir final Path tempDir) throws IOException {
assumeTrue(readAttributes(tempDir, BasicFileAttributes.class) instanceof DosFileAttributes,
"We assume that on Windows the file system uses DOS attributes; otherwise this test will not work.");
final Path directory = createDirectory(tempDir.resolve("dir"));
setAttribute(directory, ATTRIBUTE_DOS_READONLY, true);
assertThat(exists(directory), is(true));
assertThrows(AccessDeniedException.class, () -> Files.deleteIfExists(directory, false));
}
/** @see {@link Files#deleteIfExists(Path, boolean)} */
@Test
@EnabledOnOs(value = OS.WINDOWS, disabledReason = "Working with DOS read-only files should only be done on Windows.")
void verifyDeleteIfExistsForceDeletesReadOnlyDirectory(@TempDir final Path tempDir) throws IOException {
assumeTrue(readAttributes(tempDir, BasicFileAttributes.class) instanceof DosFileAttributes,
"We assume that on Windows the file system uses DOS attributes; otherwise this test will not work.");
final Path directory = createDirectory(tempDir.resolve("dir"));
setAttribute(directory, ATTRIBUTE_DOS_READONLY, true);
assertThat(exists(directory), is(true));
assertThat(Files.deleteIfExists(directory, true), is(true));
assertThat(exists(directory), is(false));
}Same as before, except in the first method, verifyDeleteIfExistsNotForceFailsOnReadOnlyDirectory(…), JUnit itself fails when cleaning things up:
Stack Trace
java.io.IOException: Failed to delete temp directory C:\Users\user\AppData\Local\Temp\junit…. The following paths could not be deleted (see suppressed exceptions for details): <root>, dir
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath.createIOExceptionWithAttachedFailures(TempDirectory.java:351)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath.close(TempDirectory.java:252)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$closeAllStoredCloseableValues$3(ExtensionValuesStore.java:68)
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.ArrayList.forEach(ArrayList.java:1540)
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.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)
at org.junit.jupiter.engine.execution.ExtensionValuesStore.closeAllStoredCloseableValues(ExtensionValuesStore.java:68)
at org.junit.jupiter.engine.descriptor.AbstractExtensionContext.close(AbstractExtensionContext.java:80)
at org.junit.jupiter.engine.execution.JupiterEngineExecutionContext.close(JupiterEngineExecutionContext.java:53)
at org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.cleanUp(JupiterTestDescriptor.java:222)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$cleanUp$1(TestMethodTestDescriptor.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.cleanUp(TestMethodTestDescriptor.java:155)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.cleanUp(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$cleanUp$10(NodeTestTask.java:167)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.cleanUp(NodeTestTask.java:167)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:98)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:95)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Suppressed: java.nio.file.DirectoryNotEmptyException: C:\Users\user\AppData\Local\Temp\junit…
at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:267)
at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
at java.base/java.nio.file.Files.delete(Files.java:1141)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.deleteAndContinue(TempDirectory.java:294)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.postVisitDirectory(TempDirectory.java:289)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.postVisitDirectory(TempDirectory.java:265)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2742)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2796)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath.deleteAllFilesAndDirectories(TempDirectory.java:265)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath.close(TempDirectory.java:250)
... 64 more
Suppressed: java.nio.file.AccessDeniedException: C:\Users\user\AppData\Local\Temp\junit…\dir
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:89)
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:270)
at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
at java.base/java.nio.file.Files.delete(Files.java:1141)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.deleteAndContinue(TempDirectory.java:294)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.postVisitDirectory(TempDirectory.java:289)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.postVisitDirectory(TempDirectory.java:265)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2742)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2796)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.resetPermissionsAndTryToDeleteAgain(TempDirectory.java:315)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.deleteAndContinue(TempDirectory.java:304)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.postVisitDirectory(TempDirectory.java:289)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath$1.postVisitDirectory(TempDirectory.java:265)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2742)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2796)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath.deleteAllFilesAndDirectories(TempDirectory.java:265)
at org.junit.jupiter.engine.extension.TempDirectory$CloseablePath.close(TempDirectory.java:250)
... 64 more
The workaround is to add my own cleanup code:
/** @see {@link Files#deleteIfExists(Path, boolean)} */
@Test
@EnabledOnOs(value = OS.WINDOWS, disabledReason = "Working with DOS read-only files should only be done on Windows.")
void verifyDeleteIfExistsNotForceFailsOnReadOnlyDirectory(@TempDir final Path tempDir) throws IOException {
assumeTrue(readAttributes(tempDir, BasicFileAttributes.class) instanceof DosFileAttributes,
"We assume that on Windows the file system uses DOS attributes; otherwise this test will not work.");
final Path directory = createDirectory(tempDir.resolve("dir"));
setAttribute(directory, ATTRIBUTE_DOS_READONLY, true);
try {
assertThat(exists(directory), is(true));
assertThrows(AccessDeniedException.class, () -> Files.deleteIfExists(directory, false));
} finally {
setAttribute(directory, ATTRIBUTE_DOS_READONLY, false); //always remove the read-only attribute to allow JUnit to clean up
}
}But that to some extent defeats the purpose of @TempDir. As JUnit cleans up read-only files on Windows, I'm sure it was just an oversight that it doesn't clean up read-only directories as well.