Skip to content

Commit 65e5c0e

Browse files
committed
Add new factory methods to DynamicTest and DynamicContainer
The new methods allow configuring the execution mode of dynamic tests and containers as well as the default child execution mode for dynamic containers in addition to all previously existing properties.
1 parent 771bcc9 commit 65e5c0e

File tree

5 files changed

+238
-17
lines changed

5 files changed

+238
-17
lines changed

junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313
import static org.apiguardian.api.API.Status.MAINTAINED;
1414

1515
import java.net.URI;
16+
import java.util.List;
17+
import java.util.Optional;
18+
import java.util.function.Consumer;
1619
import java.util.stream.Stream;
1720
import java.util.stream.StreamSupport;
1821

1922
import org.apiguardian.api.API;
2023
import org.jspecify.annotations.Nullable;
24+
import org.junit.jupiter.api.parallel.ExecutionMode;
2125
import org.junit.platform.commons.util.Preconditions;
2226

2327
/**
@@ -38,6 +42,8 @@
3842
@API(status = MAINTAINED, since = "5.3")
3943
public class DynamicContainer extends DynamicNode {
4044

45+
private final @Nullable ExecutionMode defaultChildExecutionMode;
46+
4147
/**
4248
* Factory for creating a new {@code DynamicContainer} for the supplied display
4349
* name and collection of dynamic nodes.
@@ -51,7 +57,7 @@ public class DynamicContainer extends DynamicNode {
5157
* @see #dynamicContainer(String, Stream)
5258
*/
5359
public static DynamicContainer dynamicContainer(String displayName, Iterable<? extends DynamicNode> dynamicNodes) {
54-
return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false));
60+
return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes));
5561
}
5662

5763
/**
@@ -67,7 +73,7 @@ public static DynamicContainer dynamicContainer(String displayName, Iterable<? e
6773
* @see #dynamicContainer(String, Iterable)
6874
*/
6975
public static DynamicContainer dynamicContainer(String displayName, Stream<? extends DynamicNode> dynamicNodes) {
70-
return dynamicContainer(displayName, null, dynamicNodes);
76+
return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes));
7177
}
7278

7379
/**
@@ -88,15 +94,21 @@ public static DynamicContainer dynamicContainer(String displayName, Stream<? ext
8894
public static DynamicContainer dynamicContainer(String displayName, @Nullable URI testSourceUri,
8995
Stream<? extends DynamicNode> dynamicNodes) {
9096

91-
return new DynamicContainer(displayName, testSourceUri, dynamicNodes);
97+
return dynamicContainer(config -> config.displayName(displayName).source(testSourceUri).children(dynamicNodes));
98+
}
99+
100+
public static DynamicContainer dynamicContainer(Consumer<Configuration> configurer) {
101+
var configuration = new DefaultConfiguration();
102+
configurer.accept(configuration);
103+
return new DynamicContainer(configuration);
92104
}
93105

94106
private final Stream<? extends DynamicNode> children;
95107

96-
private DynamicContainer(String displayName, @Nullable URI testSourceUri, Stream<? extends DynamicNode> children) {
97-
super(displayName, testSourceUri);
98-
Preconditions.notNull(children, "children must not be null");
99-
this.children = children;
108+
private DynamicContainer(DefaultConfiguration configuration) {
109+
super(configuration);
110+
this.children = Preconditions.notNull(configuration.children, "children must not be null");
111+
this.defaultChildExecutionMode = configuration.defaultChildExecutionMode;
100112
}
101113

102114
/**
@@ -107,4 +119,90 @@ public Stream<? extends DynamicNode> getChildren() {
107119
return children;
108120
}
109121

122+
public Optional<ExecutionMode> getDefaultChildExecutionMode() {
123+
return Optional.ofNullable(defaultChildExecutionMode);
124+
}
125+
126+
public interface Configuration extends DynamicNode.Configuration {
127+
128+
@Override
129+
Configuration displayName(String displayName);
130+
131+
@Override
132+
Configuration source(@Nullable URI testSourceUri);
133+
134+
@Override
135+
Configuration execution(ExecutionMode executionMode);
136+
137+
@Override
138+
Configuration execution(ExecutionMode executionMode, String reason);
139+
140+
Configuration defaultChildExecutionMode(ExecutionMode executionMode);
141+
142+
Configuration defaultChildExecutionMode(ExecutionMode executionMode, String reason);
143+
144+
default Configuration children(Iterable<? extends DynamicNode> children) {
145+
Preconditions.notNull(children, "children must not be null");
146+
return children(StreamSupport.stream(children.spliterator(), false));
147+
}
148+
149+
default Configuration children(DynamicNode... children) {
150+
Preconditions.notNull(children, "children must not be null");
151+
Preconditions.containsNoNullElements(children, "children must not contain null elements");
152+
return children(List.of(children));
153+
}
154+
155+
Configuration children(Stream<? extends DynamicNode> children);
156+
157+
}
158+
159+
private static class DefaultConfiguration extends AbstractConfiguration implements Configuration {
160+
161+
private @Nullable Stream<? extends DynamicNode> children;
162+
private @Nullable ExecutionMode defaultChildExecutionMode;
163+
164+
@Override
165+
public Configuration displayName(String displayName) {
166+
super.displayName(displayName);
167+
return this;
168+
}
169+
170+
@Override
171+
public Configuration source(@Nullable URI testSourceUri) {
172+
super.source(testSourceUri);
173+
return this;
174+
}
175+
176+
@Override
177+
public Configuration execution(ExecutionMode executionMode) {
178+
super.execution(executionMode);
179+
return this;
180+
}
181+
182+
@Override
183+
public Configuration execution(ExecutionMode executionMode, String reason) {
184+
super.execution(executionMode, reason);
185+
return this;
186+
}
187+
188+
@Override
189+
public Configuration defaultChildExecutionMode(ExecutionMode executionMode) {
190+
this.defaultChildExecutionMode = executionMode;
191+
return this;
192+
}
193+
194+
@Override
195+
public Configuration defaultChildExecutionMode(ExecutionMode executionMode, String reason) {
196+
defaultChildExecutionMode(executionMode);
197+
return this;
198+
}
199+
200+
@Override
201+
public Configuration children(Stream<? extends DynamicNode> children) {
202+
Preconditions.notNull(children, "children must not be null");
203+
Preconditions.condition(this.children == null, "children can only be set once");
204+
this.children = children;
205+
return this;
206+
}
207+
}
110208
}

junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.apiguardian.api.API;
1919
import org.jspecify.annotations.Nullable;
20+
import org.junit.jupiter.api.parallel.ExecutionMode;
2021
import org.junit.platform.commons.util.Preconditions;
2122
import org.junit.platform.commons.util.ToStringBuilder;
2223

@@ -34,12 +35,14 @@ public abstract class DynamicNode {
3435
private final String displayName;
3536

3637
/** Custom test source {@link URI} associated with this node; potentially {@code null}. */
37-
@Nullable
38-
private final URI testSourceUri;
38+
private final @Nullable URI testSourceUri;
3939

40-
DynamicNode(String displayName, @Nullable URI testSourceUri) {
41-
this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank");
42-
this.testSourceUri = testSourceUri;
40+
private final @Nullable ExecutionMode executionMode;
41+
42+
DynamicNode(AbstractConfiguration configuration) {
43+
this.displayName = Preconditions.notBlank(configuration.displayName, "displayName must not be null or blank");
44+
this.testSourceUri = configuration.testSourceUri;
45+
this.executionMode = configuration.executionMode;
4346
}
4447

4548
/**
@@ -62,6 +65,10 @@ public Optional<URI> getTestSourceUri() {
6265
return Optional.ofNullable(testSourceUri);
6366
}
6467

68+
public Optional<ExecutionMode> getExecutionMode() {
69+
return Optional.ofNullable(executionMode);
70+
}
71+
6572
@Override
6673
public String toString() {
6774
return new ToStringBuilder(this) //
@@ -70,4 +77,46 @@ public String toString() {
7077
.toString();
7178
}
7279

80+
public interface Configuration {
81+
82+
Configuration displayName(String displayName);
83+
84+
Configuration source(@Nullable URI testSourceUri);
85+
86+
Configuration execution(ExecutionMode executionMode);
87+
88+
Configuration execution(ExecutionMode executionMode, String reason);
89+
90+
}
91+
92+
abstract static class AbstractConfiguration implements Configuration {
93+
94+
private @Nullable String displayName;
95+
private @Nullable URI testSourceUri;
96+
private @Nullable ExecutionMode executionMode;
97+
98+
@Override
99+
public Configuration displayName(String displayName) {
100+
this.displayName = displayName;
101+
return this;
102+
}
103+
104+
@Override
105+
public Configuration source(@Nullable URI testSourceUri) {
106+
this.testSourceUri = testSourceUri;
107+
return this;
108+
}
109+
110+
@Override
111+
public Configuration execution(ExecutionMode executionMode) {
112+
this.executionMode = executionMode;
113+
return this;
114+
}
115+
116+
@Override
117+
public Configuration execution(ExecutionMode executionMode, String reason) {
118+
return execution(executionMode);
119+
}
120+
}
121+
73122
}

junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.net.URI;
1919
import java.util.Iterator;
20+
import java.util.function.Consumer;
2021
import java.util.function.Function;
2122
import java.util.stream.Stream;
2223
import java.util.stream.StreamSupport;
@@ -25,6 +26,7 @@
2526
import org.jspecify.annotations.Nullable;
2627
import org.junit.jupiter.api.function.Executable;
2728
import org.junit.jupiter.api.function.ThrowingConsumer;
29+
import org.junit.jupiter.api.parallel.ExecutionMode;
2830
import org.junit.platform.commons.util.Preconditions;
2931

3032
/**
@@ -62,7 +64,7 @@ public class DynamicTest extends DynamicNode {
6264
* @see #stream(Iterator, Function, ThrowingConsumer)
6365
*/
6466
public static DynamicTest dynamicTest(String displayName, Executable executable) {
65-
return new DynamicTest(displayName, null, executable);
67+
return dynamicTest(config -> config.displayName(displayName).executable(executable));
6668
}
6769

6870
/**
@@ -80,7 +82,13 @@ public static DynamicTest dynamicTest(String displayName, Executable executable)
8082
* @see #stream(Iterator, Function, ThrowingConsumer)
8183
*/
8284
public static DynamicTest dynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) {
83-
return new DynamicTest(displayName, testSourceUri, executable);
85+
return dynamicTest(config -> config.displayName(displayName).source(testSourceUri).executable(executable));
86+
}
87+
88+
public static DynamicTest dynamicTest(Consumer<Configuration> configurer) {
89+
var configuration = new DefaultConfiguration();
90+
configurer.accept(configuration);
91+
return new DynamicTest(configuration);
8492
}
8593

8694
/**
@@ -291,9 +299,9 @@ public static <T extends Named<E>, E extends Executable> Stream<DynamicTest> str
291299

292300
private final Executable executable;
293301

294-
private DynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) {
295-
super(displayName, testSourceUri);
296-
this.executable = Preconditions.notNull(executable, "executable must not be null");
302+
private DynamicTest(DefaultConfiguration configuration) {
303+
super(configuration);
304+
this.executable = Preconditions.notNull(configuration.executable, "executable must not be null");
297305
}
298306

299307
/**
@@ -303,4 +311,56 @@ public Executable getExecutable() {
303311
return this.executable;
304312
}
305313

314+
public interface Configuration extends DynamicNode.Configuration {
315+
316+
@Override
317+
Configuration displayName(String displayName);
318+
319+
@Override
320+
Configuration source(@Nullable URI testSourceUri);
321+
322+
@Override
323+
Configuration execution(ExecutionMode executionMode);
324+
325+
@Override
326+
Configuration execution(ExecutionMode executionMode, String reason);
327+
328+
Configuration executable(Executable executable);
329+
}
330+
331+
private static class DefaultConfiguration extends AbstractConfiguration implements Configuration {
332+
333+
private @Nullable Executable executable;
334+
335+
@Override
336+
public Configuration displayName(String displayName) {
337+
super.displayName(displayName);
338+
return this;
339+
}
340+
341+
@Override
342+
public Configuration source(@Nullable URI testSourceUri) {
343+
super.source(testSourceUri);
344+
return this;
345+
}
346+
347+
@Override
348+
public Configuration execution(ExecutionMode executionMode) {
349+
super.execution(executionMode);
350+
return this;
351+
}
352+
353+
@Override
354+
public Configuration execution(ExecutionMode executionMode, String reason) {
355+
super.execution(executionMode, reason);
356+
return this;
357+
}
358+
359+
@Override
360+
public Configuration executable(Executable executable) {
361+
this.executable = executable;
362+
return this;
363+
}
364+
}
365+
306366
}

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ public Type getType() {
5858
return Type.CONTAINER;
5959
}
6060

61+
@Override
62+
Optional<ExecutionMode> getDefaultChildExecutionMode() {
63+
return this.dynamicContainer.getDefaultChildExecutionMode().map(JupiterTestDescriptor::toExecutionMode);
64+
}
65+
6166
@Override
6267
public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context,
6368
DynamicTestExecutor dynamicTestExecutor) throws Exception {

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
package org.junit.jupiter.engine.descriptor;
1212

13+
import java.util.Optional;
14+
1315
import org.jspecify.annotations.Nullable;
1416
import org.junit.jupiter.api.DynamicNode;
1517
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -27,11 +29,18 @@
2729
abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor {
2830

2931
protected final int index;
32+
private final Optional<ExecutionMode> executionMode;
3033

3134
DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, @Nullable TestSource testSource,
3235
JupiterConfiguration configuration) {
3336
super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration);
3437
this.index = index;
38+
this.executionMode = dynamicNode.getExecutionMode().map(JupiterTestDescriptor::toExecutionMode);
39+
}
40+
41+
@Override
42+
Optional<ExecutionMode> getExplicitExecutionMode() {
43+
return executionMode;
3544
}
3645

3746
@Override

0 commit comments

Comments
 (0)