Skip to content

Commit

Permalink
[JUnit Platform] Support discovery selectors with FilePosition
Browse files Browse the repository at this point in the history
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: junit-team/junit5#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`.
  • Loading branch information
mpkorstanje committed Sep 13, 2020
1 parent 92ee5cf commit 560676b
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 45 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 10 additions & 2 deletions junit-platform-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,20 @@ 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 a `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.
included in the discovery result. So for example selecting a `Rule` will
execute all scenarios contained within.

## Tags ##

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

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -211,38 +213,13 @@ private Stream<FeatureDescriptor> resolveUri(URI uri) {
}

void resolveUri(UriSelector selector) {
URI uri = selector.getUri();

Predicate<TestDescriptor> 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<TestDescriptor> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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.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;

@SuppressWarnings("Convert2MethodRef")
class TestDescriptorOnLine {

static Predicate<TestDescriptor> testDescriptorOnLine(int 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 -> filePosition.getLine())
.map(testSourceLine -> line == testSourceLine)
.orElse(false);
}

private static boolean anyTestDescriptor(TestDescriptor testDescriptor) {
return true;
}

static Predicate<TestDescriptor> from(UriSelector selector) {
String query = selector.getUri().getQuery();
return fromQuery(query)
.map(filePosition -> filePosition.getLine())
.map(TestDescriptorOnLine::testDescriptorOnLine)
.orElse(TestDescriptorOnLine::anyTestDescriptor);
}

static Predicate<TestDescriptor> from(ClasspathResourceSelector selector) {
return selector.getPosition()
.map(filePosition -> filePosition.getLine())
.map(TestDescriptorOnLine::testDescriptorOnLine)
.orElse(TestDescriptorOnLine::anyTestDescriptor);
}

static Predicate<TestDescriptor> from(FileSelector selector) {
return selector.getPosition()
.map(filePosition -> filePosition.getLine())
.map(TestDescriptorOnLine::testDescriptorOnLine)
.orElse(TestDescriptorOnLine::anyTestDescriptor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@

<!--Test Dependencies-->
<junit.version>4.13</junit.version>
<junit-jupiter.version>5.6.2</junit-jupiter.version>
<junit-platform.version>1.6.2</junit-platform.version>
<junit-jupiter.version>5.7.0</junit-jupiter.version>
<junit-platform.version>1.7.0</junit-platform.version>
<hamcrest.version>2.2</hamcrest.version>
<mockito.version>3.5.10</mockito.version>
<!--Maven plugins-->
Expand Down

0 comments on commit 560676b

Please sign in to comment.