Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ repository on GitHub.
and a usage example.
* Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as
JUnit Jupiter, Spock, and Cucumber.
* Provide cancellation support for Suite engine
* Introduce `TestTask.getTestDescriptor()` method for use in
`HierarchicalTestExecutorService` implementations.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,9 @@ Cancelling tests relies on <<test-engines>> checking and responding to the
`Launcher` will also check the token and cancel test execution when multiple test engines
are present at runtime.

At the time of writing the following test engines support cancellation:
At the time of writing, the following test engines support cancellation:

* `{junit-jupiter-engine}`
* `{junit-platform-suite-engine}`
* Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber
====
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,11 @@ LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, Uniq
return discoveryOrchestrator.discover(discoveryRequest, parentId);
}

TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult,
EngineExecutionListener parentEngineExecutionListener,
NamespacedHierarchicalStore<Namespace> requestLevelStore) {
TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener executionListener,
NamespacedHierarchicalStore<Namespace> requestLevelStore, CancellationToken cancellationToken) {
SummaryGeneratingListener listener = new SummaryGeneratingListener();
// TODO #4725 Provide cancellation support for Suite engine
executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore,
CancellationToken.disabled());
executionOrchestrator.execute(discoveryResult, executionListener, listener, requestLevelStore,
cancellationToken);
return listener.getSummary();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.engine.CancellationToken;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.DiscoveryIssue;
import org.junit.platform.engine.EngineDiscoveryListener;
Expand Down Expand Up @@ -148,20 +149,26 @@ private static String getSuiteDisplayName(Class<?> testClass) {
// @formatter:on
}

void execute(EngineExecutionListener parentEngineExecutionListener,
NamespacedHierarchicalStore<Namespace> requestLevelStore) {
parentEngineExecutionListener.executionStarted(this);
void execute(EngineExecutionListener executionListener, NamespacedHierarchicalStore<Namespace> requestLevelStore,
CancellationToken cancellationToken) {

if (cancellationToken.isCancellationRequested()) {
executionListener.executionSkipped(this, "Execution cancelled");
return;
}

executionListener.executionStarted(this);
ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector();

executeBeforeSuiteMethods(throwableCollector);

TestExecutionSummary summary = executeTests(parentEngineExecutionListener, requestLevelStore,
TestExecutionSummary summary = executeTests(executionListener, requestLevelStore, cancellationToken,
throwableCollector);

executeAfterSuiteMethods(throwableCollector);

TestExecutionResult testExecutionResult = computeTestExecutionResult(summary, throwableCollector);
parentEngineExecutionListener.executionFinished(this, testExecutionResult);
executionListener.executionFinished(this, testExecutionResult);
}

private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) {
Expand All @@ -176,8 +183,10 @@ private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) {
}
}

private @Nullable TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener,
NamespacedHierarchicalStore<Namespace> requestLevelStore, ThrowableCollector throwableCollector) {
private @Nullable TestExecutionSummary executeTests(EngineExecutionListener executionListener,
NamespacedHierarchicalStore<Namespace> requestLevelStore, CancellationToken cancellationToken,
ThrowableCollector throwableCollector) {

if (throwableCollector.isNotEmpty()) {
return null;
}
Expand All @@ -187,7 +196,9 @@ private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) {
// be pruned accordingly.
LauncherDiscoveryResult discoveryResult = requireNonNull(this.launcherDiscoveryResult).withRetainedEngines(
getChildren()::contains);
return requireNonNull(launcher).execute(discoveryResult, parentEngineExecutionListener, requestLevelStore);

return requireNonNull(launcher).execute(discoveryResult, executionListener, requestLevelStore,
cancellationToken);
}

private void executeAfterSuiteMethods(ThrowableCollector throwableCollector) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Optional;

import org.apiguardian.api.API;
import org.junit.platform.engine.CancellationToken;
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.EngineExecutionListener;
import org.junit.platform.engine.ExecutionRequest;
Expand Down Expand Up @@ -66,14 +67,15 @@ public void execute(ExecutionRequest request) {
SuiteEngineDescriptor suiteEngineDescriptor = (SuiteEngineDescriptor) request.getRootTestDescriptor();
EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener();
NamespacedHierarchicalStore<Namespace> requestLevelStore = request.getStore();
CancellationToken cancellationToken = request.getCancellationToken();

engineExecutionListener.executionStarted(suiteEngineDescriptor);

// @formatter:off
suiteEngineDescriptor.getChildren()
.stream()
.map(SuiteTestDescriptor.class::cast)
.forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore));
.forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore, cancellationToken));
// @formatter:on
engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.platform.suite.engine;

import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
Expand All @@ -22,6 +23,8 @@
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.started;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
Expand All @@ -32,13 +35,15 @@

import java.nio.file.Path;

import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.engine.descriptor.ClassTestDescriptor;
import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor;
import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.engine.CancellationToken;
import org.junit.platform.engine.DiscoveryIssue;
import org.junit.platform.engine.DiscoveryIssue.Severity;
import org.junit.platform.engine.EngineExecutionListener;
Expand All @@ -51,6 +56,8 @@
import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.PostDiscoveryFilter;
import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders;
import org.junit.platform.suite.api.AfterSuite;
import org.junit.platform.suite.api.BeforeSuite;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.engine.testcases.ConfigurationSensitiveTestCase;
Expand Down Expand Up @@ -628,11 +635,6 @@ void discoveryIssueOfNestedTestEnginesAreReported() throws Exception {
// @formatter:on
}

@Suite
@SelectClasses(SingleTestTestCase.class)
abstract private static class AbstractPrivateSuite {
}

@Test
void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() {
UniqueId engineId = UniqueId.forEngine(SuiteEngineDescriptor.ENGINE_ID);
Expand All @@ -643,15 +645,88 @@ void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() {

EngineExecutionListener listener = mock(EngineExecutionListener.class);
NamespacedHierarchicalStore<Namespace> requestLevelStore = NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore();
var cancellationToken = CancellationToken.create();

ExecutionRequest request = mock();
when(request.getRootTestDescriptor()).thenReturn(engineDescriptor);
when(request.getEngineExecutionListener()).thenReturn(listener);
when(request.getStore()).thenReturn(requestLevelStore);
when(request.getCancellationToken()).thenReturn(cancellationToken);

new SuiteTestEngine().execute(request);

verify(mockDescriptor).execute(same(listener), same(requestLevelStore));
verify(mockDescriptor).execute(same(listener), same(requestLevelStore), same(cancellationToken));
}

@Test
void reportsSuiteClassAsSkippedWhenCancelledBeforeExecution() {
CancellingSuite.cancellationToken = CancellationToken.create();
try {
var testKit = EngineTestKit.engine(ENGINE_ID) //
.selectors(selectClass(CancellingSuite.class), selectClass(SelectMethodsSuite.class)) //
.cancellationToken(CancellingSuite.cancellationToken);

var results = testKit.execute();

results.allEvents() //
.assertStatistics(stats -> stats.started(3).succeeded(2).aborted(1).skipped(2)) //
.assertEventsMatchLooselyInOrder( //
event(container(CancellingSuite.class), started()), //
event(container(SingleTestTestCase.class), skippedWithReason("Execution cancelled")), //
event(container(CancellingSuite.class), finishedSuccessfully()), //
event(container(SelectMethodsSuite.class), skippedWithReason("Execution cancelled")) //
);
}
finally {
CancellingSuite.cancellationToken = null;
}
}

@Test
void reportsChildrenOfEnginesInSuiteAsSkippedWhenCancelledDuringExecution() {
CancellingSuite.cancellationToken = CancellationToken.create();
try {
var testKit = EngineTestKit.engine(ENGINE_ID) //
.selectors(selectClass(CancellingSuite.class)) //
.cancellationToken(CancellingSuite.cancellationToken);

var results = testKit.execute();

results.allEvents().assertThatEvents() //
.haveExactly(1, event(container(SingleTestTestCase.class),
skippedWithReason("Execution cancelled"))).haveExactly(0, event(test(), started()));

assertThat(CancellingSuite.afterCalled) //
.describedAs("@AfterSuite method was called") //
.isTrue();
}
finally {
CancellingSuite.cancellationToken = null;
}
}

// -----------------------------------------------------------------------------------------------------------------

static class CancellingSuite extends SelectClassesSuite {

static @Nullable CancellationToken cancellationToken;
static boolean afterCalled;

@BeforeSuite
static void beforeSuite() {
CancellingSuite.afterCalled = false;
requireNonNull(cancellationToken).cancel();
}

@AfterSuite
static void afterSuite() {
afterCalled = true;
}
}

@Suite
@SelectClasses(SingleTestTestCase.class)
abstract private static class AbstractPrivateSuite {
}

@Suite
Expand Down