Skip to content

Commit 5c64fce

Browse files
authored
Allow using DynamicTestExecutor with custom listener (#2238)
- Add `Node.DynamicTestExecutor#execute(TestDescriptor, EngineExecutionListener)` for engines that wish to pass a custom `EngineExecutionListener` and cancel or wait for the execution of a submitted test via the returned `Future`. - Add `EngineExecutionListener.NOOP` and change all declared methods to have empty default implementations. Resolves #2188.
1 parent 9613738 commit 5c64fce

File tree

7 files changed

+190
-36
lines changed

7 files changed

+190
-36
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0-M1.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ on GitHub.
3131
one to specify a comma-separated list of patterns for deactivating
3232
`TestExecutionListener` implementations registered via the `ServiceLoader` mechanism.
3333
* The `@Testable` annotation may now be applied _directly_ to fields.
34+
* Add `Node.DynamicTestExecutor#execute(TestDescriptor, EngineExecutionListener)` for
35+
engines that wish to pass a custom `EngineExecutionListener` and cancel or wait for the
36+
execution of a submitted test via the returned `Future`.
37+
* Add `EngineExecutionListener.NOOP` and change all declared methods to have empty default
38+
implementations.
3439

3540

3641
[[release-notes-5.7.0-M1-junit-jupiter]]

junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131
@API(status = STABLE, since = "1.0")
3232
public interface EngineExecutionListener {
3333

34+
/**
35+
* No-op implementation of {@code EngineExecutionListener}
36+
*/
37+
EngineExecutionListener NOOP = new EngineExecutionListener() {
38+
};
39+
3440
/**
3541
* Must be called when a new, dynamic {@link TestDescriptor} has been
3642
* registered.
@@ -41,7 +47,8 @@ public interface EngineExecutionListener {
4147
* @param testDescriptor the descriptor of the newly registered test
4248
* or container
4349
*/
44-
void dynamicTestRegistered(TestDescriptor testDescriptor);
50+
default void dynamicTestRegistered(TestDescriptor testDescriptor) {
51+
}
4552

4653
/**
4754
* Must be called when the execution of a leaf or subtree of the test tree
@@ -59,7 +66,8 @@ public interface EngineExecutionListener {
5966
* @param reason a human-readable message describing why the execution
6067
* has been skipped
6168
*/
62-
void executionSkipped(TestDescriptor testDescriptor, String reason);
69+
default void executionSkipped(TestDescriptor testDescriptor, String reason) {
70+
}
6371

6472
/**
6573
* Must be called when the execution of a leaf or subtree of the test tree
@@ -78,7 +86,8 @@ public interface EngineExecutionListener {
7886
*
7987
* @param testDescriptor the descriptor of the started test or container
8088
*/
81-
void executionStarted(TestDescriptor testDescriptor);
89+
default void executionStarted(TestDescriptor testDescriptor) {
90+
}
8291

8392
/**
8493
* Must be called when the execution of a leaf or subtree of the test tree
@@ -106,7 +115,8 @@ public interface EngineExecutionListener {
106115
*
107116
* @see TestExecutionResult
108117
*/
109-
void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult);
118+
default void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) {
119+
}
110120

111121
/**
112122
* Can be called for any {@link TestDescriptor} in order to publish additional
@@ -124,6 +134,7 @@ public interface EngineExecutionListener {
124134
* the reporting entry belongs
125135
* @param entry a {@code ReportEntry} instance to be published
126136
*/
127-
void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry);
137+
default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) {
138+
}
128139

129140
}

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
import java.util.Optional;
1818
import java.util.Set;
19+
import java.util.concurrent.Future;
1920

2021
import org.apiguardian.api.API;
2122
import org.junit.platform.commons.util.ToStringBuilder;
23+
import org.junit.platform.engine.EngineExecutionListener;
2224
import org.junit.platform.engine.TestDescriptor;
2325
import org.junit.platform.engine.TestExecutionResult;
2426

@@ -282,10 +284,26 @@ interface DynamicTestExecutor {
282284
/**
283285
* Submit a dynamic test descriptor for immediate execution.
284286
*
285-
* @param testDescriptor the test descriptor to be executed
287+
* @param testDescriptor the test descriptor to be executed; never
288+
* {@code null}
286289
*/
287290
void execute(TestDescriptor testDescriptor);
288291

292+
/**
293+
* Submit a dynamic test descriptor for immediate execution with a
294+
* custom, potentially no-op, execution listener.
295+
*
296+
* @param testDescriptor the test descriptor to be executed; never
297+
* {@code null}
298+
* @param executionListener the executionListener to be notified; never
299+
* {@code null}
300+
* @return a future to cancel or wait for the execution
301+
* @see EngineExecutionListener#NOOP
302+
* @since 5.7
303+
*/
304+
@API(status = EXPERIMENTAL, since = "5.7")
305+
Future<?> execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener);
306+
289307
/**
290308
* Block until all dynamic test descriptors submitted to this executor
291309
* are finished.

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010

1111
package org.junit.platform.engine.support.hierarchical;
1212

13+
import static java.util.concurrent.CompletableFuture.completedFuture;
1314
import static java.util.stream.Collectors.toCollection;
1415
import static org.junit.platform.engine.TestExecutionResult.failed;
1516

1617
import java.util.ArrayList;
1718
import java.util.List;
1819
import java.util.Set;
20+
import java.util.concurrent.CancellationException;
1921
import java.util.concurrent.ExecutionException;
2022
import java.util.concurrent.Future;
2123

@@ -24,6 +26,8 @@
2426
import org.junit.platform.commons.logging.LoggerFactory;
2527
import org.junit.platform.commons.util.BlacklistedExceptions;
2628
import org.junit.platform.commons.util.ExceptionUtils;
29+
import org.junit.platform.commons.util.Preconditions;
30+
import org.junit.platform.engine.EngineExecutionListener;
2731
import org.junit.platform.engine.TestDescriptor;
2832
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask;
2933
import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor;
@@ -184,18 +188,30 @@ private class DefaultDynamicTestExecutor implements DynamicTestExecutor {
184188
private final List<Future<?>> futures = new ArrayList<>();
185189

186190
@Override
187-
public void execute(TestDescriptor dynamicTestDescriptor) {
188-
taskContext.getListener().dynamicTestRegistered(dynamicTestDescriptor);
189-
Set<ExclusiveResource> exclusiveResources = NodeUtils.asNode(dynamicTestDescriptor).getExclusiveResources();
191+
public void execute(TestDescriptor testDescriptor) {
192+
execute(testDescriptor, taskContext.getListener());
193+
}
194+
195+
@Override
196+
public Future<?> execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener) {
197+
Preconditions.notNull(testDescriptor, "testDescriptor must not be null");
198+
Preconditions.notNull(executionListener, "executionListener must not be null");
199+
200+
executionListener.dynamicTestRegistered(testDescriptor);
201+
Set<ExclusiveResource> exclusiveResources = NodeUtils.asNode(testDescriptor).getExclusiveResources();
190202
if (!exclusiveResources.isEmpty()) {
191-
taskContext.getListener().executionStarted(dynamicTestDescriptor);
203+
executionListener.executionStarted(testDescriptor);
192204
String message = "Dynamic test descriptors must not declare exclusive resources: " + exclusiveResources;
193-
taskContext.getListener().executionFinished(dynamicTestDescriptor, failed(new JUnitException(message)));
205+
executionListener.executionFinished(testDescriptor, failed(new JUnitException(message)));
206+
return completedFuture(null);
194207
}
195208
else {
196-
NodeTestTask<C> nodeTestTask = new NodeTestTask<>(taskContext, dynamicTestDescriptor);
209+
NodeTestTask<C> nodeTestTask = new NodeTestTask<>(taskContext.withListener(executionListener),
210+
testDescriptor);
197211
nodeTestTask.setParentContext(context);
198-
futures.add(taskContext.getExecutorService().submit(nodeTestTask));
212+
Future<?> future = taskContext.getExecutorService().submit(nodeTestTask);
213+
futures.add(future);
214+
return future;
199215
}
200216
}
201217

@@ -205,6 +221,9 @@ public void awaitFinished() throws InterruptedException {
205221
try {
206222
future.get();
207223
}
224+
catch (CancellationException ignore) {
225+
// Futures returned by execute() may have been cancelled
226+
}
208227
catch (ExecutionException e) {
209228
ExceptionUtils.throwAsUncheckedException(e.getCause());
210229
}

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ public NodeTestTaskContext(EngineExecutionListener listener, HierarchicalTestExe
3030
this.executionAdvisor = executionAdvisor;
3131
}
3232

33+
NodeTestTaskContext withListener(EngineExecutionListener listener) {
34+
if (this.listener == listener) {
35+
return this;
36+
}
37+
return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor);
38+
}
39+
3340
EngineExecutionListener getListener() {
3441
return listener;
3542
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2015-2020 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.platform.launcher.core;
12+
13+
import java.util.Map;
14+
15+
import org.junit.platform.engine.ConfigurationParameters;
16+
17+
public class ConfigurationParametersFactoryForTests {
18+
19+
private ConfigurationParametersFactoryForTests() {
20+
}
21+
22+
public static ConfigurationParameters create(Map<String, String> configParams) {
23+
return new LauncherConfigurationParameters(configParams, "/dev/null");
24+
}
25+
}

0 commit comments

Comments
 (0)