Skip to content

Draft support for cancelling test execution #4709

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from

Conversation

marcphilipp
Copy link
Member

Overview

  • Introduce CancellationToken interface and default implementation
  • Add Launcher.execute(TestPlan, CancellationToken, ...) method
  • Add ExecutionRequest.getCancellationToken() method
  • Implement support for cancellation for engines extending
    HierarchicalTestEngine

Eventually resolves #1880.


I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

* @since 6.0
*/
@API(status = EXPERIMENTAL, since = "6.0")
void execute(TestPlan testPlan, CancellationToken cancellationToken, TestExecutionListener... listeners);
Copy link
Member Author

@marcphilipp marcphilipp Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New API for build tools and IDEs

@@ -171,4 +174,8 @@ public NamespacedHierarchicalStore<Namespace> getStore() {
"No NamespacedHierarchicalStore was configured for this request");
}

public CancellationToken getCancellationToken() {
Copy link
Member Author

@marcphilipp marcphilipp Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New API for TestEngine implementors

Comment on lines +145 to +148
private SkipResult checkWhetherSkipped() throws Exception {
return taskContext.cancellationToken().isCancellationRequested() //
? CANCELLED_SKIP_RESULT //
: node.shouldBeSkipped(requiredContext());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support for engines extending HierarchicalTestEngine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Cucumber this means that cancellation will work out of the box. 👍

Comment on lines 233 to 236
if (cancellationToken.isCancellationRequested()) {
listener.executionSkipped(engineDescriptor, "Cancellation of execution requested");
return;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skip entire engine if cancellation has already been requested. This avoid the overhead of initializing an engine's execution only to skip its root node. Moreover, this is at least a stop-gap solution for engines not supporting cancellation at all.

Comment on lines +50 to +58
results.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(test("first"), started()), //
event(test("first"), reportEntry(Map.of("cancelled", "true"))), //
event(test("first"), finishedSuccessfully()), //
event(test("second"), skippedWithReason("Test execution cancelled")), //
event(container(testClass), finishedSuccessfully()), //
event(engine(), finishedSuccessfully()));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test demonstrates what happens if execution is cancelled during execution of the first test. The second test is reported as skipped and all later test classes would be as well.

* Introduce `CancellationToken` interface and default implementation
* Add `Launcher.execute(TestPlan, CancellationToken, ...)` method
* Add `ExecutionRequest.getCancellationToken()` method
* Implement support for cancellation for engines extending
  `HierarchicalTestEngine`

Eventually resolves #1880.
@marcphilipp marcphilipp force-pushed the marc/1880-execution-cancellation branch from 8ec7807 to 1172d71 Compare July 1, 2025 15:59
@marcphilipp marcphilipp force-pushed the marc/1880-execution-cancellation branch from 8eb0df7 to 45ad996 Compare July 3, 2025 06:38
@marcphilipp marcphilipp force-pushed the marc/1880-execution-cancellation branch from 45ad996 to 617b66e Compare July 3, 2025 06:39
@@ -171,4 +174,8 @@ public NamespacedHierarchicalStore<Namespace> getStore() {
"No NamespacedHierarchicalStore was configured for this request");
}

public CancellationToken getCancellationToken() {
return Preconditions.notNull(this.cancellationToken, "No CancellationToken was configured for this request");
Copy link
Contributor

@mpkorstanje mpkorstanje Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had some difficulty inferring from what code how I could safely use the cancellation token. Instead of making the cancellationToken nullable, I would expect a default implementation to be provided in the deprecated constructor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can instead return a default one here since it has a sensible default.

@@ -73,7 +73,7 @@ public void execute(ExecutionRequest request) {
suiteEngineDescriptor.getChildren()
.stream()
.map(SuiteTestDescriptor.class::cast)
.forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore));
.forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore, request.getCancellationToken()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail if a request was created with the deprecated constructor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExecutionRequest really should be created by anyone outside of the Platform.

Since many tools; for example, Maven Surefire; do not report events for
engine-level descriptors, reporting their children as skipped increases
compatibility with such tools.
marcphilipp added a commit that referenced this pull request Jul 5, 2025
The new `Launcher.execute(LauncherExecutionRequest)` method paves the
road for adding additional parameters to test execution without having
to add additional overloads of `execute()`. For example, for #1880
we will likely need to introduce a `CancellationToken` (as drafted
in #4709). Instead of having to add two new overloads, such changes will
then be easy because only `LauncherExecutionRequest` will need to be
changed.

`LauncherExecutionRequestBuilder` provides a fluent API for constructing
execution requests starting either with a `TestPlan` or a
`LauncherDiscoveryRequest`. In addition,
`LauncherDiscoveryRequestBuilder.forExecution()` is a convenience method
to create an execution request from a discovery request.
@marcphilipp
Copy link
Member Author

Superseded by #4728 and #4725.

@marcphilipp marcphilipp closed this Jul 7, 2025
@marcphilipp marcphilipp deleted the marc/1880-execution-cancellation branch July 7, 2025 07:47
marcphilipp added a commit that referenced this pull request Jul 7, 2025
The new `Launcher.execute(LauncherExecutionRequest)` method paves the
road for adding additional parameters to test execution without having
to add additional overloads of `execute()`. For example, for #1880
we will likely need to introduce a `CancellationToken` (as drafted
in #4709). Instead of having to add two new overloads, such changes will
then be easy because only `LauncherExecutionRequest` will need to be
changed.

`LauncherExecutionRequestBuilder` provides a fluent API for constructing
execution requests starting either with a `TestPlan` or a
`LauncherDiscoveryRequest`. In addition,
`LauncherDiscoveryRequestBuilder.forExecution()` is a convenience method
to create an execution request from a discovery request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce support for cancelling a running execution to the Launcher API
2 participants