Skip to content

Add new factory methods to DynamicTest and DynamicContainer #4621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@
import static org.apiguardian.api.API.Status.MAINTAINED;

import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apiguardian.api.API;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.util.Preconditions;

/**
Expand All @@ -38,6 +45,8 @@
@API(status = MAINTAINED, since = "5.3")
public class DynamicContainer extends DynamicNode {

private final @Nullable ExecutionMode defaultChildExecutionMode;

/**
* Factory for creating a new {@code DynamicContainer} for the supplied display
* name and collection of dynamic nodes.
Expand All @@ -51,7 +60,7 @@ public class DynamicContainer extends DynamicNode {
* @see #dynamicContainer(String, Stream)
*/
public static DynamicContainer dynamicContainer(String displayName, Iterable<? extends DynamicNode> dynamicNodes) {
return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false));
return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes));
}

/**
Expand All @@ -67,7 +76,7 @@ public static DynamicContainer dynamicContainer(String displayName, Iterable<? e
* @see #dynamicContainer(String, Iterable)
*/
public static DynamicContainer dynamicContainer(String displayName, Stream<? extends DynamicNode> dynamicNodes) {
return dynamicContainer(displayName, null, dynamicNodes);
return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes));
}

/**
Expand All @@ -88,15 +97,21 @@ public static DynamicContainer dynamicContainer(String displayName, Stream<? ext
public static DynamicContainer dynamicContainer(String displayName, @Nullable URI testSourceUri,
Stream<? extends DynamicNode> dynamicNodes) {

return new DynamicContainer(displayName, testSourceUri, dynamicNodes);
return dynamicContainer(config -> config.displayName(displayName).source(testSourceUri).children(dynamicNodes));
}

public static DynamicContainer dynamicContainer(Consumer<Configuration> configurer) {
var configuration = new DefaultConfiguration();
configurer.accept(configuration);
return new DynamicContainer(configuration);
}

private final Stream<? extends DynamicNode> children;

private DynamicContainer(String displayName, @Nullable URI testSourceUri, Stream<? extends DynamicNode> children) {
super(displayName, testSourceUri);
Preconditions.notNull(children, "children must not be null");
this.children = children;
private DynamicContainer(DefaultConfiguration configuration) {
super(configuration);
this.children = Preconditions.notNull(configuration.children, "children must not be null");
this.defaultChildExecutionMode = configuration.defaultChildExecutionMode;
}

/**
Expand All @@ -107,4 +122,101 @@ public Stream<? extends DynamicNode> getChildren() {
return children;
}

public Optional<ExecutionMode> getDefaultChildExecutionMode() {
return Optional.ofNullable(defaultChildExecutionMode);
}

public interface Configuration extends DynamicNode.Configuration {

@Override
Configuration displayName(String displayName);

@Override
Configuration source(@Nullable URI testSourceUri);

@Override
Configuration executionCondition(
Function<? super ExtensionContext, ? extends ConditionEvaluationResult> condition);

@Override
Configuration executionMode(ExecutionMode executionMode);

@Override
Configuration executionMode(ExecutionMode executionMode, String reason);

Configuration defaultChildExecutionMode(ExecutionMode executionMode);

Configuration defaultChildExecutionMode(ExecutionMode executionMode, String reason);

default Configuration children(Iterable<? extends DynamicNode> children) {
Preconditions.notNull(children, "children must not be null");
return children(StreamSupport.stream(children.spliterator(), false));
}

default Configuration children(DynamicNode... children) {
Preconditions.notNull(children, "children must not be null");
Preconditions.containsNoNullElements(children, "children must not contain null elements");
return children(List.of(children));
}

Configuration children(Stream<? extends DynamicNode> children);

}

private static class DefaultConfiguration extends AbstractConfiguration implements Configuration {

private @Nullable Stream<? extends DynamicNode> children;
private @Nullable ExecutionMode defaultChildExecutionMode;

@Override
public Configuration displayName(String displayName) {
super.displayName(displayName);
return this;
}

@Override
public Configuration source(@Nullable URI testSourceUri) {
super.source(testSourceUri);
return this;
}

@Override
public Configuration executionCondition(
Function<? super ExtensionContext, ? extends ConditionEvaluationResult> condition) {
super.executionCondition(condition);
return this;
}

@Override
public Configuration executionMode(ExecutionMode executionMode) {
super.executionMode(executionMode);
return this;
}

@Override
public Configuration executionMode(ExecutionMode executionMode, String reason) {
super.executionMode(executionMode, reason);
return this;
}

@Override
public Configuration defaultChildExecutionMode(ExecutionMode executionMode) {
this.defaultChildExecutionMode = executionMode;
return this;
}

@Override
public Configuration defaultChildExecutionMode(ExecutionMode executionMode, String reason) {
defaultChildExecutionMode(executionMode);
return this;
}

@Override
public Configuration children(Stream<? extends DynamicNode> children) {
Preconditions.notNull(children, "children must not be null");
Preconditions.condition(this.children == null, "children can only be set once");
this.children = children;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@

import java.net.URI;
import java.util.Optional;
import java.util.function.Function;

import org.apiguardian.api.API;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ToStringBuilder;

Expand All @@ -34,12 +38,16 @@ public abstract class DynamicNode {
private final String displayName;

/** Custom test source {@link URI} associated with this node; potentially {@code null}. */
@Nullable
private final URI testSourceUri;
private final @Nullable URI testSourceUri;

DynamicNode(String displayName, @Nullable URI testSourceUri) {
this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank");
this.testSourceUri = testSourceUri;
private final @Nullable ExecutionMode executionMode;
private final @Nullable Function<? super ExtensionContext, ? extends ConditionEvaluationResult> executionCondition;

DynamicNode(AbstractConfiguration configuration) {
this.displayName = Preconditions.notBlank(configuration.displayName, "displayName must not be null or blank");
this.testSourceUri = configuration.testSourceUri;
this.executionMode = configuration.executionMode;
this.executionCondition = configuration.executionCondition;
}

/**
Expand All @@ -62,6 +70,14 @@ public Optional<URI> getTestSourceUri() {
return Optional.ofNullable(testSourceUri);
}

public Optional<ExecutionMode> getExecutionMode() {
return Optional.ofNullable(executionMode);
}

public Optional<Function<? super ExtensionContext, ? extends ConditionEvaluationResult>> getExecutionCondition() {
return Optional.ofNullable(executionCondition);
}

@Override
public String toString() {
return new ToStringBuilder(this) //
Expand All @@ -70,4 +86,58 @@ public String toString() {
.toString();
}

public interface Configuration {

Configuration displayName(String displayName);

Configuration source(@Nullable URI testSourceUri);

Configuration executionCondition(
Function<? super ExtensionContext, ? extends ConditionEvaluationResult> condition);

Configuration executionMode(ExecutionMode executionMode);

Configuration executionMode(ExecutionMode executionMode, String reason);

}

abstract static class AbstractConfiguration implements Configuration {

private @Nullable String displayName;
private @Nullable URI testSourceUri;
private @Nullable ExecutionMode executionMode;
private @Nullable Function<? super ExtensionContext, ? extends ConditionEvaluationResult> executionCondition;

@Override
public Configuration displayName(String displayName) {
this.displayName = displayName;
return this;
}

@Override
public Configuration source(@Nullable URI testSourceUri) {
this.testSourceUri = testSourceUri;
return this;
}

@Override
public Configuration executionCondition(
Function<? super ExtensionContext, ? extends ConditionEvaluationResult> condition) {
// TODO Handle multiple calls
this.executionCondition = condition;
return this;
}

@Override
public Configuration executionMode(ExecutionMode executionMode) {
this.executionMode = executionMode;
return this;
}

@Override
public Configuration executionMode(ExecutionMode executionMode, String reason) {
return executionMode(executionMode);
}
}

}
Loading