Skip to content

Commit 36e8dee

Browse files
authored
[JUnit Platform] Warn if feature files could not be found (#2182)
Both Surefire and Gradle assume that tests are contained within a class and use `ClassSelector` to discover tests in these classes. Cucumber uses plain text files. By using a class annotated with `@Cucumber` we work around this behaviour. Cucumber will then scan the package and sub-packages of the annotated class for feature files. When using this system, in case of misconfiguration it is not immediately clear if the test engine is not picked up or if the location of the feature files and annotated class do not line up. While we can not generically log a warning in case a discovery selector did not find any features, we can log a warning in this special case. It is clear that the intend was to put feature files in the package. Otherwise the annotated class could/should be removed to suppress this warning. Fixes: #2179
1 parent 156db1f commit 36e8dee

File tree

5 files changed

+60
-6
lines changed

5 files changed

+60
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
## [Unreleased] (In Git)
99

1010
### Added
11+
* [JUnit Platform] Warn if feature files could not be found ([#2179](https://github.com/cucumber/cucumber-jvm/issues/2179) M.P. Korstanje)
1112

1213
### Changed
1314

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.cucumber.core.eventbus.EventBus;
44
import io.cucumber.core.gherkin.Feature;
55
import io.cucumber.core.gherkin.Pickle;
6+
import io.cucumber.core.logging.Logger;
7+
import io.cucumber.core.logging.LoggerFactory;
68
import io.cucumber.core.plugin.PluginFactory;
79
import io.cucumber.core.plugin.Plugins;
810
import io.cucumber.core.runtime.BackendServiceLoader;
@@ -20,8 +22,6 @@
2022
import io.cucumber.core.runtime.TimeServiceEventBus;
2123
import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier;
2224
import org.apiguardian.api.API;
23-
import org.junit.platform.commons.logging.Logger;
24-
import org.junit.platform.commons.logging.LoggerFactory;
2525
import org.junit.platform.engine.ConfigurationParameters;
2626
import org.junit.platform.engine.support.hierarchical.EngineExecutionContext;
2727

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io.cucumber.core.feature.FeatureParser;
55
import io.cucumber.core.gherkin.Feature;
66
import io.cucumber.core.gherkin.Pickle;
7+
import io.cucumber.core.logging.Logger;
8+
import io.cucumber.core.logging.LoggerFactory;
79
import io.cucumber.core.resource.ClassLoaders;
810
import io.cucumber.core.resource.ResourceScanner;
911
import io.cucumber.plugin.event.Node;
@@ -20,6 +22,7 @@
2022
import org.junit.platform.engine.discovery.UriSelector;
2123

2224
import java.net.URI;
25+
import java.util.List;
2326
import java.util.UUID;
2427
import java.util.function.Predicate;
2528
import java.util.function.Supplier;
@@ -29,6 +32,8 @@
2932

3033
final class FeatureResolver {
3134

35+
private static final Logger log = LoggerFactory.getLogger(FeatureResolver.class);
36+
3237
private final FeatureParser featureParser = new FeatureParser(UUID::randomUUID);
3338
private final ResourceScanner<Feature> featureScanner = new ResourceScanner<>(
3439
ClassLoaders::getDefaultClassLoader,
@@ -142,20 +147,33 @@ void resolvePackageResource(PackageSelector selector) {
142147
resolvePackageResource(selector.getPackageName());
143148
}
144149

145-
private void resolvePackageResource(String packageName) {
146-
featureScanner
147-
.scanForResourcesInPackage(packageName, packageFilter)
150+
private List<Feature> resolvePackageResource(String packageName) {
151+
List<Feature> features = featureScanner
152+
.scanForResourcesInPackage(packageName, packageFilter);
153+
154+
features
148155
.stream()
149156
.sorted(comparing(Feature::getUri))
150157
.map(this::createFeatureDescriptor)
151158
.forEach(engineDescriptor::mergeFeature);
159+
160+
return features;
152161
}
153162

154163
void resolveClass(ClassSelector classSelector) {
155164
Class<?> javaClass = classSelector.getJavaClass();
156165
Cucumber annotation = javaClass.getAnnotation(Cucumber.class);
157166
if (annotation != null) {
158-
resolvePackageResource(javaClass.getPackage().getName());
167+
// We know now the intention is to run feature files in the
168+
// package of the annotated class.
169+
resolvePackageResourceWarnIfNone(javaClass.getPackage().getName());
170+
}
171+
}
172+
173+
private void resolvePackageResourceWarnIfNone(String packageName) {
174+
List<Feature> features = resolvePackageResource(packageName);
175+
if (features.isEmpty()) {
176+
log.warn(() -> "No features found in package '" + packageName + "'");
159177
}
160178
}
161179

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package io.cucumber.junit.platform.engine;
22

3+
import io.cucumber.core.logging.LogRecordListener;
4+
import io.cucumber.core.logging.LoggerFactory;
5+
import io.cucumber.junit.platform.engine.nofeatures.NoFeatures;
36
import org.hamcrest.CustomTypeSafeMatcher;
47
import org.hamcrest.Matcher;
8+
import org.junit.jupiter.api.AfterEach;
59
import org.junit.jupiter.api.BeforeEach;
610
import org.junit.jupiter.api.Test;
711
import org.junit.platform.engine.ConfigurationParameters;
@@ -27,8 +31,11 @@
2731
import java.util.Map;
2832
import java.util.Optional;
2933
import java.util.Set;
34+
import java.util.logging.Level;
35+
import java.util.logging.LogRecord;
3036
import java.util.stream.Collectors;
3137

38+
import static java.util.Collections.emptyList;
3239
import static java.util.Collections.singleton;
3340
import static java.util.Comparator.comparing;
3441
import static java.util.stream.Collectors.toSet;
@@ -46,15 +53,22 @@
4653
class DiscoverySelectorResolverTest {
4754

4855
private final DiscoverySelectorResolver resolver = new DiscoverySelectorResolver();
56+
private final LogRecordListener logRecordListener = new LogRecordListener();
4957
private CucumberEngineDescriptor testDescriptor;
5058

5159
@BeforeEach
5260
void before() {
61+
LoggerFactory.addListener(logRecordListener);
5362
UniqueId id = UniqueId.forEngine(new CucumberTestEngine().getId());
5463
testDescriptor = new CucumberEngineDescriptor(id);
5564
assertEquals(0, testDescriptor.getChildren().size());
5665
}
5766

67+
@AfterEach
68+
void after() {
69+
LoggerFactory.removeListener(logRecordListener);
70+
}
71+
5872
@Test
5973
void resolveRequestWithClasspathResourceSelector() {
6074
DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature");
@@ -349,6 +363,19 @@ void resolveRequestWithClassSelector() {
349363
assertEquals(5, testDescriptor.getChildren().size());
350364
}
351365

366+
@Test
367+
void resolveRequestWithClassSelectorShouldLogWarnIfNoFeaturesFound() {
368+
DiscoverySelector resource = selectClass(NoFeatures.class);
369+
EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource);
370+
resolver.resolveSelectors(discoveryRequest, testDescriptor);
371+
assertEquals(0, testDescriptor.getChildren().size());
372+
assertEquals(1, logRecordListener.getLogRecords().size());
373+
LogRecord logRecord = logRecordListener.getLogRecords().get(0);
374+
assertEquals(Level.WARNING, logRecord.getLevel());
375+
assertEquals("No features found in package 'io.cucumber.junit.platform.engine.nofeatures'",
376+
logRecord.getMessage());
377+
}
378+
352379
private static class SelectorRequest implements EngineDiscoveryRequest {
353380

354381
private final Map<Class<?>, List<DiscoverySelector>> resources = new HashMap<>();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.cucumber.junit.platform.engine.nofeatures;
2+
3+
import io.cucumber.junit.platform.engine.Cucumber;
4+
5+
@Cucumber
6+
public class NoFeatures {
7+
8+
}

0 commit comments

Comments
 (0)