diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1bc27f6594..f2294419e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+### Added
+- [JUnit Platform Engine] Improve Maven and Gradle compatibility ([#2832](https://github.com/cucumber/cucumber-jvm/pull/2832) M.P. Korstanje)
## [7.15.0] - 2023-12-11
### Changed
diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md
index 6f2b0e3084..cf5d70a947 100644
--- a/cucumber-junit-platform-engine/README.md
+++ b/cucumber-junit-platform-engine/README.md
@@ -160,9 +160,6 @@ To select the scenario on line 10 of the `example.feature` file use:
mvn test -Dsurefire.includeJUnit5Engines=cucumber -Dcucumber.plugin=pretty -Dcucumber.features=path/to/example.feature:10
```
-Note: Add `-Dcucumber.plugin=pretty` to get test reports. Maven will not
-report on tests without a class.
-
#### Gradle
TODO: (Feel free to send a pull request. ;))
@@ -342,7 +339,13 @@ cucumber.filter.name= # a regular expre
cucumber.features= # comma separated paths to feature files.
# example: path/to/example.feature, path/to/other.feature
# note: When used any discovery selectors from the JUnit
- # Platform will be ignored. Use with caution and care.
+ # Platform will be ignored. This may lead to multiple
+ # executions of Cucumber. For example when used in
+ # combination with the JUnit Platform Suite Engine.
+ # When using Cucumber through the JUnit Platform
+ # Launcher API or the JUnit Platform Suite Engine, it is
+ # recommended to respectively use JUnit's
+ # DiscoverySelectors or equivalent annotations.
cucumber.filter.tags= # a cucumber tag expression.
# only scenarios with matching tags are executed.
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 8635ffd3da..32c10414cd 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
@@ -61,8 +61,19 @@ public final class Constants {
* scenario or example at line 42 in the example feature file
*
*
- * NOTE: When used any discovery selectors from the JUnit Platform will be
- * ignored. Use with caution and care.
+ * Note: When used, any discovery selectors from the JUnit Platform will be
+ * ignored. This may lead to multiple executions of Cucumber. For example
+ * when used in combination with the JUnit Platform Suite Engine.
+ *
+ * When using Cucumber through the JUnit Platform Launcher API or the JUnit
+ * Platform Suite Engine, it is recommended to respectively use the
+ * {@link org.junit.platform.engine.discovery.DiscoverySelectors} or
+ * equivalent annotations.
+ *
+ * Additionally, when this property is used, to work around limitations in
+ * Maven Surefire and Gradle, the Cucumber Engine will report its
+ * {@link org.junit.platform.engine.TestSource} as
+ * {@link CucumberTestEngine}.
*
* @see io.cucumber.core.feature.FeatureWithLines
*/
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 e35ad9e4ae..84b0e733e8 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,7 @@
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;
import org.junit.platform.engine.support.hierarchical.Node;
@@ -11,9 +12,20 @@
class CucumberEngineDescriptor extends EngineDescriptor implements Node {
static final String ENGINE_ID = "cucumber";
+ private final TestSource source;
CucumberEngineDescriptor(UniqueId uniqueId) {
+ this(uniqueId, null);
+ }
+
+ CucumberEngineDescriptor(UniqueId uniqueId, TestSource source) {
super(uniqueId, "Cucumber");
+ this.source = source;
+ }
+
+ @Override
+ public Optional getSource() {
+ return Optional.ofNullable(this.source);
}
@Override
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 c1e0dbd852..7d5d8f2108 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
@@ -5,12 +5,15 @@
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestSource;
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.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;
@@ -39,11 +42,25 @@ public String getId() {
@Override
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
- CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId);
+ TestSource testSource = createEngineTestSource(discoveryRequest);
+ CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId, testSource);
new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor);
return engineDescriptor;
}
+ private static TestSource createEngineTestSource(EngineDiscoveryRequest discoveryRequest) {
+ // Workaround. Test Engines do not normally have test source.
+ // Maven does not count tests that do not have a ClassSource somewhere
+ // in the test descriptor tree.
+ // Gradle will report all tests as coming from an "Unknown Class"
+ // See: https://github.com/cucumber/cucumber-jvm/pull/2498
+ ConfigurationParameters configuration = discoveryRequest.getConfigurationParameters();
+ if (configuration.get(FEATURES_PROPERTY_NAME).isPresent()) {
+ return ClassSource.from(CucumberTestEngine.class);
+ }
+ return null;
+ }
+
@Override
protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) {
ConfigurationParameters config = request.getConfigurationParameters();
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 7aa48eea76..c86af75258 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,16 +1,23 @@
package io.cucumber.junit.platform.engine;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.Condition;
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.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.Event;
+import org.junit.platform.testkit.engine.EventConditions;
import java.util.Optional;
+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;
@@ -70,6 +77,31 @@ void selectAndExecuteSingleScenario() {
.haveExactly(1, event(finishedSuccessfully()));
}
+ @Test
+ void selectAndExecuteSingleScenarioThroughFeaturesProperty() {
+ 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()
+ .allEvents()
+ .assertThatEvents()
+ .haveExactly(2, event(engine(source(ClassSource.from(CucumberTestEngine.class)))))
+ .haveExactly(1, event(test(finishedSuccessfully())));
+ }
+
+ @Test
+ void selectAndExecuteSingleScenarioWithoutFeaturesProperty() {
+ 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(2, event(engine(emptySource())))
+ .haveExactly(1, event(test(finishedSuccessfully())));
+ }
+
@Test
void selectAndSkipDisabledScenarioByTags() {
EngineTestKit.engine(ENGINE_ID)
@@ -98,4 +130,17 @@ void selectAndSkipDisabledScenarioByName() {
event(skippedWithReason("'cucumber.filter.name=^Nothing$' did not match this scenario")));
}
+ private static Condition engine(Condition condition) {
+ return Assertions.allOf(EventConditions.engine(), condition);
+ }
+
+ private static Condition source(TestSource testSource) {
+ return new Condition<>(event -> event.getTestDescriptor().getSource().filter(testSource::equals).isPresent(),
+ "test engine with test source '%s'", testSource);
+ }
+
+ private static Condition emptySource() {
+ return new Condition<>(event -> !event.getTestDescriptor().getSource().isPresent(), "without a test source");
+ }
+
}