From 2cdd5974ec53010b2930f48de774705965e0d735 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Wed, 31 Jul 2024 12:46:49 +0200 Subject: [PATCH] Let `@TempDir` fail fast if `createTempDirectory` does not return a directory (#3901) Resolves #3900. Co-authored-by: Marc Philipp --- .../release-notes-5.11.0-RC1.adoc | 4 +- .../test/java/example/TempDirectoryDemo.java | 2 +- .../engine/extension/TempDirectory.java | 9 +- .../extension/CloseablePathCleanupTests.java | 117 --------- .../engine/extension/CloseablePathTests.java | 236 ++++++++++++++++++ .../TempDirectoryPerContextTests.java | 83 ++++-- .../TempDirectoryPerDeclarationTests.java | 95 ++++++- 7 files changed, 393 insertions(+), 153 deletions(-) delete mode 100644 junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java create mode 100644 junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc index 5eff609cd824..fe1434b5b6a1 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc @@ -67,7 +67,9 @@ repository on GitHub. `assertInstanceOf` methods introduced in `5.8` version. * New generators in `DynamicTest` that take a `Stream`/`Iterator` of `Named` along with a convenient `NamedExecutable` interface that can simplify writing dynamic - tests, in particular in recent version of Java that support records. + tests, in particular in recent versions of Java that support records. +* `@TempDir` now fails fast in case `TempDirFactory::createTempDirectory` returns + `null`, a file, or a symbolic link to a file. [[release-notes-5.11.0-RC1-junit-vintage]] diff --git a/documentation/src/test/java/example/TempDirectoryDemo.java b/documentation/src/test/java/example/TempDirectoryDemo.java index 4998f939fd9b..758710000fb3 100644 --- a/documentation/src/test/java/example/TempDirectoryDemo.java +++ b/documentation/src/test/java/example/TempDirectoryDemo.java @@ -137,7 +137,7 @@ static class JimfsTempDirFactory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { - return Files.createTempDirectory(fileSystem.getPath("/"), "junit"); + return Files.createTempDirectory(fileSystem.getPath("/"), "junit-"); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java index f165b2e55968..eb2148e544f6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -284,14 +284,19 @@ static class CloseablePath implements CloseableResource { private final CleanupMode cleanupMode; private final ExtensionContext extensionContext; - CloseablePath(TempDirFactory factory, CleanupMode cleanupMode, AnnotatedElementContext elementContext, + private CloseablePath(TempDirFactory factory, CleanupMode cleanupMode, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { - this.dir = factory.createTempDirectory(elementContext, extensionContext); + this.dir = validateTempDirectory(factory.createTempDirectory(elementContext, extensionContext)); this.factory = factory; this.cleanupMode = cleanupMode; this.extensionContext = extensionContext; } + private static Path validateTempDirectory(Path dir) { + Preconditions.condition(dir != null && Files.isDirectory(dir), "temp directory must be a directory"); + return dir; + } + Path get() { return dir; } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java deleted file mode 100644 index 2ed3e370b58a..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2015-2024 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.engine.extension; - -import static java.nio.file.Files.deleteIfExists; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; -import static org.junit.jupiter.api.io.CleanupMode.NEVER; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.util.Optional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AnnotatedElementContext; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.api.io.TempDirFactory; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; - -/** - * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is - * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. - * - * @since 5.9 - * - * @see TempDir - * @see CleanupMode - */ -class CloseablePathCleanupTests extends AbstractJupiterTestEngineTests { - - private final AnnotatedElementContext elementContext = mock(); - private final ExtensionContext extensionContext = mock(); - private final TempDirFactory factory = spy(TempDirFactory.Standard.INSTANCE); - - private TempDirectory.CloseablePath closeablePath; - - @BeforeEach - void setUpExtensionContext() { - var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL); - when(extensionContext.getStore(any())).thenReturn(store); - } - - @AfterEach - void cleanupTempDirectory() throws IOException { - deleteIfExists(closeablePath.get()); - } - - @Test - @DisplayName("is cleaned up for a cleanup mode of ALWAYS") - void always() throws IOException { - closeablePath = TempDirectory.createTempDir(factory, ALWAYS, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).doesNotExist(); - verify(factory).close(); - } - - @Test - @DisplayName("is not cleaned up for a cleanup mode of NEVER") - void never() throws IOException { - closeablePath = TempDirectory.createTempDir(factory, NEVER, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).exists(); - verify(factory).close(); - } - - @Test - @DisplayName("is not cleaned up for a cleanup mode of ON_SUCCESS, if there is an exception") - void onSuccessWithException() throws IOException { - when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); - - closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).exists(); - verify(factory).close(); - } - - @Test - @DisplayName("is cleaned up for a cleanup mode of ON_SUCCESS, if there is no exception") - void onSuccessWithNoException() throws IOException { - when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); - - closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).doesNotExist(); - verify(factory).close(); - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java new file mode 100644 index 000000000000..a2bfc075ee59 --- /dev/null +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java @@ -0,0 +1,236 @@ +/* + * Copyright 2015-2024 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.engine.extension; + +import static java.nio.file.Files.createDirectory; +import static java.nio.file.Files.createFile; +import static java.nio.file.Files.createSymbolicLink; +import static java.nio.file.Files.createTempDirectory; +import static java.nio.file.Files.delete; +import static java.nio.file.Files.deleteIfExists; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; +import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AnnotatedElementContext; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.io.TempDirFactory; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; + +/** + * Integration tests for the creation and cleanup of the {@link TempDirectory}. + * + * @since 5.9 + */ +@DisplayName("Temporary directory") +class CloseablePathTests extends AbstractJupiterTestEngineTests { + + private final AnnotatedElementContext elementContext = mock(); + private final ExtensionContext extensionContext = mock(); + + private TempDirectory.CloseablePath closeablePath; + + @BeforeEach + void setUpExtensionContext() { + var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL); + when(extensionContext.getStore(any())).thenReturn(store); + } + + /** + * Integration tests for the creation of the {@link TempDirectory} based on the different result + * that {@link TempDirFactory#createTempDirectory(AnnotatedElementContext, ExtensionContext)} may provide. + * + * @since 5.11 + * + * @see TempDirFactory + */ + @Nested + @DisplayName("creation") + class Creation { + + private Path root; + + @BeforeEach + void setUpRootFolder() throws IOException { + root = createTempDirectory("root"); + } + + @AfterEach + void cleanupRoot() throws IOException { + delete(root); + } + + @Test + @DisplayName("succeeds if the factory returns a directory") + void factoryReturnsDirectory() throws Exception { + TempDirFactory factory = (elementContext, extensionContext) -> createDirectory(root.resolve("directory")); + + closeablePath = TempDirectory.createTempDir(factory, DEFAULT, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + delete(closeablePath.get()); + } + + @Test + @DisplayName("succeeds if the factory returns a symbolic link to a directory") + void factoryReturnsSymbolicLinkToDirectory() throws Exception { + Path directory = createDirectory(root.resolve("directory")); + TempDirFactory factory = (elementContext, + extensionContext) -> createSymbolicLink(root.resolve("symbolicLink"), directory); + + closeablePath = TempDirectory.createTempDir(factory, DEFAULT, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + delete(closeablePath.get()); + delete(directory); + } + + @Test + @DisplayName("fails if the factory returns null") + void factoryReturnsNull() { + TempDirFactory factory = (elementContext, extensionContext) -> null; + + assertThatExtensionConfigurationExceptionIsThrownBy( + () -> TempDirectory.createTempDir(factory, DEFAULT, elementContext, extensionContext)); + } + + @Test + @DisplayName("fails if the factory returns a file") + void factoryReturnsFile() throws IOException { + Path file = createFile(root.resolve("file")); + TempDirFactory factory = (elementContext, extensionContext) -> file; + + assertThatExtensionConfigurationExceptionIsThrownBy( + () -> TempDirectory.createTempDir(factory, DEFAULT, elementContext, extensionContext)); + + delete(file); + } + + @Test + @DisplayName("fails if the factory returns a symbolic link to a file") + void factoryReturnsSymbolicLinkToFile() throws IOException { + Path file = createFile(root.resolve("file")); + Path symbolicLink = createSymbolicLink(root.resolve("symbolicLink"), file); + TempDirFactory factory = (elementContext, extensionContext) -> symbolicLink; + + assertThatExtensionConfigurationExceptionIsThrownBy( + () -> TempDirectory.createTempDir(factory, DEFAULT, elementContext, extensionContext)); + + delete(symbolicLink); + delete(file); + } + + private static void assertThatExtensionConfigurationExceptionIsThrownBy(ThrowingCallable callable) { + assertThatExceptionOfType(ExtensionConfigurationException.class)// + .isThrownBy(callable)// + .withMessage("Failed to create default temp directory")// + .withCauseInstanceOf(PreconditionViolationException.class)// + .havingCause().withMessage("temp directory must be a directory"); + } + + } + + /** + * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is + * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. + * + * @since 5.9 + * + * @see TempDir + * @see CleanupMode + */ + @Nested + @DisplayName("cleanup") + class Cleanup { + + private final TempDirFactory factory = spy(TempDirFactory.Standard.INSTANCE); + + @AfterEach + void cleanupTempDirectory() throws IOException { + deleteIfExists(closeablePath.get()); + } + + @Test + @DisplayName("is done for a cleanup mode of ALWAYS") + void always() throws IOException { + closeablePath = TempDirectory.createTempDir(factory, ALWAYS, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + assertThat(closeablePath.get()).doesNotExist(); + verify(factory).close(); + } + + @Test + @DisplayName("is not done for a cleanup mode of NEVER") + void never() throws IOException { + closeablePath = TempDirectory.createTempDir(factory, NEVER, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + assertThat(closeablePath.get()).exists(); + verify(factory).close(); + } + + @Test + @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception") + void onSuccessWithException() throws IOException { + when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); + + closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + assertThat(closeablePath.get()).exists(); + verify(factory).close(); + } + + @Test + @DisplayName("is done for a cleanup mode of ON_SUCCESS, if there is no exception") + void onSuccessWithNoException() throws IOException { + when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); + + closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + assertThat(closeablePath.get()).doesNotExist(); + verify(factory).close(); + } + + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java index 49b28d2dcc3a..c47bb9112815 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java @@ -54,14 +54,15 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirFactory; +import org.junit.jupiter.api.io.TempDirFactory.Standard; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; /** * Integration tests for the legacy behavior of the {@link TempDirectory} - * extension to create a single temp directory per context, i.e. test class or + * extension to create a single temp directory per context, i.e., test class or * method. * * @since 5.4 @@ -74,6 +75,13 @@ protected EngineExecutionResults executeTestsForClass(Class testClass) { return executeTests(requestBuilder(testClass).build()); } + private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, + Class factoryClass) { + return executeTests(requestBuilder(testClass) // + .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // + .build()); + } + @SuppressWarnings("deprecation") private static LauncherDiscoveryRequestBuilder requestBuilder(Class testClass) { return request() // @@ -295,25 +303,20 @@ void resolvesSeparateTempDirWhenAnnotationIsUsedOnAfterAllMethodParameterOnly() @TestMethodOrder(OrderAnnotation.class) class DefaultFactory { - private Events executeTestsForClassWithDefaultFactory(Class testClass, - Class factoryClass) { - return TempDirectoryPerContextTests.super.executeTests(requestBuilder(testClass) // - .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // - .build()).testEvents(); - } - @Test @DisplayName("set to Jupiter's default") void supportsStandardDefaultFactory() { - executeTestsForClassWithDefaultFactory(StandardDefaultFactoryTestCase.class, TempDirFactory.Standard.class) // - .assertStatistics(stats -> stats.started(1).succeeded(1)); + var results = executeTestsForClassWithDefaultFactory(StandardDefaultFactoryTestCase.class, Standard.class); + + results.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("set to custom factory") void supportsCustomDefaultFactory() { - executeTestsForClassWithDefaultFactory(NonStandardDefaultFactoryTestCase.class, Factory.class) // - .assertStatistics(stats -> stats.started(1).succeeded(1)); + var results = executeTestsForClassWithDefaultFactory(CustomDefaultFactoryTestCase.class, Factory.class); + + results.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); } private static class Factory implements TempDirFactory { @@ -359,8 +362,7 @@ void onlySupportsParametersOfTypePathAndFile() { var results = executeTestsForClass(InvalidTestCase.class); // @formatter:off - TempDirectoryPerContextTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), @@ -389,14 +391,13 @@ void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerCla } @Test - @DisplayName("when @TempDir factory is not Standard") + @DisplayName("when non-default @TempDir factory is set") @Order(32) - void onlySupportsStandardTempDirFactory() { - var results = executeTestsForClass(NonStandardFactoryTestCase.class); + void doesNotSupportNonDefaultTempDirFactory() { + var results = executeTestsForClass(NonDefaultFactoryTestCase.class); // @formatter:off - TempDirectoryPerContextTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), @@ -405,6 +406,35 @@ void onlySupportsStandardTempDirFactory() { // @formatter:on } + @Test + @DisplayName("when default @TempDir factory does not return directory") + @Order(33) + void doesNotSupportCustomDefaultTempDirFactoryNotReturningDirectory() { + var results = executeTestsForClassWithDefaultFactory( + CustomDefaultFactoryNotReturningDirectoryTestCase.class, FactoryNotReturningDirectory.class); + + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory must be a directory") + ) + )); + // @formatter:on + } + + private static class FactoryNotReturningDirectory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return null; + } + } + } @Nested @@ -685,7 +715,7 @@ static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase } } - static class NonStandardFactoryTestCase { + static class NonDefaultFactoryTestCase { @Test void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tempDir) { @@ -712,7 +742,7 @@ void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { } - static class NonStandardDefaultFactoryTestCase { + static class CustomDefaultFactoryTestCase { @Test void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { @@ -721,6 +751,15 @@ void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { } + static class CustomDefaultFactoryNotReturningDirectoryTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir Path tempDir) { + // never called + } + + } + static class AnnotationOnBeforeAllMethodParameterTestCase extends BaseSharedTempDirParameterInjectionTestCase { @BeforeAll diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java index 153146a782af..c49cb101784c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java @@ -83,6 +83,7 @@ import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.Constants; import org.junit.jupiter.engine.extension.TempDirectory.FileOperations; +import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -95,6 +96,14 @@ @DisplayName("TempDirectory extension (per declaration)") class TempDirectoryPerDeclarationTests extends AbstractJupiterTestEngineTests { + private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, + Class factoryClass) { + return executeTests(request() // + .selectors(selectClass(testClass)) // + .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // + .build()); + } + @BeforeEach @AfterEach void resetStaticVariables() { @@ -272,8 +281,7 @@ void onlySupportsParametersOfTypePathAndFile() { var results = executeTestsForClass(InvalidTestCase.class); // @formatter:off - TempDirectoryPerDeclarationTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), @@ -301,6 +309,55 @@ void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerCla "@TempDir is not supported on constructor parameters. Please use field injection instead."); } + @Test + @DisplayName("when @TempDir factory does not return directory") + @Order(32) + void doesNotSupportTempDirFactoryNotReturningDirectory() { + var results = executeTestsForClass(FactoryNotReturningDirectoryTestCase.class); + + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory must be a directory") + ) + )); + // @formatter:on + } + + @Test + @DisplayName("when default @TempDir factory does not return directory") + @Order(33) + void doesNotSupportCustomDefaultTempDirFactoryReturningNull() { + var results = executeTestsForClassWithDefaultFactory( + CustomDefaultFactoryNotReturningDirectoryTestCase.class, FactoryNotReturningDirectory.class); + + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory must be a directory") + ) + )); + // @formatter:on + } + + private static class FactoryNotReturningDirectory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return null; + } + } + } @Nested @@ -379,14 +436,6 @@ void supportsFactoryWithCustomMetaAnnotation() { @TestMethodOrder(OrderAnnotation.class) class DefaultFactory { - private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, - Class factoryClass) { - return TempDirectoryPerDeclarationTests.super.executeTests(request() // - .selectors(selectClass(testClass)) // - .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // - .build()); - } - @Test @DisplayName("set to Jupiter's default") void supportsStandardDefaultFactory() { @@ -1369,6 +1418,23 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio } + static class FactoryNotReturningDirectoryTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tempDir) { + // never called + } + + private static class Factory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return null; + } + } + + } + static class StandardDefaultFactoryTestCase { @Test @@ -1402,4 +1468,13 @@ void test(@TempDir Path tempDir1, @TempDir(factory = Standard.class) Path tempDi } + static class CustomDefaultFactoryNotReturningDirectoryTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir Path tempDir) { + // never called + } + + } + }