Skip to content

Commit d301304

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 e4c617b commit d301304

File tree

5 files changed

+237
-15
lines changed

5 files changed

+237
-15
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: 53 additions & 3 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

@@ -36,9 +37,12 @@ public abstract class DynamicNode {
3637
/** Custom test source {@link URI} associated with this node; potentially {@code null}. */
3738
private final @Nullable URI testSourceUri;
3839

39-
DynamicNode(String displayName, @Nullable URI testSourceUri) {
40-
this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank");
41-
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;
4246
}
4347

4448
/**
@@ -61,6 +65,10 @@ public Optional<URI> getTestSourceUri() {
6165
return Optional.ofNullable(testSourceUri);
6266
}
6367

68+
public Optional<ExecutionMode> getExecutionMode() {
69+
return Optional.ofNullable(executionMode);
70+
}
71+
6472
@Override
6573
public String toString() {
6674
return new ToStringBuilder(this) //
@@ -69,4 +77,46 @@ public String toString() {
6977
.toString();
7078
}
7179

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+
72122
}

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
@@ -16,6 +16,7 @@
1616

1717
import java.net.URI;
1818
import java.util.Iterator;
19+
import java.util.function.Consumer;
1920
import java.util.function.Function;
2021
import java.util.stream.Stream;
2122
import java.util.stream.StreamSupport;
@@ -24,6 +25,7 @@
2425
import org.jspecify.annotations.Nullable;
2526
import org.junit.jupiter.api.function.Executable;
2627
import org.junit.jupiter.api.function.ThrowingConsumer;
28+
import org.junit.jupiter.api.parallel.ExecutionMode;
2729
import org.junit.platform.commons.util.Preconditions;
2830

2931
/**
@@ -61,7 +63,7 @@ public class DynamicTest extends DynamicNode {
6163
* @see #stream(Iterator, Function, ThrowingConsumer)
6264
*/
6365
public static DynamicTest dynamicTest(String displayName, Executable executable) {
64-
return new DynamicTest(displayName, null, executable);
66+
return dynamicTest(config -> config.displayName(displayName).executable(executable));
6567
}
6668

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

8593
/**
@@ -290,9 +298,9 @@ public static <T extends Named<E>, E extends Executable> Stream<DynamicTest> str
290298

291299
private final Executable executable;
292300

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

298306
/**
@@ -302,4 +310,56 @@ public Executable getExecutable() {
302310
return this.executable;
303311
}
304312

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

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)