From b9bd1ce93dafaafc7c6524e32e2ce5f477d5755a Mon Sep 17 00:00:00 2001 From: Sambathkumar-Sekar Date: Wed, 24 Aug 2022 16:41:37 +0530 Subject: [PATCH 1/8] added changes for new configuration cucumber.execution.execution-mode.scenario --- cucumber-junit-platform-engine/README.md | 37 +++++++++++++++++++ .../junit/platform/engine/Constants.java | 17 +++++++++ .../engine/CucumberEngineOptions.java | 9 +++++ .../platform/engine/FeatureResolver.java | 3 ++ .../junit/platform/engine/NodeDescriptor.java | 13 ++++++- .../platform/engine/PickleDescriptor.java | 7 ++++ .../engine/CucumberEngineOptionsTest.java | 20 ++++++++++ 7 files changed, 104 insertions(+), 2 deletions(-) diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md index 4feeec8804..da3943f880 100644 --- a/cucumber-junit-platform-engine/README.md +++ b/cucumber-junit-platform-engine/README.md @@ -266,9 +266,39 @@ Feature: Isolated scenarios with this configuration: + ```properties cucumber.execution.exclusive-resources.isolated.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY ``` +### Sequential Scenario Execution With Parallel Run + +By default, when the configuration parameter `cucumber.execution.parallel.enabled` set to `true`, all applicable +scenarios would be executed in parallel. setting the configuration parameter +`cucumber.execution.execution-mode.scenario` to `same_thread` results in features executed in parallel but scenarios +within the feature runs sequential (parallel run behaviour of junit4). + +Example - consider a test suite of 2 Features + +```gherkin +Feature: Sequential Scenario Execution Feature 1 + + Scenario: scenario 1 + + Scenario: scenario 2 + +Feature: Sequential Scenario Execution Feature 2 + + Scenario: scenario 1 + + Scenario: scenario 2 +``` +when configuration parameter `cucumber.execution.parallel.enabled` set to `true` and +configuration parameter `cucumber.execution.execution-mode.scenario` set to `same_thread` Then the execution would be + +Thread 1 -> Feature 1 -> scenario 1 -> scenario 2 +

Thread 2 -> Feature 2 -> scenario 1 -> scenario 2 + +default value of configuration parameter `cucumber.execution.execution-mode.scenario` is `concurrent` ## Configuration Options ## @@ -345,6 +375,13 @@ cucumber.execution.parallel.config.dynamic.factor= # positive double. cucumber.execution.parallel.config.custom.class= # class name. # example: com.example.MyCustomParallelStrategy +cucumber.execution.execution-mode.scenario= # same_thread or concurrent + # default: concurrent + # same_thread - executes scenarios sequentially in the + # same thread as the parent feature + # conncurrent - executes scenarios concurrently on any + # available thread + cucumber.execution.exclusive-resources..read-write= # a comma separated list of strings # example: resource-a, resource-b. diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java index 5b3dd1db84..9921738302 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java @@ -280,6 +280,23 @@ public final class Constants { public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + CONFIG_CUSTOM_CLASS_PROPERTY_NAME; + /** + * Property name used to enable sequential execution of scenarios during parallel run: {@value} + *

+ * Valid values are {@code same_thread} or {@code concurrent}. + * Default value is {@code concurrent}. + *

+ * By default, when parallel execution is enabled, scenarios + * are executed in parallel on any available thread. setting this property to {@code same_thread} + * executes scenarios sequentially in the same thread as the parent feature + *

+ * setting this property to {@code concurrent} yields the same result as + * just setting the PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME property to true + * + * @see #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME + */ + public static final String PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME = "cucumber.execution.execution-mode.scenario"; + private Constants() { } diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index c72a2af41d..a1430ed2ee 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -12,6 +12,7 @@ import io.cucumber.tagexpressions.Expression; import io.cucumber.tagexpressions.TagExpressionParser; import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; import java.net.URI; import java.util.ArrayList; @@ -34,6 +35,7 @@ import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME; @@ -178,4 +180,11 @@ List featuresWithLines() { .collect(Collectors.toList())) .orElse(Collections.emptyList()); } + + ExecutionMode getExecutionModeForScenario() { + return configurationParameters + .get(PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME, + value -> ExecutionMode.valueOf(value.toUpperCase())) + .orElse(ExecutionMode.CONCURRENT); + } } diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java index eccc83f581..b4aa329002 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java @@ -86,6 +86,7 @@ private FeatureDescriptor createFeatureDescriptor(Feature feature) { feature), (Node.Rule node, TestDescriptor parent) -> { TestDescriptor descriptor = new NodeDescriptor( + parameters, source.ruleSegment(parent.getUniqueId(), node), namingStrategy.name(node), source.nodeSource(node)); @@ -104,6 +105,7 @@ private FeatureDescriptor createFeatureDescriptor(Feature feature) { }, (Node.ScenarioOutline node, TestDescriptor parent) -> { TestDescriptor descriptor = new NodeDescriptor( + parameters, source.scenarioSegment(parent.getUniqueId(), node), namingStrategy.name(node), source.nodeSource(node)); @@ -112,6 +114,7 @@ private FeatureDescriptor createFeatureDescriptor(Feature feature) { }, (Node.Examples node, TestDescriptor parent) -> { NodeDescriptor descriptor = new NodeDescriptor( + parameters, source.examplesSegment(parent.getUniqueId(), node), namingStrategy.name(node), source.nodeSource(node)); diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java index 9746859f7f..779edbabcf 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java @@ -1,13 +1,18 @@ package io.cucumber.junit.platform.engine; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.hierarchical.Node; -class NodeDescriptor extends AbstractTestDescriptor { +class NodeDescriptor extends AbstractTestDescriptor implements Node { - NodeDescriptor(UniqueId uniqueId, String name, TestSource source) { + private final CucumberEngineOptions options; + + NodeDescriptor(ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source) { super(uniqueId, name, source); + this.options = new CucumberEngineOptions(parameters); } @Override @@ -15,4 +20,8 @@ public Type getType() { return Type.CONTAINER; } + @Override + public ExecutionMode getExecutionMode() { + return this.options.getExecutionModeForScenario(); + } } diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java index 16c9f2aaca..7c581b72ea 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java @@ -31,6 +31,7 @@ class PickleDescriptor extends AbstractTestDescriptor implements Node tags; private final Set exclusiveResources = new LinkedHashSet<>(0); + private final CucumberEngineOptions options; PickleDescriptor( ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source, Pickle pickleEvent @@ -47,6 +48,7 @@ class PickleDescriptor extends AbstractTestDescriptor implements Node new ExclusiveResource(resource, LockMode.READ)) .forEach(exclusiveResources::add); }); + this.options = new CucumberEngineOptions(parameters); } private Set getTags(Pickle pickleEvent) { @@ -128,6 +130,11 @@ Optional getPackage() { .map(ClasspathSupport::packageNameOfResource); } + @Override + public ExecutionMode getExecutionMode() { + return options.getExecutionModeForScenario(); + } + private static final class ExclusiveResourceOptions { private final ConfigurationParameters parameters; diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java index e1670eae86..20271b9326 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java @@ -4,6 +4,7 @@ import io.cucumber.core.snippets.SnippetType; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.support.hierarchical.Node; import java.net.URI; @@ -152,4 +153,23 @@ void isParallelExecutionEnabled() { } + @Test + void getExecutionModeForScenario() { + ConfigurationParameters concurrent = new MapConfigurationParameters( + Constants.PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME, + "concurrent"); + assertThat(new CucumberEngineOptions(concurrent).getExecutionModeForScenario(), + is(Node.ExecutionMode.CONCURRENT)); + + ConfigurationParameters sameThread = new MapConfigurationParameters( + Constants.PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME, + "same_thread"); + assertThat(new CucumberEngineOptions(sameThread).getExecutionModeForScenario(), + is(Node.ExecutionMode.SAME_THREAD)); + + ConfigurationParameters defaultValue = new MapConfigurationParameters("", ""); + assertThat(new CucumberEngineOptions(defaultValue).getExecutionModeForScenario(), + is(Node.ExecutionMode.CONCURRENT)); + } + } From 3fa3d2dd3afbeb89ae5dd24ba5213d4a088c6097 Mon Sep 17 00:00:00 2001 From: Sambathkumar Sekar Date: Mon, 5 Sep 2022 11:53:22 +0530 Subject: [PATCH 2/8] fixed formatting issues --- .../junit/platform/engine/Constants.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java index 9921738302..54a4acf706 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java @@ -281,17 +281,20 @@ public final class Constants { + CONFIG_CUSTOM_CLASS_PROPERTY_NAME; /** - * Property name used to enable sequential execution of scenarios during parallel run: {@value} + * Property name used to enable sequential execution of scenarios during + * parallel run: {@value} *

- * Valid values are {@code same_thread} or {@code concurrent}. - * Default value is {@code concurrent}. + * Valid values are {@code same_thread} or {@code concurrent}. Default value + * is {@code concurrent}. *

- * By default, when parallel execution is enabled, scenarios - * are executed in parallel on any available thread. setting this property to {@code same_thread} - * executes scenarios sequentially in the same thread as the parent feature + * By default, when parallel execution is enabled, scenarios are executed in + * parallel on any available thread. setting this property to + * {@code same_thread} executes scenarios sequentially in the same thread as + * the parent feature *

* setting this property to {@code concurrent} yields the same result as - * just setting the PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME property to true + * just setting the PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME property to + * true * * @see #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME */ From 699b4702c73f6c8bca36b4ac0281ed1d90b0398a Mon Sep 17 00:00:00 2001 From: Sambathkumar Sekar Date: Tue, 6 Sep 2022 17:14:43 +0530 Subject: [PATCH 3/8] * renamed the constant PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME to PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME * added additional tests to cover changes made in NodeDescriptor and PickleDescriptor * modified README.md with proper phrases and introduced references to Junit4 behaviour * added an entry in the CHANGELOG.md --- CHANGELOG.md | 2 + cucumber-junit-platform-engine/README.md | 22 +++++----- .../junit/platform/engine/Constants.java | 13 ++---- .../engine/CucumberEngineOptions.java | 4 +- .../engine/CucumberEngineOptionsTest.java | 4 +- .../platform/engine/FeatureResolverTest.java | 41 +++++++++++++++++++ 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 310b9cabda..77fa644987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- [JUnit Platform] enable parallel execution behaviour of Junit4-Cucumber platform ([#2604](https://github.com/cucumber/cucumber-jvm/pull/2604) Sambathkumar Sekar) ## [7.6.0] - 2022-08-08 ### Changed diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md index da3943f880..5c62712e4a 100644 --- a/cucumber-junit-platform-engine/README.md +++ b/cucumber-junit-platform-engine/README.md @@ -270,14 +270,14 @@ with this configuration: ```properties cucumber.execution.exclusive-resources.isolated.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY ``` -### Sequential Scenario Execution With Parallel Run +### Sequential scenario execution with parallel feature run - Junit4 behaviour -By default, when the configuration parameter `cucumber.execution.parallel.enabled` set to `true`, all applicable -scenarios would be executed in parallel. setting the configuration parameter -`cucumber.execution.execution-mode.scenario` to `same_thread` results in features executed in parallel but scenarios -within the feature runs sequential (parallel run behaviour of junit4). +When parallel execution in enabled, all applicable scenarios are executed in parallel. Setting the configuration parameter +`cucumber.execution.execution-mode.feature` to `same_thread` results in features executed in parallel but scenarios +within the feature runs sequential in the same thread as the parent feature. This functionality replicates the parallel +execution behaviour of Junit4-Cucumber platform. -Example - consider a test suite of 2 Features +Example - consider a test suite of 2 features ```gherkin Feature: Sequential Scenario Execution Feature 1 @@ -292,14 +292,13 @@ Feature: Sequential Scenario Execution Feature 2 Scenario: scenario 2 ``` -when configuration parameter `cucumber.execution.parallel.enabled` set to `true` and -configuration parameter `cucumber.execution.execution-mode.scenario` set to `same_thread` Then the execution would be +When parallel execution is enabled and +configuration parameter `cucumber.execution.execution-mode.feature` set to `same_thread`, Then the execution flow is +described as below Thread 1 -> Feature 1 -> scenario 1 -> scenario 2

Thread 2 -> Feature 2 -> scenario 1 -> scenario 2 -default value of configuration parameter `cucumber.execution.execution-mode.scenario` is `concurrent` - ## Configuration Options ## Cucumber receives its configuration from the JUnit Platform. To see how these can be supplied; see the JUnit @@ -375,12 +374,13 @@ cucumber.execution.parallel.config.dynamic.factor= # positive double. cucumber.execution.parallel.config.custom.class= # class name. # example: com.example.MyCustomParallelStrategy -cucumber.execution.execution-mode.scenario= # same_thread or concurrent +cucumber.execution.execution-mode.feature= # same_thread or concurrent # default: concurrent # same_thread - executes scenarios sequentially in the # same thread as the parent feature # conncurrent - executes scenarios concurrently on any # available thread + # enables the behaviour of junit4-cucumber platform cucumber.execution.exclusive-resources..read-write= # a comma separated list of strings # example: resource-a, resource-b. diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java index 54a4acf706..7cef1a2527 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java @@ -287,18 +287,13 @@ public final class Constants { * Valid values are {@code same_thread} or {@code concurrent}. Default value * is {@code concurrent}. *

- * By default, when parallel execution is enabled, scenarios are executed in - * parallel on any available thread. setting this property to - * {@code same_thread} executes scenarios sequentially in the same thread as - * the parent feature - *

- * setting this property to {@code concurrent} yields the same result as - * just setting the PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME property to - * true + * When parallel execution is enabled, scenarios are executed in parallel on + * any available thread. setting this property to {@code same_thread} + * executes scenarios sequentially in the same thread as the parent feature * * @see #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME */ - public static final String PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME = "cucumber.execution.execution-mode.scenario"; + public static final String PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME = "cucumber.execution.execution-mode.feature"; private Constants() { diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index a1430ed2ee..5b5872acc7 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -35,7 +35,7 @@ import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; -import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME; @@ -183,7 +183,7 @@ List featuresWithLines() { ExecutionMode getExecutionModeForScenario() { return configurationParameters - .get(PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME, + .get(PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, value -> ExecutionMode.valueOf(value.toUpperCase())) .orElse(ExecutionMode.CONCURRENT); } diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java index 20271b9326..0b354f05b1 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java @@ -156,13 +156,13 @@ void isParallelExecutionEnabled() { @Test void getExecutionModeForScenario() { ConfigurationParameters concurrent = new MapConfigurationParameters( - Constants.PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME, + Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, "concurrent"); assertThat(new CucumberEngineOptions(concurrent).getExecutionModeForScenario(), is(Node.ExecutionMode.CONCURRENT)); ConfigurationParameters sameThread = new MapConfigurationParameters( - Constants.PARALLEL_EXECUTION_MODE_SCENARIOS_PROPERTY_NAME, + Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, "same_thread"); assertThat(new CucumberEngineOptions(sameThread).getExecutionModeForScenario(), is(Node.ExecutionMode.SAME_THREAD)); diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java index 6452d31130..9c0f634eba 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java @@ -6,22 +6,26 @@ import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; +import org.junit.platform.engine.support.hierarchical.Node; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME_PREFIX; import static io.cucumber.junit.platform.engine.Constants.EXECUTION_EXCLUSIVE_RESOURCES_PREFIX; import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.READ_SUFFIX; import static io.cucumber.junit.platform.engine.Constants.READ_WRITE_SUFFIX; import static java.util.Arrays.asList; import static java.util.Collections.emptySet; import static java.util.Optional.of; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.TestDescriptor.Type.CONTAINER; import static org.junit.platform.engine.TestDescriptor.Type.TEST; import static org.junit.platform.engine.TestTag.create; @@ -157,4 +161,41 @@ private TestDescriptor getExample() { return getOutline().getChildren().iterator().next().getChildren().iterator().next(); } + @Test + void parallelExecutionForFeaturesEnabled() { + configurationParameters = new MapConfigurationParameters( + PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, "concurrent"); + + assertTrue(getNodes().size() > 0); + assertTrue(getPickles().size() > 0); + getNodes().forEach(node -> assertEquals(Node.ExecutionMode.CONCURRENT, node.getExecutionMode())); + getPickles().forEach(pickle -> assertEquals(Node.ExecutionMode.CONCURRENT, pickle.getExecutionMode())); + } + + @Test + void parallelExecutionForFeaturesDisabled() { + configurationParameters = new MapConfigurationParameters( + PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, "same_thread"); + + assertTrue(getNodes().size() > 0); + assertTrue(getPickles().size() > 0); + getNodes().forEach(node -> assertEquals(Node.ExecutionMode.SAME_THREAD, node.getExecutionMode())); + getPickles().forEach(pickle -> assertEquals(Node.ExecutionMode.SAME_THREAD, pickle.getExecutionMode())); + } + + private Set getNodes() { + return getFeature().getChildren().stream() + .filter(TestDescriptor::isContainer) + .map(node -> (NodeDescriptor) node) + .collect(Collectors.toSet()); + } + + private Set getPickles() { + return getFeature().getChildren().stream() + .filter(TestDescriptor::isContainer) + .flatMap(examplesNode -> examplesNode.getChildren().stream()) + .flatMap(exampleNode -> exampleNode.getChildren().stream()) + .map(example -> (PickleDescriptor) example) + .collect(Collectors.toSet()); + } } From 193caa9dec52b9a80e4ec08f335fc3ada2a34d7d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 8 Sep 2022 11:12:13 +0200 Subject: [PATCH 4/8] Fixups --- CHANGELOG.md | 2 +- cucumber-junit-platform-engine/README.md | 47 +++++-------------- .../junit/platform/engine/Constants.java | 30 ++++++------ .../engine/CucumberEngineOptions.java | 4 +- .../engine/CucumberEngineOptionsTest.java | 4 +- .../platform/engine/FeatureResolverTest.java | 6 +-- 6 files changed, 35 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77fa644987..7cd71f04cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- [JUnit Platform] enable parallel execution behaviour of Junit4-Cucumber platform ([#2604](https://github.com/cucumber/cucumber-jvm/pull/2604) Sambathkumar Sekar) +- [JUnit Platform] Enable parallel execution of features ([#2604](https://github.com/cucumber/cucumber-jvm/pull/2604) Sambathkumar Sekar) ## [7.6.0] - 2022-08-08 ### Changed diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md index 5c62712e4a..a1f9a99d3d 100644 --- a/cucumber-junit-platform-engine/README.md +++ b/cucumber-junit-platform-engine/README.md @@ -270,34 +270,12 @@ with this configuration: ```properties cucumber.execution.exclusive-resources.isolated.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY ``` -### Sequential scenario execution with parallel feature run - Junit4 behaviour +### Executing features in parallel -When parallel execution in enabled, all applicable scenarios are executed in parallel. Setting the configuration parameter -`cucumber.execution.execution-mode.feature` to `same_thread` results in features executed in parallel but scenarios -within the feature runs sequential in the same thread as the parent feature. This functionality replicates the parallel -execution behaviour of Junit4-Cucumber platform. - -Example - consider a test suite of 2 features - -```gherkin -Feature: Sequential Scenario Execution Feature 1 - - Scenario: scenario 1 - - Scenario: scenario 2 - -Feature: Sequential Scenario Execution Feature 2 - - Scenario: scenario 1 - - Scenario: scenario 2 -``` -When parallel execution is enabled and -configuration parameter `cucumber.execution.execution-mode.feature` set to `same_thread`, Then the execution flow is -described as below - -Thread 1 -> Feature 1 -> scenario 1 -> scenario 2 -

Thread 2 -> Feature 2 -> scenario 1 -> scenario 2 +By default, when parallel execution in enabled, scenarios and examples are +executed in parallel. Due to limitations JUnit 4 could only execute features in +parallel. This behaviour can be restored by setting the configuration parameter +`cucumber.execution.execution-mode.feature` to `same_thread`. ## Configuration Options ## @@ -359,6 +337,13 @@ cucumber.snippet-type= # underscore or ca cucumber.execution.dry-run= # true or false. # default: false +cucumber.execution.execution-mode.feature= # same_thread or concurrent + # default: concurrent + # same_thread - executes scenarios sequentially in the + # same thread as the parent feature + # conncurrent - executes scenarios concurrently on any + # available thread + cucumber.execution.parallel.enabled= # true or false. # default: false @@ -374,14 +359,6 @@ cucumber.execution.parallel.config.dynamic.factor= # positive double. cucumber.execution.parallel.config.custom.class= # class name. # example: com.example.MyCustomParallelStrategy -cucumber.execution.execution-mode.feature= # same_thread or concurrent - # default: concurrent - # same_thread - executes scenarios sequentially in the - # same thread as the parent feature - # conncurrent - executes scenarios concurrently on any - # available thread - # enables the behaviour of junit4-cucumber platform - cucumber.execution.exclusive-resources..read-write= # a comma separated list of strings # example: resource-a, resource-b. diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java index 7cef1a2527..233d6909a5 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java @@ -182,6 +182,21 @@ public final class Constants { */ public static final String SNIPPET_TYPE_PROPERTY_NAME = io.cucumber.core.options.Constants.SNIPPET_TYPE_PROPERTY_NAME; + /** + * Property name used to set the executing thread for all scenarios and + * examples in a feature: {@value} + *

+ * Valid values are {@code same_thread} or {@code concurrent}. Default value + * is {@code concurrent}. + *

+ * When parallel execution is enabled, scenarios are executed in parallel on + * any available thread. setting this property to {@code same_thread} + * executes scenarios sequentially in the same thread as the parent feature. + * + * @see #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME + */ + public static final String EXECUTION_MODE_FEATURE_PROPERTY_NAME = "cucumber.execution.execution-mode.feature"; + /** * Property name used to enable parallel test execution: {@value} *

@@ -280,21 +295,6 @@ public final class Constants { public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + CONFIG_CUSTOM_CLASS_PROPERTY_NAME; - /** - * Property name used to enable sequential execution of scenarios during - * parallel run: {@value} - *

- * Valid values are {@code same_thread} or {@code concurrent}. Default value - * is {@code concurrent}. - *

- * When parallel execution is enabled, scenarios are executed in parallel on - * any available thread. setting this property to {@code same_thread} - * executes scenarios sequentially in the same thread as the parent feature - * - * @see #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME - */ - public static final String PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME = "cucumber.execution.execution-mode.feature"; - private Constants() { } diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index 5b5872acc7..d6b265d093 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -28,6 +28,7 @@ import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME_PREFIX; import static io.cucumber.junit.platform.engine.Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.EXECUTION_DRY_RUN_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME; @@ -35,7 +36,6 @@ import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; -import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME; @@ -183,7 +183,7 @@ List featuresWithLines() { ExecutionMode getExecutionModeForScenario() { return configurationParameters - .get(PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, + .get(EXECUTION_MODE_FEATURE_PROPERTY_NAME, value -> ExecutionMode.valueOf(value.toUpperCase())) .orElse(ExecutionMode.CONCURRENT); } diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java index 0b354f05b1..4b5a907d8a 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java @@ -156,13 +156,13 @@ void isParallelExecutionEnabled() { @Test void getExecutionModeForScenario() { ConfigurationParameters concurrent = new MapConfigurationParameters( - Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, + Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME, "concurrent"); assertThat(new CucumberEngineOptions(concurrent).getExecutionModeForScenario(), is(Node.ExecutionMode.CONCURRENT)); ConfigurationParameters sameThread = new MapConfigurationParameters( - Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, + Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME, "same_thread"); assertThat(new CucumberEngineOptions(sameThread).getExecutionModeForScenario(), is(Node.ExecutionMode.SAME_THREAD)); diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java index 9c0f634eba..d9a29c3a7c 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java @@ -17,8 +17,8 @@ import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME_PREFIX; import static io.cucumber.junit.platform.engine.Constants.EXECUTION_EXCLUSIVE_RESOURCES_PREFIX; +import static io.cucumber.junit.platform.engine.Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME; -import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.READ_SUFFIX; import static io.cucumber.junit.platform.engine.Constants.READ_WRITE_SUFFIX; import static java.util.Arrays.asList; @@ -164,7 +164,7 @@ private TestDescriptor getExample() { @Test void parallelExecutionForFeaturesEnabled() { configurationParameters = new MapConfigurationParameters( - PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, "concurrent"); + EXECUTION_MODE_FEATURE_PROPERTY_NAME, "concurrent"); assertTrue(getNodes().size() > 0); assertTrue(getPickles().size() > 0); @@ -175,7 +175,7 @@ void parallelExecutionForFeaturesEnabled() { @Test void parallelExecutionForFeaturesDisabled() { configurationParameters = new MapConfigurationParameters( - PARALLEL_EXECUTION_MODE_FEATURE_PROPERTY_NAME, "same_thread"); + EXECUTION_MODE_FEATURE_PROPERTY_NAME, "same_thread"); assertTrue(getNodes().size() > 0); assertTrue(getPickles().size() > 0); From 112bddcc865ecbd5a0cd040b14d5b93c04388858 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 8 Sep 2022 11:26:07 +0200 Subject: [PATCH 5/8] Refactor --- .../engine/DiscoverySelectorResolver.java | 1 + .../platform/engine/FeatureResolver.java | 10 +- .../junit/platform/engine/NodeDescriptor.java | 210 +++++++++++++++++- .../platform/engine/PickleDescriptor.java | 162 -------------- .../engine/CucumberEngineOptionsTest.java | 19 -- .../engine/DiscoverySelectorResolverTest.java | 1 + .../platform/engine/FeatureResolverTest.java | 1 + 7 files changed, 212 insertions(+), 192 deletions(-) delete mode 100644 cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolver.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolver.java index d55b62db7a..9ecc186237 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolver.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolver.java @@ -3,6 +3,7 @@ import io.cucumber.core.feature.FeatureWithLines; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; +import io.cucumber.junit.platform.engine.NodeDescriptor.PickleDescriptor; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.Filter; diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java index b4aa329002..991616c3ea 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java @@ -9,6 +9,10 @@ import io.cucumber.core.logging.LoggerFactory; import io.cucumber.core.resource.ClassLoaders; import io.cucumber.core.resource.ResourceScanner; +import io.cucumber.junit.platform.engine.NodeDescriptor.ExamplesDescriptor; +import io.cucumber.junit.platform.engine.NodeDescriptor.PickleDescriptor; +import io.cucumber.junit.platform.engine.NodeDescriptor.RuleDescriptor; +import io.cucumber.junit.platform.engine.NodeDescriptor.ScenarioOutlineDescriptor; import io.cucumber.plugin.event.Node; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; @@ -85,7 +89,7 @@ private FeatureDescriptor createFeatureDescriptor(Feature feature) { source.featureSource(), feature), (Node.Rule node, TestDescriptor parent) -> { - TestDescriptor descriptor = new NodeDescriptor( + TestDescriptor descriptor = new RuleDescriptor( parameters, source.ruleSegment(parent.getUniqueId(), node), namingStrategy.name(node), @@ -104,7 +108,7 @@ private FeatureDescriptor createFeatureDescriptor(Feature feature) { return descriptor; }, (Node.ScenarioOutline node, TestDescriptor parent) -> { - TestDescriptor descriptor = new NodeDescriptor( + TestDescriptor descriptor = new ScenarioOutlineDescriptor( parameters, source.scenarioSegment(parent.getUniqueId(), node), namingStrategy.name(node), @@ -113,7 +117,7 @@ private FeatureDescriptor createFeatureDescriptor(Feature feature) { return descriptor; }, (Node.Examples node, TestDescriptor parent) -> { - NodeDescriptor descriptor = new NodeDescriptor( + NodeDescriptor descriptor = new ExamplesDescriptor( parameters, source.examplesSegment(parent.getUniqueId(), node), namingStrategy.name(node), diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java index 779edbabcf..9d18e26ca0 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java @@ -1,27 +1,221 @@ package io.cucumber.junit.platform.engine; +import io.cucumber.core.gherkin.Pickle; +import io.cucumber.core.resource.ClasspathSupport; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.config.PrefixedConfigurationParameters; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import org.junit.platform.engine.support.hierarchical.Node; -class NodeDescriptor extends AbstractTestDescriptor implements Node { +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; - private final CucumberEngineOptions options; +import static io.cucumber.junit.platform.engine.Constants.EXECUTION_EXCLUSIVE_RESOURCES_PREFIX; +import static io.cucumber.junit.platform.engine.Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.READ_SUFFIX; +import static io.cucumber.junit.platform.engine.Constants.READ_WRITE_SUFFIX; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toCollection; + +abstract class NodeDescriptor extends AbstractTestDescriptor implements Node { + + private final ExecutionMode executionMode; NodeDescriptor(ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source) { super(uniqueId, name, source); - this.options = new CucumberEngineOptions(parameters); + this.executionMode = parameters + .get(EXECUTION_MODE_FEATURE_PROPERTY_NAME, + value -> ExecutionMode.valueOf(value.toUpperCase(Locale.US))) + .orElse(ExecutionMode.CONCURRENT); } @Override - public Type getType() { - return Type.CONTAINER; + public ExecutionMode getExecutionMode() { + return executionMode; } - @Override - public ExecutionMode getExecutionMode() { - return this.options.getExecutionModeForScenario(); + static final class ExamplesDescriptor extends NodeDescriptor { + + ExamplesDescriptor(ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source) { + super(parameters, uniqueId, name, source); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + + } + + static final class RuleDescriptor extends NodeDescriptor { + + RuleDescriptor(ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source) { + super(parameters, uniqueId, name, source); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + + } + + static final class ScenarioOutlineDescriptor extends NodeDescriptor { + + ScenarioOutlineDescriptor( + ConfigurationParameters parameters, UniqueId uniqueId, String name, + TestSource source + ) { + super(parameters, uniqueId, name, source); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + } + + static final class PickleDescriptor extends NodeDescriptor { + + private final Pickle pickleEvent; + private final Set tags; + private final Set exclusiveResources = new LinkedHashSet<>(0); + + PickleDescriptor( + ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source, + Pickle pickleEvent + ) { + super(parameters, uniqueId, name, source); + this.pickleEvent = pickleEvent; + this.tags = getTags(pickleEvent); + this.tags.forEach(tag -> { + ExclusiveResourceOptions exclusiveResourceOptions = new ExclusiveResourceOptions(parameters, tag); + exclusiveResourceOptions.exclusiveReadWriteResource() + .map(resource -> new ExclusiveResource(resource, LockMode.READ_WRITE)) + .forEach(exclusiveResources::add); + exclusiveResourceOptions.exclusiveReadResource() + .map(resource -> new ExclusiveResource(resource, LockMode.READ)) + .forEach(exclusiveResources::add); + }); + } + + private Set getTags(Pickle pickleEvent) { + return pickleEvent.getTags().stream() + .map(tag -> tag.substring(1)) + .filter(TestTag::isValid) + .map(TestTag::create) + // Retain input order + .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); + } + + @Override + public Type getType() { + return Type.TEST; + } + + @Override + public SkipResult shouldBeSkipped(CucumberEngineExecutionContext context) { + return Stream.of(shouldBeSkippedByTagFilter(context), shouldBeSkippedByNameFilter(context)) + .flatMap(skipResult -> skipResult.map(Stream::of).orElseGet(Stream::empty)) + .filter(SkipResult::isSkipped) + .findFirst() + .orElseGet(SkipResult::doNotSkip); + } + + private Optional shouldBeSkippedByTagFilter(CucumberEngineExecutionContext context) { + return context.getOptions().tagFilter().map(expression -> { + if (expression.evaluate(pickleEvent.getTags())) { + return SkipResult.doNotSkip(); + } + return SkipResult + .skip( + "'" + Constants.FILTER_TAGS_PROPERTY_NAME + "=" + expression + + "' did not match this scenario"); + }); + } + + private Optional shouldBeSkippedByNameFilter(CucumberEngineExecutionContext context) { + return context.getOptions().nameFilter().map(pattern -> { + if (pattern.matcher(pickleEvent.getName()).matches()) { + return SkipResult.doNotSkip(); + } + return SkipResult + .skip("'" + Constants.FILTER_NAME_PROPERTY_NAME + "=" + pattern + + "' did not match this scenario"); + }); + } + + @Override + public CucumberEngineExecutionContext execute( + CucumberEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor + ) { + context.runTestCase(pickleEvent); + return context; + } + + @Override + public Set getExclusiveResources() { + return exclusiveResources; + } + + /** + * Returns the set of {@linkplain TestTag tags} for a pickle. + *

+ * Note that Cucumber will remove the {code @} symbol from all Gherkin + * tags. So a scenario tagged with {@code @Smoke} becomes a test tagged + * with {@code Smoke}. + * + * @return the set of tags + */ + @Override + public Set getTags() { + return tags; + } + + Optional getPackage() { + return getSource() + .filter(ClasspathResourceSource.class::isInstance) + .map(ClasspathResourceSource.class::cast) + .map(ClasspathResourceSource::getClasspathResourceName) + .map(ClasspathSupport::packageNameOfResource); + } + + private static final class ExclusiveResourceOptions { + + private final ConfigurationParameters parameters; + + ExclusiveResourceOptions(ConfigurationParameters parameters, TestTag tag) { + this.parameters = new PrefixedConfigurationParameters( + parameters, + EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + tag.getName()); + } + + public Stream exclusiveReadWriteResource() { + return parameters.get(READ_WRITE_SUFFIX, s -> Arrays.stream(s.split(",")) + .map(String::trim)) + .orElse(Stream.empty()); + } + + public Stream exclusiveReadResource() { + return parameters.get(READ_SUFFIX, s -> Arrays.stream(s.split(",")) + .map(String::trim)) + .orElse(Stream.empty()); + } + + } + + } + } diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java deleted file mode 100644 index 7c581b72ea..0000000000 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java +++ /dev/null @@ -1,162 +0,0 @@ -package io.cucumber.junit.platform.engine; - -import io.cucumber.core.gherkin.Pickle; -import io.cucumber.core.resource.ClasspathSupport; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.config.PrefixedConfigurationParameters; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; -import org.junit.platform.engine.support.hierarchical.Node; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import static io.cucumber.junit.platform.engine.Constants.EXECUTION_EXCLUSIVE_RESOURCES_PREFIX; -import static io.cucumber.junit.platform.engine.Constants.READ_SUFFIX; -import static io.cucumber.junit.platform.engine.Constants.READ_WRITE_SUFFIX; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toCollection; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; - -class PickleDescriptor extends AbstractTestDescriptor implements Node { - - private final Pickle pickleEvent; - private final Set tags; - private final Set exclusiveResources = new LinkedHashSet<>(0); - private final CucumberEngineOptions options; - - PickleDescriptor( - ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source, Pickle pickleEvent - ) { - super(uniqueId, name, source); - this.pickleEvent = pickleEvent; - this.tags = getTags(pickleEvent); - this.tags.forEach(tag -> { - ExclusiveResourceOptions exclusiveResourceOptions = new ExclusiveResourceOptions(parameters, tag); - exclusiveResourceOptions.exclusiveReadWriteResource() - .map(resource -> new ExclusiveResource(resource, LockMode.READ_WRITE)) - .forEach(exclusiveResources::add); - exclusiveResourceOptions.exclusiveReadResource() - .map(resource -> new ExclusiveResource(resource, LockMode.READ)) - .forEach(exclusiveResources::add); - }); - this.options = new CucumberEngineOptions(parameters); - } - - private Set getTags(Pickle pickleEvent) { - return pickleEvent.getTags().stream() - .map(tag -> tag.substring(1)) - .filter(TestTag::isValid) - .map(TestTag::create) - // Retain input order - .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); - } - - @Override - public Type getType() { - return Type.TEST; - } - - @Override - public SkipResult shouldBeSkipped(CucumberEngineExecutionContext context) { - return Stream.of(shouldBeSkippedByTagFilter(context), shouldBeSkippedByNameFilter(context)) - .flatMap(skipResult -> skipResult.map(Stream::of).orElseGet(Stream::empty)) - .filter(SkipResult::isSkipped) - .findFirst() - .orElseGet(SkipResult::doNotSkip); - } - - private Optional shouldBeSkippedByTagFilter(CucumberEngineExecutionContext context) { - return context.getOptions().tagFilter().map(expression -> { - if (expression.evaluate(pickleEvent.getTags())) { - return SkipResult.doNotSkip(); - } - return SkipResult - .skip( - "'" + Constants.FILTER_TAGS_PROPERTY_NAME + "=" + expression + "' did not match this scenario"); - }); - } - - private Optional shouldBeSkippedByNameFilter(CucumberEngineExecutionContext context) { - return context.getOptions().nameFilter().map(pattern -> { - if (pattern.matcher(pickleEvent.getName()).matches()) { - return SkipResult.doNotSkip(); - } - return SkipResult - .skip("'" + Constants.FILTER_NAME_PROPERTY_NAME + "=" + pattern + "' did not match this scenario"); - }); - } - - @Override - public CucumberEngineExecutionContext execute( - CucumberEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor - ) { - context.runTestCase(pickleEvent); - return context; - } - - @Override - public Set getExclusiveResources() { - return exclusiveResources; - } - - /** - * Returns the set of {@linkplain TestTag tags} for a pickle. - *

- * Note that Cucumber will remove the {code @} symbol from all Gherkin tags. - * So a scenario tagged with {@code @Smoke} becomes a test tagged with - * {@code Smoke}. - * - * @return the set of tags - */ - @Override - public Set getTags() { - return tags; - } - - Optional getPackage() { - return getSource() - .filter(ClasspathResourceSource.class::isInstance) - .map(ClasspathResourceSource.class::cast) - .map(ClasspathResourceSource::getClasspathResourceName) - .map(ClasspathSupport::packageNameOfResource); - } - - @Override - public ExecutionMode getExecutionMode() { - return options.getExecutionModeForScenario(); - } - - private static final class ExclusiveResourceOptions { - - private final ConfigurationParameters parameters; - - ExclusiveResourceOptions(ConfigurationParameters parameters, TestTag tag) { - this.parameters = new PrefixedConfigurationParameters( - parameters, - EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + tag.getName()); - } - - public Stream exclusiveReadWriteResource() { - return parameters.get(READ_WRITE_SUFFIX, s -> Arrays.stream(s.split(",")) - .map(String::trim)) - .orElse(Stream.empty()); - } - - public Stream exclusiveReadResource() { - return parameters.get(READ_SUFFIX, s -> Arrays.stream(s.split(",")) - .map(String::trim)) - .orElse(Stream.empty()); - } - - } - -} diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java index 4b5a907d8a..3f1bdaf915 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java @@ -153,23 +153,4 @@ void isParallelExecutionEnabled() { } - @Test - void getExecutionModeForScenario() { - ConfigurationParameters concurrent = new MapConfigurationParameters( - Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME, - "concurrent"); - assertThat(new CucumberEngineOptions(concurrent).getExecutionModeForScenario(), - is(Node.ExecutionMode.CONCURRENT)); - - ConfigurationParameters sameThread = new MapConfigurationParameters( - Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME, - "same_thread"); - assertThat(new CucumberEngineOptions(sameThread).getExecutionModeForScenario(), - is(Node.ExecutionMode.SAME_THREAD)); - - ConfigurationParameters defaultValue = new MapConfigurationParameters("", ""); - assertThat(new CucumberEngineOptions(defaultValue).getExecutionModeForScenario(), - is(Node.ExecutionMode.CONCURRENT)); - } - } diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java index a6b4d15691..07f91e0825 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java @@ -1,6 +1,7 @@ package io.cucumber.junit.platform.engine; import io.cucumber.core.logging.LogRecordListener; +import io.cucumber.junit.platform.engine.NodeDescriptor.PickleDescriptor; import io.cucumber.junit.platform.engine.nofeatures.NoFeatures; import org.hamcrest.CustomTypeSafeMatcher; import org.hamcrest.Matcher; diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java index d9a29c3a7c..a584bf65cb 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java @@ -1,5 +1,6 @@ package io.cucumber.junit.platform.engine; +import io.cucumber.junit.platform.engine.NodeDescriptor.PickleDescriptor; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; From dba437ad8b7ab177d3f36ceeec8acd349b00ab81 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 8 Sep 2022 11:38:10 +0200 Subject: [PATCH 6/8] Coverage --- .../junit/platform/engine/CucumberEngineOptions.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index d6b265d093..36e26b02cd 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -181,10 +181,4 @@ List featuresWithLines() { .orElse(Collections.emptyList()); } - ExecutionMode getExecutionModeForScenario() { - return configurationParameters - .get(EXECUTION_MODE_FEATURE_PROPERTY_NAME, - value -> ExecutionMode.valueOf(value.toUpperCase())) - .orElse(ExecutionMode.CONCURRENT); - } } From f4bf51d8b473fcdb6c453f744497cbe608801552 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 8 Sep 2022 11:39:33 +0200 Subject: [PATCH 7/8] Coverage --- .../cucumber/junit/platform/engine/CucumberEngineOptions.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index 36e26b02cd..c72a2af41d 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -12,7 +12,6 @@ import io.cucumber.tagexpressions.Expression; import io.cucumber.tagexpressions.TagExpressionParser; import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; import java.net.URI; import java.util.ArrayList; @@ -28,7 +27,6 @@ import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME_PREFIX; import static io.cucumber.junit.platform.engine.Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.EXECUTION_DRY_RUN_PROPERTY_NAME; -import static io.cucumber.junit.platform.engine.Constants.EXECUTION_MODE_FEATURE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME; @@ -180,5 +178,4 @@ List featuresWithLines() { .collect(Collectors.toList())) .orElse(Collections.emptyList()); } - } From 019f8b103894f1e47e44f68ea7158a09762d237f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 8 Sep 2022 11:40:40 +0200 Subject: [PATCH 8/8] Touchups --- cucumber-junit-platform-engine/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md index a1f9a99d3d..70656d29e2 100644 --- a/cucumber-junit-platform-engine/README.md +++ b/cucumber-junit-platform-engine/README.md @@ -266,7 +266,6 @@ Feature: Isolated scenarios with this configuration: - ```properties cucumber.execution.exclusive-resources.isolated.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY ```