Skip to content

Commit 938b8c3

Browse files
committed
Provide cancellation support for Vintage engine
Issue: #4725
1 parent 0bc2b95 commit 938b8c3

File tree

6 files changed

+108
-1
lines changed

6 files changed

+108
-1
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ repository on GitHub.
4545
now causes test execution to be cancelled after the first failed test.
4646
* Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as
4747
JUnit Jupiter, Spock, and Cucumber.
48-
* Provide cancellation support for Suite engine
48+
* Provide cancellation support for the Suite and Vintage test engines
4949
* Introduce `TestTask.getTestDescriptor()` method for use in
5050
`HierarchicalTestExecutorService` implementations.
5151

documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ are present at runtime.
387387
At the time of writing, the following test engines support cancellation:
388388
389389
* `{junit-jupiter-engine}`
390+
* `{junit-vintage-engine}`
390391
* `{junit-platform-suite-engine}`
391392
* Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber
392393
====

junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.platform.engine.EngineExecutionListener;
2020
import org.junit.platform.engine.TestExecutionResult;
2121
import org.junit.runner.notification.RunNotifier;
22+
import org.junit.runner.notification.StoppedByUserException;
2223
import org.junit.vintage.engine.descriptor.RunnerTestDescriptor;
2324
import org.junit.vintage.engine.descriptor.TestSourceProvider;
2425

@@ -38,19 +39,44 @@ public RunnerExecutor(EngineExecutionListener engineExecutionListener, Cancellat
3839
}
3940

4041
public void execute(RunnerTestDescriptor runnerTestDescriptor) {
42+
if (cancellationToken.isCancellationRequested()) {
43+
engineExecutionListener.executionSkipped(runnerTestDescriptor, "Execution cancelled");
44+
return;
45+
}
4146
var notifier = new RunNotifier();
4247
var testRun = new TestRun(runnerTestDescriptor);
4348
var listener = new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider);
4449
notifier.addListener(listener);
50+
CancellationToken.Listener cancellationListener = __ -> notifier.pleaseStop();
51+
cancellationToken.addListener(cancellationListener);
4552
try {
4653
listener.testRunStarted(runnerTestDescriptor.getDescription());
4754
runnerTestDescriptor.getRunner().run(notifier);
4855
listener.testRunFinished();
4956
}
57+
catch (StoppedByUserException e) {
58+
reportEventsForCancellation(e, testRun);
59+
}
5060
catch (Throwable t) {
5161
UnrecoverableExceptions.rethrowIfUnrecoverable(t);
5262
reportUnexpectedFailure(testRun, runnerTestDescriptor, failed(t));
5363
}
64+
finally {
65+
cancellationToken.removeListener(cancellationListener);
66+
}
67+
}
68+
69+
private void reportEventsForCancellation(StoppedByUserException exception, TestRun testRun) {
70+
testRun.getInProgressTestDescriptors().forEach(startedDescriptor -> {
71+
startedDescriptor.getChildren().forEach(child -> {
72+
if (!testRun.isFinishedOrSkipped(child)) {
73+
engineExecutionListener.executionSkipped(child, "Execution cancelled");
74+
testRun.markSkipped(child);
75+
}
76+
});
77+
engineExecutionListener.executionFinished(startedDescriptor, TestExecutionResult.aborted(exception));
78+
testRun.markFinished(startedDescriptor);
79+
});
5480
}
5581

5682
private void reportUnexpectedFailure(TestRun testRun, RunnerTestDescriptor runnerTestDescriptor,

junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ Collection<TestDescriptor> getInProgressTestDescriptorsWithSyntheticStartEvents(
8787
return result;
8888
}
8989

90+
Collection<TestDescriptor> getInProgressTestDescriptors() {
91+
List<TestDescriptor> result = new ArrayList<>(inProgressDescriptors.keySet());
92+
Collections.reverse(result);
93+
return result;
94+
}
95+
9096
boolean isDescendantOfRunnerTestDescriptor(TestDescriptor testDescriptor) {
9197
return runnerDescendants.contains(testDescriptor);
9298
}

junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.junit.jupiter.api.Test;
4545
import org.junit.jupiter.api.extension.DisabledInEclipse;
4646
import org.junit.platform.commons.util.ReflectionUtils;
47+
import org.junit.platform.engine.CancellationToken;
4748
import org.junit.platform.engine.EngineExecutionListener;
4849
import org.junit.platform.engine.ExecutionRequest;
4950
import org.junit.platform.engine.TestDescriptor;
@@ -57,13 +58,15 @@
5758
import org.junit.runner.RunWith;
5859
import org.junit.runner.Runner;
5960
import org.junit.runner.notification.RunNotifier;
61+
import org.junit.runner.notification.StoppedByUserException;
6062
import org.junit.runners.Suite;
6163
import org.junit.runners.Suite.SuiteClasses;
6264
import org.junit.vintage.engine.samples.junit3.IgnoredJUnit3TestCase;
6365
import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites;
6466
import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites;
6567
import org.junit.vintage.engine.samples.junit3.JUnit4SuiteWithIgnoredJUnit3TestCase;
6668
import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails;
69+
import org.junit.vintage.engine.samples.junit4.CancellingTestCase;
6770
import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase;
6871
import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase;
6972
import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase;
@@ -926,6 +929,32 @@ void executesJUnit4SuiteWithIgnoredJUnit3TestCase() {
926929
event(engine(), finishedSuccessfully()));
927930
}
928931

932+
@Test
933+
void supportsCancellation() {
934+
CancellingTestCase.cancellationToken = CancellationToken.create();
935+
try {
936+
var results = vintageTestEngine() //
937+
.selectors(selectClass(CancellingTestCase.class),
938+
selectClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class)) //
939+
.cancellationToken(CancellingTestCase.cancellationToken) //
940+
.execute();
941+
942+
results.allEvents().assertEventsMatchExactly( //
943+
event(engine(), started()), //
944+
event(container(CancellingTestCase.class), started()), //
945+
event(test(), started()), //
946+
event(test(), finishedWithFailure()), //
947+
event(test(), skippedWithReason("Execution cancelled")), //
948+
event(container(CancellingTestCase.class), abortedWithReason(instanceOf(StoppedByUserException.class))), //
949+
event(container(PlainJUnit4TestCaseWithSingleTestWhichFails.class),
950+
skippedWithReason("Execution cancelled")), //
951+
event(engine(), finishedSuccessfully()));
952+
}
953+
finally {
954+
CancellingTestCase.cancellationToken = null;
955+
}
956+
}
957+
929958
private static EngineExecutionResults execute(Class<?> testClass) {
930959
return execute(request(testClass));
931960
}
@@ -935,6 +964,12 @@ private static EngineExecutionResults execute(LauncherDiscoveryRequest request)
935964
return EngineTestKit.execute(new VintageTestEngine(), request);
936965
}
937966

967+
@SuppressWarnings("deprecation")
968+
private static EngineTestKit.Builder vintageTestEngine() {
969+
return EngineTestKit.engine(new VintageTestEngine()) //
970+
.enableImplicitConfigurationParameters(false);
971+
}
972+
938973
@SuppressWarnings("deprecation")
939974
private static void execute(Class<?> testClass, EngineExecutionListener listener) {
940975
var testEngine = new VintageTestEngine();
@@ -943,6 +978,7 @@ private static void execute(Class<?> testClass, EngineExecutionListener listener
943978
when(executionRequest.getRootTestDescriptor()).thenReturn(engineTestDescriptor);
944979
when(executionRequest.getEngineExecutionListener()).thenReturn(listener);
945980
when(executionRequest.getConfigurationParameters()).thenReturn(mock());
981+
when(executionRequest.getCancellationToken()).thenReturn(CancellationToken.disabled());
946982
testEngine.execute(executionRequest);
947983
}
948984

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.vintage.engine.samples.junit4;
12+
13+
import static java.util.Objects.requireNonNull;
14+
import static org.junit.Assert.fail;
15+
16+
import org.junit.Before;
17+
import org.junit.Test;
18+
import org.junit.platform.engine.CancellationToken;
19+
20+
public class CancellingTestCase {
21+
22+
public static CancellationToken cancellationToken;
23+
24+
@Before
25+
public void cancelExecution() {
26+
requireNonNull(cancellationToken).cancel();
27+
}
28+
29+
@Test
30+
public void first() {
31+
fail();
32+
}
33+
34+
@Test
35+
public void second() {
36+
fail();
37+
}
38+
}

0 commit comments

Comments
 (0)