Skip to content

Commit

Permalink
Provide access to source element annotations for TempDirFactory
Browse files Browse the repository at this point in the history
This allows inspecting the declaration site of `@TempDir` in
`TempDirFactory`.

Resolves #3390.
  • Loading branch information
scordio authored Jul 17, 2023
1 parent 73818a1 commit acb6e65
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 85 deletions.
5 changes: 5 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<<running-tests-config-params, configuration parameter>> to specify the fully qualified
class name of the `TempDirFactory` you would like to use by default. Just like for
Expand Down
9 changes: 6 additions & 3 deletions documentation/src/test/java/example/TempDirectoryDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

}
Expand All @@ -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");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <em>context</em> in which an
* {@link #getAnnotatedElement() AnnotatedElement} is declared.
*
* <p>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.
*
* <p>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.
*
* <h4>WARNING</h4>
* <p>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
* <em>present</em> or <em>meta-present</em> on the {@link AnnotatedElement} for
* this context.
*
* <h4>WARNING</h4>
* <p>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<? extends Annotation> annotationType) {
return AnnotationSupport.isAnnotated(getAnnotatedElement(), annotationType);
}

/**
* Find the first annotation of {@code annotationType} that is either
* <em>present</em> or <em>meta-present</em> on the {@link AnnotatedElement} for
* this context.
*
* <h4>WARNING</h4>
* <p>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 <A> 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 <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType) {
return AnnotationSupport.findAnnotation(getAnnotatedElement(), annotationType);
}

/**
* Find all <em>repeatable</em> {@linkplain Annotation annotations} of
* {@code annotationType} that are either <em>present</em> or
* <em>meta-present</em> on the {@link AnnotatedElement} for this context.
*
* <h4>WARNING</h4>
* <p>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 <A> 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 <A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType) {
return AnnotationSupport.findRepeatableAnnotations(getAnnotatedElement(), annotationType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -89,63 +91,43 @@ default Executable getDeclaringExecutable() {
Optional<Object> getTarget();

/**
* Determine if an annotation of {@code annotationType} is either
* <em>present</em> or <em>meta-present</em> on the {@link Parameter} for
* this context.
*
* <h4>WARNING</h4>
* <p>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<? extends Annotation> annotationType);
@API(status = STABLE, since = "5.10")
@Override
default boolean isAnnotated(Class<? extends Annotation> annotationType) {
return AnnotatedElementContext.super.isAnnotated(annotationType);
}

/**
* Find the first annotation of {@code annotationType} that is either
* <em>present</em> or <em>meta-present</em> on the {@link Parameter} for
* this context.
*
* <h4>WARNING</h4>
* <p>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 <A> 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)
*/
<A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType);
@API(status = STABLE, since = "5.10")
@Override
default <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType) {
return AnnotatedElementContext.super.findAnnotation(annotationType);
}

/**
* Find all <em>repeatable</em> {@linkplain Annotation annotations} of
* {@code annotationType} that are either <em>present</em> or
* <em>meta-present</em> on the {@link Parameter} for this context.
*
* <h4>WARNING</h4>
* <p>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 <A> 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
*/
<A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType);
@API(status = STABLE, since = "5.10")
@Override
default <A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType) {
return AnnotatedElementContext.super.findRepeatableAnnotations(annotationType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -30,8 +31,9 @@
*
* <p>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.
*
* <p>A {@link TempDirFactory} can be configured <em>globally</em> for the
* entire test suite via the {@value TempDir#DEFAULT_FACTORY_PROPERTY_NAME}
Expand All @@ -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}
Expand All @@ -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);
}

Expand Down
Loading

0 comments on commit acb6e65

Please sign in to comment.