From 65e5c0e5c8060cf1baec3cd678595b73b63be0e4 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 5 Jun 2025 11:47:09 +0200 Subject: [PATCH 1/3] 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. --- .../junit/jupiter/api/DynamicContainer.java | 112 ++++++++++++++++-- .../org/junit/jupiter/api/DynamicNode.java | 59 ++++++++- .../org/junit/jupiter/api/DynamicTest.java | 70 ++++++++++- .../DynamicContainerTestDescriptor.java | 5 + .../descriptor/DynamicNodeTestDescriptor.java | 9 ++ 5 files changed, 238 insertions(+), 17 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java index 2bdf44fda25d..8cf022879347 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -13,11 +13,15 @@ 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.stream.Stream; import java.util.stream.StreamSupport; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.Preconditions; /** @@ -38,6 +42,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. @@ -51,7 +57,7 @@ public class DynamicContainer extends DynamicNode { * @see #dynamicContainer(String, Stream) */ public static DynamicContainer dynamicContainer(String displayName, Iterable dynamicNodes) { - return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false)); + return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes)); } /** @@ -67,7 +73,7 @@ public static DynamicContainer dynamicContainer(String displayName, Iterable dynamicNodes) { - return dynamicContainer(displayName, null, dynamicNodes); + return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes)); } /** @@ -88,15 +94,21 @@ public static DynamicContainer dynamicContainer(String displayName, Stream dynamicNodes) { - return new DynamicContainer(displayName, testSourceUri, dynamicNodes); + return dynamicContainer(config -> config.displayName(displayName).source(testSourceUri).children(dynamicNodes)); + } + + public static DynamicContainer dynamicContainer(Consumer configurer) { + var configuration = new DefaultConfiguration(); + configurer.accept(configuration); + return new DynamicContainer(configuration); } private final Stream children; - private DynamicContainer(String displayName, @Nullable URI testSourceUri, Stream 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; } /** @@ -107,4 +119,90 @@ public Stream getChildren() { return children; } + public Optional getDefaultChildExecutionMode() { + return Optional.ofNullable(defaultChildExecutionMode); + } + + public interface Configuration extends DynamicNode.Configuration { + + @Override + Configuration displayName(String displayName); + + @Override + Configuration source(@Nullable URI testSourceUri); + + @Override + Configuration execution(ExecutionMode executionMode); + + @Override + Configuration execution(ExecutionMode executionMode, String reason); + + Configuration defaultChildExecutionMode(ExecutionMode executionMode); + + Configuration defaultChildExecutionMode(ExecutionMode executionMode, String reason); + + default Configuration children(Iterable 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 children); + + } + + private static class DefaultConfiguration extends AbstractConfiguration implements Configuration { + + private @Nullable Stream 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 execution(ExecutionMode executionMode) { + super.execution(executionMode); + return this; + } + + @Override + public Configuration execution(ExecutionMode executionMode, String reason) { + super.execution(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 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; + } + } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java index 59c990ee8b99..a752133b7baa 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -17,6 +17,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; @@ -34,12 +35,14 @@ 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; + + DynamicNode(AbstractConfiguration configuration) { + this.displayName = Preconditions.notBlank(configuration.displayName, "displayName must not be null or blank"); + this.testSourceUri = configuration.testSourceUri; + this.executionMode = configuration.executionMode; } /** @@ -62,6 +65,10 @@ public Optional getTestSourceUri() { return Optional.ofNullable(testSourceUri); } + public Optional getExecutionMode() { + return Optional.ofNullable(executionMode); + } + @Override public String toString() { return new ToStringBuilder(this) // @@ -70,4 +77,46 @@ public String toString() { .toString(); } + public interface Configuration { + + Configuration displayName(String displayName); + + Configuration source(@Nullable URI testSourceUri); + + Configuration execution(ExecutionMode executionMode); + + Configuration execution(ExecutionMode executionMode, String reason); + + } + + abstract static class AbstractConfiguration implements Configuration { + + private @Nullable String displayName; + private @Nullable URI testSourceUri; + private @Nullable ExecutionMode executionMode; + + @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 execution(ExecutionMode executionMode) { + this.executionMode = executionMode; + return this; + } + + @Override + public Configuration execution(ExecutionMode executionMode, String reason) { + return execution(executionMode); + } + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java index 82e8018cb0ba..4d4e0c0ff6f0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -17,6 +17,7 @@ import java.net.URI; import java.util.Iterator; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -25,6 +26,7 @@ import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.Preconditions; /** @@ -62,7 +64,7 @@ public class DynamicTest extends DynamicNode { * @see #stream(Iterator, Function, ThrowingConsumer) */ public static DynamicTest dynamicTest(String displayName, Executable executable) { - return new DynamicTest(displayName, null, executable); + return dynamicTest(config -> config.displayName(displayName).executable(executable)); } /** @@ -80,7 +82,13 @@ public static DynamicTest dynamicTest(String displayName, Executable executable) * @see #stream(Iterator, Function, ThrowingConsumer) */ public static DynamicTest dynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) { - return new DynamicTest(displayName, testSourceUri, executable); + return dynamicTest(config -> config.displayName(displayName).source(testSourceUri).executable(executable)); + } + + public static DynamicTest dynamicTest(Consumer configurer) { + var configuration = new DefaultConfiguration(); + configurer.accept(configuration); + return new DynamicTest(configuration); } /** @@ -291,9 +299,9 @@ public static , E extends Executable> Stream str private final Executable executable; - private DynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) { - super(displayName, testSourceUri); - this.executable = Preconditions.notNull(executable, "executable must not be null"); + private DynamicTest(DefaultConfiguration configuration) { + super(configuration); + this.executable = Preconditions.notNull(configuration.executable, "executable must not be null"); } /** @@ -303,4 +311,56 @@ public Executable getExecutable() { return this.executable; } + public interface Configuration extends DynamicNode.Configuration { + + @Override + Configuration displayName(String displayName); + + @Override + Configuration source(@Nullable URI testSourceUri); + + @Override + Configuration execution(ExecutionMode executionMode); + + @Override + Configuration execution(ExecutionMode executionMode, String reason); + + Configuration executable(Executable executable); + } + + private static class DefaultConfiguration extends AbstractConfiguration implements Configuration { + + private @Nullable Executable executable; + + @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 execution(ExecutionMode executionMode) { + super.execution(executionMode); + return this; + } + + @Override + public Configuration execution(ExecutionMode executionMode, String reason) { + super.execution(executionMode, reason); + return this; + } + + @Override + public Configuration executable(Executable executable) { + this.executable = executable; + return this; + } + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java index c5ade66011e3..ec69e8f0045d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java @@ -58,6 +58,11 @@ public Type getType() { return Type.CONTAINER; } + @Override + Optional getDefaultChildExecutionMode() { + return this.dynamicContainer.getDefaultChildExecutionMode().map(JupiterTestDescriptor::toExecutionMode); + } + @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java index f68c98b480bf..2a84f4757b03 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -10,6 +10,8 @@ package org.junit.jupiter.engine.descriptor; +import java.util.Optional; + import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.extension.ExtensionContext; @@ -27,11 +29,18 @@ abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { protected final int index; + private final Optional executionMode; DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, @Nullable TestSource testSource, JupiterConfiguration configuration) { super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration); this.index = index; + this.executionMode = dynamicNode.getExecutionMode().map(JupiterTestDescriptor::toExecutionMode); + } + + @Override + Optional getExplicitExecutionMode() { + return executionMode; } @Override From 7ccf39e8bcb077e203d459a3976a093cfeb0678a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 5 Jun 2025 12:00:36 +0200 Subject: [PATCH 2/3] Rename to `executionMode` --- .../java/org/junit/jupiter/api/DynamicContainer.java | 12 ++++++------ .../main/java/org/junit/jupiter/api/DynamicNode.java | 10 +++++----- .../main/java/org/junit/jupiter/api/DynamicTest.java | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java index 8cf022879347..2c35a6736c82 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -132,10 +132,10 @@ public interface Configuration extends DynamicNode.Configuration { Configuration source(@Nullable URI testSourceUri); @Override - Configuration execution(ExecutionMode executionMode); + Configuration executionMode(ExecutionMode executionMode); @Override - Configuration execution(ExecutionMode executionMode, String reason); + Configuration executionMode(ExecutionMode executionMode, String reason); Configuration defaultChildExecutionMode(ExecutionMode executionMode); @@ -174,14 +174,14 @@ public Configuration source(@Nullable URI testSourceUri) { } @Override - public Configuration execution(ExecutionMode executionMode) { - super.execution(executionMode); + public Configuration executionMode(ExecutionMode executionMode) { + super.executionMode(executionMode); return this; } @Override - public Configuration execution(ExecutionMode executionMode, String reason) { - super.execution(executionMode, reason); + public Configuration executionMode(ExecutionMode executionMode, String reason) { + super.executionMode(executionMode, reason); return this; } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java index a752133b7baa..17e6be6402c7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -83,9 +83,9 @@ public interface Configuration { Configuration source(@Nullable URI testSourceUri); - Configuration execution(ExecutionMode executionMode); + Configuration executionMode(ExecutionMode executionMode); - Configuration execution(ExecutionMode executionMode, String reason); + Configuration executionMode(ExecutionMode executionMode, String reason); } @@ -108,14 +108,14 @@ public Configuration source(@Nullable URI testSourceUri) { } @Override - public Configuration execution(ExecutionMode executionMode) { + public Configuration executionMode(ExecutionMode executionMode) { this.executionMode = executionMode; return this; } @Override - public Configuration execution(ExecutionMode executionMode, String reason) { - return execution(executionMode); + public Configuration executionMode(ExecutionMode executionMode, String reason) { + return executionMode(executionMode); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java index 4d4e0c0ff6f0..02f731915ab5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -320,10 +320,10 @@ public interface Configuration extends DynamicNode.Configuration { Configuration source(@Nullable URI testSourceUri); @Override - Configuration execution(ExecutionMode executionMode); + Configuration executionMode(ExecutionMode executionMode); @Override - Configuration execution(ExecutionMode executionMode, String reason); + Configuration executionMode(ExecutionMode executionMode, String reason); Configuration executable(Executable executable); } @@ -345,14 +345,14 @@ public Configuration source(@Nullable URI testSourceUri) { } @Override - public Configuration execution(ExecutionMode executionMode) { - super.execution(executionMode); + public Configuration executionMode(ExecutionMode executionMode) { + super.executionMode(executionMode); return this; } @Override - public Configuration execution(ExecutionMode executionMode, String reason) { - super.execution(executionMode, reason); + public Configuration executionMode(ExecutionMode executionMode, String reason) { + super.executionMode(executionMode, reason); return this; } From fb590bf32ee06cde8a92645caf96feff511a6d27 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 5 Jun 2025 12:22:29 +0200 Subject: [PATCH 3/3] Add `executionCondition()` to `Configuration` interface Issue: #4620 --- .../junit/jupiter/api/DynamicContainer.java | 14 +++++++++++++ .../org/junit/jupiter/api/DynamicNode.java | 21 +++++++++++++++++++ .../org/junit/jupiter/api/DynamicTest.java | 13 ++++++++++++ .../extension/ConditionEvaluationResult.java | 7 ++++--- .../descriptor/DynamicNodeTestDescriptor.java | 9 +++++++- .../descriptor/JupiterTestDescriptor.java | 2 +- 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java index 2c35a6736c82..fbc379ad92c6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -16,11 +16,14 @@ 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; @@ -131,6 +134,10 @@ public interface Configuration extends DynamicNode.Configuration { @Override Configuration source(@Nullable URI testSourceUri); + @Override + Configuration executionCondition( + Function condition); + @Override Configuration executionMode(ExecutionMode executionMode); @@ -173,6 +180,13 @@ public Configuration source(@Nullable URI testSourceUri) { return this; } + @Override + public Configuration executionCondition( + Function condition) { + super.executionCondition(condition); + return this; + } + @Override public Configuration executionMode(ExecutionMode executionMode) { super.executionMode(executionMode); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java index 17e6be6402c7..9281257d2f53 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -14,9 +14,12 @@ 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; @@ -38,11 +41,13 @@ public abstract class DynamicNode { private final @Nullable URI testSourceUri; private final @Nullable ExecutionMode executionMode; + private final @Nullable Function 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; } /** @@ -69,6 +74,10 @@ public Optional getExecutionMode() { return Optional.ofNullable(executionMode); } + public Optional> getExecutionCondition() { + return Optional.ofNullable(executionCondition); + } + @Override public String toString() { return new ToStringBuilder(this) // @@ -83,6 +92,9 @@ public interface Configuration { Configuration source(@Nullable URI testSourceUri); + Configuration executionCondition( + Function condition); + Configuration executionMode(ExecutionMode executionMode); Configuration executionMode(ExecutionMode executionMode, String reason); @@ -94,6 +106,7 @@ abstract static class AbstractConfiguration implements Configuration { private @Nullable String displayName; private @Nullable URI testSourceUri; private @Nullable ExecutionMode executionMode; + private @Nullable Function executionCondition; @Override public Configuration displayName(String displayName) { @@ -107,6 +120,14 @@ public Configuration source(@Nullable URI testSourceUri) { return this; } + @Override + public Configuration executionCondition( + Function condition) { + // TODO Handle multiple calls + this.executionCondition = condition; + return this; + } + @Override public Configuration executionMode(ExecutionMode executionMode) { this.executionMode = executionMode; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java index 02f731915ab5..4d88968b2a8d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -24,6 +24,8 @@ 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.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -319,6 +321,10 @@ public interface Configuration extends DynamicNode.Configuration { @Override Configuration source(@Nullable URI testSourceUri); + @Override + Configuration executionCondition( + Function condition); + @Override Configuration executionMode(ExecutionMode executionMode); @@ -344,6 +350,13 @@ public Configuration source(@Nullable URI testSourceUri) { return this; } + @Override + public Configuration executionCondition( + Function condition) { + super.executionCondition(condition); + return this; + } + @Override public Configuration executionMode(ExecutionMode executionMode) { super.executionMode(executionMode); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 5b683e43f32b..bbb5bc68b03e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; @@ -32,7 +33,7 @@ public class ConditionEvaluationResult { * @param reason the reason why the container or test should be enabled * @return an enabled {@code ConditionEvaluationResult} with the given reason */ - public static ConditionEvaluationResult enabled(String reason) { + public static ConditionEvaluationResult enabled(@Nullable String reason) { return new ConditionEvaluationResult(true, reason); } @@ -42,7 +43,7 @@ public static ConditionEvaluationResult enabled(String reason) { * @param reason the reason why the container or test should be disabled * @return a disabled {@code ConditionEvaluationResult} with the given reason */ - public static ConditionEvaluationResult disabled(String reason) { + public static ConditionEvaluationResult disabled(@Nullable String reason) { return new ConditionEvaluationResult(false, reason); } @@ -67,7 +68,7 @@ public static ConditionEvaluationResult disabled(String reason, String customRea private final Optional reason; - private ConditionEvaluationResult(boolean enabled, String reason) { + private ConditionEvaluationResult(boolean enabled, @Nullable String reason) { this.enabled = enabled; this.reason = Optional.ofNullable(reason); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java index 2a84f4757b03..b2c9e1b5c0c2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -11,9 +11,11 @@ package org.junit.jupiter.engine.descriptor; import java.util.Optional; +import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; @@ -30,12 +32,14 @@ abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { protected final int index; private final Optional executionMode; + private final Optional> executionCondition; DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, @Nullable TestSource testSource, JupiterConfiguration configuration) { super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration); this.index = index; this.executionMode = dynamicNode.getExecutionMode().map(JupiterTestDescriptor::toExecutionMode); + this.executionCondition = dynamicNode.getExecutionCondition(); } @Override @@ -67,7 +71,10 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte @Override public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { - return SkipResult.doNotSkip(); + return this.executionCondition // + .map(condition -> condition.apply(context.getExtensionContext())) // + .map(this::toSkipResult) // + .orElse(SkipResult.doNotSkip()); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java index 8bd1c7941b5a..a8f971bc561d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -191,7 +191,7 @@ public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { return toSkipResult(evaluationResult); } - private SkipResult toSkipResult(ConditionEvaluationResult evaluationResult) { + protected SkipResult toSkipResult(ConditionEvaluationResult evaluationResult) { if (evaluationResult.isDisabled()) { return SkipResult.skip(evaluationResult.getReason().orElse("")); }