diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java new file mode 100644 index 000000000000..fa25414529c6 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2025 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.platform.engine; + +import org.apiguardian.api.API; + +/** + * Token that should be checked to determine whether an operation was requested + * to be cancelled. + * + *

For example, this is used by {@link org.junit.platform.engine.TestEngine} + * implementations to determine whether + * + *

This interface is not intended to be implemented by clients. + * + * @since 6.0 + * @see ExecutionRequest#getCancellationToken() + */ +@API(status = API.Status.EXPERIMENTAL, since = "6.0") +public interface CancellationToken { + + static CancellationToken create() { + return new DefaultCancellationToken(); + } + + boolean isCancellationRequested(); + + void cancel(); + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultCancellationToken.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultCancellationToken.java new file mode 100644 index 000000000000..b354baa3a4ed --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultCancellationToken.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2025 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.platform.engine; + +import java.util.concurrent.atomic.AtomicBoolean; + +class DefaultCancellationToken implements CancellationToken { + + private final AtomicBoolean cancelled = new AtomicBoolean(); + + @Override + public boolean isCancellationRequested() { + return cancelled.get(); + } + + @Override + public void cancel() { + cancelled.set(true); + } +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java index 620f6057b94b..3b23eb59c85c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java @@ -43,21 +43,21 @@ public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; - private final @Nullable OutputDirectoryProvider outputDirectoryProvider; - private final @Nullable NamespacedHierarchicalStore requestLevelStore; + private final @Nullable CancellationToken cancellationToken; @Deprecated @API(status = DEPRECATED, since = "1.11") public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { - this(rootTestDescriptor, engineExecutionListener, configurationParameters, null, null); + this(rootTestDescriptor, engineExecutionListener, configurationParameters, null, null, null); } private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, @Nullable OutputDirectoryProvider outputDirectoryProvider, - @Nullable NamespacedHierarchicalStore requestLevelStore) { + @Nullable NamespacedHierarchicalStore requestLevelStore, + @Nullable CancellationToken cancellationToken) { this.rootTestDescriptor = Preconditions.notNull(rootTestDescriptor, "rootTestDescriptor must not be null"); this.engineExecutionListener = Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); @@ -65,6 +65,7 @@ private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListe "configurationParameters must not be null"); this.outputDirectoryProvider = outputDirectoryProvider; this.requestLevelStore = requestLevelStore; + this.cancellationToken = cancellationToken; } /** @@ -105,11 +106,13 @@ public static ExecutionRequest create(TestDescriptor rootTestDescriptor, @API(status = INTERNAL, since = "1.13") public static ExecutionRequest create(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, - OutputDirectoryProvider outputDirectoryProvider, NamespacedHierarchicalStore requestLevelStore) { + OutputDirectoryProvider outputDirectoryProvider, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters, Preconditions.notNull(outputDirectoryProvider, "outputDirectoryProvider must not be null"), - Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null")); + Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"), + Preconditions.notNull(cancellationToken, "cancellationToken must not be null")); } /** @@ -171,4 +174,8 @@ public NamespacedHierarchicalStore getStore() { "No NamespacedHierarchicalStore was configured for this request"); } + public CancellationToken getCancellationToken() { + return Preconditions.notNull(this.cancellationToken, "No CancellationToken was configured for this request"); + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java index 68549dbf632a..888e7b30384c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java @@ -13,6 +13,7 @@ import java.util.concurrent.Future; import org.jspecify.annotations.Nullable; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -51,8 +52,9 @@ class HierarchicalTestExecutor { TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(rootTestDescriptor); + CancellationToken cancellationToken = this.request.getCancellationToken(); NodeTestTaskContext taskContext = new NodeTestTaskContext(executionListener, this.executorService, - this.throwableCollectorFactory, executionAdvisor); + this.throwableCollectorFactory, executionAdvisor, cancellationToken); NodeTestTask rootTestTask = new NodeTestTask<>(taskContext, rootTestDescriptor); rootTestTask.setParentContext(this.rootContext); return this.executorService.submit(rootTestTask); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java index 5249236fd729..1937e1b77cc7 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java @@ -46,6 +46,9 @@ class NodeTestTask implements TestTask { private static final Logger logger = LoggerFactory.getLogger(NodeTestTask.class); + + private static final SkipResult CANCELLED_SKIP_RESULT = SkipResult.skip("Test execution cancelled"); + private static final Runnable NOOP = () -> { }; @@ -101,7 +104,7 @@ public void execute() { throwableCollector = taskContext.throwableCollectorFactory().create(); prepare(); if (throwableCollector.isEmpty()) { - checkWhetherSkipped(); + throwableCollector.execute(() -> skipResult = checkWhetherSkipped()); } if (throwableCollector.isEmpty() && !requiredSkipResult().isSkipped()) { executeRecursively(); @@ -139,8 +142,10 @@ private void prepare() { parentContext = null; } - private void checkWhetherSkipped() { - requiredThrowableCollector().execute(() -> skipResult = node.shouldBeSkipped(requiredContext())); + private SkipResult checkWhetherSkipped() throws Exception { + return taskContext.cancellationToken().isCancellationRequested() // + ? CANCELLED_SKIP_RESULT // + : node.shouldBeSkipped(requiredContext()); } private void executeRecursively() { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java index 005d8ab6d02c..d5dbc34c5e09 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java @@ -10,19 +10,22 @@ package org.junit.platform.engine.support.hierarchical; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; /** * @since 1.3.1 */ record NodeTestTaskContext(EngineExecutionListener listener, HierarchicalTestExecutorService executorService, - ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor) { + ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor, + CancellationToken cancellationToken) { NodeTestTaskContext withListener(EngineExecutionListener listener) { if (this.listener == listener) { return this; } - return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor); + return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor, + cancellationToken); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 75a5f07deabf..4c25ec3939cc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -10,9 +10,11 @@ package org.junit.platform.launcher; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.CancellationToken; /** * The {@code Launcher} API is the main entry point for client code that @@ -108,6 +110,37 @@ public interface Launcher { */ void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners); + /** + * Execute a {@link TestPlan} which is built according to the supplied + * {@link LauncherDiscoveryRequest} by querying all registered engines and + * collecting their results, and notify + * {@linkplain #registerTestExecutionListeners registered listeners} about + * the progress and results of the execution. + * + *

Supplied test execution listeners are registered in addition to already + * registered listeners but only for the supplied launcher discovery request. + * + *

Additionally, it's possible to request cancellation of the started + * execution via the supplied {@link CancellationToken}. + * + * @apiNote Calling this method will cause test discovery to be executed for + * all registered engines. If the same {@link LauncherDiscoveryRequest} was + * previously passed to {@link #discover(LauncherDiscoveryRequest)}, you + * should instead call {@link #execute(TestPlan, TestExecutionListener...)} + * and pass the already acquired {@link TestPlan} to avoid the potential + * performance degradation (e.g., classpath scanning) of running test + * discovery twice. + * + * @param launcherDiscoveryRequest the launcher discovery request; never {@code null} + * @param cancellationToken the token used to request cancellation of the + * started execution; never {@code null} + * @param listeners additional test execution listeners; never {@code null} + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, CancellationToken cancellationToken, + TestExecutionListener... listeners); + /** * Execute the supplied {@link TestPlan} and notify * {@linkplain #registerTestExecutionListeners registered listeners} about @@ -127,4 +160,28 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); + /** + * Execute the supplied {@link TestPlan} and notify + * {@linkplain #registerTestExecutionListeners registered listeners} about + * the progress and results of the execution. + * + *

Supplied test execution listeners are registered in addition to + * already registered listeners but only for the execution of the supplied + * test plan. + * + *

Additionally, it's possible to request cancellation of the started + * execution via the supplied {@link CancellationToken}. + * + * @apiNote The supplied {@link TestPlan} must not have been executed + * previously. + * + * @param testPlan the test plan to execute; never {@code null} + * @param cancellationToken the token used to request cancellation of the + * started execution; never {@code null} + * @param listeners additional test execution listeners; never {@code null} + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + void execute(TestPlan testPlan, CancellationToken cancellationToken, TestExecutionListener... listeners); + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 509467bf108a..9cb3d611f225 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -18,6 +18,7 @@ import java.util.Collection; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @@ -85,28 +86,42 @@ public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { @Override public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { + execute(discoveryRequest, CancellationToken.create(), listeners); + } + + @Override + public void execute(LauncherDiscoveryRequest discoveryRequest, CancellationToken cancellationToken, + TestExecutionListener... listeners) { + Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - execute(InternalTestPlan.from(discover(discoveryRequest, EXECUTION)), listeners); + execute(InternalTestPlan.from(discover(discoveryRequest, EXECUTION)), cancellationToken, listeners); } @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { + execute(testPlan, CancellationToken.create(), listeners); + } + + @Override + public void execute(TestPlan testPlan, CancellationToken cancellationToken, TestExecutionListener... listeners) { Preconditions.notNull(testPlan, "TestPlan must not be null"); Preconditions.condition(testPlan instanceof InternalTestPlan, "TestPlan was not returned by this Launcher"); + Preconditions.notNull(cancellationToken, "CancellationToken must not be null"); Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - execute((InternalTestPlan) testPlan, listeners); + execute((InternalTestPlan) testPlan, cancellationToken, listeners); } private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, LauncherPhase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } - private void execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners) { + private void execute(InternalTestPlan internalTestPlan, CancellationToken cancellationToken, + TestExecutionListener[] listeners) { try (NamespacedHierarchicalStore requestLevelStore = createRequestLevelStore()) { - executionOrchestrator.execute(internalTestPlan, requestLevelStore, listeners); + executionOrchestrator.execute(internalTestPlan, requestLevelStore, cancellationToken, listeners); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index b128e09b1258..a842233660a5 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -17,6 +17,7 @@ import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; @@ -121,10 +122,22 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu throw new PreconditionViolationException("Launcher session has already been closed"); } + @Override + public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, CancellationToken cancellationToken, + TestExecutionListener... listeners) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } + @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + + @Override + public void execute(TestPlan testPlan, CancellationToken cancellationToken, + TestExecutionListener... listeners) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 3cc6be2316f0..41af9e39d6fb 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.core; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -47,9 +48,19 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu delegate.execute(launcherDiscoveryRequest, listeners); } + @Override + public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, CancellationToken cancellationToken, + TestExecutionListener... listeners) { + delegate.execute(launcherDiscoveryRequest, cancellationToken, listeners); + } + @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } + @Override + public void execute(TestPlan testPlan, CancellationToken cancellationToken, TestExecutionListener... listeners) { + delegate.execute(testPlan, cancellationToken, listeners); + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index 0536b5134736..f1328cc0c44c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -23,6 +23,7 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; @@ -56,13 +57,13 @@ public EngineExecutionOrchestrator() { } void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore, - TestExecutionListener... listeners) { + CancellationToken cancellationToken, TestExecutionListener... listeners) { ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters(); ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution( listeners); withInterceptedStreams(configurationParameters, testExecutionListenerListeners, testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener, - requestLevelStore)); + requestLevelStore, cancellationToken)); } /** @@ -72,20 +73,22 @@ void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore) { + TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); Preconditions.notNull(testExecutionListener, "testExecutionListener must not be null"); Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"); InternalTestPlan internalTestPlan = InternalTestPlan.from(discoveryResult); - execute(internalTestPlan, engineExecutionListener, testExecutionListener, requestLevelStore); + execute(internalTestPlan, engineExecutionListener, testExecutionListener, requestLevelStore, cancellationToken); } private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener parentEngineExecutionListener, - TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore) { + TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { internalTestPlan.markStarted(); // Do not directly pass the internal test plan to test execution listeners. @@ -100,7 +103,7 @@ private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener else { execute(discoveryResult, buildEngineExecutionListener(parentEngineExecutionListener, testExecutionListener, testPlan), - requestLevelStore); + requestLevelStore, cancellationToken); } testExecutionListener.testPlanExecutionFinished(testPlan); } @@ -159,9 +162,9 @@ private void withInterceptedStreams(ConfigurationParameters configurationParamet * discovery results} and notifies the supplied {@linkplain * EngineExecutionListener listener} of execution events. */ - @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit" }) + @API(status = INTERNAL, since = "6.0", consumers = { "org.junit.platform.testkit" }) public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener, - NamespacedHierarchicalStore requestLevelStore) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); @@ -169,7 +172,7 @@ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionList EngineExecutionListener listener = selectExecutionListener(engineExecutionListener, configurationParameters); for (TestEngine testEngine : discoveryResult.getTestEngines()) { - failOrExecuteEngine(discoveryResult, listener, testEngine, requestLevelStore); + failOrExecuteEngine(discoveryResult, listener, testEngine, requestLevelStore, cancellationToken); } } @@ -184,7 +187,8 @@ private static EngineExecutionListener selectExecutionListener(EngineExecutionLi } private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, EngineExecutionListener listener, - TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore) { + TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { EngineResultInfo engineDiscoveryResult = discoveryResult.getEngineResult(testEngine); DiscoveryIssueNotifier discoveryIssueNotifier = shouldReportDiscoveryIssues(discoveryResult) // ? engineDiscoveryResult.getDiscoveryIssueNotifier() // @@ -202,7 +206,8 @@ private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, Engine } else { executeEngine(engineDescriptor, listener, discoveryResult.getConfigurationParameters(), testEngine, - discoveryResult.getOutputDirectoryProvider(), discoveryIssueNotifier, requestLevelStore); + discoveryResult.getOutputDirectoryProvider(), discoveryIssueNotifier, requestLevelStore, + cancellationToken); } } @@ -223,12 +228,21 @@ private ListenerRegistry buildListenerRegistryForExecutio private void executeEngine(TestDescriptor engineDescriptor, EngineExecutionListener listener, ConfigurationParameters configurationParameters, TestEngine testEngine, OutputDirectoryProvider outputDirectoryProvider, DiscoveryIssueNotifier discoveryIssueNotifier, - NamespacedHierarchicalStore requestLevelStore) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { + + if (cancellationToken.isCancellationRequested()) { + listener.executionStarted(engineDescriptor); + engineDescriptor.getChildren().forEach( + child -> listener.executionSkipped(child, "Cancellation of execution requested")); + listener.executionFinished(engineDescriptor, TestExecutionResult.aborted(null)); + return; + } + OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, engineDescriptor); try { testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters, - outputDirectoryProvider, requestLevelStore)); + outputDirectoryProvider, requestLevelStore, cancellationToken)); discoveryIssueNotifier.logNonCriticalIssues(testEngine); delayingListener.reportEngineOutcome(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index cb12eafb7282..3457d4d050e0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -14,6 +14,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; @@ -67,6 +68,14 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu } } + @Override + public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, CancellationToken cancellationToken, + TestExecutionListener... listeners) { + try (LauncherSession session = createSession()) { + session.getLauncher().execute(launcherDiscoveryRequest, cancellationToken, listeners); + } + } + @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { try (LauncherSession session = createSession()) { @@ -74,6 +83,13 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } + @Override + public void execute(TestPlan testPlan, CancellationToken cancellationToken, TestExecutionListener... listeners) { + try (LauncherSession session = createSession()) { + session.getLauncher().execute(testPlan, cancellationToken, listeners); + } + } + private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, this.launcherFactory); diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java index 640d90f984e1..88dfdbf8fecc 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java @@ -16,6 +16,7 @@ import java.util.Set; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; @@ -60,9 +61,10 @@ LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, Uniq TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener parentEngineExecutionListener, - NamespacedHierarchicalStore requestLevelStore) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { SummaryGeneratingListener listener = new SummaryGeneratingListener(); - executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore); + executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore, + cancellationToken); return listener.getSummary(); } diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java index 356fe4e5ff17..1cbcb4089f46 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java @@ -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; @@ -149,13 +150,13 @@ private static String getSuiteDisplayName(Class testClass) { } void execute(EngineExecutionListener parentEngineExecutionListener, - NamespacedHierarchicalStore requestLevelStore) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { parentEngineExecutionListener.executionStarted(this); ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector(); executeBeforeSuiteMethods(throwableCollector); - TestExecutionSummary summary = executeTests(parentEngineExecutionListener, requestLevelStore, + TestExecutionSummary summary = executeTests(parentEngineExecutionListener, requestLevelStore, cancellationToken, throwableCollector); executeAfterSuiteMethods(throwableCollector); @@ -177,7 +178,8 @@ private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) { } private @Nullable TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener, - NamespacedHierarchicalStore requestLevelStore, ThrowableCollector throwableCollector) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken, + ThrowableCollector throwableCollector) { if (throwableCollector.isNotEmpty()) { return null; } @@ -187,7 +189,8 @@ 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, parentEngineExecutionListener, requestLevelStore, + cancellationToken); } private void executeAfterSuiteMethods(ThrowableCollector throwableCollector) { diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java index d75cf8c3dfbf..9a95190fe0db 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java @@ -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())); // @formatter:on engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful()); } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java index b12e781325bb..e7a4ef26556c 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -12,6 +12,7 @@ import static java.util.Collections.emptySet; import static java.util.Collections.singleton; +import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -30,6 +31,7 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; @@ -244,16 +246,18 @@ public static EngineExecutionResults execute(TestEngine testEngine, LauncherDisc Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); ExecutionRecorder executionRecorder = new ExecutionRecorder(); - executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder); + executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder, CancellationToken.create()); return executionRecorder.getExecutionResults(); } private static void executeUsingLauncherOrchestration(TestEngine testEngine, - LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { + LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener, + CancellationToken cancellationToken) { LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); - withRequestLevelStore(store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store)); + withRequestLevelStore( + store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store, cancellationToken)); } private static void withRequestLevelStore(Consumer> action) { @@ -308,8 +312,11 @@ public static final class Builder { private final LauncherDiscoveryRequestBuilder requestBuilder = LauncherDiscoveryRequestBuilder.request() // .enableImplicitConfigurationParameters(false) // .outputDirectoryProvider(DisabledOutputDirectoryProvider.INSTANCE); + private final TestEngine testEngine; + private @Nullable CancellationToken cancellationToken; + private Builder(TestEngine testEngine) { this.testEngine = testEngine; } @@ -430,6 +437,15 @@ public Builder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryPr return this; } + /** + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + public Builder cancellationToken(CancellationToken cancellationToken) { + this.cancellationToken = Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); + return this; + } + /** * Discover tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, @@ -464,7 +480,8 @@ public EngineDiscoveryResults discover() { public EngineExecutionResults execute() { LauncherDiscoveryRequest request = this.requestBuilder.build(); ExecutionRecorder executionRecorder = new ExecutionRecorder(); - EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder); + EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder, + requireNonNullElseGet(this.cancellationToken, CancellationToken::create)); return executionRecorder.getExecutionResults(); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java index 2fa6af237698..23752532c65f 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java @@ -74,6 +74,10 @@ public Request toRequest() { return new RunnerRequest(this.runner); } + public Runner getRunner() { + return this.runner; + } + @Override protected boolean tryToExcludeFromRunner(Description description) { boolean excluded = tryToFilterRunner(description); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java index 6a0b4756f7fd..6c48e48e81a4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -103,6 +103,10 @@ public void testSuiteFinished(Description description) { @Override public void testRunFinished(Result result) { + testRunFinished(); + } + + void testRunFinished() { reportContainerFinished(testRun.getRunnerTestDescriptor()); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java index 7e5bf6b1306a..921418c6ec58 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java @@ -11,13 +11,13 @@ package org.junit.vintage.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.UnrecoverableExceptions.rethrowIfUnrecoverable; import static org.junit.platform.engine.TestExecutionResult.failed; import org.apiguardian.api.API; -import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestExecutionResult; -import org.junit.runner.JUnitCore; +import org.junit.runner.notification.RunNotifier; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.TestSourceProvider; @@ -35,14 +35,17 @@ public RunnerExecutor(EngineExecutionListener engineExecutionListener) { } public void execute(RunnerTestDescriptor runnerTestDescriptor) { - TestRun testRun = new TestRun(runnerTestDescriptor); - JUnitCore core = new JUnitCore(); - core.addListener(new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider)); + var notifier = new RunNotifier(); + var testRun = new TestRun(runnerTestDescriptor); + var listener = new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider); + notifier.addListener(listener); try { - core.run(runnerTestDescriptor.toRequest()); + listener.testRunStarted(runnerTestDescriptor.getDescription()); + runnerTestDescriptor.getRunner().run(notifier); + listener.testRunFinished(); } catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); + rethrowIfUnrecoverable(t); reportUnexpectedFailure(testRun, runnerTestDescriptor, failed(t)); } } diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index 7044e3f5076a..81d7c376ffbd 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -943,7 +944,7 @@ private static void execute(Class testClass, EngineExecutionListener listener var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); testEngine.execute( ExecutionRequest.create(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters(), - dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore())); + dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore(), CancellationToken.create())); } private static LauncherDiscoveryRequest request(Class testClass) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 8b6b7c5ae009..02d084aa65be 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -97,6 +97,14 @@ protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequest request) return EngineTestKit.discover(this.engine, request); } + protected EngineTestKit.Builder jupiterTestEngine() { + return EngineTestKit.engine(this.engine) // + .outputDirectoryProvider(dummyOutputDirectoryProvider()) // + .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)) // + .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.INFO.name()) // + .enableImplicitConfigurationParameters(false); + } + protected static LauncherDiscoveryRequestBuilder defaultRequest() { return request() // .outputDirectoryProvider(dummyOutputDirectoryProvider()) // diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java new file mode 100644 index 000000000000..9dcd0677b50d --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2025 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.engine; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +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.reportEntry; +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 java.util.Map; + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; +import org.junit.platform.engine.CancellationToken; + +class ExecutionCancellationTests extends AbstractJupiterTestEngineTests { + + @Test + void canCancelExecutionWhileTestsAreRunning() { + var testClass = TestCase.class; + var cancellationToken = CancellationToken.create(); + + TestCase.cancellationToken = cancellationToken; + + var results = jupiterTestEngine() // + .selectors(selectClass(testClass)) // + .cancellationToken(cancellationToken) // + .execute(); + + 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())); + } + + @AfterEach + void resetCancellationToken() { + TestCase.cancellationToken = null; + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @TestMethodOrder(OrderAnnotation.class) + static class TestCase { + + static @Nullable CancellationToken cancellationToken; + + @Test + @Order(1) + void first() { + requiredCancellationToken().cancel(); + } + + @AfterEach + void afterEach(TestReporter reporter) { + reporter.publishEntry("cancelled", String.valueOf(requiredCancellationToken().isCancellationRequested())); + } + + @Test + @Order(2) + void second() { + fail("should not be called"); + } + + private CancellationToken requiredCancellationToken() { + return requireNonNull(cancellationToken); + } + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index 85fb2c028746..16df6e590b78 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; @@ -81,7 +82,7 @@ void init() { private HierarchicalTestExecutor createExecutor( HierarchicalTestExecutorService executorService) { var request = ExecutionRequest.create(root, listener, mock(ConfigurationParameters.class), - dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore()); + dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore(), CancellationToken.create()); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java index 91b8b5d73676..daed711ad368 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java @@ -38,6 +38,7 @@ 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.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; @@ -644,13 +645,15 @@ void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() { EngineExecutionListener listener = mock(EngineExecutionListener.class); NamespacedHierarchicalStore requestLevelStore = NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore(); + CancellationToken cancellationToken = CancellationToken.create(); ExecutionRequest request = ExecutionRequest.create(engineDescriptor, listener, - mock(ConfigurationParameters.class), mock(OutputDirectoryProvider.class), requestLevelStore); + mock(ConfigurationParameters.class), mock(OutputDirectoryProvider.class), requestLevelStore, + cancellationToken); new SuiteTestEngine().execute(request); - verify(mockDescriptor).execute(same(listener), same(requestLevelStore)); + verify(mockDescriptor).execute(same(listener), same(requestLevelStore), same(cancellationToken)); } @Suite diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java index dffb0255fe7d..3fbfe54c359d 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java @@ -80,7 +80,7 @@ void verifyRequestLevelStoreIsUsedInExecution() { ArgumentCaptor> storeCaptor = forClass( NamespacedHierarchicalStore.class); - verify(mockOrchestrator).execute(any(), any(), storeCaptor.capture()); + verify(mockOrchestrator).execute(any(), any(), storeCaptor.capture(), any()); assertNotNull(storeCaptor.getValue(), "Request level store should be passed to execute"); } }