Skip to content

Commit

Permalink
[JUnit Platform] Warn if feature files could not be found
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mpkorstanje committed Dec 9, 2020
1 parent cea219d commit a3dcc4c
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<Feature> featureScanner = new ResourceScanner<>(
ClassLoaders::getDefaultClassLoader,
Expand Down Expand Up @@ -142,20 +147,33 @@ void resolvePackageResource(PackageSelector selector) {
resolvePackageResource(selector.getPackageName());
}

private void resolvePackageResource(String packageName) {
featureScanner
.scanForResourcesInPackage(packageName, packageFilter)
private List<Feature> resolvePackageResource(String packageName) {
List<Feature> 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<Feature> features = resolvePackageResource(packageName);
if (features.isEmpty()) {
log.warn(() -> "No features found in package '" + packageName + "'");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -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<Class<?>, List<DiscoverySelector>> resources = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.cucumber.junit.platform.engine.nofeatures;

import io.cucumber.junit.platform.engine.Cucumber;

@Cucumber
public class NoFeatures {

}

0 comments on commit a3dcc4c

Please sign in to comment.