Skip to content

Commit 3781a67

Browse files
authored
Add factory SPI for @TempDir (#2958)
This adds a factory SPI to `@TempDir`, allowing to define how the temporary directory is created. This can be used for custom file systems, but it may also used for customizing how and where the directory is created for the default file system. The intended usage is to extend `TempDirFactory` and provide the class to the new `factory` attribute of `@TempDir`. Resolves #2088. Resolves #2400.
1 parent 5cac4a3 commit 3781a67

File tree

14 files changed

+461
-62
lines changed

14 files changed

+461
-62
lines changed

documentation/documentation.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ dependencies {
5454
because("ApiReportGenerator needs it")
5555
}
5656

57+
testImplementation(libs.jimfs) {
58+
because("Jimfs is used in src/test/java")
59+
}
60+
5761
standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone)
5862
}
5963

documentation/src/docs/asciidoc/link-attributes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ endif::[]
181181
:AssertJ: https://assertj.github.io/doc/[AssertJ]
182182
:Gitter: https://gitter.im/junit-team/junit5[Gitter]
183183
:Hamcrest: https://hamcrest.org/JavaHamcrest/[Hamcrest]
184+
:Jimfs: https://google.github.io/jimfs/[Jimfs]
184185
:Log4j: https://logging.apache.org/log4j/2.x/[Log4j]
185186
:Log4j_JDK_Logging_Adapter: https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter]
186187
:Logback: https://logback.qos.ch/[Logback]

documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ default.
8282
parameter to set the maximum pool size factor.
8383
* New `junit.jupiter.execution.parallel.config.dynamic.saturate` configuration
8484
parameter to disable pool saturation.
85+
* New `TempDirFactory` SPI for customizing how the `TempDirectory` extension creates
86+
temporary directories. See the
87+
<<../user-guide/index.adoc#writing-tests-built-in-extensions-TempDirectory, User Guide>>
88+
for details.
8589

8690

8791
[[release-notes-5.10.0-M1-junit-vintage]]

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2613,5 +2613,33 @@ The default cleanup mode is `ALWAYS`. You can use the
26132613
[source,java,indent=0]
26142614
.A test class with a temporary directory that doesn't get cleaned up
26152615
----
2616-
include::{testDir}/example/TempDirCleanupModeDemo.java[tags=user_guide]
2616+
include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_cleanup_mode]
2617+
----
2618+
2619+
`@TempDir` supports custom creation of temporary directories via the optional
2620+
`factory` attribute. This allows to define how the temporary directory is created,
2621+
giving control over characteristics such as the directory name, root folder,
2622+
and underlying file system.
2623+
2624+
Factories can be created by implementing `TempDirFactory`. The default implementation
2625+
available in Jupiter delegates the directory creation to
2626+
`java.nio.file.Files::createTempDirectory`, passing `junit` as the prefix string
2627+
to be used in generating the directory's name.
2628+
2629+
The following example defines a factory that uses the test name as the directory name
2630+
prefix instead of the `junit` constant value.
2631+
2632+
[source,java,indent=0]
2633+
.A test class with a temporary directory having the test name as the directory name prefix
2634+
----
2635+
include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_factory_name_prefix]
2636+
----
2637+
2638+
It's also possible to use an in-memory file system like `{Jimfs}` for the creation of the
2639+
temporary directory. The following example shows how to do it.
2640+
2641+
[source,java,indent=0]
2642+
.A test class with a temporary directory created with the Jimfs in-memory file system
2643+
----
2644+
include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_factory_jimfs]
26172645
----

documentation/src/test/java/example/TempDirCleanupModeDemo.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

documentation/src/test/java/example/TempDirectoryDemo.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@
1313
import static java.util.Collections.singletonList;
1414
import static org.junit.jupiter.api.Assertions.assertEquals;
1515
import static org.junit.jupiter.api.Assertions.assertNotEquals;
16+
import static org.junit.jupiter.api.Assertions.assertTrue;
17+
import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS;
1618

1719
import java.io.IOException;
20+
import java.nio.file.FileSystem;
1821
import java.nio.file.Files;
1922
import java.nio.file.Path;
2023

24+
import com.google.common.jimfs.Configuration;
25+
import com.google.common.jimfs.Jimfs;
26+
2127
import example.util.ListWriter;
2228

2329
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.extension.ExtensionContext;
2431
import org.junit.jupiter.api.io.TempDir;
32+
import org.junit.jupiter.api.io.TempDirFactory;
2533

2634
class TempDirectoryDemo {
2735

@@ -73,4 +81,65 @@ void anotherTestThatUsesTheSameTempDir() {
7381
}
7482
// end::user_guide_field_injection[]
7583

84+
static
85+
// tag::user_guide_cleanup_mode[]
86+
class CleanupModeDemo {
87+
88+
@Test
89+
void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) {
90+
// perform test
91+
}
92+
93+
}
94+
// end::user_guide_cleanup_mode[]
95+
96+
static
97+
// tag::user_guide_factory_name_prefix[]
98+
class TempDirFactoryDemo {
99+
100+
@Test
101+
void factoryTest(@TempDir(factory = Factory.class) Path tempDir) {
102+
assertTrue(tempDir.getFileName().toString().startsWith("factoryTest"));
103+
}
104+
105+
static class Factory implements TempDirFactory {
106+
107+
@Override
108+
public Path createTempDirectory(ExtensionContext context) throws IOException {
109+
return Files.createTempDirectory(context.getRequiredTestMethod().getName());
110+
}
111+
112+
}
113+
114+
}
115+
// end::user_guide_factory_name_prefix[]
116+
117+
static
118+
// tag::user_guide_factory_jimfs[]
119+
class InMemoryTempDirDemo {
120+
121+
@Test
122+
void test(@TempDir(factory = JimfsTempDirFactory.class) Path tempDir) {
123+
// perform test
124+
}
125+
126+
static class JimfsTempDirFactory implements TempDirFactory {
127+
128+
private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
129+
130+
@Override
131+
public Path createTempDirectory(ExtensionContext context) throws IOException {
132+
return Files.createTempDirectory(fileSystem.getPath("/"), "junit");
133+
}
134+
135+
@Override
136+
public void close() throws IOException {
137+
fileSystem.close();
138+
}
139+
140+
}
141+
142+
}
143+
// end::user_guide_factory_jimfs[]
144+
76145
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.11" }
3131
groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.21" }
3232
hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" }
3333
jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" }
34+
jimfs = { module = "com.google.jimfs:jimfs", version = "1.2" }
3435
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
3536
jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
3637
joox = { module = "org.jooq:joox", version = "2.0.0" }
@@ -39,6 +40,7 @@ kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core",
3940
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
4041
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
4142
maven = { module = "org.apache.maven:apache-maven", version = "3.9.1" }
43+
memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.5.1" }
4244
mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.3.1" }
4345
opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" }
4446
openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" }

junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apiguardian.api.API;
2727
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
2828
import org.junit.jupiter.api.extension.ParameterResolutionException;
29+
import org.junit.jupiter.api.io.TempDirFactory.Standard;
2930

3031
/**
3132
* {@code @TempDir} can be used to annotate a field in a test class or a
@@ -96,9 +97,22 @@
9697
@API(status = STABLE, since = "5.10")
9798
public @interface TempDir {
9899

100+
/**
101+
* Factory for the temporary directory.
102+
*
103+
* <p>If {@value #SCOPE_PROPERTY_NAME} is set to {@code per_context}, no
104+
* custom factory is allowed.
105+
*
106+
* @return the class instance of the factory
107+
*
108+
* @since 5.10
109+
*/
110+
@API(status = EXPERIMENTAL, since = "5.10")
111+
Class<? extends TempDirFactory> factory() default Standard.class;
112+
99113
/**
100114
* Property name used to set the scope of temporary directories created via
101-
* {@link org.junit.jupiter.api.io.TempDir @TempDir} annotation: {@value}
115+
* {@link TempDir @TempDir} annotation: {@value}
102116
*
103117
* <h4>Supported Values</h4>
104118
* <ul>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2015-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.api.io;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import java.io.Closeable;
16+
import java.io.IOException;
17+
import java.nio.file.Files;
18+
import java.nio.file.Path;
19+
import java.nio.file.attribute.FileAttribute;
20+
21+
import org.apiguardian.api.API;
22+
import org.junit.jupiter.api.extension.ExtensionContext;
23+
24+
/**
25+
* {@code TempDirFactory} defines the SPI for creating temporary directories
26+
* programmatically.
27+
*
28+
* <p>A temporary directory factory is typically used to gain more control on
29+
* the temporary directory creation, like defining the parent directory or even
30+
* the file system that should be used.
31+
*
32+
* <p>Concrete implementations must have a <em>default constructor</em>.
33+
*
34+
* <p>A {@link TempDirFactory} can be configured <em>locally</em>
35+
* for a test class field or method parameter via the {@link TempDir @TempDir}
36+
* annotation.
37+
*
38+
* @since 5.10
39+
* @see TempDir @TempDir
40+
*/
41+
@FunctionalInterface
42+
@API(status = EXPERIMENTAL, since = "5.10")
43+
public interface TempDirFactory extends Closeable {
44+
45+
/**
46+
* Create a new temporary directory, using the given prefix to generate its name.
47+
* Depending on the implementation, the resulting {@code Path} may or may not be
48+
* associated with the default {@code FileSystem}.
49+
*
50+
* @param context the current extension context; never {@code null}
51+
* @return the path to the newly created directory that did not exist before this method was invoked; never {@code null}
52+
* @throws Exception in case of failures
53+
*/
54+
Path createTempDirectory(ExtensionContext context) throws Exception;
55+
56+
/**
57+
* {@inheritDoc}
58+
*/
59+
@Override
60+
default void close() throws IOException {
61+
}
62+
63+
/**
64+
* Standard temporary directory factory that delegates to
65+
* {@link Files#createTempDirectory}.
66+
*
67+
* @see Files#createTempDirectory(String, FileAttribute[])
68+
*/
69+
class Standard implements TempDirFactory {
70+
71+
public static final TempDirFactory INSTANCE = new Standard();
72+
73+
private static final String TEMP_DIR_PREFIX = "junit";
74+
75+
public Standard() {
76+
}
77+
78+
@Override
79+
public Path createTempDirectory(ExtensionContext context) throws IOException {
80+
return Files.createTempDirectory(TEMP_DIR_PREFIX);
81+
}
82+
83+
}
84+
85+
}

junit-jupiter-engine/junit-jupiter-engine.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ dependencies {
2121
testImplementation(projects.junitPlatformTestkit)
2222
testImplementation(testFixtures(projects.junitPlatformCommons))
2323
testImplementation(kotlin("stdlib"))
24+
testImplementation(libs.jimfs)
2425
testImplementation(libs.junit4)
2526
testImplementation(libs.kotlinx.coroutines)
2627
testImplementation(libs.groovy4)
28+
testImplementation(libs.memoryfilesystem)
2729
testImplementation(testFixtures(projects.junitJupiterApi))
2830

2931
osgiVerification(projects.junitPlatformLauncher)

0 commit comments

Comments
 (0)