Skip to content
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

[JUnit Platform] Enable parallel execution of features #2604

Merged
merged 11 commits into from
Sep 8, 2022
37 changes: 37 additions & 0 deletions cucumber-junit-platform-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved

By default, when the configuration parameter `cucumber.execution.parallel.enabled` set to `true`, all applicable
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
scenarios would be executed in parallel. setting the configuration parameter
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
`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
<p>Thread 2 -> Feature 2 -> scenario 1 -> scenario 2

default value of configuration parameter `cucumber.execution.execution-mode.scenario` is `concurrent`
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved

## Configuration Options ##

Expand Down Expand Up @@ -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
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
# 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.<tag-name>.read-write= # a comma separated list of strings
# example: resource-a, resource-b.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,26 @@ 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}
* <p>
* Valid values are {@code same_thread} or {@code concurrent}. Default value
* is {@code concurrent}.
* <p>
* 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
* <p>
* 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() {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -178,4 +180,11 @@ List<FeatureWithLines> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
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<CucumberEngineExecutionContext> {

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
public Type getType() {
return Type.CONTAINER;
}

@Override
public ExecutionMode getExecutionMode() {
return this.options.getExecutionModeForScenario();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class PickleDescriptor extends AbstractTestDescriptor implements Node<CucumberEn
private final Pickle pickleEvent;
private final Set<TestTag> tags;
private final Set<ExclusiveResource> exclusiveResources = new LinkedHashSet<>(0);
private final CucumberEngineOptions options;

PickleDescriptor(
ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source, Pickle pickleEvent
Expand All @@ -47,6 +48,7 @@ class PickleDescriptor extends AbstractTestDescriptor implements Node<CucumberEn
.map(resource -> new ExclusiveResource(resource, LockMode.READ))
.forEach(exclusiveResources::add);
});
this.options = new CucumberEngineOptions(parameters);
}

private Set<TestTag> getTags(Pickle pickleEvent) {
Expand Down Expand Up @@ -128,6 +130,11 @@ Optional<String> getPackage() {
.map(ClasspathSupport::packageNameOfResource);
}

@Override
public ExecutionMode getExecutionMode() {
return options.getExecutionModeForScenario();
}

private static final class ExclusiveResourceOptions {

private final ConfigurationParameters parameters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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));
}

}