Skip to content

Commit 68e9256

Browse files
authored
Add cucumber.junit-platform.discovery.as-root-engine (#3023)
The SBT JUpiter Interface assumes that all tests are classes[1] but does discovery on the classpath root. As a result, it turns feature names into class selectors. Which doesn't quite work. Setting `cucumber.junit-platform.discovery.as-root-engine=false` should ensure Cucumber only discovers tests when called by another engine (e.g. the JUnit Platform Suite Engine) and by pass this behaviour.
1 parent 9155fea commit 68e9256

File tree

7 files changed

+124
-28
lines changed

7 files changed

+124
-28
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ 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] Add `cucumber.junit-platform.discovery.as-root-engine` to work around SBT issues ([#3023](https://github.com/cucumber/cucumber-jvm/pull/3023) M.P. Korstanje)
15+
1316
### Fixed
1417
- [JUnit Platform Engine] Don't use Java 9+ APIs ([#3025](https://github.com/cucumber/cucumber-jvm/pull/3025) M.P. Korstanje)
1518
- [JUnit Platform Engine] Implement toString on custom DiscoverySelectors

cucumber-junit-platform-engine/README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ erDiagram
3737

3838
In practice, integration is still limited so we discuss the most common workarounds below.
3939

40-
### Maven Surefire and Gradle
40+
### Maven Surefire, Gradle and SBT
4141

4242
Maven Surefire and Gradle do not yet support discovery of non-class based tests
4343
(see: [gradle/#4773](https://github.com/gradle/gradle/issues/4773),
44-
[SUREFIRE-1724](https://issues.apache.org/jira/browse/SUREFIRE-1724)). As a
45-
workaround, you can either use:
44+
[maven-surefire/#2065](https://github.com/apache/maven-surefire/issues/2065), [stb-jupiter-interface/#142](https://github.com/sbt/sbt-jupiter-interface/issues/142)).
45+
As a workaround, you can either use:
4646
* the [JUnit Platform Suite Engine](https://junit.org/junit5/docs/current/user-guide/#junit-platform-suite-engine);
4747
* the [JUnit Platform Console Launcher](https://junit.org/junit5/docs/current/user-guide/#running-tests-console-launcher) or;
4848
* the [Gradle Cucumber-Companion](https://github.com/gradle/cucumber-companion) plugins for Gradle and Maven.
@@ -104,6 +104,19 @@ public class RunCucumberTest {
104104
}
105105
```
106106

107+
##### SBT workarounds
108+
109+
The `sbt-jupiter-interface` assumes that all tests directly under a test engine
110+
have a class source. This is not the case for Cucumber. By running Cucumber
111+
indirectly through the JUnit Platform Suite Engine and disabling discovery when
112+
run directly as a "root engine" this problem is avoided.
113+
114+
Add to `junit-platform.properties`:
115+
116+
```
117+
cucumber.junit-platform.discovery.as-root-engine=false
118+
```
119+
107120
#### Use the JUnit Console Launcher ###
108121

109122
You can integrate the JUnit Platform Console Launcher in your build by using
@@ -435,9 +448,15 @@ cucumber.filter.tags= # a cucumber tag
435448
cucumber.glue= # comma separated package names.
436449
# example: com.example.glue
437450
451+
cucumber.junit-platform.discovery.as-root-engine # true or false
452+
# default: true
453+
# enable discovery when used as a root engine.
454+
# note: Workaround for SBT issues.
455+
438456
cucumber.junit-platform.naming-strategy= # long, short or surefire.
439457
# default: short
440-
# include parent descriptor name in test descriptor.
458+
# long: include parent descriptor names in test descriptor.
459+
# surefire: Workaround to make test names appear nicely with Surefire.
441460
442461
cucumber.junit-platform.naming-strategy.short.example-name= # number, number-and-pickle-if-parameterized or pickle.
443462
# default: number-and-pickle-if-parameterized

cucumber-junit-platform-engine/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@
6262
</dependency>
6363
<dependency>
6464
<groupId>org.junit.jupiter</groupId>
65-
<artifactId>junit-jupiter-api</artifactId>
65+
<artifactId>junit-jupiter-params</artifactId>
6666
<scope>test</scope>
6767
</dependency>
6868
<dependency>
69-
<groupId>org.junit.jupiter</groupId>
70-
<artifactId>junit-jupiter-params</artifactId>
69+
<groupId>org.junit.platform</groupId>
70+
<artifactId>junit-platform-suite</artifactId>
7171
<scope>test</scope>
7272
</dependency>
7373
<dependency>

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,26 @@ public final class Constants {
207207
@API(status = Status.EXPERIMENTAL, since = "7.16.2")
208208
public static final String JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME = "cucumber.junit-platform.naming-strategy.long.example-name";
209209

210+
/**
211+
* Property name used to enable discovery as a root engine: {@value}
212+
* <p>
213+
* Valid values are {@code true}, {@code false}. Default: {@code true}.
214+
* <p>
215+
* As an engine on the JUnit Platform, Cucumber can participate in discovery
216+
* directly as a "root" engine. Or indirectly when used through the JUnit
217+
* Platform Suite Engine.
218+
* <p>
219+
* Some build tools assume that all root engines produce class based tests.
220+
* This is not the case for Cucumber. Running Cucumber through the JUnit
221+
* Platform Suite Engine. Disabling discovery as a root engine resolves
222+
* this.
223+
* <p>
224+
* Note: If a build tool supports JUnits include/exclude Engine
225+
* configuration that option should be preferred over this property.
226+
*/
227+
@API(status = Status.EXPERIMENTAL, since = "7.26.0")
228+
public static final String JUNIT_PLATFORM_DISCOVERY_AS_ROOT_ENGINE_PROPERTY_NAME = "cucumber.junit-platform.discovery.as-root-engine";
229+
210230
/**
211231
* Property name to enable plugins: {@value}
212232
* <p>
@@ -272,7 +292,7 @@ public final class Constants {
272292
* <p>
273293
* Valid values are {@code underscore} or {@code camelcase}.
274294
* <p>
275-
* By defaults are generated using the under score naming convention.
295+
* By defaults are generated using the underscore naming convention.
276296
*/
277297
public static final String SNIPPET_TYPE_PROPERTY_NAME = io.cucumber.core.options.Constants.SNIPPET_TYPE_PROPERTY_NAME;
278298

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ class CucumberEngineDescriptor extends EngineDescriptor implements Node<Cucumber
1616
private final CucumberConfiguration configuration;
1717
private final TestSource source;
1818

19-
CucumberEngineDescriptor(UniqueId uniqueId, CucumberConfiguration configuration) {
20-
this(uniqueId, configuration, null);
21-
}
22-
2319
CucumberEngineDescriptor(UniqueId uniqueId, CucumberConfiguration configuration, TestSource source) {
2420
super(uniqueId, "Cucumber");
2521
this.configuration = requireNonNull(configuration);

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;
1616

1717
import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
18+
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_DISCOVERY_AS_ROOT_ENGINE_PROPERTY_NAME;
1819
import static io.cucumber.junit.platform.engine.Constants.PARALLEL_CONFIG_PREFIX;
1920
import static org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.deduplicating;
2021
import static org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.forwarding;
@@ -44,28 +45,45 @@ public String getId() {
4445

4546
@Override
4647
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
47-
TestSource testSource = createEngineTestSource(discoveryRequest);
48-
CucumberConfiguration configuration = new CucumberConfiguration(discoveryRequest.getConfigurationParameters());
48+
ConfigurationParameters configurationParameters = discoveryRequest.getConfigurationParameters();
49+
TestSource testSource = createEngineTestSource(configurationParameters);
50+
CucumberConfiguration configuration = new CucumberConfiguration(configurationParameters);
4951
CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId, configuration, testSource);
5052

5153
DiscoveryIssueReporter issueReporter = deduplicating(forwarding( //
5254
discoveryRequest.getDiscoveryListener(), //
5355
engineDescriptor.getUniqueId() //
5456
));
5557

58+
// Early out if Cucumber is the root engine and discovery has been
59+
// explicitly disabled. Workaround for:
60+
// https://github.com/sbt/sbt-jupiter-interface/issues/142
61+
if (!supportsDiscoveryAsRootEngine(configurationParameters) && isRootEngine(uniqueId)) {
62+
return engineDescriptor;
63+
}
64+
5665
FeaturesPropertyResolver resolver = new FeaturesPropertyResolver(new DiscoverySelectorResolver());
5766
resolver.resolveSelectors(discoveryRequest, engineDescriptor, issueReporter);
5867
return engineDescriptor;
5968
}
6069

61-
private static TestSource createEngineTestSource(EngineDiscoveryRequest discoveryRequest) {
70+
private static boolean supportsDiscoveryAsRootEngine(ConfigurationParameters configurationParameters) {
71+
return configurationParameters.getBoolean(JUNIT_PLATFORM_DISCOVERY_AS_ROOT_ENGINE_PROPERTY_NAME)
72+
.orElse(true);
73+
}
74+
75+
private boolean isRootEngine(UniqueId uniqueId) {
76+
UniqueId cucumberRootEngineId = UniqueId.forEngine(getId());
77+
return uniqueId.hasPrefix(cucumberRootEngineId);
78+
}
79+
80+
private static TestSource createEngineTestSource(ConfigurationParameters configurationParameters) {
6281
// Workaround. Test Engines do not normally have test source.
6382
// Maven does not count tests that do not have a ClassSource somewhere
6483
// in the test descriptor tree.
6584
// Gradle will report all tests as coming from an "Unknown Class"
6685
// See: https://github.com/cucumber/cucumber-jvm/pull/2498
67-
ConfigurationParameters configuration = discoveryRequest.getConfigurationParameters();
68-
if (configuration.get(FEATURES_PROPERTY_NAME).isPresent()) {
86+
if (configurationParameters.get(FEATURES_PROPERTY_NAME).isPresent()) {
6987
return ClassSource.from(CucumberTestEngine.class);
7088
}
7189
return null;

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

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import org.junit.platform.engine.support.descriptor.FileSource;
2121
import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
2222
import org.junit.platform.engine.support.hierarchical.Node;
23+
import org.junit.platform.suite.api.IncludeEngines;
24+
import org.junit.platform.suite.api.SelectClasspathResource;
25+
import org.junit.platform.suite.api.Suite;
2326
import org.junit.platform.testkit.engine.EngineDiscoveryResults;
2427
import org.junit.platform.testkit.engine.EngineTestKit;
2528
import org.junit.platform.testkit.engine.Event;
@@ -44,6 +47,7 @@
4447
import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
4548
import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME;
4649
import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME;
50+
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_DISCOVERY_AS_ROOT_ENGINE_PROPERTY_NAME;
4751
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
4852
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME;
4953
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
@@ -93,6 +97,17 @@ class CucumberTestEngineTest {
9397

9498
private final CucumberTestEngine engine = new CucumberTestEngine();
9599

100+
private static Set<UniqueId> discoverUniqueIds(DiscoverySelector discoverySelector) {
101+
return EngineTestKit.engine(ENGINE_ID)
102+
.selectors(discoverySelector)
103+
.execute()
104+
.allEvents()
105+
.map(Event::getTestDescriptor)
106+
.filter(Predicate.not(TestDescriptor::isRoot))
107+
.map(TestDescriptor::getUniqueId)
108+
.collect(toSet());
109+
}
110+
96111
@Test
97112
void id() {
98113
assertEquals(ENGINE_ID, engine.getId());
@@ -460,17 +475,6 @@ void supportsUniqueIdSelectorCachesParsedFeaturesAndPickles() {
460475
assertEquals(pickleIdsFromFeature, pickleIdsFromPickles);
461476
}
462477

463-
private static Set<UniqueId> discoverUniqueIds(DiscoverySelector discoverySelector) {
464-
return EngineTestKit.engine(ENGINE_ID)
465-
.selectors(discoverySelector)
466-
.execute()
467-
.allEvents()
468-
.map(Event::getTestDescriptor)
469-
.filter(Predicate.not(TestDescriptor::isRoot))
470-
.map(TestDescriptor::getUniqueId)
471-
.collect(toSet());
472-
}
473-
474478
@Test
475479
void supportsFilePositionFeature() {
476480
EngineTestKit.engine(ENGINE_ID)
@@ -604,6 +608,42 @@ void onlySetsEngineSourceWhenFeaturesPropertyIsUsed() {
604608
.haveExactly(1, event(test(finishedSuccessfully())));
605609
}
606610

611+
@Suite
612+
@IncludeEngines("cucumber")
613+
@SelectClasspathResource("io/cucumber/junit/platform/engine/single.feature")
614+
static class SuiteTestCase {
615+
616+
}
617+
618+
@Test
619+
void supportsDisablingDiscoveryAsRootEngine() {
620+
DiscoverySelector selector = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature");
621+
622+
// Ensure classpath resource exists.
623+
assertThat(EngineTestKit.engine(ENGINE_ID)
624+
.selectors(selector)
625+
.discover()
626+
.getEngineDescriptor()
627+
.getChildren())
628+
.isNotEmpty();
629+
630+
assertThat(EngineTestKit.engine(ENGINE_ID)
631+
.configurationParameter(JUNIT_PLATFORM_DISCOVERY_AS_ROOT_ENGINE_PROPERTY_NAME, "false")
632+
.selectors(selector)
633+
.discover()
634+
.getEngineDescriptor()
635+
.getChildren())
636+
.isEmpty();
637+
638+
assertThat(EngineTestKit.engine("junit-platform-suite")
639+
.configurationParameter(JUNIT_PLATFORM_DISCOVERY_AS_ROOT_ENGINE_PROPERTY_NAME, "false")
640+
.selectors(selectClass(SuiteTestCase.class))
641+
.discover()
642+
.getEngineDescriptor()
643+
.getChildren())
644+
.isNotEmpty();
645+
}
646+
607647
@Test
608648
void selectAndSkipDisabledScenarioByTags() {
609649
EngineTestKit.engine(ENGINE_ID)

0 commit comments

Comments
 (0)