Repository: junit-team/junit5

Issue #3910: `@TempDir` with `File` annotated elements should fail fast if temp directory file system is not default
Created: 2024-07-31T11:33:58Z
TempDirFactory implementations based on non-default file systems like jimfs and memoryfilesystem cannot work with @TempDir annotated elements of type File.
See also the Path::toFile Javadoc:

Throws:
UnsupportedOperationException - if this Path is not associated with the default provider

Right now, a test like the example in the user guide, but with tempDir of type File:
@Test
void test(@TempDir(factory = JimfsTempDirFactory.class) File tempDir) {
    // perform test
}

static class JimfsTempDirFactory implements TempDirFactory {

    private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());

    @Override
    public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException {
        return Files.createTempDirectory(fileSystem.getPath("/"), "junit-");
    }

    @Override
    public void close() throws IOException {
        fileSystem.close();
    }

}
fails with:
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [java.io.File arg0] in method [void io.github.scordio.jimfs.junit.jupiter.Tests.test(java.io.File)]

	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.UnsupportedOperationException
	at com.google.common.jimfs.JimfsPath.toFile(JimfsPath.java:386)
	... 2 more

I would like to improve the user experience, clearly stating that File annotated elements are not supported.
As of today, the factory doesn't have easy access to the type of the annotated element.
In my extension for Jimfs, I've implemented a workaround:
https://github.com/scordio/jimfs-junit-jupiter/blob/ac6f085a89368823e7f515f335eb44f21d981447/src/main/java/io/github/scordio/jimfs/junit/jupiter/JimfsTempDirFactory.java#L86-L107
Deliverables
I see two possible directions:

JUnit owns the error handling and asks the factory if File annotated elements are supported

This could be done with a new default method in TempDirFactory, e.g., boolean supportsFile().


The error handling happens in the factory

AnnotatedElementContext should expose the element type somehow so that the factory can build up the error handling around it without having to cast the result of getAnnotatedElement(). For example, a new Class<?> getAnnotatedElementType() method could be added to AnnotatedElementContext.



My preference goes to option 1 to reduce duplication in each TempDirFactory implementation.
I'm happy to contribute once a solution is agreed.

Relates to:

scordio/jimfs-junit-jupiter@99d946e
marschall/memoryfilesystem-junit-provider#2

CC @marschall

Duplicate issue #3910: `@TempDir` with `File` annotated elements should fail fast if temp directory file system is not default
Created: 2024-07-31T11:33:58Z
TempDirFactory implementations based on non-default file systems like jimfs and memoryfilesystem cannot work with @TempDir annotated elements of type File.
See also the Path::toFile Javadoc:

Throws:
UnsupportedOperationException - if this Path is not associated with the default provider

Right now, a test like the example in the user guide, but with tempDir of type File:
@Test
void test(@TempDir(factory = JimfsTempDirFactory.class) File tempDir) {
    // perform test
}

static class JimfsTempDirFactory implements TempDirFactory {

    private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());

    @Override
    public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException {
        return Files.createTempDirectory(fileSystem.getPath("/"), "junit-");
    }

    @Override
    public void close() throws IOException {
        fileSystem.close();
    }

}
fails with:
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [java.io.File arg0] in method [void io.github.scordio.jimfs.junit.jupiter.Tests.test(java.io.File)]

	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.UnsupportedOperationException
	at com.google.common.jimfs.JimfsPath.toFile(JimfsPath.java:386)
	... 2 more

I would like to improve the user experience, clearly stating that File annotated elements are not supported.
As of today, the factory doesn't have easy access to the type of the annotated element.
In my extension for Jimfs, I've implemented a workaround:
https://github.com/scordio/jimfs-junit-jupiter/blob/ac6f085a89368823e7f515f335eb44f21d981447/src/main/java/io/github/scordio/jimfs/junit/jupiter/JimfsTempDirFactory.java#L86-L107
Deliverables
I see two possible directions:

JUnit owns the error handling and asks the factory if File annotated elements are supported

This could be done with a new default method in TempDirFactory, e.g., boolean supportsFile().


The error handling happens in the factory

AnnotatedElementContext should expose the element type somehow so that the factory can build up the error handling around it without having to cast the result of getAnnotatedElement(). For example, a new Class<?> getAnnotatedElementType() method could be added to AnnotatedElementContext.



My preference goes to option 1 to reduce duplication in each TempDirFactory implementation.
I'm happy to contribute once a solution is agreed.

Relates to:

scordio/jimfs-junit-jupiter@99d946e
marschall/memoryfilesystem-junit-provider#2

CC @marschall

Closing comment (2024-08-05T14:18:05Z): I think the validation should occur before creating the temporary directory, if possible.

Theoretically, the directory is created as soon as TempDirFactory.createTempDirectory returns. To perform the validation before the directory is created, we'd have to introduce some new API for TempDirFactory (related to #3910 (comment)). Or do you have something else in mind?

You're right. Let's not introduce a new API at this time.

Indeed, that's already a gap caused by #3901, which I addressed separately in #3911.

Therefore, I think we should not throw an exception from the CloseablePath constructor but validate in the getPathOrFile method.

If the CloseablePath constructor throws an exception, it would be handled similarly to #3911, i.e., with a try/catch that ensures the underlying factory is also closed in case a PreconditionViolationException is thrown by the temp directory validation. This way, no CloseablePath instance would be created, and no close handling would need to be in the getPathOrFile caller.

Shouldn't we also delete the Path returned from TempDirFactory.createTempDirectory if it did not throw an exception before closing the factory? In most cases, the latter should take care of the former but that's not necessarily the case.

Of course, we can push this logic up to getPathOrFile and that will require explicit closure of the CloseablePath, as you mentioned.
I think both directions can work so happy to follow your preference. I just wanted to bring both options to the table 🙂

I'll leave the decision where to put the check up to you.