From e5aab0d5603f0811137b91ef6047c4f4fe99a1bb Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 13 Sep 2020 21:49:33 +0200 Subject: [PATCH] [JUnit Platform] Support discovery selectors with FilePosition There was no good way to discover and execute a single test when using a file based test system like Cucumber. Recently JUnit added support for file position in the `FileSelector` and `ClasspathResourceSelector`. See: https://github.com/junit-team/junit5/issues/2146 This implements a filter that prunes all scenarios that do not match the `FilePosition` from the discovery selector. This feature itself is not new. It was already implemented for the `UriSelector`. --- CHANGELOG.md | 2 + junit-platform-engine/README.md | 9 ++- .../platform/engine/FeatureResolver.java | 59 ++++++----------- .../platform/engine/TestDescriptorOnLine.java | 63 +++++++++++++++++++ .../engine/DiscoverySelectorResolverTest.java | 45 +++++++++++++ 5 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c7981c788..95acbc4dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] (In Git) ### Added + * [JUnit Platform] Support discovery selectors with FilePosition ([2121](https://github.com/cucumber/cucumber-jvm/pull/2121) M.P. Korstanje) ### Changed + * [JUnit Platform] Update dependency org.junit.platform:junit-platform-engine to v1.7.0 ### Deprecated diff --git a/junit-platform-engine/README.md b/junit-platform-engine/README.md index a08b2912ed..52b728d128 100644 --- a/junit-platform-engine/README.md +++ b/junit-platform-engine/README.md @@ -237,10 +237,17 @@ Supported `DiscoverySelector`s are: The only supported `DiscoveryFilter` is the `PackageNameFilter` and only when features are selected from the classpath. +### Selecting individual scenarios, rules and examples + +The `FileSelector` and `ClasspathResourceSelector` support the `FilePosition`. + + * `DiscoverySelectors.selectClasspathResource("rule.feature", FilePosition.from(5))` + * `DiscoverySelectors.selectFile("rule.feature", FilePosition.from(5))` + The `UriSelector` supports URI's with a `line` query parameter: - `classpath:/com/example/example.feature?line=20` - `file:/path/to/com/example/example.feature?line=20` - + Any `TestDescriptor` that matches the line *and* its descendants will be included in the discovery result. 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 0ddbcbbd92..39133b5f73 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 @@ -18,20 +18,14 @@ import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; -import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; -import org.junit.platform.engine.support.descriptor.FilePosition; -import org.junit.platform.engine.support.descriptor.FileSource; import java.net.URI; -import java.nio.file.Path; -import java.util.Optional; import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import static java.util.Comparator.comparing; -import static org.junit.platform.engine.support.descriptor.FilePosition.fromQuery; final class FeatureResolver { @@ -62,16 +56,15 @@ static FeatureResolver createFeatureResolver( } void resolveFile(FileSelector selector) { - resolvePath(selector.getPath()); - } - - private void resolvePath(Path path) { featureScanner - .scanForResourcesPath(path) + .scanForResourcesPath(selector.getPath()) .stream() .sorted(comparing(Feature::getUri)) .map(this::createFeatureDescriptor) - .forEach(engineDescriptor::mergeFeature); + .forEach(featureDescriptor -> { + featureDescriptor.prune(TestDescriptorOnLine.from(selector)); + engineDescriptor.mergeFeature(featureDescriptor); + }); } private FeatureDescriptor createFeatureDescriptor(Feature feature) { @@ -137,7 +130,12 @@ private String getNameOrKeyWord(Node node) { } void resolveDirectory(DirectorySelector selector) { - resolvePath(selector.getPath()); + featureScanner + .scanForResourcesPath(selector.getPath()) + .stream() + .sorted(comparing(Feature::getUri)) + .map(this::createFeatureDescriptor) + .forEach(engineDescriptor::mergeFeature); } void resolvePackageResource(PackageSelector selector) { @@ -163,12 +161,16 @@ void resolveClass(ClassSelector classSelector) { void resolveClasspathResource(ClasspathResourceSelector selector) { String classpathResourceName = selector.getClasspathResourceName(); + featureScanner .scanForClasspathResource(classpathResourceName, packageFilter) .stream() .sorted(comparing(Feature::getUri)) .map(this::createFeatureDescriptor) - .forEach(engineDescriptor::mergeFeature); + .forEach(featureDescriptor -> { + featureDescriptor.prune(TestDescriptorOnLine.from(selector)); + engineDescriptor.mergeFeature(featureDescriptor); + }); } void resolveClasspathRoot(ClasspathRootSelector selector) { @@ -211,38 +213,13 @@ private Stream resolveUri(URI uri) { } void resolveUri(UriSelector selector) { - URI uri = selector.getUri(); - - Predicate keepTestOnSelectedLine = fromQuery(uri.getQuery()) - .map(FilePosition::getLine) - .map(FeatureResolver::testDescriptorOnLine) - .orElse(testDescriptor -> true); - - resolveUri(stripQuery(uri)) + resolveUri(stripQuery(selector.getUri())) .forEach(featureDescriptor -> { - featureDescriptor.prune(keepTestOnSelectedLine); + featureDescriptor.prune(TestDescriptorOnLine.from(selector)); engineDescriptor.mergeFeature(featureDescriptor); }); } - private static Predicate testDescriptorOnLine(Integer line) { - return descriptor -> descriptor.getSource() - .flatMap(testSource -> { - if (testSource instanceof FileSource) { - FileSource fileSystemSource = (FileSource) testSource; - return fileSystemSource.getPosition(); - } - if (testSource instanceof ClasspathResourceSource) { - ClasspathResourceSource classpathResourceSource = (ClasspathResourceSource) testSource; - return classpathResourceSource.getPosition(); - } - return Optional.empty(); - }) - .map(FilePosition::getLine) - .map(line::equals) - .orElse(false); - } - private static URI stripQuery(URI uri) { if (uri.getQuery() == null) { return uri; diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java new file mode 100644 index 0000000000..66b828d3b0 --- /dev/null +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/TestDescriptorOnLine.java @@ -0,0 +1,63 @@ +package io.cucumber.junit.platform.engine; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.FilePosition; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.UriSelector; +import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; +import org.junit.platform.engine.support.descriptor.FileSource; + +import java.util.Optional; +import java.util.function.Predicate; + +import static org.junit.platform.engine.support.descriptor.FilePosition.fromQuery; + +class TestDescriptorOnLine { + + static Predicate testDescriptorOnLine(Integer line) { + return descriptor -> descriptor.getSource() + .flatMap(testSource -> { + if (testSource instanceof FileSource) { + FileSource fileSystemSource = (FileSource) testSource; + return fileSystemSource.getPosition(); + } + if (testSource instanceof ClasspathResourceSource) { + ClasspathResourceSource classpathResourceSource = (ClasspathResourceSource) testSource; + return classpathResourceSource.getPosition(); + } + return Optional.empty(); + }) + .map(org.junit.platform.engine.support.descriptor.FilePosition::getLine) + .map(line::equals) + .orElse(false); + } + + private static boolean anyTestDescriptor(TestDescriptor testDescriptor) { + return true; + } + + static Predicate from(UriSelector selector) { + String query = selector.getUri().getQuery(); + return fromQuery(query) + .map(filePosition -> FilePosition.from(filePosition.getLine())) + .map(FilePosition::getLine) + .map(TestDescriptorOnLine::testDescriptorOnLine) + .orElse(TestDescriptorOnLine::anyTestDescriptor); + } + + static Predicate from(ClasspathResourceSelector selector) { + return selector.getPosition() + .map(FilePosition::getLine) + .map(TestDescriptorOnLine::testDescriptorOnLine) + .orElse(TestDescriptorOnLine::anyTestDescriptor); + } + + static Predicate from(FileSelector selector) { + return selector.getPosition() + .map(FilePosition::getLine) + .map(TestDescriptorOnLine::testDescriptorOnLine) + .orElse(TestDescriptorOnLine::anyTestDescriptor); + } + +} 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 28fbdd33f7..f7754df050 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 @@ -11,6 +11,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.FilePosition; import org.junit.platform.engine.discovery.UniqueIdSelector; import java.io.File; @@ -62,6 +63,28 @@ void resolveRequestWithClasspathResourceSelector() { assertEquals(1, testDescriptor.getChildren().size()); } + @Test + void resolveRequestWithClasspathResourceSelectorAndFilePosition() { + DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/rule.feature", FilePosition.from(5)); + EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource); + resolver.resolveSelectors(discoveryRequest, testDescriptor); + assertEquals(1L, testDescriptor.getDescendants() + .stream() + .filter(TestDescriptor::isTest) + .count()); + } + + @Test + void resolveRequestWithClasspathResourceSelectorAndFilePositionOfContainer() { + DiscoverySelector resource = selectClasspathResource("io/cucumber/junit/platform/engine/rule.feature", FilePosition.from(3)); + EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource); + resolver.resolveSelectors(discoveryRequest, testDescriptor); + assertEquals(2L, testDescriptor.getDescendants() + .stream() + .filter(TestDescriptor::isTest) + .count()); + } + @Test void resolveRequestWithMultipleClasspathResourceSelector() { DiscoverySelector resource1 = selectClasspathResource("io/cucumber/junit/platform/engine/single.feature"); @@ -155,6 +178,28 @@ void resolveRequestWithFileSelector() { assertEquals(1, testDescriptor.getChildren().size()); } + @Test + void resolveRequestWithFileSelectorAndPosition() { + DiscoverySelector resource = selectFile("src/test/resources/io/cucumber/junit/platform/engine/rule.feature", FilePosition.from(5)); + EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource); + resolver.resolveSelectors(discoveryRequest, testDescriptor); + assertEquals(1L, testDescriptor.getDescendants() + .stream() + .filter(TestDescriptor::isTest) + .count()); + } + + @Test + void resolveRequestWithFileSelectorAndPositionOfContainer() { + DiscoverySelector resource = selectFile("src/test/resources/io/cucumber/junit/platform/engine/rule.feature", FilePosition.from(3)); + EngineDiscoveryRequest discoveryRequest = new SelectorRequest(resource); + resolver.resolveSelectors(discoveryRequest, testDescriptor); + assertEquals(2L, testDescriptor.getDescendants() + .stream() + .filter(TestDescriptor::isTest) + .count()); + } + @Test void resolveRequestWithDirectorySelector() { DiscoverySelector resource = selectDirectory("src/test/resources/io/cucumber/junit/platform/engine");