diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml
index 578863b795..e7c2121a67 100644
--- a/.github/workflows/test-java.yml
+++ b/.github/workflows/test-java.yml
@@ -33,8 +33,6 @@ jobs:
run: ./mvnw install -Pinclude-extra-modules -DskipTests=true -DskipITs=true -D"archetype.test.skip=true" -D"maven.javadoc.skip=true" --batch-mode -D"style.color=always" --show-version
- name: Test
run: ./mvnw verify -Pinclude-extra-modules -D"style.color=always"
- env:
- CUCUMBER_PUBLISH_TOKEN: ${{ secrets.CUCUMBER_PUBLISH_TOKEN }}
javadoc:
name: 'Javadoc'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab8549ac40..b1623f23e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,9 +10,19 @@ 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 Engine] Option to include a parameterized scenario name only if the scenario is parameterized ([#2835](https://github.com/cucumber/cucumber-jvm/pull/2835) M.P. Korstanje)
+- [JUnit Platform Engine] Option to order features and scenarios ([#2835](https://github.com/cucumber/cucumber-jvm/pull/2835) M.P. Korstanje)
+- [JUnit Platform Engine] Log discovery issues when a classpath resource selector is (e.g. `@SelectClasspathResource`) is used to select a directory. ([#2835](https://github.com/cucumber/cucumber-jvm/pull/2835) M.P. Korstanje)
+
### Changed
+- [JUnit Platform Engine] Use JUnit's `EngineDiscoveryRequestResolver` to resolve classpath based resources. ([#2835](https://github.com/cucumber/cucumber-jvm/pull/2835) M.P. Korstanje)
- [JUnit Platform Engine] Use JUnit Platform 1.13.1 (JUnit Jupiter 5.13.1)
+### Fixed
+- [JUnit Platform Engine] Log discovery issues for feature files with parse errors. ([#2835](https://github.com/cucumber/cucumber-jvm/pull/2835) M.P. Korstanje)
+
+
## [7.23.0] - 2025-05-29
### Added
- [JUnit Platform Engine, TestNG] Remove framework elements from `UndefinedStepException` stacktrace ([#3002](https://github.com/cucumber/cucumber-jvm/pull/3002) M.P. Korstanje)
diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
index 113c8cf7d6..84d7391f33 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
@@ -32,11 +32,15 @@ public static URI parse(URI featureIdentifier) {
}
public static boolean isFeature(URI featureIdentifier) {
- return featureIdentifier.getSchemeSpecificPart().endsWith(FEATURE_FILE_SUFFIX);
+ return isFeature(featureIdentifier.getSchemeSpecificPart());
}
public static boolean isFeature(Path path) {
- return path.getFileName().toString().endsWith(FEATURE_FILE_SUFFIX);
+ return isFeature(path.getFileName().toString());
+ }
+
+ public static boolean isFeature(String fileName) {
+ return fileName.endsWith(FEATURE_FILE_SUFFIX);
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java b/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java
index 6f314e4eea..637c79edea 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java
@@ -42,7 +42,8 @@ public final class Constants {
* Valid values are {@code lexical}, {@code reverse}, {@code random} or
* {@code random:[seed]}.
*
- * By default features are executed in lexical file name order
+ * By default, features are executed in lexical file name order and
+ * scenarios in a feature from top to bottom.
*/
public static final String EXECUTION_ORDER_PROPERTY_NAME = "cucumber.execution.order";
diff --git a/cucumber-core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java b/cucumber-core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java
index 95cdb6d501..c5216edfa0 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java
@@ -8,8 +8,9 @@
public final class StandardPickleOrders {
- private static final Comparator pickleUriComparator = Comparator.comparing(Pickle::getUri)
- .thenComparing(pickle -> pickle.getLocation().getLine());
+ private static final Comparator pickleUriComparator = Comparator
+ .comparing(Pickle::getUri)
+ .thenComparing(Pickle::getLocation);
private StandardPickleOrders() {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java b/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java
index ea12bf21c7..a4ec320864 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java
@@ -2,6 +2,7 @@
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
+import org.apiguardian.api.API;
import java.io.IOException;
import java.net.URI;
@@ -20,8 +21,10 @@
import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.Files.exists;
import static java.nio.file.Files.walkFileTree;
+import static org.apiguardian.api.API.Status.INTERNAL;
-class PathScanner {
+@API(status = INTERNAL)
+public class PathScanner {
private static final Logger log = LoggerFactory.getLogger(PathScanner.class);
@@ -48,10 +51,14 @@ void findResourcesForPath(Path path, Predicate filter, Function filter, Consumer consumer) {
try {
- walkFileTree(path, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
- new ResourceFileVisitor(filter, consumer.apply(path)));
+ EnumSet options = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
+ ResourceFileVisitor visitor = new ResourceFileVisitor(filter, consumer);
+ walkFileTree(path, options, Integer.MAX_VALUE, visitor);
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExample.java b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExample.java
index 5a38fa1bd7..2f64d03bda 100644
--- a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExample.java
+++ b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExample.java
@@ -4,6 +4,7 @@
import io.cucumber.plugin.event.Location;
import io.cucumber.plugin.event.Node;
+import java.net.URI;
import java.util.Optional;
final class GherkinMessagesExample implements Node.Example {
@@ -20,6 +21,11 @@ final class GherkinMessagesExample implements Node.Example {
this.rowIndex = rowIndex;
}
+ @Override
+ public URI getUri() {
+ return parent.getUri();
+ }
+
@Override
public Location getLocation() {
return GherkinMessagesLocation.from(tableRow.getLocation());
diff --git a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExamples.java b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExamples.java
index fda9d54964..e7c2265bb0 100644
--- a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExamples.java
+++ b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesExamples.java
@@ -3,6 +3,7 @@
import io.cucumber.plugin.event.Location;
import io.cucumber.plugin.event.Node;
+import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -31,6 +32,11 @@ public Collection elements() {
return children;
}
+ @Override
+ public URI getUri() {
+ return parent.getUri();
+ }
+
@Override
public Location getLocation() {
return location;
diff --git a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesRule.java b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesRule.java
index c42c378317..e34cde6f26 100644
--- a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesRule.java
+++ b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesRule.java
@@ -4,6 +4,7 @@
import io.cucumber.plugin.event.Location;
import io.cucumber.plugin.event.Node;
+import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -42,6 +43,11 @@ public Collection elements() {
return children;
}
+ @Override
+ public URI getUri() {
+ return parent.getUri();
+ }
+
@Override
public Location getLocation() {
return GherkinMessagesLocation.from(rule.getLocation());
diff --git a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenario.java b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenario.java
index f35cce97b8..55684eae2f 100644
--- a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenario.java
+++ b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenario.java
@@ -3,6 +3,7 @@
import io.cucumber.plugin.event.Location;
import io.cucumber.plugin.event.Node;
+import java.net.URI;
import java.util.Optional;
final class GherkinMessagesScenario implements Node.Scenario {
@@ -20,6 +21,11 @@ public Optional getParent() {
return Optional.of(parent);
}
+ @Override
+ public URI getUri() {
+ return parent.getUri();
+ }
+
@Override
public Location getLocation() {
return GherkinMessagesLocation.from(scenario.getLocation());
diff --git a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenarioOutline.java b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenarioOutline.java
index b34180c013..8349f9419a 100644
--- a/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenarioOutline.java
+++ b/cucumber-gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesScenarioOutline.java
@@ -3,6 +3,7 @@
import io.cucumber.plugin.event.Location;
import io.cucumber.plugin.event.Node;
+import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -34,6 +35,11 @@ public Collection elements() {
return children;
}
+ @Override
+ public URI getUri() {
+ return parent.getUri();
+ }
+
@Override
public Location getLocation() {
return GherkinMessagesLocation.from(scenario.getLocation());
diff --git a/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java b/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java
index e46951dff8..a8cb5b3b52 100644
--- a/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java
+++ b/cucumber-gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java
@@ -2,7 +2,6 @@
import io.cucumber.plugin.event.Node;
-import java.net.URI;
import java.util.List;
public interface Feature extends Node.Feature {
@@ -11,8 +10,6 @@ public interface Feature extends Node.Feature {
List getPickles();
- URI getUri();
-
String getSource();
Iterable> getParseEvents();
diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md
index c5a8cfb0aa..b673dee36f 100644
--- a/cucumber-junit-platform-engine/README.md
+++ b/cucumber-junit-platform-engine/README.md
@@ -26,7 +26,8 @@ like this:
```mermaid
erDiagram
- "IDE, Maven, Gradle or Console Launcher" ||--|{ "JUnit Platform" : "requests discovery and execution"
+ "IDE" ||--|{ "JUnit Platform" : "requests discovery and execution"
+ "Maven or Gradle" ||--|{ "JUnit Platform" : "requests discovery and execution"
"Console Launcher" ||--|{ "JUnit Platform" : "requests discovery and execution"
"JUnit Platform" ||--|{ "Cucumber Test Engine": "forwards request"
"JUnit Platform" ||--|{ "Jupiter Test Engine": "forwards request"
@@ -211,11 +212,12 @@ different configurations. Conceptually this looks like this:
```mermaid
erDiagram
- "IDE, Maven, Gradle or Console Launcher" ||--|{ "JUnit Platform" : "requests discovery and execution"
+ "IDE" ||--|{ "JUnit Platform" : "requests discovery and execution"
+ "Maven or Gradle" ||--|{ "JUnit Platform" : "requests discovery and execution"
+ "Console Launcher" ||--|{ "JUnit Platform" : "requests discovery and execution"
"JUnit Platform" ||--|{ "Suite Test Engine": "forwards request"
"Suite Test Engine" ||--|{ "@Suite annotated class A" : "discovers and executes"
"Suite Test Engine" ||--|{ "@Suite annotated class B" : "discovers and executes"
-
"@Suite annotated class A" ||--|{ "JUnit Platform (A)" : "requests discovery and execution"
"@Suite annotated class B" ||--|{ "JUnit Platform (B)" : "requests discovery and execution"
"JUnit Platform (A)" ||--|{ "Cucumber Test Engine (A)": "forwards request"
@@ -424,12 +426,12 @@ cucumber.junit-platform.naming-strategy= # long, short or
cucumber.junit-platform.naming-strategy.short.example-name= # number, number-and-pickle-if-parameterized or pickle.
# default: number-and-pickle-if-parameterized
- # Use example number or pickle name for examples when
+ # Use example number and/or pickle name for examples when
# short naming strategy is used
cucumber.junit-platform.naming-strategy.long.example-name= # number, number-and-pickle-if-parameterized or pickle.
# default: number-and-pickle-if-parameterized
- # Use example number or pickle name for examples when
+ # Use example number and/or pickle name for examples when
# long naming strategy is used
cucumber.junit-platform.naming-strategy.surefire.example-name= # number or pickle.
@@ -471,6 +473,16 @@ cucumber.execution.execution-mode.feature= # same_thread or
# concurrent - executes scenarios concurrently on any
# available thread
+cucumber.execution.order= # lexical, reverse or random
+ # default: lexical
+ # lexical - executes features in lexical uri order, scenarios and examples from top to bottom
+ # reverse - as lexical, but with the elements of each container reversed
+ # random - executes scenarios and examples in a random order within their parent container
+
+cucumber.execution.order.random.seed= # any long
+ # example: 20090120
+ # enables deterministic random execution
+
cucumber.execution.parallel.enabled= # true or false.
# default: false
diff --git a/cucumber-junit-platform-engine/pom.xml b/cucumber-junit-platform-engine/pom.xml
index ca741a096a..3b70cca899 100644
--- a/cucumber-junit-platform-engine/pom.xml
+++ b/cucumber-junit-platform-engine/pom.xml
@@ -65,6 +65,11 @@
junit-jupiter-api
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
org.junit.platform
junit-platform-testkit
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CachingFeatureParser.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CachingFeatureParser.java
deleted file mode 100644
index 621cda5fe3..0000000000
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CachingFeatureParser.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package io.cucumber.junit.platform.engine;
-
-import io.cucumber.core.feature.FeatureParser;
-import io.cucumber.core.gherkin.Feature;
-import io.cucumber.core.resource.Resource;
-
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-class CachingFeatureParser {
-
- private final Map> cache = new HashMap<>();
- private final FeatureParser delegate;
-
- CachingFeatureParser(FeatureParser delegate) {
- this.delegate = delegate;
- }
-
- Optional parseResource(Resource resource) {
- return cache.computeIfAbsent(resource.getUri(), uri -> delegate.parseResource(resource));
- }
-}
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 0349498808..1cacae44cb 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
@@ -291,6 +291,27 @@ public final class Constants {
*/
public static final String EXECUTION_MODE_FEATURE_PROPERTY_NAME = "cucumber.execution.execution-mode.feature";
+ /**
+ * Property name used to set execution order: {@value}
+ *
+ * Valid values are {@code lexical}, {@code reverse} or {@code random}.
+ *
+ * By default, features are executed in lexical file name order and
+ * scenarios in a feature from top to bottom.
+ */
+ public static final String EXECUTION_ORDER_PROPERTY_NAME = io.cucumber.core.options.Constants.EXECUTION_ORDER_PROPERTY_NAME;
+
+ /**
+ * Property name used to set the seed for random execution order: {@value}
+ *
+ * Valid values are any value understood by {@link Long#decode(String)}. If
+ * omitted a random seed is used instead. The exact value can be obtained by
+ *
+ * listening for discovery issues.
+ */
+ public static final String EXECUTION_ORDER_RANDOM_SEED_PROPERTY_NAME = "cucumber.execution.order.random.seed";
+
/**
* Property name used to enable parallel test execution: {@value}
*
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Cucumber.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Cucumber.java
index 41ea03f685..4684e78719 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Cucumber.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Cucumber.java
@@ -24,7 +24,9 @@
*
*
* @deprecated Please use the JUnit Platform Suite to run Cucumber in
- * combination with Surefire or Gradle. E.g: {@code
+ * combination with Surefire or Gradle. E.g:
+ *
+ * {@code
*package com.example;
*
*import org.junit.platform.suite.api.ConfigurationParameter;
@@ -33,15 +35,15 @@
*
*import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
*
- *@Suite
- *@SelectPackages("com.example")
- *@ConfigurationParameter(
- * key = GLUE_PROPERTY_NAME,
- * value = "com.example"
- *)
- *public class RunCucumberTest {
- *}
- *}
+ * @Suite
+ * @SelectPackages("com.example")
+ * @ConfigurationParameter(
+ * key = GLUE_PROPERTY_NAME,
+ * value = "com.example")
+ * public class RunCucumberTest {
+ * }
+ * }
+ *
* @see CucumberTestEngine
*/
@API(status = Status.DEPRECATED)
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/CucumberConfiguration.java
similarity index 83%
rename from cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java
rename to cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberConfiguration.java
index f12b963b12..3c0a8909de 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/CucumberConfiguration.java
@@ -11,23 +11,29 @@
import io.cucumber.core.plugin.NoPublishFormatter;
import io.cucumber.core.plugin.PublishFormatter;
import io.cucumber.core.snippets.SnippetType;
+import io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureWithLinesSelector;
import io.cucumber.tagexpressions.Expression;
import io.cucumber.tagexpressions.TagExpressionParser;
import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
+import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
+import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
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_EXCLUSIVE_RESOURCES_PREFIX;
+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;
@@ -41,8 +47,9 @@
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.SNIPPET_TYPE_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.UUID_GENERATOR_PROPERTY_NAME;
+import static java.util.Objects.requireNonNull;
-class CucumberEngineOptions implements
+class CucumberConfiguration implements
io.cucumber.core.plugin.Options,
io.cucumber.core.runner.Options,
io.cucumber.core.backend.Options,
@@ -50,8 +57,8 @@ class CucumberEngineOptions implements
private final ConfigurationParameters configurationParameters;
- CucumberEngineOptions(ConfigurationParameters configurationParameters) {
- this.configurationParameters = configurationParameters;
+ CucumberConfiguration(ConfigurationParameters configurationParameters) {
+ this.configurationParameters = requireNonNull(configurationParameters);
}
@Override
@@ -177,14 +184,28 @@ NamingStrategy namingStrategy() {
.create(configurationParameters);
}
- List featuresWithLines() {
+ Set featuresWithLines() {
return configurationParameters.get(FEATURES_PROPERTY_NAME,
s -> Arrays.stream(s.split(","))
.map(String::trim)
.map(FeatureWithLines::parse)
- .sorted(Comparator.comparing(FeatureWithLines::uri))
- .distinct()
- .collect(Collectors.toList()))
- .orElse(Collections.emptyList());
+ .map(FeatureWithLinesSelector::from)
+ .collect(Collectors.toSet()))
+ .orElse(Collections.emptySet());
}
+
+ ExecutionMode getExecutionModeFeature() {
+ return configurationParameters.get(EXECUTION_MODE_FEATURE_PROPERTY_NAME,
+ value -> ExecutionMode.valueOf(value.toUpperCase(Locale.US)))
+ .orElse(ExecutionMode.CONCURRENT);
+ }
+
+ ExclusiveResourceConfiguration getExclusiveResourceConfiguration(String tag) {
+ requireNonNull(tag);
+ return new ExclusiveResourceConfiguration(new PrefixedConfigurationParameters(
+ configurationParameters,
+ EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + tag));
+
+ }
+
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberDiscoverySelectors.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberDiscoverySelectors.java
new file mode 100644
index 0000000000..f82e1e83ec
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberDiscoverySelectors.java
@@ -0,0 +1,159 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.core.feature.FeatureWithLines;
+import io.cucumber.core.gherkin.Feature;
+import io.cucumber.plugin.event.Node;
+import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.discovery.FilePosition;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toSet;
+
+class CucumberDiscoverySelectors {
+
+ static final class FeatureWithLinesSelector implements DiscoverySelector {
+ private final URI uri;
+ private final Set filePositions;
+
+ private FeatureWithLinesSelector(URI uri, Set filePositions) {
+ this.uri = requireNonNull(uri);
+ this.filePositions = requireNonNull(filePositions);
+ }
+
+ static FeatureWithLinesSelector from(FeatureWithLines featureWithLines) {
+ Set lines = featureWithLines.lines().stream()
+ .map(FilePosition::from)
+ .collect(Collectors.toSet());
+ return new FeatureWithLinesSelector(featureWithLines.uri(), lines);
+ }
+
+ static Set from(UniqueId uniqueId) {
+ return uniqueId.getSegments()
+ .stream()
+ .filter(FeatureOrigin::isFeatureSegment)
+ .map(featureSegment -> {
+ URI uri = URI.create(featureSegment.getValue());
+ Set filePosition = getFilePosition(uniqueId.getLastSegment());
+ return new FeatureWithLinesSelector(uri, filePosition);
+ })
+ .collect(Collectors.toSet());
+ }
+
+ static FeatureWithLinesSelector from(URI uri) {
+ Set positions = FilePosition.fromQuery(uri.getQuery())
+ .map(Collections::singleton)
+ .orElseGet(Collections::emptySet);
+ return new FeatureWithLinesSelector(stripQuery(uri), positions);
+ }
+
+ private static URI stripQuery(URI uri) {
+ if (uri.getQuery() == null) {
+ return uri;
+ }
+ String uriString = uri.toString();
+ return URI.create(uriString.substring(0, uriString.indexOf('?')));
+ }
+
+ private static Set getFilePosition(UniqueId.Segment segment) {
+ if (FeatureOrigin.isFeatureSegment(segment)) {
+ return Collections.emptySet();
+ }
+
+ int line = Integer.parseInt(segment.getValue());
+ return Collections.singleton(FilePosition.from(line));
+ }
+
+ URI getUri() {
+ return uri;
+ }
+
+ Optional> getFilePositions() {
+ return filePositions.isEmpty() ? Optional.empty() : Optional.of(filePositions);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ FeatureWithLinesSelector that = (FeatureWithLinesSelector) o;
+ return uri.equals(that.uri) && filePositions.equals(that.filePositions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, filePositions);
+ }
+ }
+
+ static class FeatureElementSelector implements DiscoverySelector {
+
+ private final Feature feature;
+ private final Node element;
+
+ private FeatureElementSelector(Feature feature) {
+ this(feature, feature);
+ }
+
+ private FeatureElementSelector(Feature feature, Node element) {
+ this.feature = requireNonNull(feature);
+ this.element = requireNonNull(element);
+ }
+
+ static FeatureElementSelector selectFeature(Feature feature) {
+ return new FeatureElementSelector(feature);
+ }
+
+ static FeatureElementSelector selectElement(Feature feature, Node element) {
+ return new FeatureElementSelector(feature, element);
+ }
+
+ static Optional selectElementAt(Feature feature, FilePosition filePosition) {
+ return feature.findPathTo(candidate -> candidate.getLocation().getLine() == filePosition.getLine())
+ .map(nodes -> nodes.get(nodes.size() - 1))
+ .map(node -> new FeatureElementSelector(feature, node));
+ }
+
+ static Set selectElementsOf(Feature feature, Node selected) {
+ if (selected instanceof Node.Container) {
+ Node.Container> container = (Node.Container>) selected;
+ return container.elements().stream()
+ .map(element -> new FeatureElementSelector(feature, element))
+ .collect(toSet());
+ }
+ return Collections.emptySet();
+ }
+
+ Feature getFeature() {
+ return feature;
+ }
+
+ Node getElement() {
+ return element;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ FeatureElementSelector that = (FeatureElementSelector) o;
+ return feature.equals(that.feature) && element.equals(that.element);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(feature, element);
+ }
+ }
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineDescriptor.java
index 84b0e733e8..2986c670ff 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineDescriptor.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineDescriptor.java
@@ -1,6 +1,5 @@
package io.cucumber.junit.platform.engine;
-import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
@@ -9,20 +8,28 @@
import java.util.Optional;
import java.util.function.Consumer;
+import static java.util.Objects.requireNonNull;
+
class CucumberEngineDescriptor extends EngineDescriptor implements Node {
static final String ENGINE_ID = "cucumber";
+ private final CucumberConfiguration configuration;
private final TestSource source;
- CucumberEngineDescriptor(UniqueId uniqueId) {
- this(uniqueId, null);
+ CucumberEngineDescriptor(UniqueId uniqueId, CucumberConfiguration configuration) {
+ this(uniqueId, configuration, null);
}
- CucumberEngineDescriptor(UniqueId uniqueId, TestSource source) {
+ CucumberEngineDescriptor(UniqueId uniqueId, CucumberConfiguration configuration, TestSource source) {
super(uniqueId, "Cucumber");
+ this.configuration = requireNonNull(configuration);
this.source = source;
}
+ public CucumberConfiguration getConfiguration() {
+ return configuration;
+ }
+
@Override
public Optional getSource() {
return Optional.ofNullable(this.source);
@@ -65,18 +72,4 @@ private CucumberEngineExecutionContext ifChildren(
return context;
}
- void mergeFeature(FeatureDescriptor descriptor) {
- recursivelyMerge(descriptor, this);
- }
-
- private static void recursivelyMerge(TestDescriptor descriptor, TestDescriptor parent) {
- Optional extends TestDescriptor> byUniqueId = parent.findByUniqueId(descriptor.getUniqueId());
- if (!byUniqueId.isPresent()) {
- parent.addChild(descriptor);
- } else {
- byUniqueId.ifPresent(
- existingParent -> descriptor.getChildren()
- .forEach(child -> recursivelyMerge(child, existingParent)));
- }
- }
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java
index 447b98d279..2609add2bd 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java
@@ -21,7 +21,6 @@
import io.cucumber.core.runtime.TimeServiceEventBus;
import io.cucumber.core.runtime.UuidGeneratorServiceLoader;
import org.apiguardian.api.API;
-import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.support.hierarchical.EngineExecutionContext;
import java.time.Clock;
@@ -34,41 +33,43 @@
public final class CucumberEngineExecutionContext implements EngineExecutionContext {
private static final Logger log = LoggerFactory.getLogger(CucumberEngineExecutionContext.class);
- private final CucumberEngineOptions options;
+ private final CucumberConfiguration configuration;
private CucumberExecutionContext context;
- CucumberEngineExecutionContext(ConfigurationParameters configurationParameters) {
- options = new CucumberEngineOptions(configurationParameters);
+ CucumberEngineExecutionContext(CucumberConfiguration configuration) {
+ this.configuration = configuration;
}
- CucumberEngineOptions getOptions() {
- return options;
+ CucumberConfiguration getConfiguration() {
+ return configuration;
}
private CucumberExecutionContext createCucumberExecutionContext() {
Supplier classLoader = CucumberEngineExecutionContext.class::getClassLoader;
- UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, options);
+ UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader,
+ configuration);
EventBus bus = synchronize(
new TimeServiceEventBus(Clock.systemUTC(), uuidGeneratorServiceLoader.loadUuidGenerator()));
- ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(classLoader, options);
- Plugins plugins = new Plugins(new PluginFactory(), options);
- ExitStatus exitStatus = new ExitStatus(options);
+ ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(classLoader,
+ configuration);
+ Plugins plugins = new Plugins(new PluginFactory(), configuration);
+ ExitStatus exitStatus = new ExitStatus(configuration);
plugins.addPlugin(exitStatus);
RunnerSupplier runnerSupplier;
- if (options.isParallelExecutionEnabled()) {
+ if (configuration.isParallelExecutionEnabled()) {
plugins.setSerialEventBusOnEventListenerPlugins(bus);
ObjectFactorySupplier objectFactorySupplier = new ThreadLocalObjectFactorySupplier(
objectFactoryServiceLoader);
BackendSupplier backendSupplier = new BackendServiceLoader(classLoader, objectFactorySupplier);
- runnerSupplier = new ThreadLocalRunnerSupplier(options, bus, backendSupplier, objectFactorySupplier);
+ runnerSupplier = new ThreadLocalRunnerSupplier(configuration, bus, backendSupplier, objectFactorySupplier);
} else {
plugins.setEventBusOnEventListenerPlugins(bus);
ObjectFactorySupplier objectFactorySupplier = new SingletonObjectFactorySupplier(
objectFactoryServiceLoader);
BackendSupplier backendSupplier = new BackendServiceLoader(classLoader, objectFactorySupplier);
- runnerSupplier = new SingletonRunnerSupplier(options, bus, backendSupplier, objectFactorySupplier);
+ runnerSupplier = new SingletonRunnerSupplier(configuration, bus, backendSupplier, objectFactorySupplier);
}
return new CucumberExecutionContext(bus, exitStatus, runnerSupplier);
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestDescriptor.java
new file mode 100644
index 0000000000..4adecda831
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestDescriptor.java
@@ -0,0 +1,253 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.core.gherkin.Feature;
+import io.cucumber.core.gherkin.Pickle;
+import io.cucumber.plugin.event.Location;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.TestTag;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
+import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
+import org.junit.platform.engine.support.hierarchical.Node;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toSet;
+
+abstract class CucumberTestDescriptor extends AbstractTestDescriptor {
+
+ protected CucumberTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) {
+ super(uniqueId, displayName, source);
+ }
+
+ protected abstract URI getUri();
+
+ protected abstract Location getLocation();
+
+ static class FeatureDescriptor extends CucumberTestDescriptor implements Node {
+
+ private final Feature feature;
+
+ FeatureDescriptor(UniqueId uniqueId, String name, TestSource source, Feature feature) {
+ super(uniqueId, name, source);
+ this.feature = feature;
+ }
+
+ Feature getFeature() {
+ return feature;
+ }
+
+ @Override
+ public CucumberEngineExecutionContext prepare(CucumberEngineExecutionContext context) {
+ context.beforeFeature(feature);
+ return context;
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+
+ @Override
+ protected URI getUri() {
+ return feature.getUri();
+ }
+
+ @Override
+ protected Location getLocation() {
+ return feature.getLocation();
+ }
+ }
+
+ abstract static class FeatureElementDescriptor extends CucumberTestDescriptor
+ implements Node {
+
+ private final CucumberConfiguration configuration;
+ private final io.cucumber.plugin.event.Node element;
+
+ FeatureElementDescriptor(
+ CucumberConfiguration configuration, UniqueId uniqueId, String name, TestSource source,
+ io.cucumber.plugin.event.Node element
+ ) {
+ super(uniqueId, name, source);
+ this.configuration = configuration;
+ this.element = element;
+ }
+
+ @Override
+ public ExecutionMode getExecutionMode() {
+ return configuration.getExecutionModeFeature();
+ }
+
+ @Override
+ protected Location getLocation() {
+ return element.getLocation();
+ }
+
+ @Override
+ protected URI getUri() {
+ return element.getUri();
+ }
+
+ static final class ExamplesDescriptor extends FeatureElementDescriptor {
+
+ ExamplesDescriptor(
+ CucumberConfiguration configuration, UniqueId uniqueId, String name, TestSource source,
+ io.cucumber.plugin.event.Node element
+ ) {
+ super(configuration, uniqueId, name, source, element);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+
+ }
+
+ static final class RuleDescriptor extends FeatureElementDescriptor {
+
+ RuleDescriptor(
+ CucumberConfiguration configuration, UniqueId uniqueId, String name, TestSource source,
+ io.cucumber.plugin.event.Node element
+ ) {
+ super(configuration, uniqueId, name, source, element);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+
+ }
+
+ static final class ScenarioOutlineDescriptor extends FeatureElementDescriptor {
+
+ ScenarioOutlineDescriptor(
+ CucumberConfiguration configuration, UniqueId uniqueId, String name,
+ TestSource source, io.cucumber.plugin.event.Node element
+ ) {
+ super(configuration, uniqueId, name, source, element);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+
+ }
+ }
+
+ static final class PickleDescriptor extends CucumberTestDescriptor implements Node {
+
+ private final Pickle pickle;
+ private final CucumberConfiguration configuration;
+
+ PickleDescriptor(
+ CucumberConfiguration configuration, UniqueId uniqueId, String name, TestSource source,
+ Pickle pickle
+ ) {
+ super(uniqueId, name, source);
+ this.configuration = configuration;
+ this.pickle = pickle;
+ }
+
+ Pickle getPickle() {
+ return pickle;
+ }
+
+ @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.getConfiguration().tagFilter().map(expression -> {
+ if (expression.evaluate(pickle.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.getConfiguration().nameFilter().map(pattern -> {
+ if (pattern.matcher(pickle.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(pickle);
+ return context;
+ }
+
+ @Override
+ public Set getExclusiveResources() {
+ return getTags().stream()
+ .map(tag -> configuration.getExclusiveResourceConfiguration(tag.getName()))
+ .flatMap(ExclusiveResourceConfiguration::getExclusiveResources)
+ .collect(toSet());
+ }
+
+ /**
+ * 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 pickle.getTags().stream()
+ .map(tag -> tag.substring(1))
+ .filter(TestTag::isValid)
+ .map(TestTag::create)
+ // Retain input order
+ .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet));
+ }
+
+ @Override
+ protected URI getUri() {
+ return pickle.getUri();
+ }
+
+ @Override
+ protected Location getLocation() {
+ return pickle.getLocation();
+ }
+
+ @Override
+ public ExecutionMode getExecutionMode() {
+ return configuration.getExecutionModeFeature();
+ }
+ }
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestEngine.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestEngine.java
index 7d5d8f2108..b3b302ad90 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestEngine.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestEngine.java
@@ -9,13 +9,15 @@
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;
import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.PARALLEL_CONFIG_PREFIX;
-import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;
+import static org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.deduplicating;
+import static org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.forwarding;
/**
* The Cucumber {@link org.junit.platform.engine.TestEngine TestEngine}.
@@ -43,8 +45,16 @@ public String getId() {
@Override
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
TestSource testSource = createEngineTestSource(discoveryRequest);
- CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId, testSource);
- new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor);
+ CucumberConfiguration configuration = new CucumberConfiguration(discoveryRequest.getConfigurationParameters());
+ CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId, configuration, testSource);
+
+ DiscoveryIssueReporter issueReporter = deduplicating(forwarding( //
+ discoveryRequest.getDiscoveryListener(), //
+ engineDescriptor.getUniqueId() //
+ ));
+
+ FeaturesPropertyResolver resolver = new FeaturesPropertyResolver(new DiscoverySelectorResolver());
+ resolver.resolveSelectors(discoveryRequest, engineDescriptor, issueReporter);
return engineDescriptor;
}
@@ -63,17 +73,23 @@ private static TestSource createEngineTestSource(EngineDiscoveryRequest discover
@Override
protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) {
- ConfigurationParameters config = request.getConfigurationParameters();
- if (config.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false)) {
+ CucumberConfiguration configuration = getCucumberConfiguration(request);
+ if (configuration.isParallelExecutionEnabled()) {
return new ForkJoinPoolHierarchicalTestExecutorService(
- new PrefixedConfigurationParameters(config, PARALLEL_CONFIG_PREFIX));
+ new PrefixedConfigurationParameters(request.getConfigurationParameters(), PARALLEL_CONFIG_PREFIX));
}
return super.createExecutorService(request);
}
@Override
protected CucumberEngineExecutionContext createExecutionContext(ExecutionRequest request) {
- return new CucumberEngineExecutionContext(request.getConfigurationParameters());
+ CucumberConfiguration configuration = getCucumberConfiguration(request);
+ return new CucumberEngineExecutionContext(configuration);
+ }
+
+ private CucumberConfiguration getCucumberConfiguration(ExecutionRequest request) {
+ CucumberEngineDescriptor engineDescriptor = (CucumberEngineDescriptor) request.getRootTestDescriptor();
+ return engineDescriptor.getConfiguration();
}
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultDescriptorOrderingStrategy.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultDescriptorOrderingStrategy.java
new file mode 100644
index 0000000000..c0f6124393
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultDescriptorOrderingStrategy.java
@@ -0,0 +1,72 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.core.logging.Logger;
+import io.cucumber.core.logging.LoggerFactory;
+import org.junit.platform.engine.ConfigurationParameters;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.function.UnaryOperator;
+
+import static io.cucumber.junit.platform.engine.Constants.EXECUTION_ORDER_PROPERTY_NAME;
+import static io.cucumber.junit.platform.engine.Constants.EXECUTION_ORDER_RANDOM_SEED_PROPERTY_NAME;
+
+enum DefaultDescriptorOrderingStrategy implements DescriptorOrderingStrategy {
+
+ LEXICAL {
+ @Override
+ public UnaryOperator> create(ConfigurationParameters configuration) {
+ return pickles -> {
+ pickles.sort(lexical);
+ return pickles;
+ };
+ }
+ },
+ REVERSE {
+ @Override
+ public UnaryOperator> create(ConfigurationParameters configuration) {
+ return pickles -> {
+ pickles.sort(lexical.reversed());
+ return pickles;
+ };
+ }
+ },
+ RANDOM {
+ @Override
+ public UnaryOperator> create(ConfigurationParameters configuration) {
+ long seed = configuration
+ .get(EXECUTION_ORDER_RANDOM_SEED_PROPERTY_NAME, Long::decode)
+ .orElseGet(this::createRandomSeed);
+ // Invoked multiple times, keep state outside of closure.
+ Random random = new Random(seed);
+ return testDescriptors -> {
+ // Sort in expected order first to remove arbitrary initial
+ // ordering before applying a deterministic shuffle.
+ testDescriptors.sort(lexical);
+ Collections.shuffle(testDescriptors, random);
+ return testDescriptors;
+ };
+
+ }
+
+ private long createRandomSeed() {
+ long generatedSeed = Math.abs(new Random().nextLong());
+ log.config(() -> String.format("Using generated seed for configuration parameter '%s' with value '%s'.",
+ EXECUTION_ORDER_RANDOM_SEED_PROPERTY_NAME, generatedSeed));
+ return generatedSeed;
+ }
+ };
+ private static final Logger log = LoggerFactory.getLogger(DefaultDescriptorOrderingStrategy.class);
+
+ private static final Comparator lexical = Comparator
+ .comparing(CucumberTestDescriptor::getUri)
+ .thenComparing(CucumberTestDescriptor::getLocation);
+
+ static DefaultDescriptorOrderingStrategy getStrategy(ConfigurationParameters configurationParameters) {
+ return valueOf(
+ configurationParameters.get(EXECUTION_ORDER_PROPERTY_NAME).orElse("lexical").toUpperCase(Locale.ROOT));
+ }
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultNamingStrategyProvider.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultNamingStrategyProvider.java
index 55508153b4..0af1c0f709 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultNamingStrategyProvider.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultNamingStrategyProvider.java
@@ -108,7 +108,7 @@ public String name(Node node) {
}
@Override
- public String nameExample(Node.Example node, Pickle pickle) {
+ public String nameExample(Node node, Pickle pickle) {
return exampleNameFunction.apply(node, pickle);
}
};
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DescriptorOrderingStrategy.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DescriptorOrderingStrategy.java
new file mode 100644
index 0000000000..222c0caaaa
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DescriptorOrderingStrategy.java
@@ -0,0 +1,20 @@
+package io.cucumber.junit.platform.engine;
+
+import org.junit.platform.engine.ConfigurationParameters;
+
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+interface DescriptorOrderingStrategy {
+
+ /**
+ * Creates a unary operator used by
+ * {@link org.junit.platform.engine.TestDescriptor#orderChildren(UnaryOperator)}.
+ *
+ * @param configuration to pull configuration values from, never
+ * {@code null}.
+ * @return an operator, never {@code null}.
+ */
+ UnaryOperator> create(ConfigurationParameters configuration);
+
+}
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 6088759f4c..abbc75d11f 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
@@ -1,115 +1,35 @@
package io.cucumber.junit.platform.engine;
-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 io.cucumber.core.feature.FeatureIdentifier;
import org.junit.platform.engine.EngineDiscoveryRequest;
-import org.junit.platform.engine.Filter;
-import org.junit.platform.engine.TestDescriptor;
-import org.junit.platform.engine.discovery.ClassSelector;
-import org.junit.platform.engine.discovery.ClasspathResourceSelector;
-import org.junit.platform.engine.discovery.ClasspathRootSelector;
-import org.junit.platform.engine.discovery.DirectorySelector;
-import org.junit.platform.engine.discovery.FileSelector;
-import org.junit.platform.engine.discovery.PackageNameFilter;
-import org.junit.platform.engine.discovery.PackageSelector;
-import org.junit.platform.engine.discovery.UniqueIdSelector;
-import org.junit.platform.engine.discovery.UriSelector;
+import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
+import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver;
-import java.util.List;
-import java.util.function.Predicate;
-
-import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
-import static io.cucumber.junit.platform.engine.FeatureResolver.create;
-import static org.junit.platform.engine.Filter.composeFilters;
+import static io.cucumber.core.feature.FeatureIdentifier.isFeature;
class DiscoverySelectorResolver {
- private static final Logger log = LoggerFactory.getLogger(DiscoverySelectorResolver.class);
-
- private static boolean warnedWhenCucumberFeaturesPropertyIsUsed = false;
-
- private static void warnWhenCucumberFeaturesPropertyIsUsed() {
- if (warnedWhenCucumberFeaturesPropertyIsUsed) {
- return;
- }
- warnedWhenCucumberFeaturesPropertyIsUsed = true;
- log.warn(
- () -> "Discovering tests using the " + FEATURES_PROPERTY_NAME + " property. Other discovery " +
- "selectors are ignored!\n" +
- "\n" +
- "This is a work around for the limited JUnit 5 support in Maven and Gradle. " +
- "Please request/upvote/sponsor/ect better support for JUnit 5 discovery selectors. " +
- "For details see: https://github.com/cucumber/cucumber-jvm/pull/2498\n" +
- "\n" +
- "If you are using the JUnit 5 Suite Engine, Platform Launcher API or Console Launcher you " +
- "should not use this property. Please consult the JUnit 5 documentation on test selection.");
- }
-
- void resolveSelectors(EngineDiscoveryRequest request, CucumberEngineDescriptor engineDescriptor) {
- Predicate packageFilter = buildPackageFilter(request);
- resolve(request, engineDescriptor, packageFilter);
- filter(engineDescriptor, packageFilter);
- pruneTree(engineDescriptor);
- }
-
- private Predicate buildPackageFilter(EngineDiscoveryRequest request) {
- Filter packageFilter = composeFilters(request.getFiltersByType(PackageNameFilter.class));
- return packageFilter.toPredicate();
- }
-
- private void resolve(
- EngineDiscoveryRequest request, CucumberEngineDescriptor engineDescriptor, Predicate packageFilter
+ private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver
+ . builder()
+ .addSelectorResolver(context -> new FileContainerSelectorResolver( //
+ FeatureIdentifier::isFeature //
+ ))
+ .addResourceContainerSelectorResolver(resource -> isFeature(resource.getName()))
+ .addSelectorResolver(context -> new FeatureResolver(
+ context.getEngineDescriptor().getConfiguration(), //
+ context.getPackageFilter(), //
+ context.getIssueReporter() //
+ ))
+ .addTestDescriptorVisitor(context -> new OrderingVisitor(
+ context.getDiscoveryRequest().getConfigurationParameters() //
+ ))
+ .build();
+
+ void resolveSelectors(
+ EngineDiscoveryRequest request, CucumberEngineDescriptor engineDescriptor,
+ DiscoveryIssueReporter issueReporter
) {
- ConfigurationParameters configuration = request.getConfigurationParameters();
- FeatureResolver featureResolver = create(
- configuration,
- engineDescriptor,
- packageFilter);
-
- CucumberEngineOptions options = new CucumberEngineOptions(configuration);
- List featureWithLines = options.featuresWithLines();
- if (!featureWithLines.isEmpty()) {
- warnWhenCucumberFeaturesPropertyIsUsed();
- featureWithLines.forEach(featureResolver::resolveFeatureWithLines);
- return;
- }
-
- request.getSelectorsByType(ClasspathRootSelector.class).forEach(featureResolver::resolveClasspathRoot);
- request.getSelectorsByType(ClasspathResourceSelector.class).forEach(featureResolver::resolveClasspathResource);
- request.getSelectorsByType(ClassSelector.class).forEach(featureResolver::resolveClass);
- request.getSelectorsByType(PackageSelector.class).forEach(featureResolver::resolvePackageResource);
- request.getSelectorsByType(FileSelector.class).forEach(featureResolver::resolveFile);
- request.getSelectorsByType(DirectorySelector.class).forEach(featureResolver::resolveDirectory);
- request.getSelectorsByType(UniqueIdSelector.class).forEach(featureResolver::resolveUniqueId);
- request.getSelectorsByType(UriSelector.class).forEach(featureResolver::resolveUri);
- }
-
- private void filter(TestDescriptor engineDescriptor, Predicate packageFilter) {
- applyPackagePredicate(packageFilter, engineDescriptor);
- }
-
- private void pruneTree(TestDescriptor rootDescriptor) {
- rootDescriptor.accept(TestDescriptor::prune);
- }
-
- private void applyPackagePredicate(Predicate packageFilter, TestDescriptor engineDescriptor) {
- engineDescriptor.accept(descriptor -> {
- if (descriptor instanceof PickleDescriptor) {
- PickleDescriptor pickleDescriptor = (PickleDescriptor) descriptor;
- if (!includePickle(pickleDescriptor, packageFilter)) {
- descriptor.removeFromHierarchy();
- }
- }
- });
- }
-
- private boolean includePickle(PickleDescriptor pickleDescriptor, Predicate packageFilter) {
- return pickleDescriptor.getPackage()
- .map(packageFilter::test)
- .orElse(true);
+ resolver.resolve(request, engineDescriptor, issueReporter);
}
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/ExclusiveResourceConfiguration.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/ExclusiveResourceConfiguration.java
new file mode 100644
index 0000000000..2ef7655737
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/ExclusiveResourceConfiguration.java
@@ -0,0 +1,43 @@
+package io.cucumber.junit.platform.engine;
+
+import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+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.Objects.requireNonNull;
+import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ;
+import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE;
+
+final class ExclusiveResourceConfiguration {
+
+ private final ConfigurationParameters configuration;
+
+ ExclusiveResourceConfiguration(ConfigurationParameters configuration) {
+ this.configuration = requireNonNull(configuration);
+ }
+
+ private Stream exclusiveReadWriteResource() {
+ return configuration.get(READ_WRITE_SUFFIX, s -> Arrays.stream(s.split(","))
+ .map(String::trim))
+ .orElse(Stream.empty());
+ }
+
+ private Stream exclusiveReadResource() {
+ return configuration.get(READ_SUFFIX, s -> Arrays.stream(s.split(","))
+ .map(String::trim))
+ .orElse(Stream.empty());
+ }
+
+ Stream getExclusiveResources() {
+ Stream readWrite = exclusiveReadWriteResource()
+ .map(resource -> new ExclusiveResource(resource, READ_WRITE));
+ Stream read = exclusiveReadResource()
+ .map(resource -> new ExclusiveResource(resource, READ));
+ return Stream.concat(readWrite, read);
+ }
+
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureDescriptor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureDescriptor.java
deleted file mode 100644
index b8d9d003ab..0000000000
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureDescriptor.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package io.cucumber.junit.platform.engine;
-
-import io.cucumber.core.gherkin.Feature;
-import org.junit.platform.engine.TestDescriptor;
-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;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Predicate;
-
-class FeatureDescriptor extends AbstractTestDescriptor implements Node {
-
- private final Feature feature;
-
- FeatureDescriptor(UniqueId uniqueId, String name, TestSource source, Feature feature) {
- super(uniqueId, name, source);
- this.feature = feature;
- }
-
- Feature getFeature() {
- return feature;
- }
-
- private static void pruneRecursively(TestDescriptor descriptor, Predicate toKeep) {
- if (!toKeep.test(descriptor)) {
- if (descriptor.isTest()) {
- descriptor.removeFromHierarchy();
- }
- List children = new ArrayList<>(descriptor.getChildren());
- children.forEach(child -> pruneRecursively(child, toKeep));
- }
- }
-
- void prune(Predicate toKeep) {
- pruneRecursively(this, toKeep);
- }
-
- @Override
- public CucumberEngineExecutionContext prepare(CucumberEngineExecutionContext context) {
- context.beforeFeature(feature);
- return context;
- }
-
- @Override
- public Type getType() {
- return Type.CONTAINER;
- }
-
-}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureOrigin.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureOrigin.java
index 128e75bb87..9d6b648a38 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureOrigin.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureOrigin.java
@@ -1,6 +1,5 @@
package io.cucumber.junit.platform.engine;
-import io.cucumber.core.gherkin.Feature;
import io.cucumber.plugin.event.Location;
import io.cucumber.plugin.event.Node;
import org.junit.platform.engine.TestSource;
@@ -16,11 +15,11 @@
abstract class FeatureOrigin {
- private static final String RULE_SEGMENT_TYPE = "rule";
- private static final String FEATURE_SEGMENT_TYPE = "feature";
- private static final String SCENARIO_SEGMENT_TYPE = "scenario";
- private static final String EXAMPLES_SEGMENT_TYPE = "examples";
- private static final String EXAMPLE_SEGMENT_TYPE = "example";
+ static final String RULE_SEGMENT_TYPE = "rule";
+ static final String FEATURE_SEGMENT_TYPE = "feature";
+ static final String SCENARIO_SEGMENT_TYPE = "scenario";
+ static final String EXAMPLES_SEGMENT_TYPE = "examples";
+ static final String EXAMPLE_SEGMENT_TYPE = "example";
private static FilePosition createFilePosition(Location location) {
return FilePosition.from(location.getLine(), location.getColumn());
@@ -50,27 +49,9 @@ static boolean isFeatureSegment(UniqueId.Segment segment) {
return FEATURE_SEGMENT_TYPE.equals(segment.getType());
}
- abstract TestSource featureSource();
-
abstract TestSource nodeSource(Node node);
- abstract UniqueId featureSegment(UniqueId parent, Feature feature);
-
- UniqueId ruleSegment(UniqueId parent, Node rule) {
- return parent.append(RULE_SEGMENT_TYPE, String.valueOf(rule.getLocation().getLine()));
- }
-
- UniqueId scenarioSegment(UniqueId parent, Node scenarioDefinition) {
- return parent.append(SCENARIO_SEGMENT_TYPE, String.valueOf(scenarioDefinition.getLocation().getLine()));
- }
-
- UniqueId examplesSegment(UniqueId parent, Node examples) {
- return parent.append(EXAMPLES_SEGMENT_TYPE, String.valueOf(examples.getLocation().getLine()));
- }
-
- UniqueId exampleSegment(UniqueId parent, Node tableRow) {
- return parent.append(EXAMPLE_SEGMENT_TYPE, String.valueOf(tableRow.getLocation().getLine()));
- }
+ abstract TestSource source();
private static class FileFeatureOrigin extends FeatureOrigin {
@@ -80,19 +61,14 @@ private static class FileFeatureOrigin extends FeatureOrigin {
this.source = source;
}
- @Override
- TestSource featureSource() {
- return source;
- }
-
@Override
TestSource nodeSource(Node node) {
return FileSource.from(source.getFile(), createFilePosition(node.getLocation()));
}
@Override
- UniqueId featureSegment(UniqueId parent, Feature feature) {
- return parent.append(FEATURE_SEGMENT_TYPE, source.getUri().toString());
+ TestSource source() {
+ return source;
}
}
@@ -105,21 +81,15 @@ private static class UriFeatureOrigin extends FeatureOrigin {
this.source = source;
}
- @Override
- TestSource featureSource() {
- return source;
- }
-
@Override
TestSource nodeSource(Node node) {
return source;
}
@Override
- UniqueId featureSegment(UniqueId parent, Feature feature) {
- return parent.append(FEATURE_SEGMENT_TYPE, source.getUri().toString());
+ TestSource source() {
+ return source;
}
-
}
private static class ClasspathFeatureOrigin extends FeatureOrigin {
@@ -130,11 +100,6 @@ private static class ClasspathFeatureOrigin extends FeatureOrigin {
this.source = source;
}
- @Override
- TestSource featureSource() {
- return source;
- }
-
@Override
TestSource nodeSource(Node node) {
return ClasspathResourceSource.from(source.getClasspathResourceName(),
@@ -142,10 +107,9 @@ TestSource nodeSource(Node node) {
}
@Override
- UniqueId featureSegment(UniqueId parent, Feature feature) {
- return parent.append(FEATURE_SEGMENT_TYPE, feature.getUri().toString());
+ TestSource source() {
+ return source;
}
-
}
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureParserWithCaching.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureParserWithCaching.java
new file mode 100644
index 0000000000..65616e1480
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureParserWithCaching.java
@@ -0,0 +1,80 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.gherkin.Feature;
+import io.cucumber.core.resource.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+class FeatureParserWithCaching {
+
+ private final Map> cache = new HashMap<>();
+ private final FeatureParserWithIssueReporting delegate;
+
+ FeatureParserWithCaching(FeatureParserWithIssueReporting delegate) {
+ this.delegate = delegate;
+ }
+
+ Optional parseResource(Resource resource) {
+ return cache.computeIfAbsent(resource.getUri(), uri -> delegate.parseResource(resource));
+ }
+
+ Optional parseResource(Path resource) {
+ return parseResource(new PathAdapter(resource));
+ }
+
+ Optional parseResource(org.junit.platform.commons.support.Resource resource) {
+ return parseResource(new ResourceAdapter(resource));
+ }
+
+ private static class ResourceAdapter implements Resource {
+ private final org.junit.platform.commons.support.Resource resource;
+
+ public ResourceAdapter(org.junit.platform.commons.support.Resource resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public URI getUri() {
+ String name = resource.getName();
+ try {
+ return new URI("classpath", name, null);
+ } catch (URISyntaxException e) {
+ String message = String.format("Could not create classpath uri for resource '%s'", name);
+ throw new CucumberException(message, e);
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return resource.getInputStream();
+ }
+ }
+
+ private static class PathAdapter implements Resource {
+ private final Path resource;
+
+ public PathAdapter(Path resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public URI getUri() {
+ return resource.toUri();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return Files.newInputStream(resource);
+ }
+ }
+
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureParserWithIssueReporting.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureParserWithIssueReporting.java
new file mode 100644
index 0000000000..fd056910b6
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureParserWithIssueReporting.java
@@ -0,0 +1,38 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.core.feature.FeatureParser;
+import io.cucumber.core.gherkin.Feature;
+import io.cucumber.core.gherkin.FeatureParserException;
+import io.cucumber.core.resource.Resource;
+import org.junit.platform.engine.DiscoveryIssue;
+import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
+
+import java.util.Optional;
+
+import static org.junit.platform.engine.DiscoveryIssue.Severity.ERROR;
+
+class FeatureParserWithIssueReporting {
+
+ private final FeatureParser delegate;
+ private final DiscoveryIssueReporter issueReporter;
+
+ FeatureParserWithIssueReporting(FeatureParser delegate, DiscoveryIssueReporter issueReporter) {
+ this.delegate = delegate;
+ this.issueReporter = issueReporter;
+ }
+
+ Optional parseResource(Resource resource) {
+ try {
+ return delegate.parseResource(resource);
+ } catch (FeatureParserException e) {
+ FeatureOrigin featureOrigin = FeatureOrigin.fromUri(resource.getUri());
+ issueReporter.reportIssue(DiscoveryIssue
+ // TODO: Improve parse exception to separate out source uri
+ // and individual errors.
+ .builder(ERROR, e.getMessage())
+ .cause(e.getCause())
+ .source(featureOrigin.source()));
+ return Optional.empty();
+ }
+ }
+}
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 5003a04873..46c665630e 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
@@ -3,275 +3,307 @@
import io.cucumber.core.eventbus.UuidGenerator;
import io.cucumber.core.feature.FeatureIdentifier;
import io.cucumber.core.feature.FeatureParser;
-import io.cucumber.core.feature.FeatureWithLines;
import io.cucumber.core.gherkin.Feature;
import io.cucumber.core.gherkin.Pickle;
-import io.cucumber.core.logging.Logger;
-import io.cucumber.core.logging.LoggerFactory;
import io.cucumber.core.resource.ClassLoaders;
import io.cucumber.core.resource.ResourceScanner;
import io.cucumber.core.runtime.UuidGeneratorServiceLoader;
-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.junit.platform.engine.CucumberDiscoverySelectors.FeatureElementSelector;
+import io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureWithLinesSelector;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureDescriptor;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureElementDescriptor.ExamplesDescriptor;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureElementDescriptor.RuleDescriptor;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureElementDescriptor.ScenarioOutlineDescriptor;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.PickleDescriptor;
import io.cucumber.plugin.event.Node;
-import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.commons.support.Resource;
+import org.junit.platform.engine.DiscoveryIssue;
+import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.discovery.ClassSelector;
import org.junit.platform.engine.discovery.ClasspathResourceSelector;
-import org.junit.platform.engine.discovery.ClasspathRootSelector;
-import org.junit.platform.engine.discovery.DirectorySelector;
import org.junit.platform.engine.discovery.FileSelector;
-import org.junit.platform.engine.discovery.PackageSelector;
import org.junit.platform.engine.discovery.UniqueIdSelector;
import org.junit.platform.engine.discovery.UriSelector;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
+import org.junit.platform.engine.support.discovery.SelectorResolver;
import java.net.URI;
-import java.util.List;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
-import static java.util.Comparator.comparing;
-
-final class FeatureResolver {
-
- private static final Logger log = LoggerFactory.getLogger(FeatureResolver.class);
-
+import static io.cucumber.core.feature.FeatureIdentifier.isFeature;
+import static io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureElementSelector.selectElement;
+import static io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureElementSelector.selectElementAt;
+import static io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureElementSelector.selectElementsOf;
+import static io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureElementSelector.selectFeature;
+import static io.cucumber.junit.platform.engine.FeatureOrigin.EXAMPLES_SEGMENT_TYPE;
+import static io.cucumber.junit.platform.engine.FeatureOrigin.EXAMPLE_SEGMENT_TYPE;
+import static io.cucumber.junit.platform.engine.FeatureOrigin.FEATURE_SEGMENT_TYPE;
+import static io.cucumber.junit.platform.engine.FeatureOrigin.RULE_SEGMENT_TYPE;
+import static io.cucumber.junit.platform.engine.FeatureOrigin.SCENARIO_SEGMENT_TYPE;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static org.junit.platform.engine.DiscoveryIssue.Severity.WARNING;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
+
+final class FeatureResolver implements SelectorResolver {
private final ResourceScanner featureScanner;
- private final CucumberEngineDescriptor engineDescriptor;
+ private final CucumberConfiguration configuration;
+ private final FeatureParserWithCaching featureParser;
private final Predicate packageFilter;
- private final ConfigurationParameters parameters;
- private final NamingStrategy namingStrategy;
+ private final DiscoveryIssueReporter issueReporter;
- private FeatureResolver(
- ConfigurationParameters parameters, CucumberEngineDescriptor engineDescriptor,
- Predicate packageFilter
+ FeatureResolver(
+ CucumberConfiguration configuration, Predicate packageFilter, DiscoveryIssueReporter issueReporter
) {
- this.parameters = parameters;
- this.engineDescriptor = engineDescriptor;
+ this.configuration = configuration;
this.packageFilter = packageFilter;
- CucumberEngineOptions options = new CucumberEngineOptions(parameters);
- this.namingStrategy = options.namingStrategy();
- CachingFeatureParser featureParser = createFeatureParser(options);
+ this.issueReporter = issueReporter;
+ this.featureParser = createFeatureParser(configuration, issueReporter);
this.featureScanner = new ResourceScanner<>(
ClassLoaders::getDefaultClassLoader,
FeatureIdentifier::isFeature,
featureParser::parseResource);
}
- private static CachingFeatureParser createFeatureParser(CucumberEngineOptions options) {
+ private static FeatureParserWithCaching createFeatureParser(
+ CucumberConfiguration options, DiscoveryIssueReporter issueReporter
+ ) {
Supplier classLoader = FeatureResolver.class::getClassLoader;
UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, options);
UuidGenerator uuidGenerator = uuidGeneratorServiceLoader.loadUuidGenerator();
FeatureParser featureParser = new FeatureParser(uuidGenerator::generateId);
- return new CachingFeatureParser(featureParser);
+ FeatureParserWithIssueReporting featureParserWithIssueReporting = new FeatureParserWithIssueReporting(
+ featureParser, issueReporter);
+ return new FeatureParserWithCaching(featureParserWithIssueReporting);
}
- static FeatureResolver create(
- ConfigurationParameters parameters, CucumberEngineDescriptor engineDescriptor,
- Predicate packageFilter
- ) {
- return new FeatureResolver(parameters, engineDescriptor, packageFilter);
+ @Override
+ public Resolution resolve(DiscoverySelector selector, Context context) {
+ if (selector instanceof FeatureElementSelector) {
+ return resolve((FeatureElementSelector) selector, context);
+ }
+ if (selector instanceof FeatureWithLinesSelector) {
+ return resolve((FeatureWithLinesSelector) selector);
+ }
+ return SelectorResolver.super.resolve(selector, context);
}
- void resolveFile(FileSelector selector) {
- featureScanner
- .scanForResourcesPath(selector.getPath())
- .stream()
- .sorted(comparing(Feature::getUri))
- .map(this::createFeatureDescriptor)
- .forEach(featureDescriptor -> {
- featureDescriptor.prune(TestDescriptorOnLine.from(selector));
- engineDescriptor.mergeFeature(featureDescriptor);
- });
+ public Resolution resolve(FeatureElementSelector selector, Context context) {
+ Feature feature = selector.getFeature();
+ Node selected = selector.getElement();
+ return selected.getParent()
+ .map(parent -> context.addToParent(() -> selectElement(feature, parent),
+ createTestDescriptor(feature, selected)))
+ .orElseGet(() -> context.addToParent(createTestDescriptor(feature, selected)))
+ .map(descriptor -> Match.exact(descriptor, () -> selectElementsOf(feature, selected)))
+ .map(Resolution::match)
+ .orElseGet(Resolution::unresolved);
}
- private FeatureDescriptor createFeatureDescriptor(Feature feature) {
- FeatureOrigin source = FeatureOrigin.fromUri(feature.getUri());
-
- return (FeatureDescriptor) feature.map(
- engineDescriptor,
- (Node.Feature self, TestDescriptor parent) -> new FeatureDescriptor(
- source.featureSegment(parent.getUniqueId(), feature),
- namingStrategy.name(self),
- source.featureSource(),
- feature),
- (Node.Rule node, TestDescriptor parent) -> {
- TestDescriptor descriptor = new RuleDescriptor(
- parameters,
- source.ruleSegment(parent.getUniqueId(), node),
- namingStrategy.name(node),
- source.nodeSource(node));
- parent.addChild(descriptor);
- return descriptor;
- }, (Node.Scenario node, TestDescriptor parent) -> {
- Pickle pickle = feature.getPickleAt(node);
- TestDescriptor descriptor = new PickleDescriptor(
- parameters,
- source.scenarioSegment(parent.getUniqueId(), node),
- namingStrategy.name(node),
- source.nodeSource(node),
- pickle);
- parent.addChild(descriptor);
- return descriptor;
- },
- (Node.ScenarioOutline node, TestDescriptor parent) -> {
- TestDescriptor descriptor = new ScenarioOutlineDescriptor(
- parameters,
- source.scenarioSegment(parent.getUniqueId(), node),
- namingStrategy.name(node),
- source.nodeSource(node));
- parent.addChild(descriptor);
- return descriptor;
- },
- (Node.Examples node, TestDescriptor parent) -> {
- NodeDescriptor descriptor = new ExamplesDescriptor(
- parameters,
- source.examplesSegment(parent.getUniqueId(), node),
- namingStrategy.name(node),
- source.nodeSource(node));
- parent.addChild(descriptor);
- return descriptor;
- },
- (Node.Example node, TestDescriptor parent) -> {
- Pickle pickle = feature.getPickleAt(node);
- PickleDescriptor descriptor = new PickleDescriptor(
- parameters,
- source.exampleSegment(parent.getUniqueId(), node),
- namingStrategy.nameExample(node, pickle),
- source.nodeSource(node),
- pickle);
- parent.addChild(descriptor);
- return descriptor;
- });
+ public Resolution resolve(FeatureWithLinesSelector selector) {
+ URI uri = selector.getUri();
+ Set selectors = featureScanner
+ .scanForResourcesUri(uri)
+ .stream()
+ .flatMap(feature -> selector.getFilePositions()
+ .map(filePositions -> filePositions.stream()
+ .map(position -> selectElementAt(feature, position))
+ .filter(Optional::isPresent)
+ .map(Optional::get))
+ .orElseGet(() -> Stream.of(selectFeature(feature))))
+ .collect(toSet());
+
+ return toResolution(selectors);
}
- void resolveDirectory(DirectorySelector selector) {
- featureScanner
- .scanForResourcesPath(selector.getPath())
+ @Override
+ public Resolution resolve(FileSelector selector, Context context) {
+ Set selectors = featureParser.parseResource(selector.getPath())
.stream()
- .sorted(comparing(Feature::getUri))
- .map(this::createFeatureDescriptor)
- .forEach(engineDescriptor::mergeFeature);
+ .map(feature -> selector.getPosition()
+ .map(position -> selectElementAt(feature, position))
+ .orElseGet(() -> Optional.of(selectFeature(feature))))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toSet());
+ return toResolution(selectors);
}
- void resolvePackageResource(PackageSelector selector) {
- resolvePackageResource(selector.getPackageName());
+ @Override
+ public Resolution resolve(ClasspathResourceSelector selector, Context context) {
+ Set resources = selector.getClasspathResources();
+ if (!resources.stream().allMatch(resource -> isFeature(resource.getName()))) {
+ return resolveClasspathResourceSelectorAsPackageSelector(selector);
+ }
+ if (resources.size() > 1) {
+ throw new IllegalArgumentException(String.format(
+ "Found %s resources named %s on the classpath %s.",
+ resources.size(), selector.getClasspathResourceName(),
+ resources.stream().map(Resource::getUri).collect(toList())));
+ }
+ return resources.stream()
+ .findFirst()
+ .flatMap(featureParser::parseResource)
+ .map(feature -> selector.getPosition()
+ .map(position -> selectElementAt(feature, position))
+ .orElseGet(() -> Optional.of(selectFeature(feature))))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(Collections::singleton)
+ .map(FeatureResolver::toResolution)
+ .orElseGet(Resolution::unresolved);
}
- private List resolvePackageResource(String packageName) {
- List features = featureScanner
- .scanForResourcesInPackage(packageName, packageFilter);
-
- features
+ @SuppressWarnings("DeprecatedIsStillUsed")
+ @Deprecated
+ private Resolution resolveClasspathResourceSelectorAsPackageSelector(ClasspathResourceSelector selector) {
+ Set selectors = featureScanner
+ .scanForClasspathResource(selector.getClasspathResourceName(), packageFilter)
.stream()
- .sorted(comparing(Feature::getUri))
- .map(this::createFeatureDescriptor)
- .forEach(engineDescriptor::mergeFeature);
-
- return features;
- }
+ .map(feature -> selector.getPosition()
+ .map(position -> selectElementAt(feature, position))
+ .orElseGet(() -> Optional.of(selectFeature(feature))))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toSet());
- void resolveClass(ClassSelector classSelector) {
- Class> javaClass = classSelector.getJavaClass();
- Cucumber annotation = javaClass.getAnnotation(Cucumber.class);
- if (annotation != null) {
- // We know now the intention is to run feature files in the
- // package of the annotated class.
- resolvePackageResourceWarnIfNone(javaClass.getPackage().getName());
- }
- }
+ warnClasspathResourceSelectorUsedForPackage(selector);
- private void resolvePackageResourceWarnIfNone(String packageName) {
- List features = resolvePackageResource(packageName);
- if (features.isEmpty()) {
- log.warn(() -> "No features found in package '" + packageName + "'");
- }
+ return toResolution(selectors);
}
- void resolveClasspathResource(ClasspathResourceSelector selector) {
+ private void warnClasspathResourceSelectorUsedForPackage(ClasspathResourceSelector selector) {
String classpathResourceName = selector.getClasspathResourceName();
-
- featureScanner
- .scanForClasspathResource(classpathResourceName, packageFilter)
- .stream()
- .sorted(comparing(Feature::getUri))
- .map(this::createFeatureDescriptor)
- .forEach(featureDescriptor -> {
- featureDescriptor.prune(TestDescriptorOnLine.from(selector));
- engineDescriptor.mergeFeature(featureDescriptor);
- });
+ String packageName = classpathResourceName.replaceAll("/", ".");
+ String message = String.format(
+ "The classpath resource selector '%s' should not be used to select features in a package. Use the package selector with '%s' instead",
+ classpathResourceName,
+ packageName);
+ issueReporter.reportIssue(DiscoveryIssue.builder(WARNING, message));
}
- void resolveClasspathRoot(ClasspathRootSelector selector) {
- featureScanner
- .scanForResourcesInClasspathRoot(selector.getClasspathRoot(), packageFilter)
- .stream()
- .sorted(comparing(Feature::getUri))
- .map(this::createFeatureDescriptor)
- .forEach(engineDescriptor::mergeFeature);
+ @Override
+ public Resolution resolve(UriSelector selector, Context context) {
+ URI uri = selector.getUri();
+ Set selectors = singleton(FeatureWithLinesSelector.from(uri));
+ return toResolution(selectors);
}
- void resolveUniqueId(UniqueIdSelector uniqueIdSelector) {
- UniqueId uniqueId = uniqueIdSelector.getUniqueId();
- // Ignore any ids not from our own engine
- if (!uniqueId.hasPrefix(engineDescriptor.getUniqueId())) {
- return;
+ @SuppressWarnings("deprecation")
+ @Override
+ public Resolution resolve(ClassSelector selector, Context context) {
+ Class> javaClass = selector.getJavaClass();
+ Cucumber annotation = javaClass.getAnnotation(Cucumber.class);
+ if (annotation != null) {
+ warnAboutDeprecatedCucumberClass(javaClass);
+ String packageName = javaClass.getPackage().getName();
+ Set selectors = singleton(selectPackage(packageName));
+ return toResolution(selectors);
}
-
- Predicate keepTestWithSelectedId = testDescriptor -> uniqueId
- .equals(testDescriptor.getUniqueId());
-
- List resolvedSegments = engineDescriptor.getUniqueId().getSegments();
-
- uniqueId.getSegments()
- .stream()
- .skip(resolvedSegments.size())
- .findFirst()
- .filter(FeatureOrigin::isFeatureSegment)
- .map(UniqueId.Segment::getValue)
- .map(URI::create)
- .map(this::resolveUri)
- .ifPresent(featureDescriptors -> featureDescriptors.forEach(featureDescriptor -> {
- featureDescriptor.prune(keepTestWithSelectedId);
- engineDescriptor.mergeFeature(featureDescriptor);
- }));
+ return Resolution.unresolved();
}
- private Stream resolveUri(URI uri) {
- return featureScanner
- .scanForResourcesUri(uri)
- .stream()
- .sorted(comparing(Feature::getUri))
- .map(this::createFeatureDescriptor);
+ private void warnAboutDeprecatedCucumberClass(Class> javaClass) {
+ String message = "The @Cucumber annotation has been deprecated. See the Javadoc for more details.";
+ DiscoveryIssue issue = DiscoveryIssue.builder(WARNING, message)
+ .source(ClassSource.from(javaClass))
+ .build();
+ issueReporter.reportIssue(issue);
}
- void resolveUri(UriSelector selector) {
- resolveUri(stripQuery(selector.getUri()))
- .forEach(featureDescriptor -> {
- featureDescriptor.prune(TestDescriptorOnLine.from(selector));
- engineDescriptor.mergeFeature(featureDescriptor);
- });
+ @Override
+ public Resolution resolve(UniqueIdSelector selector, Context context) {
+ UniqueId uniqueId = selector.getUniqueId();
+ Set selectors = FeatureWithLinesSelector.from(uniqueId);
+ return toResolution(selectors);
}
- void resolveFeatureWithLines(FeatureWithLines selector) {
- resolveUri(selector.uri())
- .forEach(featureDescriptor -> {
- featureDescriptor.prune(TestDescriptorOnLine.from(selector));
- engineDescriptor.mergeFeature(featureDescriptor);
- });
+ private Function> createTestDescriptor(Feature feature, Node node) {
+ return parent -> {
+ NamingStrategy namingStrategy = configuration.namingStrategy();
+ FeatureOrigin source = FeatureOrigin.fromUri(feature.getUri());
+ String name = namingStrategy.name(node);
+ TestSource testSource = source.nodeSource(node);
+ if (node instanceof Node.Feature) {
+ return Optional.of(new FeatureDescriptor(
+ parent.getUniqueId().append(FEATURE_SEGMENT_TYPE, feature.getUri().toString()),
+ name,
+ testSource,
+ feature));
+ }
+
+ int line = node.getLocation().getLine();
+
+ if (node instanceof Node.Rule) {
+ return Optional.of(new RuleDescriptor(
+ configuration,
+ parent.getUniqueId().append(RULE_SEGMENT_TYPE,
+ String.valueOf(line)),
+ name,
+ testSource,
+ node));
+ }
+
+ if (node instanceof Node.Scenario) {
+ return Optional.of(new PickleDescriptor(
+ configuration,
+ parent.getUniqueId().append(SCENARIO_SEGMENT_TYPE,
+ String.valueOf(line)),
+ name,
+ testSource,
+ feature.getPickleAt(node)));
+ }
+
+ if (node instanceof Node.ScenarioOutline) {
+ return Optional.of(new ScenarioOutlineDescriptor(
+ configuration,
+ parent.getUniqueId().append(SCENARIO_SEGMENT_TYPE,
+ String.valueOf(line)),
+ name,
+ testSource,
+ node));
+ }
+
+ if (node instanceof Node.Examples) {
+ return Optional.of(new ExamplesDescriptor(
+ configuration,
+ parent.getUniqueId().append(EXAMPLES_SEGMENT_TYPE,
+ String.valueOf(line)),
+ name,
+ testSource,
+ node));
+ }
+
+ if (node instanceof Node.Example) {
+ Pickle pickle = feature.getPickleAt(node);
+ return Optional.of(new PickleDescriptor(
+ configuration,
+ parent.getUniqueId().append(EXAMPLE_SEGMENT_TYPE,
+ String.valueOf(line)),
+ namingStrategy.nameExample(node, pickle),
+ testSource,
+ pickle));
+ }
+ throw new IllegalStateException("Got a " + node.getClass() + " but didn't have a case to handle it");
+ };
}
- private static URI stripQuery(URI uri) {
- if (uri.getQuery() == null) {
- return uri;
+ private static Resolution toResolution(Set extends DiscoverySelector> selectors) {
+ if (selectors.isEmpty()) {
+ return Resolution.unresolved();
}
- String uriString = uri.toString();
- return URI.create(uriString.substring(0, uriString.indexOf('?')));
+ return Resolution.selectors(selectors);
}
-
}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeaturesPropertyResolver.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeaturesPropertyResolver.java
new file mode 100644
index 0000000000..34c9d3e6b7
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeaturesPropertyResolver.java
@@ -0,0 +1,103 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureWithLinesSelector;
+import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.DiscoveryFilter;
+import org.junit.platform.engine.DiscoveryIssue;
+import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.engine.EngineDiscoveryRequest;
+import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
+
+import java.util.List;
+import java.util.Set;
+
+import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+import static org.junit.platform.engine.DiscoveryIssue.Severity.WARNING;
+
+/**
+ * Decorator to support resolving the
+ * {@value io.cucumber.junit.platform.engine.Constants#FEATURES_PROPERTY_NAME}
+ * property.
+ *
+ * The JUnit Platform provides various discovery selectors to select feature
+ * files. Unfortunately, these do not yet receive support from IDEs, Maven or
+ * Gradle. Resolving this property allows uses to target a single feature,
+ * scenario or example from the commandline.
+ *
+ * This class decorates the {@link DiscoverySelectorResolver}. When the features
+ * property is provided it replaces the discovery request.
+ *
+ * Note: This effectively causes Cucumber to ignore any requests from the JUnit
+ * Platform. So features will be discovered even when none are expected to be.
+ */
+class FeaturesPropertyResolver {
+
+ private final DiscoverySelectorResolver delegate;
+
+ FeaturesPropertyResolver(DiscoverySelectorResolver delegate) {
+ this.delegate = delegate;
+ }
+
+ void resolveSelectors(
+ EngineDiscoveryRequest request, CucumberEngineDescriptor engineDescriptor,
+ DiscoveryIssueReporter issueReporter
+ ) {
+ ConfigurationParameters configuration = request.getConfigurationParameters();
+ CucumberConfiguration options = new CucumberConfiguration(configuration);
+ Set selectors = options.featuresWithLines();
+
+ if (selectors.isEmpty()) {
+ delegate.resolveSelectors(request, engineDescriptor, issueReporter);
+ return;
+ }
+ issueReporter.reportIssue(createCucumberFeaturesPropertyIsUsedIssue());
+ EngineDiscoveryRequest replacement = new FeaturesPropertyDiscoveryRequest(request, selectors);
+ delegate.resolveSelectors(replacement, engineDescriptor, issueReporter);
+ }
+
+ private static DiscoveryIssue createCucumberFeaturesPropertyIsUsedIssue() {
+ return DiscoveryIssue.create(WARNING,
+ "Discovering tests using the " + FEATURES_PROPERTY_NAME + " property. Other discovery " +
+ "selectors are ignored!\n" +
+ "\n" +
+ "This is a work around for the limited JUnit 5 support in Maven and Gradle. " +
+ "Please request/upvote/sponsor/ect better support for JUnit 5 discovery selectors. " +
+ "For details see: https://github.com/cucumber/cucumber-jvm/pull/2498\n" +
+ "\n" +
+ "If you are using the JUnit 5 Suite Engine, Platform Launcher API or Console Launcher you " +
+ "should not use this property. Please consult the JUnit 5 documentation on test selection.");
+ }
+
+ private static class FeaturesPropertyDiscoveryRequest implements EngineDiscoveryRequest {
+
+ private final EngineDiscoveryRequest delegate;
+ private final Set extends DiscoverySelector> selectors;
+
+ public FeaturesPropertyDiscoveryRequest(
+ EngineDiscoveryRequest delegate,
+ Set extends DiscoverySelector> selectors
+ ) {
+ this.delegate = delegate;
+ this.selectors = selectors;
+ }
+
+ @Override
+ public List getSelectorsByType(Class selectorType) {
+ requireNonNull(selectorType);
+ return this.selectors.stream().filter(selectorType::isInstance).map(selectorType::cast).collect(toList());
+ }
+
+ @Override
+ public > List getFiltersByType(Class filterType) {
+ return delegate.getFiltersByType(filterType);
+ }
+
+ @Override
+ public ConfigurationParameters getConfigurationParameters() {
+ return delegate.getConfigurationParameters();
+ }
+ }
+
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FileContainerSelectorResolver.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FileContainerSelectorResolver.java
new file mode 100644
index 0000000000..7749dd6ad6
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FileContainerSelectorResolver.java
@@ -0,0 +1,33 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.core.resource.PathScanner;
+import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.engine.discovery.DirectorySelector;
+import org.junit.platform.engine.support.discovery.SelectorResolver;
+
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile;
+
+class FileContainerSelectorResolver implements SelectorResolver {
+
+ private final PathScanner pathScanner = new PathScanner();
+ private final Predicate filter;
+
+ FileContainerSelectorResolver(Predicate filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public Resolution resolve(DirectorySelector selector, Context context) {
+ Set selectors = new HashSet<>();
+ pathScanner.findResourcesForPath(selector.getPath(), filter, path -> selectors.add(selectFile(path.toFile())));
+ if (selectors.isEmpty()) {
+ return Resolution.unresolved();
+ }
+ return Resolution.selectors(selectors);
+ }
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NamingStrategy.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NamingStrategy.java
index 81d3b81bf4..4a45773791 100644
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NamingStrategy.java
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NamingStrategy.java
@@ -7,5 +7,5 @@ interface NamingStrategy {
String name(Node node);
- String nameExample(Node.Example node, Pickle pickle);
+ String nameExample(Node node, Pickle pickle);
}
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
deleted file mode 100644
index 5312941d2e..0000000000
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/NodeDescriptor.java
+++ /dev/null
@@ -1,225 +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.ExclusiveResource.LockMode;
-import org.junit.platform.engine.support.hierarchical.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;
-
-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.executionMode = parameters
- .get(EXECUTION_MODE_FEATURE_PROPERTY_NAME,
- value -> ExecutionMode.valueOf(value.toUpperCase(Locale.US)))
- .orElse(ExecutionMode.CONCURRENT);
- }
-
- @Override
- public ExecutionMode getExecutionMode() {
- return executionMode;
- }
-
- 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 pickle;
- private final Set tags;
- private final Set exclusiveResources = new LinkedHashSet<>(0);
-
- PickleDescriptor(
- ConfigurationParameters parameters, UniqueId uniqueId, String name, TestSource source,
- Pickle pickle
- ) {
- super(parameters, uniqueId, name, source);
- this.pickle = pickle;
- this.tags = getTags(pickle);
- 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);
- });
- }
-
- Pickle getPickle() {
- return pickle;
- }
-
- 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(pickle.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(pickle.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(pickle);
- 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/OrderingVisitor.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/OrderingVisitor.java
new file mode 100644
index 0000000000..3bb32cca9c
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/OrderingVisitor.java
@@ -0,0 +1,35 @@
+package io.cucumber.junit.platform.engine;
+
+import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.TestDescriptor;
+
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+import static io.cucumber.junit.platform.engine.DefaultDescriptorOrderingStrategy.getStrategy;
+
+class OrderingVisitor implements TestDescriptor.Visitor {
+
+ private final UnaryOperator> orderer;
+
+ OrderingVisitor(ConfigurationParameters configuration) {
+ this(getStrategy(configuration).create(configuration));
+ }
+
+ private OrderingVisitor(UnaryOperator> orderer) {
+ this.orderer = orderer;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void visit(TestDescriptor descriptor) {
+ descriptor.orderChildren(children -> {
+ // Ok. All TestDescriptors are AbstractCucumberTestDescriptor
+ @SuppressWarnings("rawtypes")
+ List cucumberDescriptors = (List) children;
+ orderer.apply(cucumberDescriptors);
+ return children;
+ });
+ }
+
+}
diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java
deleted file mode 100644
index 3725891788..0000000000
--- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package io.cucumber.junit.platform.engine;
-
-import io.cucumber.core.feature.FeatureWithLines;
-import org.junit.platform.engine.TestDescriptor;
-import org.junit.platform.engine.discovery.ClasspathResourceSelector;
-import org.junit.platform.engine.discovery.FileSelector;
-import org.junit.platform.engine.discovery.UriSelector;
-import org.junit.platform.engine.support.descriptor.ClasspathResourceSource;
-import org.junit.platform.engine.support.descriptor.FileSource;
-
-import java.util.Optional;
-import java.util.function.Predicate;
-
-import static org.junit.platform.engine.support.descriptor.FilePosition.fromQuery;
-
-@SuppressWarnings("Convert2MethodRef")
-class TestDescriptorOnLine {
-
- static Predicate testDescriptorOnLine(int line) {
- return descriptor -> descriptor.getSource()
- .flatMap(testSource -> {
- if (testSource instanceof FileSource) {
- FileSource fileSystemSource = (FileSource) testSource;
- return fileSystemSource.getPosition();
- }
- if (testSource instanceof ClasspathResourceSource) {
- ClasspathResourceSource classpathResourceSource = (ClasspathResourceSource) testSource;
- return classpathResourceSource.getPosition();
- }
- return Optional.empty();
- })
- .map(filePosition -> filePosition.getLine())
- .map(testSourceLine -> line == testSourceLine)
- .orElse(false);
- }
-
- private static boolean anyTestDescriptor(TestDescriptor testDescriptor) {
- return true;
- }
-
- private static Predicate eitherTestDescriptor(
- Predicate a, Predicate b
- ) {
- return a.or(b);
- }
-
- static Predicate from(FeatureWithLines selector) {
- return selector.lines().stream()
- .map(TestDescriptorOnLine::testDescriptorOnLine)
- .reduce(TestDescriptorOnLine::eitherTestDescriptor)
- .orElse(TestDescriptorOnLine::anyTestDescriptor);
- }
-
- static Predicate from(UriSelector selector) {
- String query = selector.getUri().getQuery();
- return fromQuery(query)
- .map(filePosition -> filePosition.getLine())
- .map(TestDescriptorOnLine::testDescriptorOnLine)
- .orElse(TestDescriptorOnLine::anyTestDescriptor);
- }
-
- static Predicate from(ClasspathResourceSelector selector) {
- return selector.getPosition()
- .map(filePosition -> filePosition.getLine())
- .map(TestDescriptorOnLine::testDescriptorOnLine)
- .orElse(TestDescriptorOnLine::anyTestDescriptor);
- }
-
- static Predicate from(FileSelector selector) {
- return selector.getPosition()
- .map(filePosition -> filePosition.getLine())
- .map(TestDescriptorOnLine::testDescriptorOnLine)
- .orElse(TestDescriptorOnLine::anyTestDescriptor);
- }
-
-}
diff --git a/cucumber-junit-platform-engine/src/test/bad-features/parse-error.feature b/cucumber-junit-platform-engine/src/test/bad-features/parse-error.feature
new file mode 100644
index 0000000000..b5b77f48e3
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/test/bad-features/parse-error.feature
@@ -0,0 +1,5 @@
+Feature: A feature with a parse error
+
+ Scenario: A single scenario
+ Given a single scenario
+ AndAStep with an invalid keyword
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/CucumberConfigurationTest.java
similarity index 83%
rename from cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java
rename to cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberConfigurationTest.java
index 271bcd1d9c..155e1dde3b 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/CucumberConfigurationTest.java
@@ -20,7 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-class CucumberEngineOptionsTest {
+class CucumberConfigurationTest {
@Test
void getPluginNames() {
@@ -28,12 +28,12 @@ void getPluginNames() {
Constants.PLUGIN_PROPERTY_NAME,
"html:path/to/report.html");
- assertThat(new CucumberEngineOptions(config).plugins().stream()
+ assertThat(new CucumberConfiguration(config).plugins().stream()
.map(Options.Plugin::pluginString)
.collect(toList()),
hasItem("html:path/to/report.html"));
- CucumberEngineOptions htmlAndJson = new CucumberEngineOptions(
+ CucumberConfiguration htmlAndJson = new CucumberConfiguration(
new MapConfigurationParameters(Constants.PLUGIN_PROPERTY_NAME,
"html:path/with spaces/to/report.html, message:path/with spaces/to/report.ndjson"));
@@ -48,7 +48,7 @@ void getPluginNamesWithPublishToken() {
ConfigurationParameters config = new MapConfigurationParameters(
Constants.PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME, "some/token");
- assertThat(new CucumberEngineOptions(config).plugins().stream()
+ assertThat(new CucumberConfiguration(config).plugins().stream()
.map(Options.Plugin::pluginString)
.collect(toList()),
hasItem("io.cucumber.core.plugin.PublishFormatter:some/token"));
@@ -58,7 +58,7 @@ void getPluginNamesWithPublishToken() {
void getPluginNamesWithNothingEnabled() {
ConfigurationParameters config = new EmptyConfigurationParameters();
- assertThat(new CucumberEngineOptions(config).plugins().stream()
+ assertThat(new CucumberConfiguration(config).plugins().stream()
.map(Options.Plugin::pluginString)
.collect(toList()),
empty());
@@ -69,7 +69,7 @@ void getPluginNamesWithPublishQuiteEnabled() {
ConfigurationParameters config = new MapConfigurationParameters(
Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true");
- assertThat(new CucumberEngineOptions(config).plugins().stream()
+ assertThat(new CucumberConfiguration(config).plugins().stream()
.map(Options.Plugin::pluginString)
.collect(toList()),
empty());
@@ -80,7 +80,7 @@ void getPluginNamesWithPublishEnabled() {
ConfigurationParameters config = new MapConfigurationParameters(
Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME, "true");
- assertThat(new CucumberEngineOptions(config).plugins().stream()
+ assertThat(new CucumberConfiguration(config).plugins().stream()
.map(Options.Plugin::pluginString)
.collect(toList()),
hasItem("io.cucumber.core.plugin.PublishFormatter"));
@@ -92,7 +92,7 @@ void getPluginNamesWithPublishDisabledAndPublishToken() {
Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME, "false",
Constants.PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME, "some/token"));
- assertThat(new CucumberEngineOptions(config).plugins().stream()
+ assertThat(new CucumberConfiguration(config).plugins().stream()
.map(Options.Plugin::pluginString)
.collect(toList()),
empty());
@@ -103,12 +103,12 @@ void isMonochrome() {
MapConfigurationParameters ansiColors = new MapConfigurationParameters(
Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME,
"true");
- assertTrue(new CucumberEngineOptions(ansiColors).isMonochrome());
+ assertTrue(new CucumberConfiguration(ansiColors).isMonochrome());
MapConfigurationParameters noAnsiColors = new MapConfigurationParameters(
Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME,
"false");
- assertFalse(new CucumberEngineOptions(noAnsiColors).isMonochrome());
+ assertFalse(new CucumberConfiguration(noAnsiColors).isMonochrome());
}
@Test
@@ -117,7 +117,7 @@ void getGlue() {
Constants.GLUE_PROPERTY_NAME,
"com.example.app, com.example.glue");
- assertThat(new CucumberEngineOptions(config).getGlue(),
+ assertThat(new CucumberConfiguration(config).getGlue(),
contains(
URI.create("classpath:/com/example/app"),
URI.create("classpath:/com/example/glue")));
@@ -128,12 +128,12 @@ void isDryRun() {
ConfigurationParameters dryRun = new MapConfigurationParameters(
Constants.EXECUTION_DRY_RUN_PROPERTY_NAME,
"true");
- assertTrue(new CucumberEngineOptions(dryRun).isDryRun());
+ assertTrue(new CucumberConfiguration(dryRun).isDryRun());
ConfigurationParameters noDryRun = new MapConfigurationParameters(
Constants.EXECUTION_DRY_RUN_PROPERTY_NAME,
"false");
- assertFalse(new CucumberEngineOptions(noDryRun).isDryRun());
+ assertFalse(new CucumberConfiguration(noDryRun).isDryRun());
}
@Test
@@ -142,12 +142,12 @@ void getSnippetType() {
Constants.SNIPPET_TYPE_PROPERTY_NAME,
"underscore");
- assertThat(new CucumberEngineOptions(underscore).getSnippetType(), is(SnippetType.UNDERSCORE));
+ assertThat(new CucumberConfiguration(underscore).getSnippetType(), is(SnippetType.UNDERSCORE));
ConfigurationParameters camelcase = new MapConfigurationParameters(
Constants.SNIPPET_TYPE_PROPERTY_NAME,
"camelcase");
- assertThat(new CucumberEngineOptions(camelcase).getSnippetType(), is(SnippetType.CAMELCASE));
+ assertThat(new CucumberConfiguration(camelcase).getSnippetType(), is(SnippetType.CAMELCASE));
}
@Test
@@ -155,15 +155,15 @@ void isParallelExecutionEnabled() {
ConfigurationParameters enabled = new MapConfigurationParameters(
Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME,
"true");
- assertTrue(new CucumberEngineOptions(enabled).isParallelExecutionEnabled());
+ assertTrue(new CucumberConfiguration(enabled).isParallelExecutionEnabled());
ConfigurationParameters disabled = new MapConfigurationParameters(
Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME,
"false");
- assertFalse(new CucumberEngineOptions(disabled).isParallelExecutionEnabled());
+ assertFalse(new CucumberConfiguration(disabled).isParallelExecutionEnabled());
ConfigurationParameters absent = new MapConfigurationParameters(
"some key", "some value");
- assertFalse(new CucumberEngineOptions(absent).isParallelExecutionEnabled());
+ assertFalse(new CucumberConfiguration(absent).isParallelExecutionEnabled());
}
@Test
@@ -172,7 +172,7 @@ void objectFactory() {
Constants.OBJECT_FACTORY_PROPERTY_NAME,
DefaultObjectFactory.class.getName());
- assertThat(new CucumberEngineOptions(configurationParameters).getObjectFactoryClass(),
+ assertThat(new CucumberConfiguration(configurationParameters).getObjectFactoryClass(),
is(DefaultObjectFactory.class));
}
@@ -182,7 +182,7 @@ void uuidGenerator() {
Constants.UUID_GENERATOR_PROPERTY_NAME,
IncrementingUuidGenerator.class.getName());
- assertThat(new CucumberEngineOptions(configurationParameters).getUuidGeneratorClass(),
+ assertThat(new CucumberConfiguration(configurationParameters).getUuidGeneratorClass(),
is(IncrementingUuidGenerator.class));
}
}
diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEventConditions.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEventConditions.java
new file mode 100644
index 0000000000..73842a773e
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEventConditions.java
@@ -0,0 +1,134 @@
+package io.cucumber.junit.platform.engine;
+
+import org.assertj.core.api.Condition;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.TestTag;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.testkit.engine.Event;
+import org.junit.platform.testkit.engine.EventConditions;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.allOf;
+import static org.junit.platform.commons.util.FunctionUtils.where;
+import static org.junit.platform.testkit.engine.Event.byTestDescriptor;
+import static org.junit.platform.testkit.engine.EventConditions.displayName;
+import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring;
+
+class CucumberEventConditions {
+
+ static Condition engine(Condition condition) {
+ return allOf(EventConditions.engine(), condition);
+ }
+
+ static Condition examples() {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getUniqueId, lastSegmentTYpe("examples"))),
+ "examples descriptor");
+ }
+
+ static Condition super Event> example(String uniqueIdSubstring, String displayName) {
+ return allOf(example(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName));
+ }
+
+ static Condition super Event> example(String uniqueIdSubstring) {
+ return allOf(example(), uniqueIdSubstring(uniqueIdSubstring));
+ }
+
+ static Condition example() {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getUniqueId, lastSegmentTYpe("example"))),
+ "examples descriptor");
+ }
+
+ static Condition super Event> examples(String uniqueIdSubstring) {
+ return allOf(examples(), uniqueIdSubstring(uniqueIdSubstring));
+ }
+
+ static Condition super Event> examples(String uniqueIdSubstring, String displayName) {
+ return allOf(examples(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName));
+ }
+
+ static Condition feature() {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getUniqueId, lastSegmentTYpe("feature"))),
+ "feature descriptor");
+ }
+
+ static Condition tags(Set tags) {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getTags, hasTags(tags))),
+ "has tags " + tags);
+ }
+
+ static Condition tags(String... tags) {
+ return tags(new HashSet<>(Arrays.asList(tags)));
+ }
+
+ private static Predicate> hasTags(Set expected) {
+ return testTags -> {
+ Set actual = testTags.stream().map(TestTag::getName).collect(Collectors.toSet());
+ return expected.equals(actual);
+ };
+ }
+
+ static Condition feature(String uniqueIdSubstring, String displayName) {
+ return allOf(feature(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName));
+ }
+
+ static Condition feature(String uniqueIdSubstring) {
+ return allOf(feature(), uniqueIdSubstring(uniqueIdSubstring));
+ }
+
+ static Condition rule() {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getUniqueId, lastSegmentTYpe("rule"))),
+ "rule descriptor");
+ }
+
+ static Condition super Event> rule(String uniqueIdSubstring, String displayName) {
+ return allOf(rule(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName));
+ }
+
+ static Condition scenario() {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getUniqueId, lastSegmentTYpe("scenario"))),
+ "feature descriptor");
+ }
+
+ static Condition super Event> scenario(String uniqueIdSubstring, String displayName) {
+ return allOf(scenario(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName));
+ }
+
+ static Condition super Event> scenario(Condition super Event> condition) {
+ return allOf(scenario(), condition);
+ }
+
+ static Condition super Event> scenario(String uniqueIdSubstring) {
+ return allOf(scenario(), uniqueIdSubstring(uniqueIdSubstring));
+ }
+
+ static Condition source(TestSource testSource) {
+ return new Condition<>(event -> event.getTestDescriptor().getSource().filter(testSource::equals).isPresent(),
+ "test descriptor with test source '%s'", testSource);
+ }
+
+ static Condition emptySource() {
+ return new Condition<>(event -> !event.getTestDescriptor().getSource().isPresent(), "without a test source");
+ }
+
+ private static Predicate lastSegmentTYpe(String type) {
+ return uniqueId -> uniqueId.getLastSegment().getType().equals(type);
+ }
+
+ static Condition prefix(UniqueId uniqueId) {
+ return new Condition<>(
+ byTestDescriptor(where(TestDescriptor::getUniqueId, candidate -> candidate.hasPrefix(uniqueId))),
+ "test descriptor with prefix " + uniqueId);
+ }
+}
diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java
index c86af75258..abc5590131 100644
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java
+++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java
@@ -1,35 +1,94 @@
package io.cucumber.junit.platform.engine;
-import org.assertj.core.api.Assertions;
-import org.assertj.core.api.Condition;
+import io.cucumber.core.gherkin.Feature;
+import io.cucumber.core.gherkin.Pickle;
+import io.cucumber.core.logging.LogRecordListener;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureDescriptor;
+import io.cucumber.junit.platform.engine.CucumberTestDescriptor.PickleDescriptor;
import org.junit.jupiter.api.Test;
-import org.junit.platform.engine.ConfigurationParameters;
-import org.junit.platform.engine.EngineDiscoveryRequest;
-import org.junit.platform.engine.EngineExecutionListener;
-import org.junit.platform.engine.ExecutionRequest;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.platform.commons.support.Resource;
+import org.junit.platform.engine.DiscoveryIssue;
+import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.TestDescriptor;
-import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.discovery.DiscoverySelectors;
+import org.junit.platform.engine.discovery.FilePosition;
import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.engine.support.descriptor.ClasspathResourceSource;
+import org.junit.platform.engine.support.descriptor.FileSource;
+import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
+import org.junit.platform.engine.support.hierarchical.Node;
+import org.junit.platform.testkit.engine.EngineDiscoveryResults;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Event;
-import org.junit.platform.testkit.engine.EventConditions;
+import java.io.File;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+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.EXECUTION_ORDER_PROPERTY_NAME;
+import static io.cucumber.junit.platform.engine.Constants.EXECUTION_ORDER_RANDOM_SEED_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;
-import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME;
+import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
+import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME;
+import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_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 io.cucumber.junit.platform.engine.CucumberEngineDescriptor.ENGINE_ID;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.emptySource;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.engine;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.example;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.examples;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.feature;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.prefix;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.rule;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.scenario;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.source;
+import static io.cucumber.junit.platform.engine.CucumberEventConditions.tags;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.platform.engine.UniqueId.forEngine;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri;
+import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames;
+import static org.junit.platform.engine.support.descriptor.FilePosition.from;
+import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT;
+import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD;
+import static org.junit.platform.testkit.engine.EventConditions.displayName;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.test;
+// TODO: Split out tests to multiple classes, but do use EngineTestKit everywhere
+
+@WithLogRecordListener
class CucumberTestEngineTest {
private final CucumberTestEngine engine = new CucumberTestEngine();
@@ -45,42 +104,472 @@ void version() {
}
@Test
- void createExecutionContext() {
- EngineExecutionListener listener = new EmptyEngineExecutionListener();
- ConfigurationParameters configuration = new EmptyConfigurationParameters();
- EngineDiscoveryRequest discoveryRequest = new EmptyEngineDiscoveryRequest(configuration);
- UniqueId id = UniqueId.forEngine(engine.getId());
- TestDescriptor testDescriptor = engine.discover(discoveryRequest, id);
- ExecutionRequest execution = new ExecutionRequest(testDescriptor, listener, configuration);
- assertNotNull(engine.createExecutionContext(execution));
+ void empty() {
+ EngineTestKit.engine(ENGINE_ID)
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(0, event(test()));
}
@Test
- void selectAndExecuteNoScenario() {
+ void notCucumber() {
EngineTestKit.engine(ENGINE_ID)
- .configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
+ .selectors(selectUniqueId(forEngine("not-cucumber")))
.execute()
- .testEvents()
+ .allEvents()
.assertThatEvents()
.haveExactly(0, event(test()));
}
@Test
- void selectAndExecuteSingleScenario() {
+ void supportsClassSelector() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClass(RunCucumberTest.class))
+ .execute()
+ .containerEvents()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"));
+ }
+
+ @Test
+ void warnsAboutClassSelector() {
+ EngineDiscoveryResults results = EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClass(RunCucumberTest.class))
+ .discover();
+
+ DiscoveryIssue discoveryIssue = results.getDiscoveryIssues().get(0);
+ assertThat(discoveryIssue.message())
+ .isEqualTo("The @Cucumber annotation has been deprecated. See the Javadoc for more details.");
+ }
+
+ @Test
+ void supportsClasspathResourceSelector() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ scenario("scenario:3", "A single scenario"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void warnWhenResourceSelectorIsUsedToSelectAPackage() {
+ EngineTestKit.Builder selectors = EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine"));
+
+ EngineDiscoveryResults discoveryResults = selectors.discover();
+ DiscoveryIssue discoveryIssue = discoveryResults.getDiscoveryIssues().get(0);
+ assertThat(discoveryIssue.message())
+ .isEqualTo(
+ "The classpath resource selector 'io/cucumber/junit/platform/engine' should not be " +
+ "used to select features in a package. Use the package selector with " +
+ "'io.cucumber.junit.platform.engine' instead");
+
+ // It should also still work
+ selectors
+ .execute()
+ .allEvents()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"));
+
+ }
+
+ @Test
+ void classpathResourceSelectorThrowIfDuplicateResources() {
+ class TestResource implements Resource {
+
+ private final String name;
+ private final File source;
+
+ TestResource(String name, File source) {
+ this.name = name;
+ this.source = source;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public URI getUri() {
+ return source.toURI();
+ }
+ }
+ Set resources = new LinkedHashSet<>(Arrays.asList(
+ new TestResource("io/cucumber/junit/platform/engine/single.feature",
+ new File("src/test/resources/io/cucumber/junit/platform/engine/single.feature")),
+ new TestResource("io/cucumber/junit/platform/engine/single.feature",
+ new File("src/test/resources/io/cucumber/junit/platform/engine/single.feature")),
+ new TestResource("io/cucumber/junit/platform/engine/single.feature",
+ new File("src/test/resources/io/cucumber/junit/platform/engine/single.feature"))));
+
+ Throwable exception = EngineTestKit.engine(ENGINE_ID) //
+ .selectors(selectClasspathResource(resources)) //
+ .discover() //
+ .getDiscoveryIssues() //
+ .get(0) //
+ .cause() //
+ .orElseThrow();
+
+ assertThat(exception) //
+ .isInstanceOf(IllegalArgumentException.class) //
+ .hasMessage( //
+ "Found %s resources named %s on the classpath %s.", //
+ resources.size(), //
+ "io/cucumber/junit/platform/engine/single.feature", //
+ resources.stream().map(Resource::getUri).collect(toList()));
+ }
+
+ @Test
+ void supportsClasspathResourceSelectorWithFilePosition() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/rule.feature", //
+ FilePosition.from(5)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(2, event(scenario("scenario:5", "An example of this rule")));
+ }
+
+ @Test
+ void supportsMultipleClasspathResourceSelectors() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"),
+ selectClasspathResource("io/cucumber/junit/platform/engine/scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(2, event(feature("single.feature", "A feature with a single scenario")))
+ .haveExactly(2, event(feature("scenario-outline.feature", "A feature with scenario outlines")));
+ }
+
+ @Test
+ void supportsClasspathResourceSelectorWithSpaceInResourceName() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/with space.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event(scenario(), finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsClasspathRootSelector() {
+ Path classpathRoot = Paths.get("src/test/resources/");
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathRoots(singleton(classpathRoot)).get(0))
+ .execute()
+ .containerEvents()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"),
+ feature("root.feature"));
+ }
+
+ @Test
+ void supportsDirectorySelector() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectDirectory("src/test/resources/io/cucumber/junit/platform/engine"))
+ .execute()
+ .containerEvents()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"));
+ }
+
+ @Test
+ void supportsFileSelector() {
EngineTestKit.engine(ENGINE_ID)
- .configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
.selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature"))
.execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ scenario("scenario:3", "A single scenario"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsFileSelectorWithFilePosition() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/rule.feature", //
+ FilePosition.from(5)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ scenario("scenario:5", "An example of this rule"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsPackageSelector() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectPackage("io.cucumber.junit.platform.engine"))
+ .execute()
+ .containerEvents()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"));
+ }
+
+ @Test
+ void supportsUriSelector() {
+ File file = new File("src/test/resources/io/cucumber/junit/platform/engine/single.feature");
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectUri(file.toURI()))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ scenario("scenario:3", "A single scenario"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsUriSelectorWithFilePosition() {
+ File file = new File("src/test/resources/io/cucumber/junit/platform/engine/rule.feature");
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectUri(file.toURI() + "?line=5"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event(scenario("scenario:5", "An example of this rule"), finishedSuccessfully()));
+ }
+
+ @ParameterizedTest
+ @MethodSource({
+ "supportsUniqueIdSelectorFromClasspathUri",
+ "supportsUniqueIdSelectorFromFileUri",
+ "supportsUniqueIdSelectorFromJarFileUri"
+ })
+ void supportsUniqueIdSelector(UniqueId selected) {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(DiscoverySelectors.selectUniqueId(selected))
+ .execute()
.testEvents()
.assertThatEvents()
- .haveExactly(2, event(test()))
- .haveExactly(1, event(finishedSuccessfully()));
+ .haveAtLeastOne(event(prefix(selected), finishedSuccessfully()));
+ }
+
+ static Set supportsUniqueIdSelectorFromClasspathUri() {
+ return discoverUniqueIds(selectPackage("io.cucumber.junit.platform.engine"));
+
+ }
+
+ static Set supportsUniqueIdSelectorFromFileUri() {
+ return discoverUniqueIds(selectDirectory("src/test/resources/io/cucumber/junit/platform/engine"));
+
+ }
+
+ static Set supportsUniqueIdSelectorFromJarFileUri() {
+ URI uri = new File("src/test/resources/feature.jar").toURI();
+ return discoverUniqueIds(selectUri(uri));
}
@Test
- void selectAndExecuteSingleScenarioThroughFeaturesProperty() {
+ void supportsUniqueIdSelectorWithMultipleSelectors() {
+ UniqueId a = EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .map(Event::getTestDescriptor)
+ .filter(PickleDescriptor.class::isInstance)
+ .map(TestDescriptor::getUniqueId)
+ .findAny()
+ .orElseThrow();
+
+ UniqueId b = EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"))
+ .execute()
+ .allEvents()
+ .map(Event::getTestDescriptor)
+ .filter(PickleDescriptor.class::isInstance)
+ .map(TestDescriptor::getUniqueId)
+ .findAny()
+ .orElseThrow();
+
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectUniqueId(a), selectUniqueId(b))
+ .execute()
+ .testEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(prefix(a), finishedSuccessfully()))
+ .haveAtLeastOne(event(prefix(b), finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsUniqueIdSelectorCachesParsedFeaturesAndPickles() {
+ DiscoverySelector featureSelector = selectClasspathResource(
+ "io/cucumber/junit/platform/engine/scenario-outline.feature");
+ DiscoverySelector[] uniqueIdsFromFeature = discoverUniqueIds(featureSelector)
+ .stream()
+ .map(DiscoverySelectors::selectUniqueId)
+ .toArray(DiscoverySelector[]::new);
+
+ EngineDiscoveryResults results = EngineTestKit.engine(ENGINE_ID)
+ .selectors(featureSelector)
+ .selectors(uniqueIdsFromFeature)
+ .discover();
+
+ Set pickleIdsFromFeature = results
+ .getEngineDescriptor().getChildren().stream()
+ .filter(FeatureDescriptor.class::isInstance)
+ .map(FeatureDescriptor.class::cast)
+ .map(FeatureDescriptor::getFeature)
+ .map(Feature::getPickles)
+ .flatMap(Collection::stream)
+ .map(Pickle::getId)
+ .collect(toSet());
+
+ Set pickleIdsFromPickles = results
+ .getEngineDescriptor().getDescendants().stream()
+ .filter(PickleDescriptor.class::isInstance)
+ .map(PickleDescriptor.class::cast)
+ .map(PickleDescriptor::getPickle)
+ .map(Pickle::getId)
+ .collect(toSet());
+
+ assertEquals(pickleIdsFromFeature, pickleIdsFromPickles);
+ }
+
+ private static Set discoverUniqueIds(DiscoverySelector discoverySelector) {
+ return EngineTestKit.engine(ENGINE_ID)
+ .selectors(discoverySelector)
+ .execute()
+ .allEvents()
+ .map(Event::getTestDescriptor)
+ .filter(Predicate.not(TestDescriptor::isRoot))
+ .map(TestDescriptor::getUniqueId)
+ .collect(toSet());
+ }
+
+ @Test
+ void supportsFilePositionFeature() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectFile("src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature", //
+ FilePosition.from(2)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(2, event(feature("scenario-outline.feature", "A feature with scenario outlines")));
+ }
+
+ @Test
+ void supportsFilePositionScenario() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectFile("src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature", //
+ FilePosition.from(5)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ scenario("scenario:5", "A scenario"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsFilePositionScenarioOutline() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectFile("src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature", //
+ FilePosition.from(11)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ scenario("scenario:11", "A scenario outline"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsFilePositionExamples() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectFile("src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature", //
+ FilePosition.from(17)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ examples("examples:17", "With some text"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsFilePositionExample() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectFile("src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature", //
+ FilePosition.from(19)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(1, event( //
+ example("example:19", "Example #1.1"), //
+ finishedSuccessfully()));
+ }
+
+ @Test
+ void supportsFilePositionRule() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/rule.feature", //
+ FilePosition.from(3)))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(2, event(rule("rule:3", "A rule")));
+ }
+
+ @Test
+ void executesFeaturesInUriOrderByDefault() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectPackage(""))
+ .execute()
+ .containerEvents()
+ .started()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"),
+ feature("root.feature"));
+ }
+
+ @Test
+ void supportsFeaturesProperty() {
EngineTestKit.engine(ENGINE_ID)
- .configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
.configurationParameter(FEATURES_PROPERTY_NAME,
"src/test/resources/io/cucumber/junit/platform/engine/single.feature")
.execute()
@@ -91,9 +580,22 @@ void selectAndExecuteSingleScenarioThroughFeaturesProperty() {
}
@Test
- void selectAndExecuteSingleScenarioWithoutFeaturesProperty() {
+ void supportsFeaturesPropertyWillIgnoreOtherSelectors() {
+ EngineDiscoveryResults discoveryResult = EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(FEATURES_PROPERTY_NAME,
+ "src/test/resources/io/cucumber/junit/platform/engine/single.feature")
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/rule.feature"))
+ .discover();
+
+ DiscoveryIssue discoveryIssue = discoveryResult.getDiscoveryIssues().get(0);
+ assertThat(discoveryIssue.message())
+ .startsWith(
+ "Discovering tests using the cucumber.features property. Other discovery selectors are ignored!");
+ }
+
+ @Test
+ void onlySetsEngineSourceWhenFeaturesPropertyIsUsed() {
EngineTestKit.engine(ENGINE_ID)
- .configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
.selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature"))
.execute()
.allEvents()
@@ -105,7 +607,6 @@ void selectAndExecuteSingleScenarioWithoutFeaturesProperty() {
@Test
void selectAndSkipDisabledScenarioByTags() {
EngineTestKit.engine(ENGINE_ID)
- .configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
.configurationParameter(FILTER_TAGS_PROPERTY_NAME, "@Integration and not @Disabled")
.selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature"))
.execute()
@@ -119,28 +620,416 @@ void selectAndSkipDisabledScenarioByTags() {
@Test
void selectAndSkipDisabledScenarioByName() {
EngineTestKit.engine(ENGINE_ID)
- .configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
.configurationParameter(FILTER_NAME_PROPERTY_NAME, "^Nothing$")
.selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature"))
.execute()
.testEvents()
.assertThatEvents()
- .haveExactly(1, event(test()))
- .haveExactly(1,
- event(skippedWithReason("'cucumber.filter.name=^Nothing$' did not match this scenario")));
+ .haveExactly(1, event(test(),
+ event(skippedWithReason("'cucumber.filter.name=^Nothing$' did not match this scenario"))));
}
- private static Condition engine(Condition condition) {
- return Assertions.allOf(EventConditions.engine(), condition);
+ @Test
+ void cucumberTagsAreConvertedToJunitTags() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource("io/cucumber/junit/platform/engine/scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), tags(emptySet())))
+ .haveAtLeastOne(
+ event(scenario("scenario:5"), tags("FeatureTag", "ScenarioTag")))
+ .haveAtLeastOne(event(scenario("scenario:11"), tags(emptySet())))
+ .haveAtLeastOne(event(examples("examples:17"), tags(emptySet())))
+ .haveAtLeastOne(event(example("example:19"), tags("FeatureTag", "ScenarioOutlineTag", "Example1Tag")));
+ }
+
+ @Test
+ void providesClasspathSourceWhenClasspathResourceIsSelected() {
+ String feature = "io/cucumber/junit/platform/engine/scenario-outline.feature";
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathResource(feature))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), source(ClasspathResourceSource.from(feature, from(2, 1)))))
+ .haveAtLeastOne(
+ event(scenario("scenario:5"), source(ClasspathResourceSource.from(feature, from(5, 3)))))
+ .haveAtLeastOne(
+ event(scenario("scenario:11"), source(ClasspathResourceSource.from(feature, from(11, 3)))))
+ .haveAtLeastOne(
+ event(examples("examples:17"), source(ClasspathResourceSource.from(feature, from(17, 5)))))
+ .haveAtLeastOne(
+ event(example("example:19"), source(ClasspathResourceSource.from(feature, from(19, 7)))));
+ }
+
+ @Test
+ void providesFileSourceWhenFileIsSelected() {
+ File feature = new File("src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature");
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectFile(feature))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), source(FileSource.from(feature, from(2, 1)))))
+ .haveAtLeastOne(event(scenario("scenario:5"), source(FileSource.from(feature, from(5, 3)))))
+ .haveAtLeastOne(event(scenario("scenario:11"), source(FileSource.from(feature, from(11, 3)))))
+ .haveAtLeastOne(event(examples("examples:17"), source(FileSource.from(feature, from(17, 5)))))
+ .haveAtLeastOne(event(example("example:19"), source(FileSource.from(feature, from(19, 7)))));
+ }
+
+ @Test
+ void supportsPackageFilterForClasspathResources() {
+ Path classpathRoot = Paths.get("src/test/resources/");
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(selectClasspathRoots(singleton(classpathRoot)).get(0))
+ .filters(includePackageNames("io.cucumber.junit.platform"))
+ .execute()
+ .containerEvents()
+ .assertEventsMatchLooselyInOrder(
+ feature("disabled.feature"),
+ feature("empty-scenario.feature"),
+ feature("scenario-outline.feature"),
+ feature("rule.feature"),
+ feature("single.feature"),
+ feature("with%20space.feature"));
}
- private static Condition source(TestSource testSource) {
- return new Condition<>(event -> event.getTestDescriptor().getSource().filter(testSource::equals).isPresent(),
- "test engine with test source '%s'", testSource);
+ @Test
+ void defaultsToShortWithNumberAndPickleIfParameterizedNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(), displayName("A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName("Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName("Example #1.1: A scenario full of Cucumbers")));
}
- private static Condition emptySource() {
- return new Condition<>(event -> !event.getTestDescriptor().getSource().isPresent(), "without a test source");
+ @Test
+ void supportsLongWithNumberNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long")
+ .configurationParameter(JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "number")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(),
+ displayName("A feature with a parameterized scenario outline - A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName(
+ "A feature with a parameterized scenario outline - A scenario full of s - Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName(
+ "A feature with a parameterized scenario outline - A scenario full of s - Of the Gherkin variety - Example #1.1")));
+ }
+
+ @Test
+ void supportsLongWithPickleNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long")
+ .configurationParameter(JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "pickle")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(),
+ displayName("A feature with a parameterized scenario outline - A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName(
+ "A feature with a parameterized scenario outline - A scenario full of s - Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName(
+ "A feature with a parameterized scenario outline - A scenario full of s - Of the Gherkin variety - A scenario full of Cucumbers")));
}
+ @Test
+ void supportsLongWithNumberAndPickleIfParameterizedNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long")
+ .configurationParameter(JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME,
+ "number-and-pickle-if-parameterized")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(),
+ displayName("A feature with a parameterized scenario outline - A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName(
+ "A feature with a parameterized scenario outline - A scenario full of s - Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName(
+ "A feature with a parameterized scenario outline - A scenario full of s - Of the Gherkin variety - Example #1.1: A scenario full of Cucumbers")));
+ }
+
+ @Test
+ void supportsShortWithPickleNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "short")
+ .configurationParameter(JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "pickle")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(), displayName("A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName("Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName("A scenario full of Cucumbers")));
+ }
+
+ @Test
+ void supportsShortWithNumberNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "short")
+ .configurationParameter(JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "number")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(), displayName("A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName("Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName("Example #1.1")));
+ }
+
+ @Test
+ void supportsShortWithNumberAndPickleIfParameterizedNamingStrategy() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "short")
+ .configurationParameter(JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME,
+ "number-and-pickle-if-parameterized")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature"))
+ .execute()
+ .allEvents()
+ .assertThatEvents()
+ .haveAtLeastOne(event(feature(), displayName("A feature with a parameterized scenario outline")))
+ .haveAtLeastOne(event(scenario(), displayName("A scenario full of s")))
+ .haveAtLeastOne(event(examples(), displayName("Of the Gherkin variety")))
+ .haveAtLeastOne(event(example(), displayName("Example #1.1: A scenario full of Cucumbers")));
+ }
+
+ @Test
+ void defaultsToLexicalOrder() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"),
+ selectClasspathResource("io/cucumber/junit/platform/engine/ordering.feature"))
+ .execute()
+ .allEvents()
+ .started()
+ .assertThatEvents()
+ .extracting(Event::getTestDescriptor)
+ .extracting(TestDescriptor::getDisplayName)
+ .containsExactly("Cucumber",
+ "1. A feature to order scenarios",
+ "1. A feature to order scenarios - 1.1",
+ "1. A feature to order scenarios - 1.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.1 - Example #1.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.1 - Example #1.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2 - Example #2.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.2 - Example #2.2",
+ "1. A feature to order scenarios - 1.3 A rule",
+ "1. A feature to order scenarios - 1.3 A rule - 1.3.1",
+ "1. A feature to order scenarios - 1.3 A rule - 1.3.2",
+ "1. A feature to order scenarios - 1.4",
+ "1. A feature to order scenarios - 1.4 - 1.4.1",
+ "1. A feature to order scenarios - 1.4 - 1.4.2",
+ "A feature with a single scenario",
+ "A feature with a single scenario - A single scenario");
+ }
+
+ @Test
+ void supportsReverseOrder() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(EXECUTION_ORDER_PROPERTY_NAME, "reverse")
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"),
+ selectClasspathResource("io/cucumber/junit/platform/engine/ordering.feature"))
+ .execute()
+ .allEvents()
+ .started()
+ .assertThatEvents()
+ .extracting(Event::getTestDescriptor)
+ .extracting(TestDescriptor::getDisplayName)
+ .containsExactly("Cucumber",
+ "A feature with a single scenario",
+ "A feature with a single scenario - A single scenario",
+ "1. A feature to order scenarios",
+ "1. A feature to order scenarios - 1.4",
+ "1. A feature to order scenarios - 1.4 - 1.4.2",
+ "1. A feature to order scenarios - 1.4 - 1.4.1",
+ "1. A feature to order scenarios - 1.3 A rule",
+ "1. A feature to order scenarios - 1.3 A rule - 1.3.2",
+ "1. A feature to order scenarios - 1.3 A rule - 1.3.1",
+ "1. A feature to order scenarios - 1.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2 - Example #2.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2 - Example #2.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.1 - Example #1.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.1 - Example #1.1",
+ "1. A feature to order scenarios - 1.1");
+ }
+
+ @Test
+ void supportsRandomOrder(LogRecordListener logRecordListener) {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(EXECUTION_ORDER_PROPERTY_NAME, "random")
+ .discover();
+
+ LogRecord message = logRecordListener.getLogRecords()
+ .stream()
+ .filter(logRecord -> logRecord.getLoggerName()
+ .equals(DefaultDescriptorOrderingStrategy.class.getCanonicalName()))
+ .findFirst()
+ .orElseThrow();
+
+ assertAll(
+ () -> assertThat(message.getLevel()).isEqualTo(Level.CONFIG),
+ () -> assertThat(message.getMessage())
+ .matches(
+ "Using generated seed for configuration parameter 'cucumber\\.execution\\.order\\.random\\.seed' with value '\\d+'."));
+ }
+
+ @Test
+ void supportsRandomOrderWithSeed() {
+ EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(EXECUTION_ORDER_PROPERTY_NAME, "random")
+ .configurationParameter(EXECUTION_ORDER_RANDOM_SEED_PROPERTY_NAME, "1234")
+ .configurationParameter(JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"),
+ selectClasspathResource("io/cucumber/junit/platform/engine/ordering.feature"))
+ .execute()
+ .allEvents()
+ .started()
+ .assertThatEvents()
+ .extracting(Event::getTestDescriptor)
+ .extracting(TestDescriptor::getDisplayName)
+ .containsExactly("Cucumber",
+ "1. A feature to order scenarios",
+ "1. A feature to order scenarios - 1.4",
+ "1. A feature to order scenarios - 1.4 - 1.4.1",
+ "1. A feature to order scenarios - 1.4 - 1.4.2",
+ "1. A feature to order scenarios - 1.1",
+ "1. A feature to order scenarios - 1.3 A rule",
+ "1. A feature to order scenarios - 1.3 A rule - 1.3.2",
+ "1. A feature to order scenarios - 1.3 A rule - 1.3.1",
+ "1. A feature to order scenarios - 1.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.2 - Example #2.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.2 - Example #2.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.1",
+ "1. A feature to order scenarios - 1.2 - 1.2.1 - Example #1.2",
+ "1. A feature to order scenarios - 1.2 - 1.2.1 - Example #1.1",
+ "A feature with a single scenario",
+ "A feature with a single scenario - A single scenario");
+ }
+
+ @Test
+ void reportsParsErrorsAsDiscoveryIssues() {
+ EngineDiscoveryResults results = EngineTestKit.engine(ENGINE_ID)
+ .selectors(
+ selectFile("src/test/bad-features/parse-error.feature"))
+ .discover();
+
+ DiscoveryIssue issue = results.getDiscoveryIssues().get(0);
+
+ assertAll(() -> {
+ assertThat(issue.message()).startsWith("Failed to parse resource at: ");
+ assertThat(issue.source())
+ .contains(FileSource.from(new File("src/test/bad-features/parse-error.feature")));
+ });
+ }
+
+ @Test
+ void supportsExclusiveResources() {
+ PickleDescriptor pickleDescriptor = EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + "ResourceA" + READ_WRITE_SUFFIX,
+ "resource-a")
+ .configurationParameter(EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + "ResourceAReadOnly" + READ_SUFFIX,
+ "resource-a")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/resource.feature"))
+ .discover()
+ .getEngineDescriptor()
+ .getDescendants()
+ .stream()
+ .filter(PickleDescriptor.class::isInstance)
+ .map(PickleDescriptor.class::cast)
+ .findAny()
+ .orElseThrow();
+
+ assertThat(pickleDescriptor.getExclusiveResources())
+ .containsExactlyInAnyOrder(
+ new ExclusiveResource("resource-a", ExclusiveResource.LockMode.READ_WRITE),
+ new ExclusiveResource("resource-a", ExclusiveResource.LockMode.READ));
+
+ }
+
+ @Test
+ void supportsConcurrentExecutionOfFeatureElements() {
+ Set> testDescriptors = EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(EXECUTION_MODE_FEATURE_PROPERTY_NAME, "concurrent")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"))
+ .discover()
+ .getEngineDescriptor()
+ .getDescendants()
+ .stream()
+ .filter(Node.class::isInstance)
+ .map(testDescriptor -> (Node>) testDescriptor)
+ .collect(toSet());
+
+ assertThat(testDescriptors)
+ .isNotEmpty()
+ .extracting(Node::getExecutionMode)
+ .containsOnly(CONCURRENT);
+ }
+
+ @Test
+ void supportsSameThreadExecutionOfFeatureElements() {
+ Set extends TestDescriptor> testDescriptors = EngineTestKit.engine(ENGINE_ID)
+ .configurationParameter(EXECUTION_MODE_FEATURE_PROPERTY_NAME, "same_thread")
+ .selectors(
+ selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"))
+ .discover()
+ .getEngineDescriptor()
+ .getDescendants();
+
+ Set extends TestDescriptor> featureDescriptors = testDescriptors
+ .stream()
+ .filter(FeatureDescriptor.class::isInstance)
+ .collect(toSet());
+
+ assertThat(featureDescriptors)
+ .isNotEmpty()
+ .map(Node.class::cast)
+ .extracting(Node::getExecutionMode)
+ .containsOnly(CONCURRENT);
+
+ Set extends TestDescriptor> pickleDescriptors = testDescriptors
+ .stream()
+ .filter(testDescriptor -> !featureDescriptors.contains(testDescriptor))
+ .collect(toSet());
+
+ assertThat(pickleDescriptors)
+ .isNotEmpty()
+ .map(Node.class::cast)
+ .extracting(Node::getExecutionMode)
+ .containsOnly(SAME_THREAD);
+ }
}
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
deleted file mode 100644
index 2a240907aa..0000000000
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java
+++ /dev/null
@@ -1,507 +0,0 @@
-package io.cucumber.junit.platform.engine;
-
-import io.cucumber.core.gherkin.Feature;
-import io.cucumber.core.gherkin.Pickle;
-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;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.platform.engine.ConfigurationParameters;
-import org.junit.platform.engine.DiscoveryFilter;
-import org.junit.platform.engine.DiscoverySelector;
-import org.junit.platform.engine.EngineDiscoveryRequest;
-import org.junit.platform.engine.TestDescriptor;
-import org.junit.platform.engine.UniqueId;
-import org.junit.platform.engine.discovery.DiscoverySelectors;
-import org.junit.platform.engine.discovery.FilePosition;
-import org.junit.platform.engine.discovery.UniqueIdSelector;
-
-import java.io.File;
-import java.net.URI;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
-import static java.util.Collections.singleton;
-import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.toSet;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri;
-
-@WithLogRecordListener
-class DiscoverySelectorResolverTest {
-
- private final DiscoverySelectorResolver resolver = new DiscoverySelectorResolver();
- private CucumberEngineDescriptor testDescriptor;
-
- @BeforeEach
- void before() {
- UniqueId id = UniqueId.forEngine(new CucumberTestEngine().getId());
- testDescriptor = new CucumberEngineDescriptor(id);
- assertEquals(0, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithClasspathResourceSelector() {
- DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(1, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithClasspathResourceSelectorAndWithSpaceInFilename() {
- DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/with space.feature");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(1, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithClasspathResourceSelectorAndFilePosition() {
- String feature = "io/cucumber/junit/platform/engine/rule.feature";
- FilePosition line = FilePosition.from(5);
- DiscoverySelector resource = selectClasspathResource(feature, line);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(1L, testDescriptor.getDescendants()
- .stream()
- .filter(TestDescriptor::isTest)
- .count());
- }
-
- @Test
- void resolveRequestWithClasspathResourceSelectorAndFilePositionOfContainer() {
- String feature = "io/cucumber/junit/platform/engine/rule.feature";
- FilePosition line = FilePosition.from(3);
- DiscoverySelector resource = selectClasspathResource(feature, line);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(2L, testDescriptor.getDescendants()
- .stream()
- .filter(TestDescriptor::isTest)
- .count());
- }
-
- @Test
- void resolveRequestWithMultipleClasspathResourceSelector() {
- DiscoverySelector resource1 = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature");
- DiscoverySelector resource2 = selectClasspathResource(
- "io/cucumber/junit/platform/engine/feature-with-outline.feature");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource1, resource2);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(2, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithClasspathRootSelector() {
- Path classpathRoot = Paths.get("src/test/resources/");
- DiscoverySelector resource = selectClasspathRoots(singleton(classpathRoot)).get(0);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(7, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveFeatureTestDescriptorsInUriOrder() {
- Path classpathRoot = Paths.get("src/test/resources/");
- DiscoverySelector resource = selectClasspathRoots(singleton(classpathRoot)).get(0);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- Set extends TestDescriptor> features = testDescriptor.getChildren();
- List unsorted = new ArrayList<>(features);
- List sorted = new ArrayList<>(features);
- // Sorts by URI
- sorted.sort(comparing(feature -> feature.getUniqueId().getSegments().get(1).getValue()));
- assertEquals(unsorted, sorted);
- }
-
- @Test
- void resolveRequestWithUriSelectorWithScenarioOutlineLine() {
- File file = new File("src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature");
- URI uri = URI.create(file.toURI() + "?line=11");
- DiscoverySelector resource = selectUri(uri);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- List extends TestDescriptor> tests = testDescriptor.getDescendants().stream()
- .filter(TestDescriptor::isTest)
- .collect(Collectors.toList());
- assertEquals(4, tests.size()); // 4 examples in the outline
- }
-
- @Test
- void resolveRequestWithUriSelectorWithExamplesSectionLine() {
- File file = new File("src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature");
- URI uri = URI.create(file.toURI() + "?line=17");
- DiscoverySelector resource = selectUri(uri);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- List extends TestDescriptor> tests = testDescriptor.getDescendants().stream()
- .filter(TestDescriptor::isTest)
- .collect(Collectors.toList());
- assertEquals(2, tests.size()); // 2 examples in the examples section
- }
-
- @Test
- void resolveRequestWithUriSelectorWithExampleLine() {
- File file = new File("src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature");
- URI uri1 = URI.create(file.toURI() + "?line=20");
- DiscoverySelector resource = selectUri(uri1);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- List extends TestDescriptor> tests = testDescriptor.getDescendants().stream()
- .filter(TestDescriptor::isTest)
- .collect(Collectors.toList());
- assertEquals(1, tests.size());
- }
-
- @Test
- void resolveRequestWithClassPathUriSelectorWithLine() {
- URI uri = URI.create("classpath:/io/cucumber/junit/platform/engine/feature-with-outline.feature?line=20");
- DiscoverySelector resource = selectUri(uri);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- List extends TestDescriptor> tests = testDescriptor.getDescendants().stream()
- .filter(TestDescriptor::isTest)
- .collect(Collectors.toList());
- assertEquals(1, tests.size());
- }
-
- @Test
- void resolveRequestWithUriSelectorThroughProperty() {
- URI uri = URI.create("classpath:/io/cucumber/junit/platform/engine/feature-with-outline.feature:19:20");
- ConfigurationParameters parameters = new MapConfigurationParameters(FEATURES_PROPERTY_NAME, uri.toString());
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(parameters);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- List extends TestDescriptor> tests = testDescriptor.getDescendants().stream()
- .filter(TestDescriptor::isTest)
- .collect(Collectors.toList());
- assertEquals(2, tests.size());
- }
-
- @Test
- void resolveRequestWithUriSelectorThroughPropertyIgnoresOtherSelectors() {
- URI uri1 = URI.create("classpath:/io/cucumber/junit/platform/engine/feature-with-outline.feature:19");
- ConfigurationParameters parameters = new MapConfigurationParameters(FEATURES_PROPERTY_NAME, uri1.toString());
-
- URI uri2 = URI.create("classpath:/io/cucumber/junit/platform/engine/feature-with-outline.feature?line=20");
- DiscoverySelector resource = selectUri(uri2);
-
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(parameters, resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- List extends TestDescriptor> tests = testDescriptor.getDescendants().stream()
- .filter(TestDescriptor::isTest)
- .collect(Collectors.toList());
- assertEquals(1, tests.size());
- }
-
- @Test
- void resolveRequestWithFileSelector() {
- DiscoverySelector resource = selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(1, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithFileSelectorAndPosition() {
- String feature = "src/test/resources/io/cucumber/junit/platform/engine/rule.feature";
- FilePosition line = FilePosition.from(5);
- DiscoverySelector resource = selectFile(feature, line);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(1L, testDescriptor.getDescendants()
- .stream()
- .filter(TestDescriptor::isTest)
- .count());
- }
-
- @Test
- void resolveRequestWithFileSelectorAndPositionOfContainer() {
- String feature = "src/test/resources/io/cucumber/junit/platform/engine/rule.feature";
- FilePosition line = FilePosition.from(3);
- DiscoverySelector resource = selectFile(feature, line);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(2L, testDescriptor.getDescendants()
- .stream()
- .filter(TestDescriptor::isTest)
- .count());
- }
-
- @Test
- void resolveRequestWithDirectorySelector() {
- DiscoverySelector resource = selectDirectory("src/test/resources/io/cucumber/junit/platform/engine");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(6, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithPackageSelector() {
- DiscoverySelector resource = selectPackage("io.cucumber.junit.platform.engine");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(6, testDescriptor.getChildren().size());
- }
-
- @Test
- void ignoreRequestWithUniqueIdSelectorFromDifferentEngine() {
- DiscoverySelector selector = selectUniqueId(UniqueId.forEngine("not-cucumber"));
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(selector);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertTrue(testDescriptor.getDescendants().isEmpty());
- }
-
- @Test
- void resolveRequestWithUniqueIdSelectorFromClasspath() {
- DiscoverySelector resource = selectPackage("io.cucumber.junit.platform.engine");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- Set extends TestDescriptor> descendants = testDescriptor.getDescendants();
-
- descendants.forEach(targetDescriptor -> {
- resetTestDescriptor();
- resolveRequestWithUniqueIdSelector(targetDescriptor.getUniqueId());
- assertEquals(1, testDescriptor.getChildren().size());
- assertThat(testDescriptor, allDescriptorsPrefixedBy(targetDescriptor.getUniqueId()));
- });
- }
-
- private void resetTestDescriptor() {
- Set extends TestDescriptor> descendants = new HashSet<>(testDescriptor.getDescendants());
- descendants.forEach(o -> testDescriptor.removeChild(o));
- }
-
- private void resolveRequestWithUniqueIdSelector(UniqueId targetId) {
- UniqueIdSelector uniqueIdSelector = selectUniqueId(targetId);
- EngineDiscoveryRequest descendantRequest = new SelectorRequest(uniqueIdSelector);
- resolver.resolveSelectors(descendantRequest, testDescriptor);
- }
-
- private static Matcher allDescriptorsPrefixedBy(UniqueId targetId) {
- return new CustomTypeSafeMatcher("All descendants are prefixed by " + targetId) {
- @Override
- protected boolean matchesSafely(TestDescriptor descriptor) {
- return descriptor.getDescendants()
- .stream()
- .filter(TestDescriptor::isTest)
- .map(TestDescriptor::getUniqueId)
- .allMatch(selectedId -> selectedId.hasPrefix(targetId));
- }
- };
- }
-
- @Test
- void resolveRequestWithUniqueIdSelectorFromFileUri() {
- DiscoverySelector resource = selectDirectory("src/test/resources/io/cucumber/junit/platform/engine");
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- Set extends TestDescriptor> descendants = testDescriptor.getDescendants();
-
- descendants.forEach(targetDescriptor -> {
- resetTestDescriptor();
- resolveRequestWithUniqueIdSelector(targetDescriptor.getUniqueId());
- assertEquals(1, testDescriptor.getChildren().size());
- assertThat(testDescriptor, allDescriptorsPrefixedBy(targetDescriptor.getUniqueId()));
- });
- }
-
- @Test
- void resolveRequestWithUniqueIdSelectorFromJarFileUri() {
- URI uri = new File("src/test/resources/feature.jar").toURI();
- DiscoverySelector resource = selectUri(uri);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- assertEquals(1, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithUniqueIdSelectorFromJarUri() {
- String root = Paths.get("").toAbsolutePath().toUri().getSchemeSpecificPart();
- URI uri = URI.create("jar:file:" + root + "/src/test/resources/feature.jar!/single.feature");
-
- DiscoverySelector resource = selectUri(uri);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- assertEquals(1, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithMultipleUniqueIdSelector() {
- Set selectors = new HashSet<>();
-
- DiscoverySelector resource = selectDirectory(
- "src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature");
- selectSomePickle(resource).ifPresent(selectors::add);
-
- DiscoverySelector resource2 = selectDirectory(
- "src/test/resources/io/cucumber/junit/platform/engine/single.feature");
- selectSomePickle(resource2).ifPresent(selectors::add);
-
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(
- selectors.stream()
- .map(DiscoverySelectors::selectUniqueId)
- .collect(Collectors.toList()));
-
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- assertEquals(
- selectors,
- testDescriptor.getDescendants()
- .stream()
- .filter(PickleDescriptor.class::isInstance)
- .map(TestDescriptor::getUniqueId)
- .collect(toSet()));
- }
-
- @Test
- void resolveRequestWithMultipleUniqueIdSelectorFromTheSameFeature() {
- Set selectors = new HashSet<>();
-
- DiscoverySelector resource = selectDirectory(
- "src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature");
- selectAllPickles(resource).forEach(selectors::add);
-
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(
- selectors.stream()
- .map(DiscoverySelectors::selectUniqueId)
- .collect(Collectors.toList()));
-
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
-
- Set pickleIdsFromFeature = testDescriptor.getDescendants()
- .stream()
- .filter(FeatureDescriptor.class::isInstance)
- .map(FeatureDescriptor.class::cast)
- .map(FeatureDescriptor::getFeature)
- .map(Feature::getPickles)
- .flatMap(Collection::stream)
- .map(Pickle::getId)
- .collect(toSet());
-
- Set pickleIdsFromPickles = testDescriptor.getDescendants()
- .stream()
- .filter(PickleDescriptor.class::isInstance)
- .map(PickleDescriptor.class::cast)
- .map(PickleDescriptor::getPickle)
- .map(Pickle::getId)
- .collect(toSet());
-
- assertEquals(pickleIdsFromFeature, pickleIdsFromPickles);
- }
-
- private Optional selectSomePickle(DiscoverySelector resource) {
- return selectAllPickles(resource).findFirst();
- }
-
- private Stream selectAllPickles(DiscoverySelector resource) {
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- Set extends TestDescriptor> descendants = testDescriptor.getDescendants();
- resetTestDescriptor();
- return descendants.stream()
- .filter(PickleDescriptor.class::isInstance)
- .map(TestDescriptor::getUniqueId);
- }
-
- @Test
- void resolveRequestWithClassSelector() {
- DiscoverySelector resource = selectClass(RunCucumberTest.class);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(6, testDescriptor.getChildren().size());
- }
-
- @Test
- void resolveRequestWithClassSelectorShouldLogWarnIfNoFeaturesFound(LogRecordListener logRecordListener) {
- DiscoverySelector resource = selectClass(NoFeatures.class);
- EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
- resolver.resolveSelectors(discoveryRequest, testDescriptor);
- assertEquals(0, testDescriptor.getChildren().size());
- assertEquals(1, logRecordListener.getLogRecords().size());
- LogRecord logRecord = logRecordListener.getLogRecords().get(0);
- assertEquals(Level.WARNING, logRecord.getLevel());
- assertEquals("No features found in package 'io.cucumber.junit.platform.engine.nofeatures'",
- logRecord.getMessage());
- }
-
- private static class SelectorRequest implements EngineDiscoveryRequest {
-
- private final Map, List> resources = new HashMap<>();
- private final ConfigurationParameters parameters;
-
- SelectorRequest(ConfigurationParameters parameters, DiscoverySelector... selectors) {
- this(parameters, Arrays.asList(selectors));
- }
-
- SelectorRequest(DiscoverySelector... selectors) {
- this(new EmptyConfigurationParameters(), Arrays.asList(selectors));
- }
-
- SelectorRequest(List selectors) {
- this(new EmptyConfigurationParameters(), selectors);
- }
-
- SelectorRequest(ConfigurationParameters parameters, List selectors) {
- this.parameters = parameters;
- for (DiscoverySelector discoverySelector : selectors) {
- resources.putIfAbsent(discoverySelector.getClass(), new ArrayList<>());
- resources.get(discoverySelector.getClass()).add(discoverySelector);
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public List getSelectorsByType(Class selectorType) {
- if (resources.containsKey(selectorType)) {
- return (List) resources.get(selectorType);
- }
-
- return Collections.emptyList();
- }
-
- @Override
- public > List getFiltersByType(Class filterType) {
- return Collections.emptyList();
- }
-
- @Override
- public ConfigurationParameters getConfigurationParameters() {
- return parameters;
- }
-
- }
-
-}
diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/EmptyEngineDiscoveryRequest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/EmptyEngineDiscoveryRequest.java
deleted file mode 100644
index b9bee9d1b5..0000000000
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/EmptyEngineDiscoveryRequest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package io.cucumber.junit.platform.engine;
-
-import org.junit.platform.engine.ConfigurationParameters;
-import org.junit.platform.engine.DiscoveryFilter;
-import org.junit.platform.engine.DiscoverySelector;
-import org.junit.platform.engine.EngineDiscoveryRequest;
-
-import java.util.Collections;
-import java.util.List;
-
-class EmptyEngineDiscoveryRequest implements EngineDiscoveryRequest {
-
- private final ConfigurationParameters config;
-
- EmptyEngineDiscoveryRequest(ConfigurationParameters config) {
- this.config = config;
- }
-
- @Override
- public List getSelectorsByType(Class selectorType) {
- return Collections.emptyList();
- }
-
- @Override
- public > List getFiltersByType(Class filterType) {
- return Collections.emptyList();
- }
-
- @Override
- public ConfigurationParameters getConfigurationParameters() {
- return config;
- }
-
-}
diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/EmptyEngineExecutionListener.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/EmptyEngineExecutionListener.java
deleted file mode 100644
index fb998cb918..0000000000
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/EmptyEngineExecutionListener.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package io.cucumber.junit.platform.engine;
-
-import org.junit.platform.engine.EngineExecutionListener;
-import org.junit.platform.engine.TestDescriptor;
-import org.junit.platform.engine.TestExecutionResult;
-import org.junit.platform.engine.reporting.ReportEntry;
-
-class EmptyEngineExecutionListener implements EngineExecutionListener {
-
- @Override
- public void dynamicTestRegistered(TestDescriptor testDescriptor) {
-
- }
-
- @Override
- public void executionSkipped(TestDescriptor testDescriptor, String reason) {
-
- }
-
- @Override
- public void executionStarted(TestDescriptor testDescriptor) {
-
- }
-
- @Override
- public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) {
-
- }
-
- @Override
- public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) {
-
- }
-
-}
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
deleted file mode 100644
index 14c018b2c2..0000000000
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-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;
-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.Map;
-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.EXECUTION_MODE_FEATURE_PROPERTY_NAME;
-import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
-import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME;
-import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_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;
-import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource;
-import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.from;
-import static org.junit.platform.engine.support.descriptor.FilePosition.from;
-
-class FeatureResolverTest {
-
- private final String featurePath = "io/cucumber/junit/platform/engine/feature-with-outline.feature";
- private final String featureSegmentValue = CLASSPATH_SCHEME_PREFIX + featurePath;
- private final UniqueId id = UniqueId.forEngine(new CucumberTestEngine().getId());
- private final CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(id);
- private ConfigurationParameters configurationParameters = new EmptyConfigurationParameters();
-
- @Test
- void feature() {
- TestDescriptor feature = getFeature();
- assertEquals("A feature with scenario outlines", feature.getDisplayName());
- assertEquals(emptySet(), feature.getTags());
- assertEquals(of(from(featurePath)), feature.getSource());
- assertEquals(CONTAINER, feature.getType());
- assertEquals(
- id.append("feature", featureSegmentValue),
- feature.getUniqueId());
- }
-
- private TestDescriptor getFeature() {
- FeatureResolver featureResolver = FeatureResolver.create(configurationParameters, engineDescriptor,
- aPackage -> true);
- featureResolver.resolveClasspathResource(selectClasspathResource(featurePath));
- Set extends TestDescriptor> features = engineDescriptor.getChildren();
- return features.iterator().next();
- }
-
- @Test
- void scenario() {
- TestDescriptor scenario = getScenario();
- assertEquals("A scenario", scenario.getDisplayName());
- assertEquals(
- asSet(create("FeatureTag"), create("ScenarioTag"), create("ResourceA"), create("ResourceAReadOnly")),
- scenario.getTags());
- assertEquals(of(from(featurePath, from(5, 3))), scenario.getSource());
- assertEquals(TEST, scenario.getType());
- assertEquals(
- id.append("feature", featureSegmentValue)
- .append("scenario", "5"),
- scenario.getUniqueId());
- PickleDescriptor pickleDescriptor = (PickleDescriptor) scenario;
- assertEquals(Optional.of("io.cucumber.junit.platform.engine"), pickleDescriptor.getPackage());
- }
-
- @Test
- void exclusiveResources() {
- configurationParameters = new MapConfigurationParameters(
- new HashMap() {
- {
- put(EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + "ResourceA" + READ_WRITE_SUFFIX, "resource-a");
- put(EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + "ResourceAReadOnly" + READ_SUFFIX, "resource-a");
- }
- });
-
- PickleDescriptor pickleDescriptor = (PickleDescriptor) getScenario();
- assertEquals(
- asSet(
- new ExclusiveResource("resource-a", LockMode.READ_WRITE),
- new ExclusiveResource("resource-a", LockMode.READ)),
- pickleDescriptor.getExclusiveResources());
- }
-
- private TestDescriptor getScenario() {
- return getFeature().getChildren().iterator().next();
- }
-
- @SafeVarargs
- private static Set asSet(T... tags) {
- return new HashSet<>(asList(tags));
- }
-
- @Test
- void outline() {
- TestDescriptor outline = getOutline();
- assertEquals("A scenario outline", outline.getDisplayName());
- assertEquals(
- emptySet(),
- outline.getTags());
- assertEquals(of(from(featurePath, from(11, 3))), outline.getSource());
- assertEquals(CONTAINER, outline.getType());
- assertEquals(
- id.append("feature", featureSegmentValue)
- .append("scenario", "11"),
- outline.getUniqueId());
- }
-
- private TestDescriptor getOutline() {
- Iterator extends TestDescriptor> iterator = getFeature().getChildren().iterator();
- iterator.next();
- return iterator.next();
- }
-
- private TestDescriptor getParameterizedOutline() {
- Iterator extends TestDescriptor> iterator = getFeature().getChildren().iterator();
- iterator.next();
- iterator.next();
- iterator.next();
- return iterator.next();
- }
-
- @Test
- void example() {
- TestDescriptor example = getExample();
- assertEquals("Example #1.1", example.getDisplayName());
- assertEquals(
- asSet(create("FeatureTag"), create("Example1Tag"), create("ScenarioOutlineTag")),
- example.getTags());
- assertEquals(of(from(featurePath, from(19, 7))), example.getSource());
- assertEquals(TEST, example.getType());
-
- assertEquals(
- id.append("feature", featureSegmentValue)
- .append("scenario", "11")
- .append("examples", "17")
- .append("example", "19"),
- example.getUniqueId());
-
- PickleDescriptor pickleDescriptor = (PickleDescriptor) example;
- assertEquals(Optional.of("io.cucumber.junit.platform.engine"), pickleDescriptor.getPackage());
- }
-
- @Test
- void longNames() {
- configurationParameters = new MapConfigurationParameters(Map.of(
- JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long",
- JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "number"));
-
- TestDescriptor example = getExample();
- assertEquals("A feature with scenario outlines - A scenario outline - With some text - Example #1.1",
- example.getDisplayName());
- }
-
- @Test
- void longNamesWithPickleNames() {
- configurationParameters = new MapConfigurationParameters(Map.of(
- JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long",
- JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "pickle"));
-
- TestDescriptor example = getExample();
- assertEquals("A feature with scenario outlines - A scenario outline - With some text - A scenario outline",
- example.getDisplayName());
- }
-
- @Test
- void longNamesWithPickleNamesIfParameterized() {
- configurationParameters = new MapConfigurationParameters(
- JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "long");
-
- TestDescriptor example = getParametrizedExample();
- assertEquals(
- "A feature with scenario outlines - A scenario with - Examples - Example #1.1: A scenario with A",
- example.getDisplayName());
- }
-
- @Test
- void shortNamesWithExampleNumbers() {
- configurationParameters = new MapConfigurationParameters(
- JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "number");
-
- TestDescriptor example = getExample();
- assertEquals("Example #1.1", example.getDisplayName());
- }
-
- @Test
- void shortNamesWithPickleNames() {
- configurationParameters = new MapConfigurationParameters(Map.of(
- JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "short",
- JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME, "pickle"));
-
- TestDescriptor example = getExample();
- assertEquals("A scenario outline", example.getDisplayName());
- }
-
- @Test
- void shortNamesWithPickleNamesIfParameterized() {
- configurationParameters = new MapConfigurationParameters(
- JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "short");
-
- TestDescriptor example = getParametrizedExample();
- assertEquals("Example #1.1: A scenario with A", example.getDisplayName());
- }
-
- @Test
- void surefireNamesWithPickleNamesIfParameterized() {
- configurationParameters = new MapConfigurationParameters(
- JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "surefire");
-
- // The test node gets the long name, without the feature name.
- TestDescriptor example = getParametrizedExample();
- assertEquals("A scenario with - Examples - Example #1.1: A scenario with A",
- example.getDisplayName());
-
- // The parent node gets the feature name.
- TestDescriptor examples = example.getParent().get();
- assertEquals("A feature with scenario outlines",
- examples.getDisplayName());
-
- // Remaining nodes are named by their short names.
- TestDescriptor scenarioOutline = examples.getParent().get();
- assertEquals("A scenario with ",
- scenarioOutline.getDisplayName());
-
- TestDescriptor feature = scenarioOutline.getParent().get();
- assertEquals("A feature with scenario outlines",
- feature.getDisplayName());
- }
-
- private TestDescriptor getExample() {
- return getOutline().getChildren().iterator().next().getChildren().iterator().next();
- }
-
- private TestDescriptor getParametrizedExample() {
- return getParameterizedOutline().getChildren().iterator().next().getChildren().iterator().next();
- }
-
- @Test
- void parallelExecutionForFeaturesEnabled() {
- configurationParameters = new MapConfigurationParameters(
- 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(
- 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());
- }
-}
diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/StubBackendProviderService.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/StubBackendProviderService.java
index 55fe8f90b2..57348a6f46 100644
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/StubBackendProviderService.java
+++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/StubBackendProviderService.java
@@ -45,7 +45,7 @@ public void loadGlue(Glue glue, List gluePaths) {
glue.addStepDefinition(createStepDefinition("B is used"));
glue.addStepDefinition(createStepDefinition("C is used"));
glue.addStepDefinition(createStepDefinition("D is used"));
-
+ glue.addStepDefinition(createStepDefinition("a parameterized scenario outline"));
}
private StepDefinition createStepDefinition(final String pattern) {
diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java
deleted file mode 100644
index f42bb9b00e..0000000000
--- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.cucumber.junit.platform.engine.nofeatures;
-
-import io.cucumber.junit.platform.engine.Cucumber;
-
-@Cucumber
-public class NoFeatures {
-
-}
diff --git a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/ordering.feature b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/ordering.feature
new file mode 100644
index 0000000000..05fda788f2
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/ordering.feature
@@ -0,0 +1,37 @@
+Feature: 1. A feature to order scenarios
+
+ Scenario: 1.1
+ Given a single scenario
+
+ Scenario Outline: 1.2
+ Given a single scenario
+
+ Examples: 1.2.1
+
+ | key |
+ | a |
+ | b |
+
+ Examples: 1.2.2
+
+ | key |
+ | c |
+ | d |
+
+ Rule: 1.3 A rule
+
+ Example: 1.3.1
+ Given a single scenario
+
+ Example: 1.3.2
+ Given a single scenario
+
+ Rule: 1.4
+
+ Example: 1.4.1
+ Given a single scenario
+
+ Example: 1.4.2
+ Given a single scenario
+
+
diff --git a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature
new file mode 100644
index 0000000000..6d23dd45d2
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/parameterized-scenario-outline.feature
@@ -0,0 +1,10 @@
+Feature: A feature with a parameterized scenario outline
+
+ Scenario Outline: A scenario full of s
+ Given a scenario outline
+
+ @Example1Tag
+ Examples: Of the Gherkin variety
+ | vegetable |
+ | Cucumber |
+ | Zucchini |
diff --git a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/resource.feature b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/resource.feature
new file mode 100644
index 0000000000..e0e2429a3a
--- /dev/null
+++ b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/resource.feature
@@ -0,0 +1,7 @@
+Feature: A feature with a single scenario
+
+ @ResourceA @ResourceAReadOnly
+ Scenario: A single scenario
+ Given a single scenario
+ When it is executed
+ Then is only runs once
diff --git a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature
similarity index 96%
rename from cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature
rename to cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature
index 46da5441de..b9c779a886 100644
--- a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature
+++ b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/scenario-outline.feature
@@ -1,7 +1,7 @@
@FeatureTag
Feature: A feature with scenario outlines
- @ScenarioTag @ResourceA @ResourceAReadOnly
+ @ScenarioTag
Scenario: A scenario
Given a scenario
When it is executed
diff --git a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/single.feature b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/single.feature
index 23641dfe27..20236c9130 100644
--- a/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/single.feature
+++ b/cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/single.feature
@@ -3,4 +3,4 @@ Feature: A feature with a single scenario
Scenario: A single scenario
Given a single scenario
When it is executed
- Then nothing else happens
+ Then is only runs once
diff --git a/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Location.java b/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Location.java
index d046ea10c2..b85a6e5db2 100644
--- a/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Location.java
+++ b/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Location.java
@@ -5,7 +5,7 @@
import java.util.Objects;
@API(status = API.Status.EXPERIMENTAL)
-public final class Location {
+public final class Location implements Comparable {
private final int line;
private final int column;
@@ -39,4 +39,13 @@ public boolean equals(Object o) {
column == location.column;
}
+ @Override
+ public int compareTo(Location o) {
+ Objects.requireNonNull(o);
+ int c = Integer.compare(line, o.line);
+ if (c != 0) {
+ return c;
+ }
+ return Integer.compare(column, o.column);
+ }
}
diff --git a/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Node.java b/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Node.java
index 671f8ec809..79d3a44a06 100644
--- a/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Node.java
+++ b/cucumber-plugin/src/main/java/io/cucumber/plugin/event/Node.java
@@ -2,6 +2,7 @@
import org.apiguardian.api.API;
+import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -35,6 +36,10 @@
@API(status = API.Status.EXPERIMENTAL)
public interface Node {
+ default URI getUri() {
+ throw new UnsupportedOperationException("Not yet implemented");
+ };
+
Location getLocation();
Optional getKeyword();
diff --git a/cucumber-plugin/src/test/java/io/cucumber/plugin/event/NodeTest.java b/cucumber-plugin/src/test/java/io/cucumber/plugin/event/NodeTest.java
index 0c3053f315..b84298de06 100644
--- a/cucumber-plugin/src/test/java/io/cucumber/plugin/event/NodeTest.java
+++ b/cucumber-plugin/src/test/java/io/cucumber/plugin/event/NodeTest.java
@@ -2,6 +2,7 @@
import org.junit.jupiter.api.Test;
+import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -13,6 +14,11 @@
class NodeTest {
private final Node.Example example1 = new Node.Example() {
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -40,6 +46,11 @@ public Optional getParent() {
};
private final Node.Example example2 = new Node.Example() {
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -66,6 +77,11 @@ public Optional getParent() {
}
};
private final Node.Example example3 = new Node.Example() {
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -93,6 +109,11 @@ public Optional getParent() {
};
private final Node.Example example4 = new Node.Example() {
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -125,6 +146,11 @@ public Collection elements() {
return asList(example1, example2);
}
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -157,6 +183,11 @@ public Collection elements() {
return asList(example3, example4);
}
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -189,6 +220,11 @@ public Collection elements() {
return Collections.emptyList();
}
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -221,6 +257,11 @@ public Collection elements() {
return Collections.emptyList();
}
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -253,6 +294,11 @@ public Collection elements() {
return asList(examplesA, examplesB);
}
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
@@ -285,6 +331,11 @@ public Collection elements() {
return asList(emptyExamplesA, emptyExamplesB);
}
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
@Override
public Location getLocation() {
return null;
diff --git a/examples/calculator-java-junit5/pom.xml b/examples/calculator-java-junit5/pom.xml
index ded19fa8a1..bec34265dc 100644
--- a/examples/calculator-java-junit5/pom.xml
+++ b/examples/calculator-java-junit5/pom.xml
@@ -28,7 +28,7 @@
org.junit
junit-bom
- 5.12.2
+ 5.13.1
pom
import
diff --git a/examples/calculator-kotlin-junit5/pom.xml b/examples/calculator-kotlin-junit5/pom.xml
index 8f971fc7fd..2270e218ca 100644
--- a/examples/calculator-kotlin-junit5/pom.xml
+++ b/examples/calculator-kotlin-junit5/pom.xml
@@ -31,7 +31,7 @@
org.junit
junit-bom
- 5.12.2
+ 5.13.1
pom
import
diff --git a/examples/spring-java-junit5/pom.xml b/examples/spring-java-junit5/pom.xml
index 57af102446..603f10fbd7 100644
--- a/examples/spring-java-junit5/pom.xml
+++ b/examples/spring-java-junit5/pom.xml
@@ -12,8 +12,8 @@
io.cucumber.examples.spring.application
- 3.2.0
- 5.10.1
+ 3.5.0
+ 5.13.1
diff --git a/examples/wicket-java-junit4/wicket-main/pom.xml b/examples/wicket-java-junit4/wicket-main/pom.xml
index d9a5ba27f4..184b957acd 100644
--- a/examples/wicket-java-junit4/wicket-main/pom.xml
+++ b/examples/wicket-java-junit4/wicket-main/pom.xml
@@ -14,7 +14,7 @@
9.4.0
9.4.43.v20210629
2.0.5
- 5.10.1
+ 5.13.1
1.4.7
diff --git a/examples/wicket-java-junit4/wicket-test/pom.xml b/examples/wicket-java-junit4/wicket-test/pom.xml
index 3fee71e19e..185251a5f5 100644
--- a/examples/wicket-java-junit4/wicket-test/pom.xml
+++ b/examples/wicket-java-junit4/wicket-test/pom.xml
@@ -10,7 +10,7 @@
io.cucumber.examples.wicket.test
- 5.10.1
+ 5.13.1
4.13.0