diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index e4dee9ca8339..0a0862b6f2b8 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -2672,6 +2672,11 @@ The following example demonstrates how to use the custom `@JimfsTempDir` annotat include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation_usage] ---- +Meta-annotations or additional annotations on the field or parameter the `TempDir` +annotation is declared on might expose additional attributes to configure the factory. +Such annotations and related attributes can be accessed via the `AnnotatedElementContext` +parameter of `createTempDirectory`. + You can use the `junit.jupiter.tempdir.factory.default` <> to specify the fully qualified class name of the `TempDirFactory` you would like to use by default. Just like for diff --git a/documentation/src/test/java/example/TempDirectoryDemo.java b/documentation/src/test/java/example/TempDirectoryDemo.java index d45961698997..027242993e58 100644 --- a/documentation/src/test/java/example/TempDirectoryDemo.java +++ b/documentation/src/test/java/example/TempDirectoryDemo.java @@ -32,6 +32,7 @@ import example.util.ListWriter; 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.io.TempDir; import org.junit.jupiter.api.io.TempDirFactory; @@ -110,8 +111,9 @@ void factoryTest(@TempDir(factory = Factory.class) Path tempDir) { static class Factory implements TempDirFactory { @Override - public Path createTempDirectory(ExtensionContext context) throws IOException { - return Files.createTempDirectory(context.getRequiredTestMethod().getName()); + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws IOException { + return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName()); } } @@ -133,7 +135,8 @@ static class JimfsTempDirFactory implements TempDirFactory { private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()); @Override - public Path createTempDirectory(ExtensionContext context) throws IOException { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws IOException { return Files.createTempDirectory(fileSystem.getPath("/"), "junit"); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java new file mode 100644 index 000000000000..49cf3500a033 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015-2023 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.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.support.AnnotationSupport; + +/** + * {@code AnnotatedElementContext} encapsulates the context in which an + * {@link #getAnnotatedElement() AnnotatedElement} is declared. + * + *

For example, an {@code AnnotatedElementContext} is used in + * {@link org.junit.jupiter.api.io.TempDirFactory TempDirFactory} to allow inspecting + * the field or parameter the {@link org.junit.jupiter.api.io.TempDir TempDir} + * annotation is declared on. + * + *

This interface is not intended to be implemented by clients. + * + * @since 5.10 + */ +@API(status = EXPERIMENTAL, since = "5.10") +public interface AnnotatedElementContext { + + /** + * Get the {@link AnnotatedElement} for this context. + * + *

WARNING

+ *

When searching for annotations on the annotated element in this context, + * favor {@link #isAnnotated(Class)}, {@link #findAnnotation(Class)}, and + * {@link #findRepeatableAnnotations(Class)} over methods in the + * {@link AnnotatedElement} API due to a bug in {@code javac} on JDK versions prior + * to JDK 9. + * + * @return the annotated element; never {@code null} + */ + AnnotatedElement getAnnotatedElement(); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the {@link AnnotatedElement} for + * this context. + * + *

WARNING

+ *

Favor the use of this method over directly invoking + * {@link AnnotatedElement#isAnnotationPresent(Class)} due to a bug in {@code javac} + * on JDK versions prior to JDK 9. + * + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #findAnnotation(Class) + * @see #findRepeatableAnnotations(Class) + */ + default boolean isAnnotated(Class annotationType) { + return AnnotationSupport.isAnnotated(getAnnotatedElement(), annotationType); + } + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the {@link AnnotatedElement} for + * this context. + * + *

WARNING

+ *

Favor the use of this method over directly invoking annotation lookup + * methods in the {@link AnnotatedElement} API due to a bug in {@code javac} on JDK + * versions prior to JDK 9. + * + * @param the annotation type + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @see #isAnnotated(Class) + * @see #findRepeatableAnnotations(Class) + */ + default Optional findAnnotation(Class annotationType) { + return AnnotationSupport.findAnnotation(getAnnotatedElement(), annotationType); + } + + /** + * Find all repeatable {@linkplain Annotation annotations} of + * {@code annotationType} that are either present or + * meta-present on the {@link AnnotatedElement} for this context. + * + *

WARNING

+ *

Favor the use of this method over directly invoking annotation lookup + * methods in the {@link AnnotatedElement} API due to a bug in {@code javac} on JDK + * versions prior to JDK 9. + * + * @param the annotation type + * @param annotationType the repeatable annotation type to search for; never + * {@code null} + * @return the list of all such annotations found; neither {@code null} nor + * mutable, but potentially empty + * @see #isAnnotated(Class) + * @see #findAnnotation(Class) + * @see java.lang.annotation.Repeatable + */ + default List findRepeatableAnnotations(Class annotationType) { + return AnnotationSupport.findRepeatableAnnotations(getAnnotatedElement(), annotationType); + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java index 1bce9ffbe16c..88165ba8539f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java @@ -10,9 +10,11 @@ package org.junit.jupiter.api.extension; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Executable; import java.lang.reflect.Parameter; import java.util.List; @@ -36,7 +38,7 @@ * @see java.lang.reflect.Constructor */ @API(status = STABLE, since = "5.0") -public interface ParameterContext { +public interface ParameterContext extends AnnotatedElementContext { /** * Get the {@link Parameter} for this context. @@ -89,63 +91,43 @@ default Executable getDeclaringExecutable() { Optional getTarget(); /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the {@link Parameter} for - * this context. - * - *

WARNING

- *

Favor the use of this method over directly invoking - * {@link Parameter#isAnnotationPresent(Class)} due to a bug in {@code javac} - * on JDK versions prior to JDK 9. - * - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present + * {@inheritDoc} + * @since 5.10 + */ + @API(status = EXPERIMENTAL, since = "5.10") + @Override + default AnnotatedElement getAnnotatedElement() { + return getParameter(); + } + + /** + * {@inheritDoc} * @since 5.1.1 - * @see #findAnnotation(Class) - * @see #findRepeatableAnnotations(Class) */ - boolean isAnnotated(Class annotationType); + @API(status = STABLE, since = "5.10") + @Override + default boolean isAnnotated(Class annotationType) { + return AnnotatedElementContext.super.isAnnotated(annotationType); + } /** - * Find the first annotation of {@code annotationType} that is either - * present or meta-present on the {@link Parameter} for - * this context. - * - *

WARNING

- *

Favor the use of this method over directly invoking annotation lookup - * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK - * versions prior to JDK 9. - * - * @param the annotation type - * @param annotationType the annotation type to search for; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty + * {@inheritDoc} * @since 5.1.1 - * @see #isAnnotated(Class) - * @see #findRepeatableAnnotations(Class) */ - Optional findAnnotation(Class annotationType); + @API(status = STABLE, since = "5.10") + @Override + default Optional findAnnotation(Class annotationType) { + return AnnotatedElementContext.super.findAnnotation(annotationType); + } /** - * Find all repeatable {@linkplain Annotation annotations} of - * {@code annotationType} that are either present or - * meta-present on the {@link Parameter} for this context. - * - *

WARNING

- *

Favor the use of this method over directly invoking annotation lookup - * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK - * versions prior to JDK 9. - * - * @param the annotation type - * @param annotationType the repeatable annotation type to search for; never - * {@code null} - * @return the list of all such annotations found; neither {@code null} nor - * mutable, but potentially empty + * {@inheritDoc} * @since 5.1.1 - * @see #isAnnotated(Class) - * @see #findAnnotation(Class) - * @see java.lang.annotation.Repeatable */ - List findRepeatableAnnotations(Class annotationType); + @API(status = STABLE, since = "5.10") + @Override + default List findRepeatableAnnotations(Class annotationType) { + return AnnotatedElementContext.super.findRepeatableAnnotations(annotationType); + } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java index 97dfe075a7c8..b7ffbc9411d8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java @@ -18,6 +18,7 @@ import java.nio.file.Path; import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; /** @@ -30,8 +31,9 @@ * *

Implementations must provide a no-args constructor and should not make any * assumptions regarding when and how many times they are instantiated, but they - * can assume that {@link #createTempDirectory(ExtensionContext)} and {@link #close()} - * will both be called once per instance, in this order, and from the same thread. + * can assume that {@link #createTempDirectory(AnnotatedElementContext, ExtensionContext)} + * and {@link #close()} will both be called once per instance, in this order, + * and from the same thread. * *

A {@link TempDirFactory} can be configured globally for the * entire test suite via the {@value TempDir#DEFAULT_FACTORY_PROPERTY_NAME} @@ -53,11 +55,14 @@ public interface TempDirFactory extends Closeable { * not be associated with the {@link java.nio.file.FileSystems#getDefault() * default FileSystem}. * - * @param context the current extension context; never {@code null} + * @param elementContext the context of the field or parameter where + * {@code @TempDir} is declared; never {@code null} + * @param extensionContext the current extension context; never {@code null} * @return the path to the newly created temporary directory; never {@code null} * @throws Exception in case of failures */ - Path createTempDirectory(ExtensionContext context) throws Exception; + Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception; /** * {@inheritDoc} @@ -82,7 +87,8 @@ public Standard() { } @Override - public Path createTempDirectory(ExtensionContext context) throws IOException { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws IOException { return Files.createTempDirectory(TEMP_DIR_PREFIX); } 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 0c41baa4724b..fe76498bd4ae 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 @@ -41,6 +41,7 @@ import java.util.TreeMap; import java.util.function.Predicate; +import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; @@ -59,7 +60,9 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.ToStringBuilder; /** * {@code TempDirectory} is a JUnit Jupiter extension that creates and cleans @@ -128,7 +131,7 @@ private void injectFields(ExtensionContext context, Object testInstance, Class type) { } } - private Object getPathOrFile(AnnotatedElement sourceElement, Class type, TempDirFactory factory, + private Object getPathOrFile(AnnotatedElementContext elementContext, Class type, TempDirFactory factory, CleanupMode cleanupMode, Scope scope, ExtensionContext extensionContext) { Namespace namespace = scope == Scope.PER_DECLARATION // - ? NAMESPACE.append(sourceElement) // + ? NAMESPACE.append(elementContext) // : NAMESPACE; Path path = extensionContext.getStore(namespace) // - .getOrComputeIfAbsent(KEY, __ -> createTempDir(factory, cleanupMode, extensionContext), + .getOrComputeIfAbsent(KEY, __ -> createTempDir(factory, cleanupMode, elementContext, extensionContext), CloseablePath.class) // .get(); @@ -246,9 +248,9 @@ private Object getPathOrFile(AnnotatedElement sourceElement, Class type, Temp } static CloseablePath createTempDir(TempDirFactory factory, CleanupMode cleanupMode, - ExtensionContext executionContext) { + AnnotatedElementContext elementContext, ExtensionContext extensionContext) { try { - return new CloseablePath(factory, cleanupMode, executionContext); + return new CloseablePath(factory, cleanupMode, elementContext, extensionContext); } catch (Exception ex) { throw new ExtensionConfigurationException("Failed to create default temp directory", ex); @@ -262,14 +264,14 @@ static class CloseablePath implements CloseableResource { private final Path dir; private final TempDirFactory factory; private final CleanupMode cleanupMode; - private final ExtensionContext executionContext; + private final ExtensionContext extensionContext; - CloseablePath(TempDirFactory factory, CleanupMode cleanupMode, ExtensionContext executionContext) - throws Exception { - this.dir = factory.createTempDirectory(executionContext); + CloseablePath(TempDirFactory factory, CleanupMode cleanupMode, AnnotatedElementContext elementContext, + ExtensionContext extensionContext) throws Exception { + this.dir = factory.createTempDirectory(elementContext, extensionContext); this.factory = factory; this.cleanupMode = cleanupMode; - this.executionContext = executionContext; + this.extensionContext = extensionContext; } Path get() { @@ -280,12 +282,12 @@ Path get() { public void close() throws IOException { try { if (cleanupMode == NEVER - || (cleanupMode == ON_SUCCESS && executionContext.getExecutionException().isPresent())) { + || (cleanupMode == ON_SUCCESS && extensionContext.getExecutionException().isPresent())) { logger.info(() -> "Skipping cleanup of temp dir " + dir + " due to cleanup mode configuration."); return; } - FileOperations fileOperations = executionContext.getStore(NAMESPACE) // + FileOperations fileOperations = extensionContext.getStore(NAMESPACE) // .getOrDefault(FILE_OPERATIONS_KEY, FileOperations.class, FileOperations.DEFAULT); SortedMap failures = deleteAllFilesAndDirectories(fileOperations); @@ -451,4 +453,28 @@ interface FileOperations { } + private static class FieldContext implements AnnotatedElementContext { + + private final Field field; + + private FieldContext(Field field) { + this.field = Preconditions.notNull(field, "field must not be null"); + } + + @Override + public AnnotatedElement getAnnotatedElement() { + return this.field; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("field", this.field) + .toString(); + // @formatter:on + } + + } + } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index d6cfc1d41713..44d05998be02 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; @@ -127,7 +128,7 @@ void shouldGetDefaultTempDirFactorySupplierWithConfigParamSet() { private static class CustomFactory implements TempDirFactory { @Override - public Path createTempDirectory(ExtensionContext context) { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { throw new UnsupportedOperationException(); } } 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 index 43c5105047a2..85daa507785a 100644 --- 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 @@ -28,6 +28,7 @@ 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; @@ -48,6 +49,7 @@ */ class CloseablePathCleanupTests extends AbstractJupiterTestEngineTests { + private final AnnotatedElementContext elementContext = mock(); private final ExtensionContext extensionContext = mock(); private final TempDirFactory factory = spy(TempDirFactory.Standard.INSTANCE); @@ -67,7 +69,7 @@ void cleanupTempDirectory() throws IOException { @Test @DisplayName("is cleaned up for a cleanup mode of ALWAYS") void always() throws IOException { - closeablePath = TempDirectory.createTempDir(factory, ALWAYS, extensionContext); + closeablePath = TempDirectory.createTempDir(factory, ALWAYS, elementContext, extensionContext); assertThat(closeablePath.get()).exists(); closeablePath.close(); @@ -78,7 +80,7 @@ void always() throws IOException { @Test @DisplayName("is not cleaned up for a cleanup mode of NEVER") void never() throws IOException { - closeablePath = TempDirectory.createTempDir(factory, NEVER, extensionContext); + closeablePath = TempDirectory.createTempDir(factory, NEVER, elementContext, extensionContext); assertThat(closeablePath.get()).exists(); closeablePath.close(); @@ -91,7 +93,7 @@ void never() throws IOException { void onSuccessWithException() throws IOException { when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); - closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, extensionContext); + closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); assertThat(closeablePath.get()).exists(); closeablePath.close(); @@ -104,7 +106,7 @@ void onSuccessWithException() throws IOException { void onSuccessWithNoException() throws IOException { when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); - closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, extensionContext); + closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); assertThat(closeablePath.get()).exists(); closeablePath.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 4515d33b27fb..ac6f152814e3 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 @@ -48,6 +48,7 @@ import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; +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.ParameterResolutionException; @@ -318,7 +319,8 @@ void supportsCustomDefaultFactory() { private static class Factory implements TempDirFactory { @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { return Files.createTempDirectory("junit"); } } @@ -693,7 +695,8 @@ void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tem private static class Factory implements TempDirFactory { @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { return Files.createTempDirectory("junit"); } } 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 33a5297e21ae..f0575de592c0 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 @@ -10,6 +10,10 @@ package org.junit.jupiter.engine.extension; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -30,6 +34,11 @@ import java.io.File; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -62,6 +71,7 @@ import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; @@ -345,6 +355,20 @@ void supportsFactoryWithJimfs() { .assertStatistics(stats -> stats.started(1).succeeded(1)); } + @Test + @DisplayName("that uses annotated element name as temp dir name prefix") + void supportsFactoryWithAnnotatedElementNameAsPrefix() { + executeTestsForClass(FactoryWithAnnotatedElementNameAsPrefixTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("that uses custom meta-annotation") + void supportsFactoryWithCustomMetaAnnotation() { + executeTestsForClass(FactoryWithCustomMetaAnnotationTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + } @Nested @@ -390,7 +414,8 @@ private static class Factory implements TempDirFactory { private boolean closed; @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { return Files.createTempDirectory("custom"); } @@ -1178,8 +1203,9 @@ void test(@TempDir(factory = Factory.class) Path tempDir) { private static class Factory implements TempDirFactory { @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { - return Files.createTempDirectory(context.getRequiredTestMethod().getName()); + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { + return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName()); } } @@ -1203,7 +1229,8 @@ private Factory() throws IOException { } @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { return Files.createTempDirectory(parent, "prefix"); } } @@ -1223,7 +1250,8 @@ private static class Factory implements TempDirFactory { private static FileSystem fileSystem; @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { fileSystem = MemoryFileSystemBuilder.newEmpty().build(); return Files.createTempDirectory(fileSystem.getPath("/"), "prefix"); } @@ -1250,7 +1278,8 @@ private static class Factory implements TempDirFactory { private static FileSystem fileSystem; @Override - public Path createTempDirectory(ExtensionContext context) throws Exception { + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { fileSystem = Jimfs.newFileSystem(Configuration.unix()); return Files.createTempDirectory(fileSystem.getPath("/"), "prefix"); } @@ -1264,6 +1293,79 @@ public void close() throws IOException { } + static class FactoryWithAnnotatedElementNameAsPrefixTestCase { + + @TempDir(factory = Factory.class) + private Path tempDir1; + + @Test + void test(@TempDir(factory = Factory.class) Path tempDir2) { + assertThat(tempDir1.getFileName()).asString().startsWith("tempDir1"); + assertThat(tempDir2.getFileName()).asString().startsWith("tempDir2"); + } + + private static class Factory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { + return Files.createTempDirectory(getName(elementContext.getAnnotatedElement())); + } + + private static String getName(AnnotatedElement element) { + return element instanceof Field ? ((Field) element).getName() : ((Parameter) element).getName(); + } + + } + + } + + static class FactoryWithCustomMetaAnnotationTestCase { + + @TempDirForField + private Path tempDir1; + + @Test + void test(@TempDirForParameter Path tempDir2) { + assertThat(tempDir1.getFileName()).asString().startsWith("field"); + assertThat(tempDir2.getFileName()).asString().startsWith("parameter"); + } + + @Target(ANNOTATION_TYPE) + @Retention(RUNTIME) + @TempDir(factory = FactoryWithCustomMetaAnnotationTestCase.Factory.class) + private @interface TempDirWithPrefix { + + String value(); + + } + + @Target(FIELD) + @Retention(RUNTIME) + @TempDirWithPrefix("field") + private @interface TempDirForField { + } + + @Target(PARAMETER) + @Retention(RUNTIME) + @TempDirWithPrefix("parameter") + private @interface TempDirForParameter { + } + + private static class Factory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { + String prefix = elementContext.findAnnotation(TempDirWithPrefix.class) // + .map(TempDirWithPrefix::value).orElseThrow(); + return Files.createTempDirectory(prefix); + } + + } + + } + static class StandardDefaultFactoryTestCase { @Test