Skip to content

Commit 5c5a19c

Browse files
committed
[JUnit Platform Engine] Improve Maven and Gradle compatibility
Maven Surefire and Gradle do not (yet?) fully support the JUnit Platform API[1]. To work around this problem the `cucumber.features` can be used. However, because Maven Surefire and Gradle only attempt to discover class based tests, they do expect a class source. As a result of this missing source, Maven Surefire will not report on the executed tests while Gradle reports the tests as having been executed by an "Unknown Class". By having the Cucumber TestEngine pretend to have a ClassSource when `cucumber.features` is used both these problems go away. 1. #2498
1 parent 2c9a7cc commit 5c5a19c

File tree

6 files changed

+96
-7
lines changed

6 files changed

+96
-7
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Added
14+
- [JUnit Platform Engine] Improve Maven and Gradle compatibility ([#2832](https://github.com/cucumber/cucumber-jvm/pull/2832) M.P. Korstanje)
1315

1416
## [7.15.0] - 2023-12-11
1517
### Changed

Diff for: cucumber-junit-platform-engine/README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,6 @@ To select the scenario on line 10 of the `example.feature` file use:
160160
mvn test -Dsurefire.includeJUnit5Engines=cucumber -Dcucumber.plugin=pretty -Dcucumber.features=path/to/example.feature:10
161161
```
162162

163-
Note: Add `-Dcucumber.plugin=pretty` to get test reports. Maven will not
164-
report on tests without a class.
165-
166163
#### Gradle
167164

168165
TODO: (Feel free to send a pull request. ;))
@@ -342,7 +339,13 @@ cucumber.filter.name= # a regular expre
342339
cucumber.features= # comma separated paths to feature files.
343340
# example: path/to/example.feature, path/to/other.feature
344341
# note: When used any discovery selectors from the JUnit
345-
# Platform will be ignored. Use with caution and care.
342+
# Platform will be ignored. This may lead to multiple
343+
# executions of Cucumber. For example when used in
344+
# combination with the JUnit Platform Suite Engine.
345+
# When using cucumber through the JUnit Platform
346+
# Launcher API or the JUnit Platform Suite Engine, it is
347+
# recommended to respectively use JUnits
348+
# DiscoverySelectors or equivalent annotations.
346349
347350
cucumber.filter.tags= # a cucumber tag expression.
348351
# only scenarios with matching tags are executed.

Diff for: cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,19 @@ public final class Constants {
6161
* scenario or example at line 42 in the example feature file</li>
6262
* </ul>
6363
* <p>
64-
* NOTE: When used any discovery selectors from the JUnit Platform will be
65-
* ignored. Use with caution and care.
64+
* Note: When used any discovery selectors from the JUnit Platform will be
65+
* ignored. This may lead to multiple executions of Cucumber. For example
66+
* when used in combination with the JUnit Platform Suite Engine.
67+
* <p>
68+
* When using cucumber through the JUnit Platform Launcher API or the JUnit
69+
* Platform Suite Engine, it is recommended to respectively use the
70+
* {@link org.junit.platform.engine.discovery.DiscoverySelectors} or
71+
* equivalent annotations.
72+
* <p>
73+
* Additionally, when this property is used, to work around limitations in
74+
* Maven Surefire and Gradle, the Cucumber Engine will report its
75+
* {@link org.junit.platform.engine.TestSource} as
76+
* {@link CucumberTestEngine}.
6677
*
6778
* @see io.cucumber.core.feature.FeatureWithLines
6879
*/

Diff for: cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineDescriptor.java

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.cucumber.junit.platform.engine;
22

33
import org.junit.platform.engine.TestDescriptor;
4+
import org.junit.platform.engine.TestSource;
45
import org.junit.platform.engine.UniqueId;
56
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
67
import org.junit.platform.engine.support.hierarchical.Node;
@@ -11,9 +12,20 @@
1112
class CucumberEngineDescriptor extends EngineDescriptor implements Node<CucumberEngineExecutionContext> {
1213

1314
static final String ENGINE_ID = "cucumber";
15+
private final TestSource source;
1416

1517
CucumberEngineDescriptor(UniqueId uniqueId) {
18+
this(uniqueId, null);
19+
}
20+
21+
CucumberEngineDescriptor(UniqueId uniqueId, TestSource source) {
1622
super(uniqueId, "Cucumber");
23+
this.source = source;
24+
}
25+
26+
@Override
27+
public Optional<TestSource> getSource() {
28+
return Optional.ofNullable(this.source);
1729
}
1830

1931
@Override

Diff for: cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberTestEngine.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import org.junit.platform.engine.EngineDiscoveryRequest;
66
import org.junit.platform.engine.ExecutionRequest;
77
import org.junit.platform.engine.TestDescriptor;
8+
import org.junit.platform.engine.TestSource;
89
import org.junit.platform.engine.UniqueId;
910
import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
11+
import org.junit.platform.engine.support.descriptor.ClassSource;
1012
import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService;
1113
import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
1214
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;
1315

16+
import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
1417
import static io.cucumber.junit.platform.engine.Constants.PARALLEL_CONFIG_PREFIX;
1518
import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;
1619

@@ -39,11 +42,24 @@ public String getId() {
3942

4043
@Override
4144
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
42-
CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId);
45+
TestSource testSource = createEngineTestSource(discoveryRequest);
46+
CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId, testSource);
4347
new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor);
4448
return engineDescriptor;
4549
}
4650

51+
private static TestSource createEngineTestSource(EngineDiscoveryRequest discoveryRequest) {
52+
// Workaround. Test Engines do not normally have test source.
53+
// Maven does not count tests that do not have a ClassSource somewhere
54+
// in the test descriptor tree.
55+
// Gradle will report all tests as coming from an "Unknown Class"
56+
ConfigurationParameters configuration = discoveryRequest.getConfigurationParameters();
57+
if (configuration.get(FEATURES_PROPERTY_NAME).isPresent()) {
58+
return ClassSource.from(CucumberTestEngine.class);
59+
}
60+
return null;
61+
}
62+
4763
@Override
4864
protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) {
4965
ConfigurationParameters config = request.getConfigurationParameters();

Diff for: cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java

+45
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package io.cucumber.junit.platform.engine;
22

3+
import org.assertj.core.api.Assertions;
4+
import org.assertj.core.api.Condition;
35
import org.junit.jupiter.api.Test;
46
import org.junit.platform.engine.ConfigurationParameters;
57
import org.junit.platform.engine.EngineDiscoveryRequest;
68
import org.junit.platform.engine.EngineExecutionListener;
79
import org.junit.platform.engine.ExecutionRequest;
810
import org.junit.platform.engine.TestDescriptor;
11+
import org.junit.platform.engine.TestSource;
912
import org.junit.platform.engine.UniqueId;
13+
import org.junit.platform.engine.support.descriptor.ClassSource;
1014
import org.junit.platform.testkit.engine.EngineTestKit;
15+
import org.junit.platform.testkit.engine.Event;
16+
import org.junit.platform.testkit.engine.EventConditions;
1117

1218
import java.util.Optional;
1319

20+
import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
1421
import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME;
1522
import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME;
1623
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME;
@@ -70,6 +77,31 @@ void selectAndExecuteSingleScenario() {
7077
.haveExactly(1, event(finishedSuccessfully()));
7178
}
7279

80+
@Test
81+
void selectAndExecuteSingleScenarioThroughFeaturesProperty() {
82+
EngineTestKit.engine(ENGINE_ID)
83+
.configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
84+
.configurationParameter(FEATURES_PROPERTY_NAME,
85+
"src/test/resources/io/cucumber/junit/platform/engine/single.feature")
86+
.execute()
87+
.allEvents()
88+
.assertThatEvents()
89+
.haveExactly(2, event(engine(source(ClassSource.from(CucumberTestEngine.class)))))
90+
.haveExactly(1, event(test(finishedSuccessfully())));
91+
}
92+
93+
@Test
94+
void selectAndExecuteSingleScenarioWithoutFeaturesProperty() {
95+
EngineTestKit.engine(ENGINE_ID)
96+
.configurationParameter(PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, "true")
97+
.selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature"))
98+
.execute()
99+
.allEvents()
100+
.assertThatEvents()
101+
.haveExactly(2, event(engine(emptySource())))
102+
.haveExactly(1, event(test(finishedSuccessfully())));
103+
}
104+
73105
@Test
74106
void selectAndSkipDisabledScenarioByTags() {
75107
EngineTestKit.engine(ENGINE_ID)
@@ -98,4 +130,17 @@ void selectAndSkipDisabledScenarioByName() {
98130
event(skippedWithReason("'cucumber.filter.name=^Nothing$' did not match this scenario")));
99131
}
100132

133+
private static Condition<Event> engine(Condition<Event> condition) {
134+
return Assertions.allOf(EventConditions.engine(), condition);
135+
}
136+
137+
private static Condition<Event> source(TestSource testSource) {
138+
return new Condition<>(event -> event.getTestDescriptor().getSource().filter(testSource::equals).isPresent(),
139+
"test engine with test source '%s'", testSource);
140+
}
141+
142+
private static Condition<Event> emptySource() {
143+
return new Condition<>(event -> !event.getTestDescriptor().getSource().isPresent(), "without a test source");
144+
}
145+
101146
}

0 commit comments

Comments
 (0)