From 36e8dee8507d9848eb36b1d0b4e85aaad03ca9f1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 9 Dec 2020 17:48:00 +0100 Subject: [PATCH] [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: https://github.com/cucumber/cucumber-jvm/issues/2179 --- CHANGELOG.md | 1 + .../CucumberEngineExecutionContext.java | 4 +-- .../platform/engine/FeatureResolver.java | 26 +++++++++++++++--- .../engine/DiscoverySelectorResolverTest.java | 27 +++++++++++++++++++ .../engine/nofeatures/NoFeatures.java | 8 ++++++ 5 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java diff --git a/CHANGELOG.md b/CHANGELOG.md index acf6994d98..5f121985d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] (In Git) ### Added + * [JUnit Platform] Warn if feature files could not be found ([#2179](https://github.com/cucumber/cucumber-jvm/issues/2179) M.P. Korstanje) ### Changed diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java index dd3c2fae35..ccdc3ee15d 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java @@ -3,6 +3,8 @@ import io.cucumber.core.eventbus.EventBus; 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.plugin.PluginFactory; import io.cucumber.core.plugin.Plugins; import io.cucumber.core.runtime.BackendServiceLoader; @@ -20,8 +22,6 @@ import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java index 39133b5f73..d93d246b9d 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java @@ -4,6 +4,8 @@ import io.cucumber.core.feature.FeatureParser; 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.plugin.event.Node; @@ -20,6 +22,7 @@ import org.junit.platform.engine.discovery.UriSelector; import java.net.URI; +import java.util.List; import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; @@ -29,6 +32,8 @@ final class FeatureResolver { + private static final Logger log = LoggerFactory.getLogger(FeatureResolver.class); + private final FeatureParser featureParser = new FeatureParser(UUID::randomUUID); private final ResourceScanner featureScanner = new ResourceScanner<>( ClassLoaders::getDefaultClassLoader, @@ -142,20 +147,33 @@ void resolvePackageResource(PackageSelector selector) { resolvePackageResource(selector.getPackageName()); } - private void resolvePackageResource(String packageName) { - featureScanner - .scanForResourcesInPackage(packageName, packageFilter) + private List resolvePackageResource(String packageName) { + List features = featureScanner + .scanForResourcesInPackage(packageName, packageFilter); + + features .stream() .sorted(comparing(Feature::getUri)) .map(this::createFeatureDescriptor) .forEach(engineDescriptor::mergeFeature); + + return features; } void resolveClass(ClassSelector classSelector) { Class javaClass = classSelector.getJavaClass(); Cucumber annotation = javaClass.getAnnotation(Cucumber.class); if (annotation != null) { - resolvePackageResource(javaClass.getPackage().getName()); + // We know now the intention is to run feature files in the + // package of the annotated class. + resolvePackageResourceWarnIfNone(javaClass.getPackage().getName()); + } + } + + private void resolvePackageResourceWarnIfNone(String packageName) { + List features = resolvePackageResource(packageName); + if (features.isEmpty()) { + log.warn(() -> "No features found in package '" + packageName + "'"); } } diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java index 1a64fe1d89..633c8e86bb 100644 --- a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/DiscoverySelectorResolverTest.java @@ -1,7 +1,11 @@ package io.cucumber.junit.platform.engine; +import io.cucumber.core.logging.LogRecordListener; +import io.cucumber.core.logging.LoggerFactory; +import io.cucumber.junit.platform.engine.nofeatures.NoFeatures; import org.hamcrest.CustomTypeSafeMatcher; import org.hamcrest.Matcher; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; @@ -27,8 +31,11 @@ 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 static java.util.Collections.emptyList; import static java.util.Collections.singleton; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toSet; @@ -46,15 +53,22 @@ class DiscoverySelectorResolverTest { private final DiscoverySelectorResolver resolver = new DiscoverySelectorResolver(); + private final LogRecordListener logRecordListener = new LogRecordListener(); private CucumberEngineDescriptor testDescriptor; @BeforeEach void before() { + LoggerFactory.addListener(logRecordListener); UniqueId id = UniqueId.forEngine(new CucumberTestEngine().getId()); testDescriptor = new CucumberEngineDescriptor(id); assertEquals(0, testDescriptor.getChildren().size()); } + @AfterEach + void after() { + LoggerFactory.removeListener(logRecordListener); + } + @Test void resolveRequestWithClasspathResourceSelector() { DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"); @@ -349,6 +363,19 @@ void resolveRequestWithClassSelector() { assertEquals(5, testDescriptor.getChildren().size()); } + @Test + void resolveRequestWithClassSelectorShouldLogWarnIfNoFeaturesFound() { + 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<>(); diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java new file mode 100644 index 0000000000..f42bb9b00e --- /dev/null +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/nofeatures/NoFeatures.java @@ -0,0 +1,8 @@ +package io.cucumber.junit.platform.engine.nofeatures; + +import io.cucumber.junit.platform.engine.Cucumber; + +@Cucumber +public class NoFeatures { + +}