1010
1111package org .junit .jupiter .api ;
1212
13+ import static org .apiguardian .api .API .Status .EXPERIMENTAL ;
1314import static org .apiguardian .api .API .Status .MAINTAINED ;
1415
1516import java .net .URI ;
17+ import java .util .List ;
18+ import java .util .Optional ;
19+ import java .util .function .Consumer ;
1620import java .util .stream .Stream ;
1721import java .util .stream .StreamSupport ;
1822
1923import org .apiguardian .api .API ;
2024import org .jspecify .annotations .Nullable ;
25+ import org .junit .jupiter .api .parallel .ExecutionMode ;
2126import org .junit .platform .commons .util .Preconditions ;
2227
2328/**
3843@ API (status = MAINTAINED , since = "5.3" )
3944public class DynamicContainer extends DynamicNode {
4045
46+ private final @ Nullable ExecutionMode childExecutionMode ;
47+
4148 /**
4249 * Factory for creating a new {@code DynamicContainer} for the supplied display
4350 * name and collection of dynamic nodes.
@@ -51,7 +58,7 @@ public class DynamicContainer extends DynamicNode {
5158 * @see #dynamicContainer(String, Stream)
5259 */
5360 public static DynamicContainer dynamicContainer (String displayName , Iterable <? extends DynamicNode > dynamicNodes ) {
54- return dynamicContainer (displayName , null , StreamSupport . stream ( dynamicNodes . spliterator (), false ));
61+ return dynamicContainer (config -> config . displayName ( displayName ). children ( dynamicNodes ));
5562 }
5663
5764 /**
@@ -67,7 +74,7 @@ public static DynamicContainer dynamicContainer(String displayName, Iterable<? e
6774 * @see #dynamicContainer(String, Iterable)
6875 */
6976 public static DynamicContainer dynamicContainer (String displayName , Stream <? extends DynamicNode > dynamicNodes ) {
70- return dynamicContainer (displayName , null , dynamicNodes );
77+ return dynamicContainer (config -> config . displayName ( displayName ). children ( dynamicNodes ) );
7178 }
7279
7380 /**
@@ -88,15 +95,32 @@ public static DynamicContainer dynamicContainer(String displayName, Stream<? ext
8895 public static DynamicContainer dynamicContainer (String displayName , @ Nullable URI testSourceUri ,
8996 Stream <? extends DynamicNode > dynamicNodes ) {
9097
91- return new DynamicContainer (displayName , testSourceUri , dynamicNodes );
98+ return dynamicContainer (
99+ config -> config .displayName (displayName ).testSourceUri (testSourceUri ).children (dynamicNodes ));
100+ }
101+
102+ /**
103+ * Factory for creating a new {@code DynamicTest} that is configured via the
104+ * supplied {@link Consumer} of {@link DynamicTest.Configuration}.
105+ *
106+ * @param configurer callback for configuring the resulting
107+ * {@code DynamicTest}; never {@code null}.
108+ *
109+ * @since 6.1
110+ */
111+ @ API (status = EXPERIMENTAL , since = "6.1" )
112+ public static DynamicContainer dynamicContainer (Consumer <? super Configuration > configurer ) {
113+ var configuration = new DefaultConfiguration ();
114+ configurer .accept (configuration );
115+ return new DynamicContainer (configuration );
92116 }
93117
94118 private final Stream <? extends DynamicNode > children ;
95119
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 ;
120+ private DynamicContainer (DefaultConfiguration configuration ) {
121+ super (configuration );
122+ this . children = Preconditions .notNull (configuration . children , "children must not be null" );
123+ this .childExecutionMode = configuration . childExecutionMode ;
100124 }
101125
102126 /**
@@ -107,4 +131,105 @@ public Stream<? extends DynamicNode> getChildren() {
107131 return children ;
108132 }
109133
134+ /**
135+ * {@return the {@link ExecutionMode} for
136+ * {@linkplain #getChildren() children} of this {@code DynamicContainer}
137+ * that is used unless they are
138+ * {@linkplain DynamicTest#getExecutionMode() configured} differently}.
139+ *
140+ * @since 6.1
141+ * @see DynamicTest#getExecutionMode()
142+ */
143+ @ API (status = EXPERIMENTAL , since = "6.1" )
144+ public Optional <ExecutionMode > getChildExecutionMode () {
145+ return Optional .ofNullable (childExecutionMode );
146+ }
147+
148+ /**
149+ * {@code Configuration} of a {@link DynamicContainer}.
150+ *
151+ * @since 6.1
152+ * @see DynamicContainer#dynamicContainer(Consumer)
153+ */
154+ @ API (status = EXPERIMENTAL , since = "6.1" )
155+ public sealed interface Configuration extends DynamicNode .Configuration <Configuration > {
156+
157+ /**
158+ * Set the
159+ * {@linkplain DynamicContainer#getChildExecutionMode() child execution mode}
160+ * to use for the configured {@link DynamicContainer}.
161+ *
162+ * @return this configuration for method chaining
163+ */
164+ Configuration childExecutionMode (ExecutionMode executionMode );
165+
166+ /**
167+ * Set the {@linkplain DynamicContainer#getChildren() children} of the
168+ * configured {@link DynamicContainer}.
169+ *
170+ * <p>Any previously configured value is overridden.
171+ *
172+ * @param children the children; never {@code null} or containing
173+ * {@code null} elements
174+ * @return this configuration for method chaining
175+ */
176+ default Configuration children (Iterable <? extends DynamicNode > children ) {
177+ Preconditions .notNull (children , "children must not be null" );
178+ return children (StreamSupport .stream (children .spliterator (), false ));
179+ }
180+
181+ /**
182+ * Set the {@linkplain DynamicContainer#getChildren() children} of the
183+ * configured {@link DynamicContainer}.
184+ *
185+ * <p>Any previously configured value is overridden.
186+ *
187+ * @param children the children; never {@code null} or containing
188+ * {@code null} elements
189+ * @return this configuration for method chaining
190+ */
191+ default Configuration children (DynamicNode ... children ) {
192+ Preconditions .notNull (children , "children must not be null" );
193+ Preconditions .containsNoNullElements (children , "children must not contain null elements" );
194+ return children (List .of (children ));
195+ }
196+
197+ /**
198+ * Set the {@linkplain DynamicContainer#getChildren() children} of the
199+ * configured {@link DynamicContainer}.
200+ *
201+ * <p>Any previously configured value is overridden.
202+ *
203+ * @param children the children; never {@code null} or containing
204+ * {@code null} elements
205+ * @return this configuration for method chaining
206+ */
207+ Configuration children (Stream <? extends DynamicNode > children );
208+
209+ }
210+
211+ static final class DefaultConfiguration extends AbstractConfiguration <Configuration > implements Configuration {
212+
213+ private @ Nullable Stream <? extends DynamicNode > children ;
214+ private @ Nullable ExecutionMode childExecutionMode ;
215+
216+ @ Override
217+ public Configuration childExecutionMode (ExecutionMode executionMode ) {
218+ this .childExecutionMode = Preconditions .notNull (executionMode , "executionMode must not be null" );
219+ return this ;
220+ }
221+
222+ @ Override
223+ public Configuration children (Stream <? extends DynamicNode > children ) {
224+ Preconditions .notNull (children , "children must not be null" );
225+ Preconditions .condition (this .children == null , "children can only be set once" );
226+ this .children = children ;
227+ return this ;
228+ }
229+
230+ @ Override
231+ protected Configuration self () {
232+ return this ;
233+ }
234+ }
110235}
0 commit comments