blob: 55c7fad2e50f0d8d82e22c87f4ead8c4895bd6f1 [file] [log] [blame]
/*
* Copyright 2015-2022 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 static org.apiguardian.api.API.Status.STABLE;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.util.Preconditions;
/**
* {@code ExtensionContext} encapsulates the <em>context</em> in which the
* current test or container is being executed.
*
* <p>{@link Extension Extensions} are provided an instance of
* {@code ExtensionContext} to perform their work.
*
* @since 5.0
* @see Store
* @see Namespace
*/
@API(status = STABLE, since = "5.0")
public interface ExtensionContext {
/**
* Get the parent extension context, if available.
*
* @return an {@code Optional} containing the parent; never {@code null} but
* potentially empty
* @see #getRoot()
*/
Optional<ExtensionContext> getParent();
/**
* Get the <em>root</em> {@code ExtensionContext}.
*
* @return the root extension context; never {@code null} but potentially
* <em>this</em> {@code ExtensionContext}
* @see #getParent()
*/
ExtensionContext getRoot();
/**
* Get the unique ID of the current test or container.
*
* @return the unique ID of the test or container; never {@code null} or blank
*/
String getUniqueId();
/**
* Get the display name for the current test or container.
*
* <p>The display name is either a default name or a custom name configured
* via {@link org.junit.jupiter.api.DisplayName @DisplayName}.
*
* <p>For details on default display names consult the Javadoc for
* {@link org.junit.jupiter.api.TestInfo#getDisplayName()}.
*
* <p>Note that display names are typically used for test reporting in IDEs
* and build tools and may contain spaces, special characters, and even emoji.
*
* @return the display name of the test or container; never {@code null} or blank
*/
String getDisplayName();
/**
* Get the set of all tags for the current test or container.
*
* <p>Tags may be declared directly on the test element or <em>inherited</em>
* from an outer context.
*
* @return the set of tags for the test or container; never {@code null} but
* potentially empty
*/
Set<String> getTags();
/**
* Get the {@link AnnotatedElement} corresponding to the current extension
* context, if available.
*
* <p>For example, if the current extension context encapsulates a test
* class, test method, test factory method, or test template method, the
* annotated element will be the corresponding {@link Class} or {@link Method}
* reference.
*
* <p>Favor this method over more specific methods whenever the
* {@code AnnotatedElement} API suits the task at hand &mdash; for example,
* when looking up annotations regardless of concrete element type.
*
* @return an {@code Optional} containing the {@code AnnotatedElement};
* never {@code null} but potentially empty
* @see #getTestClass()
* @see #getTestMethod()
*/
Optional<AnnotatedElement> getElement();
/**
* Get the {@link Class} associated with the current test or container,
* if available.
*
* @return an {@code Optional} containing the class; never {@code null} but
* potentially empty
* @see #getRequiredTestClass()
*/
Optional<Class<?>> getTestClass();
/**
* Get the <em>required</em> {@link Class} associated with the current test
* or container.
*
* <p>Use this method as an alternative to {@link #getTestClass()} for use
* cases in which the test class is required to be present.
*
* @return the test class; never {@code null}
* @throws PreconditionViolationException if the test class is not present
* in this {@code ExtensionContext}
*/
default Class<?> getRequiredTestClass() {
return Preconditions.notNull(getTestClass().orElse(null),
"Illegal state: required test class is not present in the current ExtensionContext");
}
/**
* Get the {@link Lifecycle} of the {@linkplain #getTestInstance() test
* instance} associated with the current test or container, if available.
*
* @return an {@code Optional} containing the test instance {@code Lifecycle};
* never {@code null} but potentially empty
* @since 5.1
* @see org.junit.jupiter.api.TestInstance {@code @TestInstance}
*/
@API(status = STABLE, since = "5.1")
Optional<Lifecycle> getTestInstanceLifecycle();
/**
* Get the test instance associated with the current test or container,
* if available.
*
* @return an {@code Optional} containing the test instance; never
* {@code null} but potentially empty
* @see #getRequiredTestInstance()
* @see #getTestInstances()
*/
Optional<Object> getTestInstance();
/**
* Get the <em>required</em> test instance associated with the current test
* or container.
*
* <p>Use this method as an alternative to {@link #getTestInstance()} for use
* cases in which the test instance is required to be present.
*
* @return the test instance; never {@code null}
* @throws PreconditionViolationException if the test instance is not present
* in this {@code ExtensionContext}
*
* @see #getRequiredTestInstances()
*/
default Object getRequiredTestInstance() {
return Preconditions.notNull(getTestInstance().orElse(null),
"Illegal state: required test instance is not present in the current ExtensionContext");
}
/**
* Get the test instances associated with the current test or container,
* if available.
*
* <p>While top-level tests only have a single test instance, nested tests
* have one additional instance for each enclosing test class.
*
* @return an {@code Optional} containing the test instances; never
* {@code null} but potentially empty
* @since 5.4
* @see #getRequiredTestInstances()
*/
@API(status = STABLE, since = "5.7")
Optional<TestInstances> getTestInstances();
/**
* Get the <em>required</em> test instances associated with the current test
* or container.
*
* <p>Use this method as an alternative to {@link #getTestInstances()} for use
* cases in which the test instances are required to be present.
*
* @return the test instances; never {@code null}
* @throws PreconditionViolationException if the test instances are not present
* in this {@code ExtensionContext}
* @since 5.4
*/
@API(status = STABLE, since = "5.7")
default TestInstances getRequiredTestInstances() {
return Preconditions.notNull(getTestInstances().orElse(null),
"Illegal state: required test instances are not present in the current ExtensionContext");
}
/**
* Get the {@link Method} associated with the current test, if available.
*
* @return an {@code Optional} containing the method; never {@code null} but
* potentially empty
* @see #getRequiredTestMethod()
*/
Optional<Method> getTestMethod();
/**
* Get the <em>required</em> {@link Method} associated with the current test
* or container.
*
* <p>Use this method as an alternative to {@link #getTestMethod()} for use
* cases in which the test method is required to be present.
*
* @return the test method; never {@code null}
* @throws PreconditionViolationException if the test method is not present
* in this {@code ExtensionContext}
*/
default Method getRequiredTestMethod() {
return Preconditions.notNull(getTestMethod().orElse(null),
"Illegal state: required test method is not present in the current ExtensionContext");
}
/**
* Get the exception that was thrown during execution of the test or container
* associated with this {@code ExtensionContext}, if available.
*
* <p>This method is typically used for logging and tracing purposes. If you
* wish to actually <em>handle</em> an exception thrown during test execution,
* implement the {@link TestExecutionExceptionHandler} API.
*
* <p>Unlike the exception passed to a {@code TestExecutionExceptionHandler},
* an <em>execution exception</em> returned by this method can be any
* exception thrown during the invocation of a {@code @Test} method, its
* surrounding {@code @BeforeEach} and {@code @AfterEach} methods, or a
* test-level {@link Extension}. Similarly, if this {@code ExtensionContext}
* represents a test class, the <em>execution exception</em> returned by
* this method can be any exception thrown in a {@code @BeforeAll} or
* {@code AfterAll} method or a class-level {@link Extension}.
*
* <p>Note, however, that this method will never return an exception
* swallowed by a {@code TestExecutionExceptionHandler}. Furthermore, if
* multiple exceptions have been thrown during test execution, the exception
* returned by this method will be the first such exception with all
* additional exceptions {@linkplain Throwable#addSuppressed(Throwable)
* suppressed} in the first one.
*
* @return an {@code Optional} containing the exception thrown; never
* {@code null} but potentially empty if test execution has not (yet)
* resulted in an exception
*/
Optional<Throwable> getExecutionException();
/**
* Get the configuration parameter stored under the specified {@code key}.
*
* <p>If no such key is present in the {@code ConfigurationParameters} for
* the JUnit Platform, an attempt will be made to look up the value as a
* JVM system property. If no such system property exists, an attempt will
* be made to look up the value in the JUnit Platform properties file.
*
* @param key the key to look up; never {@code null} or blank
* @return an {@code Optional} containing the value; never {@code null}
* but potentially empty
*
* @since 5.1
* @see System#getProperty(String)
* @see org.junit.platform.engine.ConfigurationParameters
*/
@API(status = STABLE, since = "5.1")
Optional<String> getConfigurationParameter(String key);
/**
* Get and transform the configuration parameter stored under the specified
* {@code key} using the specified {@code transformer}.
*
* <p>If no such key is present in the {@code ConfigurationParameters} for
* the JUnit Platform, an attempt will be made to look up the value as a
* JVM system property. If no such system property exists, an attempt will
* be made to look up the value in the JUnit Platform properties file.
*
* <p>In case the transformer throws an exception, it will be wrapped in a
* {@link org.junit.platform.commons.JUnitException} with a helpful message.
*
* @param key the key to look up; never {@code null} or blank
* @param transformer the transformer to apply in case a value is found;
* never {@code null}
* @return an {@code Optional} containing the value; never {@code null}
* but potentially empty
*
* @since 5.7
* @see System#getProperty(String)
* @see org.junit.platform.engine.ConfigurationParameters
*/
@API(status = EXPERIMENTAL, since = "5.7")
<T> Optional<T> getConfigurationParameter(String key, Function<String, T> transformer);
/**
* Publish a map of key-value pairs to be consumed by an
* {@code org.junit.platform.engine.EngineExecutionListener} in order to
* supply additional information to the reporting infrastructure.
*
* @param map the key-value pairs to be published; never {@code null};
* keys and values within entries in the map also must not be
* {@code null} or blank
* @see #publishReportEntry(String, String)
* @see #publishReportEntry(String)
* @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished
*/
void publishReportEntry(Map<String, String> map);
/**
* Publish the specified key-value pair to be consumed by an
* {@code org.junit.platform.engine.EngineExecutionListener} in order to
* supply additional information to the reporting infrastructure.
*
* @param key the key of the published pair; never {@code null} or blank
* @param value the value of the published pair; never {@code null} or blank
* @see #publishReportEntry(Map)
* @see #publishReportEntry(String)
* @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished
*/
default void publishReportEntry(String key, String value) {
this.publishReportEntry(Collections.singletonMap(key, value));
}
/**
* Publish the specified value to be consumed by an
* {@code org.junit.platform.engine.EngineExecutionListener} in order to
* supply additional information to the reporting infrastructure.
*
* <p>This method delegates to {@link #publishReportEntry(String, String)},
* supplying {@code "value"} as the key and the supplied {@code value}
* argument as the value.
*
* @param value the value to be published; never {@code null} or blank
* @since 5.3
* @see #publishReportEntry(Map)
* @see #publishReportEntry(String, String)
* @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished
*/
@API(status = STABLE, since = "5.3")
default void publishReportEntry(String value) {
this.publishReportEntry("value", value);
}
/**
* Get the {@link Store} for the supplied {@link Namespace}.
*
* <p>Use {@code getStore(Namespace.GLOBAL)} to get the default, global {@link Namespace}.
*
* <p>A store is bound to its extension context lifecycle. When an extension
* context lifecycle ends it closes its associated store. All stored values
* that are instances of {@link ExtensionContext.Store.CloseableResource} are
* notified by invoking their {@code close()} methods.
*
* @param namespace the {@code Namespace} to get the store for; never {@code null}
* @return the store in which to put and get objects for other invocations
* working in the same namespace; never {@code null}
* @see Namespace#GLOBAL
*/
Store getStore(Namespace namespace);
/**
* Get the {@link ExecutionMode} associated with the current test or container.
*
* @return the {@code ExecutionMode} of the test; never {@code null}
*
* @since 5.8.1
* @see org.junit.jupiter.api.parallel.ExecutionMode {@code @ExecutionMode}
*/
@API(status = STABLE, since = "5.8.1")
ExecutionMode getExecutionMode();
/**
* Get an {@link ExecutableInvoker} to invoke methods and constructors
* with support for dynamic resolution of parameters.
*
* @since 5.9
*/
@API(status = EXPERIMENTAL, since = "5.9")
ExecutableInvoker getExecutableInvoker();
/**
* {@code Store} provides methods for extensions to save and retrieve data.
*/
interface Store {
/**
* Classes implementing this interface indicate that they want to {@link #close}
* some underlying resource or resources when the enclosing {@link Store Store}
* is closed.
*
* <p>Note that the {@code CloseableResource} API is only honored for
* objects stored within an extension context {@link Store Store}.
*
* <p>The resources stored in a {@link Store Store} are closed in the
* inverse order they were added in.
*
* @since 5.1
*/
@API(status = STABLE, since = "5.1")
interface CloseableResource {
/**
* Close underlying resources.
*
* @throws Throwable any throwable will be caught and rethrown
*/
void close() throws Throwable;
}
/**
* Get the value that is stored under the supplied {@code key}.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store.
*
* <p>For greater type safety, consider using {@link #get(Object, Class)}
* instead.
*
* @param key the key; never {@code null}
* @return the value; potentially {@code null}
* @see #get(Object, Class)
* @see #getOrDefault(Object, Class, Object)
*/
Object get(Object key);
/**
* Get the value of the specified required type that is stored under
* the supplied {@code key}.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store.
*
* @param key the key; never {@code null}
* @param requiredType the required type of the value; never {@code null}
* @param <V> the value type
* @return the value; potentially {@code null}
* @see #get(Object)
* @see #getOrDefault(Object, Class, Object)
*/
<V> V get(Object key, Class<V> requiredType);
/**
* Get the value of the specified required type that is stored under
* the supplied {@code key}, or the supplied {@code defaultValue} if no
* value is found for the supplied {@code key} in this store or in an
* ancestor.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store.
*
* @param key the key; never {@code null}
* @param requiredType the required type of the value; never {@code null}
* @param defaultValue the default value
* @param <V> the value type
* @return the value; potentially {@code null}
* @since 5.5
* @see #get(Object, Class)
*/
@API(status = STABLE, since = "5.5")
default <V> V getOrDefault(Object key, Class<V> requiredType, V defaultValue) {
V value = get(key, requiredType);
return (value != null ? value : defaultValue);
}
/**
* Get the object of type {@code type} that is present in this
* {@code Store} (<em>keyed</em> by {@code type}); and otherwise invoke
* the default constructor for {@code type} to generate the object,
* store it, and return it.
*
* <p>This method is a shortcut for the following, where {@code X} is
* the type of object we wish to retrieve from the store.
*
* <pre style="code">
* X x = store.getOrComputeIfAbsent(X.class, key -&gt; new X(), X.class);
* // Equivalent to:
* // X x = store.getOrComputeIfAbsent(X.class);
* </pre>
*
* <p>See {@link #getOrComputeIfAbsent(Object, Function, Class)} for
* further details.
*
* <p>If {@code type} implements {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
*
* @param type the type of object to retrieve; never {@code null}
* @param <V> the key and value type
* @return the object; never {@code null}
* @since 5.1
* @see #getOrComputeIfAbsent(Object, Function)
* @see #getOrComputeIfAbsent(Object, Function, Class)
* @see CloseableResource
*/
@API(status = STABLE, since = "5.1")
default <V> V getOrComputeIfAbsent(Class<V> type) {
return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type);
}
/**
* Get the value that is stored under the supplied {@code key}.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store. If no value is found for the supplied {@code key},
* a new value will be computed by the {@code defaultCreator} (given
* the {@code key} as input), stored, and returned.
*
* <p>For greater type safety, consider using
* {@link #getOrComputeIfAbsent(Object, Function, Class)} instead.
*
* <p>If the created value is an instance of {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
*
* @param key the key; never {@code null}
* @param defaultCreator the function called with the supplied {@code key}
* to create a new value; never {@code null}
* @param <K> the key type
* @param <V> the value type
* @return the value; potentially {@code null}
* @see #getOrComputeIfAbsent(Class)
* @see #getOrComputeIfAbsent(Object, Function, Class)
* @see CloseableResource
*/
<K, V> Object getOrComputeIfAbsent(K key, Function<K, V> defaultCreator);
/**
* Get the value of the specified required type that is stored under the
* supplied {@code key}.
*
* <p>If no value is stored in the current {@link ExtensionContext}
* for the supplied {@code key}, ancestors of the context will be queried
* for a value with the same {@code key} in the {@code Namespace} used
* to create this store. If no value is found for the supplied {@code key},
* a new value will be computed by the {@code defaultCreator} (given
* the {@code key} as input), stored, and returned.
*
* <p>If {@code requiredType} implements {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
*
* @param key the key; never {@code null}
* @param defaultCreator the function called with the supplied {@code key}
* to create a new value; never {@code null}
* @param requiredType the required type of the value; never {@code null}
* @param <K> the key type
* @param <V> the value type
* @return the value; potentially {@code null}
* @see #getOrComputeIfAbsent(Class)
* @see #getOrComputeIfAbsent(Object, Function)
* @see CloseableResource
*/
<K, V> V getOrComputeIfAbsent(K key, Function<K, V> defaultCreator, Class<V> requiredType);
/**
* Store a {@code value} for later retrieval under the supplied {@code key}.
*
* <p>A stored {@code value} is visible in child {@link ExtensionContext
* ExtensionContexts} for the store's {@code Namespace} unless they
* overwrite it.
*
* <p>If the {@code value} is an instance of {@link ExtensionContext.Store.CloseableResource}
* the {@code close()} method will be invoked on the stored object when
* the store is closed.
*
* @param key the key under which the value should be stored; never
* {@code null}
* @param value the value to store; may be {@code null}
* @see CloseableResource
*/
void put(Object key, Object value);
/**
* Remove the value that was previously stored under the supplied {@code key}.
*
* <p>The value will only be removed in the current {@link ExtensionContext},
* not in ancestors. In addition, the {@link CloseableResource} API will not
* be honored for values that are manually removed via this method.
*
* <p>For greater type safety, consider using {@link #remove(Object, Class)}
* instead.
*
* @param key the key; never {@code null}
* @return the previous value or {@code null} if no value was present
* for the specified key
* @see #remove(Object, Class)
*/
Object remove(Object key);
/**
* Remove the value of the specified required type that was previously stored
* under the supplied {@code key}.
*
* <p>The value will only be removed in the current {@link ExtensionContext},
* not in ancestors. In addition, the {@link CloseableResource} API will not
* be honored for values that are manually removed via this method.
*
* @param key the key; never {@code null}
* @param requiredType the required type of the value; never {@code null}
* @param <V> the value type
* @return the previous value or {@code null} if no value was present
* for the specified key
* @see #remove(Object)
*/
<V> V remove(Object key, Class<V> requiredType);
}
/**
* A {@code Namespace} is used to provide a <em>scope</em> for data saved by
* extensions within a {@link Store}.
*
* <p>Storing data in custom namespaces allows extensions to avoid accidentally
* mixing data between extensions or across different invocations within the
* lifecycle of a single extension.
*/
class Namespace {
/**
* The default, global namespace which allows access to stored data from
* all extensions.
*/
public static final Namespace GLOBAL = Namespace.create(new Object());
/**
* Create a namespace which restricts access to data to all extensions
* which use the same sequence of {@code parts} for creating a namespace.
*
* <p>The order of the {@code parts} is significant.
*
* <p>Internally the {@code parts} are compared using {@link Object#equals(Object)}.
*/
public static Namespace create(Object... parts) {
Preconditions.notEmpty(parts, "parts array must not be null or empty");
Preconditions.containsNoNullElements(parts, "individual parts must not be null");
return new Namespace(new ArrayList<>(Arrays.asList(parts)));
}
private final List<Object> parts;
private Namespace(List<Object> parts) {
this.parts = parts;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Namespace that = (Namespace) o;
return this.parts.equals(that.parts);
}
@Override
public int hashCode() {
return this.parts.hashCode();
}
/**
* Create a new namespace by appending the supplied {@code parts} to the
* existing sequence of parts in this namespace.
*
* @return new namespace; never {@code null}
* @since 5.8
*/
@API(status = EXPERIMENTAL, since = "5.8")
public Namespace append(Object... parts) {
Preconditions.notEmpty(parts, "parts array must not be null or empty");
Preconditions.containsNoNullElements(parts, "individual parts must not be null");
ArrayList<Object> newParts = new ArrayList<>(this.parts.size() + parts.length);
newParts.addAll(this.parts);
Collections.addAll(newParts, parts);
return new Namespace(newParts);
}
}
}