Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ asciidoc:
BeforeAllCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback]'
BeforeClassTemplateInvocationCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeClassTemplateInvocationCallback.html[BeforeClassTemplateInvocationCallback]'
BeforeEachCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback]'
AsyncReturnValueHandler: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AsyncReturnValueHandler.html[AsyncReturnValueHandler]'
BeforeTestExecutionCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback]'
ClassTemplateInvocationContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ClassTemplateInvocationContext.html[ClassTemplateInvocationContext]'
ClassTemplateInvocationContextProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ClassTemplateInvocationContextProvider.html[ClassTemplateInvocationContextProvider]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,63 @@ WARNING: Using `worker_thread_pool` is currently an _experimental_ feature. You'
to give it a try and provide feedback to the JUnit team so they can improve and eventually
xref:api-evolution.adoc[promote] this feature.

[[config-reactive-execution]]
=== Reactive Execution

The _reactive_ (cooperative) execution lane schedules nodes by composing `CompletionStage`s instead
of parking a thread for every in-flight node. This works together with the
xref:writing-tests/test-classes-and-methods.adoc#async-test-methods[asynchronous test methods] and the
`AsyncInvocationInterceptor` extension API so that an asynchronous test body can run without holding
a platform thread for its entire duration: the worker only _triggers_ the async body and is freed
while it is awaited.

There are two ways to enable the reactive lane; they are distinct and the choice has implications
for how your tests run: cooperative execution of existing thread-based parallelism, or a standalone
cooperative lane.

==== Cooperative execution of thread-based parallelism

Enable the classic thread-based parallel-execution feature _and_ ask it to run cooperatively:

[source,java]
----
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.reactive.enabled = true
----

Implications:

* The full parallel-execution contract applies: `@Execution`, `@ResourceLock`, concurrency permits,
and the strategy-based parallelism (from `junit.jupiter.execution.parallel.config.*`).
* Children are still gated by their resolved execution mode (`CONCURRENT` vs `SAME_THREAD`).
* The only difference from the normal thread-pool lane is that a thread is not parked while an
async test method's `CompletionStage` is pending.

==== Standalone cooperative lane (no thread configuration)

Enable the cooperative lane independently of the thread-based parallel feature:

[source,java]
----
junit.jupiter.execution.reactive.enabled = true
----

Implications:

* No parallel-execution contract is involved and no thread configuration is required. Concurrency
comes from the async test methods' own returned contexts (for example the common ForkJoin pool
behind `CompletableFuture.supplyAsync`); a small internal trigger pool (at most 4 threads) only
starts each async body and is shut down when the run completes.
* Execution mode is resolved per test as follows:
**explicit** `@Execution` on the method or an ancestor always wins; **otherwise** a test method
that returns an asynchronous completion signal (`CompletionStage`, `CompletableFuture`, or
`Future`) is treated as `CONCURRENT`, while a synchronous or `void` test method is treated as
`SAME_THREAD` and keeps running sequentially in discovery order.
* `@ResourceLock` and concurrency-permit semantics of the thread-based lane do not apply here.

Both modes are opt-in and currently _experimental_. `junit.jupiter.execution.parallel.reactive.enabled`
requires `parallel.enabled = true`; `junit.jupiter.execution.reactive.enabled` does not.

[[config-strategies]]
=== Strategies

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Test methods and lifecycle methods may be declared locally within the current test class,
inherited from superclasses, or inherited from interfaces (see
xref:writing-tests/test-interfaces-and-default-methods.adoc[]). In addition, test methods and
lifecycle methods must not be `abstract` and must not return a value (except `@TestFactory`
methods which are required to return a value).
xref:writing-tests/test-interfaces-and-default-methods.adoc[]). In addition, they must not be
`abstract` and must not return a value (except xref:#async-test-methods[asynchronous return types]
and `@TestFactory` methods which are required to return a value).

[NOTE]
.Class and method visibility
Expand Down Expand Up @@ -105,3 +105,126 @@ to support skipping delays during tests and gain control over virtual time and d
----
include::example$kotlin/example/KotlinCoroutinesRunTestDemo.kt[tags=user_guide]
----

[[async-test-methods]]
== Asynchronous Test Methods

In Java, a `@Test` method may return a
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletionStage.html[`CompletionStage`]
-- for example a
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletableFuture.html[`CompletableFuture`]
or a
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/Future.html[`Future`].

The test is only considered finished once the returned asynchronously-completable signal completes:

[source,java,indent=0]
.A test method that completes asynchronously
----
import static java.util.concurrent.CompletableFuture.completedFuture;

import java.util.concurrent.CompletionStage;

@Test
CompletionStage<?> myTest() {
// ... start asynchronous work ...
return completedFuture("done");
}
----

The payload of the returned signal is ignored; only its completion is awaited. If the signal
completes exceptionally, the test is reported as failed with the corresponding throwable.

NOTE: `@Timeout` -- and any other `InterceptingExecutableInvoker`-based interceptor -- applies to
the _entire_ asynchronous work, not merely the synchronous call that returns the
`CompletionStage`. See xref:writing-tests/timeouts.adoc[Testing Timeouts].

As an alternative to returning a `CompletionStage`, you may write a `suspend` function as shown in
xref:#kotlin-coroutines[Kotlin Coroutines], which JUnit internally executes via `runBlocking`.

Lifecycle methods -- `@BeforeAll`, `@AfterAll`, `@BeforeEach`, and `@AfterEach` -- may likewise
return a `CompletionStage` (or `CompletableFuture`/`Future`). Their completion is awaited before the
next phase proceeds, so ordering is preserved: the test body only runs after the `@BeforeEach`
stage completes, and the test result is only reported after the `@AfterEach` stage completes.

[source,java,indent=0]
.A lifecycle method that completes asynchronously
----
import static java.util.concurrent.CompletableFuture.completedFuture;

import java.util.concurrent.CompletionStage;

@BeforeEach
CompletionStage<?> setUp() {
// ... start asynchronous setup ...
return completedFuture(null);
}
----

As with test methods, `@Timeout` on a lifecycle method applies to the entire asynchronous work, not
merely the synchronous call that returns the `CompletionStage`.

[#custom-async-return-types]
=== Custom Asynchronous Return Types

By default only the JRE types `CompletionStage` and `Future` are recognized as asynchronously-completable
return types. If you use a custom promise-like type (for example a `MyPromise<T>`), you can teach the
engine to recognize and await it by implementing the experimental {AsyncReturnValueHandler} `Extension`
SPI:

[source,java,indent=0]
.A handler that maps a custom promise-like type to a CompletionStage
----
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.CompletionStage;

import org.junit.jupiter.api.extension.AsyncReturnValueHandler;

class MyAsyncReturnValueHandler implements AsyncReturnValueHandler {

@Override
public boolean supports(Type genericReturnType, AnnotatedElement annotatedElement) {
if (genericReturnType instanceof ParameterizedType parameterizedType) {
return MyPromise.class.isAssignableFrom((Class<?>) parameterizedType.getRawType());
}
return MyPromise.class == genericReturnType;
}

@Override
public CompletionStage<?> toCompletionStage(Object returnedValue) {
return ((MyPromise<?>) returnedValue).asCompletionStage();
}
}
----

A handler can be registered in one of two ways:

* _Globally_ via the `ServiceLoader` mechanism (that is, listed under
`META-INF/services/org.junit.jupiter.api.extension.Extension`), in which case it applies to all
tests and is loaded during discovery as an attribute:auto-detected[] extension.
* _Scoped_ to a test method or class via `@ExtendWith(MyAsyncReturnValueHandler.class)`, or through a
composed indirection annotation, for example:

[source,java,indent=0]
.A composed indirection annotation for a custom return type
----
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(MyAsyncReturnValueHandler.class)
@interface MapMyPromise {
}

@Test
@MapMyPromise
MyPromise<?> myTest() {
return MyPromise.completed("done");
}
----

NOTE: A handler must be recognizable _during discovery_ in order for the engine to classify a method
with a custom return type as a test. This is the case for a globally (via the `ServiceLoader`)
registered handler and for a handler registered on the test method or class via `{ExtendWith}`. A
handler registered only through a `@RegisterExtension` field is not visible during discovery, so a
test method whose return type is recognized only that way is rejected and not executed.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ repository on GitHub.
* `ConsoleLauncher` with `--details=verbose` now reports on each test atomically and
indents its output correctly when tests are executed in parallel. Previously, the
reported output from different tests could interleave.
* New `*Async` methods were added to the hierarchical execution SPI. The
`org.junit.platform.engine.support.hierarchical` package now offers
`AsyncTestExecution`, a non-blocking `ReactiveResourceGate`
(`ResourceLock#acquireAsync()`), and `submitAsync`/`invokeAllAsync` on
`HierarchicalTestExecutorService`, enabling reactive (e.g. `CompletionStage`-based)
test execution without parking threads.

[[v6.2.0-M1-junit-jupiter]]
=== JUnit Jupiter
Expand Down Expand Up @@ -71,6 +77,38 @@ repository on GitHub.
and `@DisabledOnOs`.
* Failures caused by `@Timeout` expirations now include a hint about enabling
xref:writing-tests/timeouts.adoc#debugging-thread-dump[thread dumps].
* A `@Test` method may now return type:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletionStage.html[`CompletionStage`]
(or a `CompletableFuture` or `Future`). The test is considered finished only once
the returned signal completes, and `@Timeout` applies to the entire asynchronous
work rather than only the synchronous method call. Lifecycle methods
(`@BeforeAll`, `@AfterAll`, `@BeforeEach`, `@AfterEach`) may also return such a signal, whose
completion is awaited before the next phase proceeds. See
xref:writing-tests/test-classes-and-methods.adoc#async-test-methods[Test Methods].
* New `AsyncInvocationInterceptor` API that allows `Extension`s to intercept test, lifecycle, and
dynamic-test invocations without blocking a thread while the intercepted method performs
asynchronous work. The legacy `InvocationInterceptor` remains fully supported and is
automatically adapted to the asynchronous invocation pipeline.* New experimental `EarlyExtension` SPI and
`AsyncReturnValueHandler` extension API that let the engine recognize user-defined asynchronous
return types (for example a `MyPromise<T>`) and await them by mapping the returned value to a
`CompletionStage`. A handler can be registered globally via the `ServiceLoader` (auto-detection) or
on a test method/class via `@ExtendWith`, including through a composed indirection annotation.
See xref:writing-tests/test-classes-and-methods.adoc#custom-async-return-types[Custom Asynchronous Return Types].
* `@Timeout` now applies to the whole asynchronous duration of a test method that returns a
`CompletionStage`/`CompletableFuture`/`Future`: the awaited body is bounded via
`CompletableFuture.orTimeout`, and for `SEPARATE_THREAD` the completing thread is interrupted on
expiry where possible (falling back to a non-preemptive timeout otherwise, so a timed-out test is
never reported as passed).
* New experimental *reactive execution lane*: when `junit.jupiter.execution.parallel.reactive.enabled=true`
is set alongside `junit.jupiter.execution.parallel.enabled=true`, the thread-based parallel lane
schedules nodes cooperatively via `CompletionStage` composition, so waits on concurrency permits and
resource locks hand off the worker thread instead of parking it.
* New experimental *standalone cooperative lane*: `junit.jupiter.execution.reactive.enabled=true`
enables cooperative execution without requiring the thread-based parallel feature or any thread
configuration. An async-returning test method (`CompletionStage`/`CompletableFuture`/`Future`) is
implicitly `CONCURRENT` and overlaps with other async tests without a dedicated thread, while
synchronous/`void` test methods are implicitly `SAME_THREAD` and run sequentially in discovery
order. An explicit `@Execution` always wins. See
xref:writing-tests/parallel-execution.adoc#config-reactive-execution[Reactive Execution].


[[v6.2.0-M1-junit-vintage]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
*
* <h2>Method Signatures</h2>
*
* <p>{@code @AfterAll} methods must have a {@code void} return type and must
* be {@code static} unless the test class is annotated with
* <p>{@code @AfterAll} methods must have a {@code void} return type, or return
* a {@link java.util.concurrent.CompletionStage}/{@link java.util.concurrent.CompletableFuture}/
* {@link java.util.concurrent.Future} whose completion is awaited before the test class is
* reported as finished, and must be {@code static} unless the test class is annotated with
* {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. In addition,
* {@code @AfterAll} methods may optionally declare parameters to be resolved by
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
*
* <h2>Method Signatures</h2>
*
* <p>{@code @AfterEach} methods must have a {@code void} return type and must
* not be {@code static}. In addition, {@code @AfterEach} methods may optionally
* <p>{@code @AfterEach} methods must have a {@code void} return type, or return
* a {@link java.util.concurrent.CompletionStage}/{@link java.util.concurrent.CompletableFuture}/
* {@link java.util.concurrent.Future} whose completion is awaited before the associated test is
* reported as finished, and must not be {@code static}. In addition, {@code @AfterEach} methods may
* optionally
* declare parameters to be resolved by
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
*
* <h2>Method Signatures</h2>
*
* <p>{@code @BeforeAll} methods must have a {@code void} return type and must
* be {@code static} unless the test class is annotated with
* <p>{@code @BeforeAll} methods must have a {@code void} return type, or return
* a {@link java.util.concurrent.CompletionStage}/{@link java.util.concurrent.CompletableFuture}/
* {@link java.util.concurrent.Future} whose completion is awaited before the test class is
* executed, and must be {@code static} unless the test class is annotated with
* {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. In addition,
* {@code @BeforeAll} methods may optionally declare parameters to be resolved by
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
*
* <h2>Method Signatures</h2>
*
* <p>{@code @BeforeEach} methods must have a {@code void} return type and must
* not be {@code static}. In addition, {@code @BeforeEach} methods may optionally
* <p>{@code @BeforeEach} methods must have a {@code void} return type, or return
* a {@link java.util.concurrent.CompletionStage}/{@link java.util.concurrent.CompletableFuture}/
* {@link java.util.concurrent.Future} whose completion is awaited before the associated test is
* executed, and must not be {@code static}. In addition, {@code @BeforeEach} methods may optionally
* declare parameters to be resolved by
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,35 @@ public final class Constants {
*/
public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled";

/**
* Property name used to enable the reactive (cooperative) execution lane:
* {@value}
*
* <p>When enabled alongside {@link #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME},
* the engine schedules nodes cooperatively via completion stages instead of
* one thread per in-flight node. By default this is disabled.
*
* @since 6.2
*/
@API(status = EXPERIMENTAL, since = "6.2")
public static final String PARALLEL_EXECUTION_REACTIVE_PROPERTY_NAME = "junit.jupiter.execution.parallel.reactive.enabled";

/**
* Property name used to enable the standalone cooperative execution lane:
* {@value}
*
* <p>Unlike {@link #PARALLEL_EXECUTION_REACTIVE_PROPERTY_NAME}, this does not
* require {@link #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME} to be enabled: it
* activates the cooperative (reactive) lane so that test methods returning an
* asynchronous completion signal overlap without a dedicated thread, while
* synchronous test methods keep running sequentially in discovery order. By
* default this is disabled.
*
* @since 6.2
*/
@API(status = EXPERIMENTAL, since = "6.2")
public static final String JUPITER_EXECUTION_REACTIVE_PROPERTY_NAME = "junit.jupiter.execution.reactive.enabled";

/**
* Property name used to set the default test execution mode: {@value}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
* method.
*
* <p>{@code @Test} methods must not be {@code private} or {@code static} and
* must not return a value.
* either return {@code void} or an async-completable
* {@link java.util.concurrent.CompletionStage}/{@link java.util.concurrent.CompletableFuture}/
* {@link java.util.concurrent.Future}, whose completion is awaited to determine the test's outcome.
*
* <p>{@code @Test} methods may optionally declare parameters to be resolved by
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}.
Expand Down
Loading
Loading