Skip to content

Commit dd135fa

Browse files
committed
[JUnit Platform] Use EngineDiscoveryRequestResolver
1 parent 65ef0eb commit dd135fa

14 files changed

+529
-400
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package io.cucumber.junit.platform.engine;
2+
3+
import io.cucumber.core.feature.FeatureWithLines;
4+
import io.cucumber.core.gherkin.Feature;
5+
import io.cucumber.plugin.event.Node;
6+
import org.junit.platform.engine.DiscoverySelector;
7+
import org.junit.platform.engine.UniqueId;
8+
import org.junit.platform.engine.discovery.FilePosition;
9+
10+
import java.net.URI;
11+
import java.util.Collections;
12+
import java.util.Objects;
13+
import java.util.Optional;
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
16+
17+
import static java.util.Objects.requireNonNull;
18+
import static java.util.stream.Collectors.toSet;
19+
20+
class CucumberDiscoverySelectors {
21+
22+
static final class FeatureWithLinesSelector implements DiscoverySelector {
23+
private final URI uri;
24+
private final Set<FilePosition> filePositions;
25+
26+
private FeatureWithLinesSelector(URI uri, Set<FilePosition> filePositions) {
27+
this.uri = requireNonNull(uri);
28+
this.filePositions = requireNonNull(filePositions);
29+
}
30+
31+
static FeatureWithLinesSelector from(FeatureWithLines featureWithLines) {
32+
Set<FilePosition> lines = featureWithLines.lines().stream()
33+
.map(FilePosition::from)
34+
.collect(Collectors.toSet());
35+
return new FeatureWithLinesSelector(featureWithLines.uri(), lines);
36+
}
37+
38+
static Set<FeatureWithLinesSelector> from(UniqueId uniqueId) {
39+
return uniqueId.getSegments()
40+
.stream()
41+
.filter(FeatureOrigin::isFeatureSegment)
42+
.map(featureSegment -> {
43+
URI uri = URI.create(featureSegment.getValue());
44+
Set<FilePosition> filePosition = getFilePosition(uniqueId.getLastSegment());
45+
return new FeatureWithLinesSelector(uri, filePosition);
46+
})
47+
.collect(Collectors.toSet());
48+
}
49+
50+
public static FeatureWithLinesSelector from(URI uri) {
51+
Set<FilePosition> positions = FilePosition.fromQuery(uri.getQuery())
52+
.map(Collections::singleton)
53+
.orElseGet(Collections::emptySet);
54+
return new FeatureWithLinesSelector(stripQuery(uri), positions);
55+
}
56+
57+
private static URI stripQuery(URI uri) {
58+
if (uri.getQuery() == null) {
59+
return uri;
60+
}
61+
String uriString = uri.toString();
62+
return URI.create(uriString.substring(0, uriString.indexOf('?')));
63+
}
64+
65+
private static Set<FilePosition> getFilePosition(UniqueId.Segment segment) {
66+
if (FeatureOrigin.isFeatureSegment(segment)) {
67+
return Collections.emptySet();
68+
}
69+
70+
int line = Integer.parseInt(segment.getValue());
71+
return Collections.singleton(FilePosition.from(line));
72+
}
73+
74+
URI getUri() {
75+
return uri;
76+
}
77+
78+
Optional<Set<FilePosition>> getFilePositions() {
79+
return filePositions.isEmpty() ? Optional.empty() : Optional.of(filePositions);
80+
}
81+
82+
@Override
83+
public boolean equals(Object o) {
84+
if (this == o)
85+
return true;
86+
if (o == null || getClass() != o.getClass())
87+
return false;
88+
FeatureWithLinesSelector that = (FeatureWithLinesSelector) o;
89+
return uri.equals(that.uri) && filePositions.equals(that.filePositions);
90+
}
91+
92+
@Override
93+
public int hashCode() {
94+
return Objects.hash(uri, filePositions);
95+
}
96+
}
97+
98+
static class FeatureElementSelector implements DiscoverySelector {
99+
100+
private final Feature feature;
101+
private final Node element;
102+
103+
private FeatureElementSelector(Feature feature) {
104+
this(feature, feature);
105+
}
106+
107+
private FeatureElementSelector(Feature feature, Node element) {
108+
this.feature = requireNonNull(feature);
109+
this.element = requireNonNull(element);
110+
}
111+
112+
static FeatureElementSelector selectFeature(Feature feature) {
113+
return new FeatureElementSelector(feature);
114+
}
115+
116+
static FeatureElementSelector selectElement(Feature feature, Node element) {
117+
return new FeatureElementSelector(feature, element);
118+
}
119+
120+
static Optional<FeatureElementSelector> selectElementAt(Feature feature, FilePosition filePosition) {
121+
return feature.findPathTo(candidate -> candidate.getLocation().getLine() == filePosition.getLine())
122+
.map(nodes -> nodes.get(nodes.size() - 1))
123+
.map(node -> new FeatureElementSelector(feature, node));
124+
}
125+
126+
static Set<FeatureElementSelector> selectElementsOf(Feature feature, Node selected) {
127+
if (selected instanceof Node.Container) {
128+
Node.Container<?> container = (Node.Container<?>) selected;
129+
return container.elements().stream()
130+
.map(element -> new FeatureElementSelector(feature, element))
131+
.collect(toSet());
132+
}
133+
return Collections.emptySet();
134+
}
135+
136+
Feature getFeature() {
137+
return feature;
138+
}
139+
140+
Node getElement() {
141+
return element;
142+
}
143+
144+
@Override
145+
public boolean equals(Object o) {
146+
if (this == o)
147+
return true;
148+
if (o == null || getClass() != o.getClass())
149+
return false;
150+
FeatureElementSelector that = (FeatureElementSelector) o;
151+
return feature.equals(that.feature) && element.equals(that.element);
152+
}
153+
154+
@Override
155+
public int hashCode() {
156+
return Objects.hash(feature, element);
157+
}
158+
}
159+
}

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

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

3-
import org.junit.platform.engine.TestDescriptor;
43
import org.junit.platform.engine.TestSource;
54
import org.junit.platform.engine.UniqueId;
65
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
@@ -65,18 +64,4 @@ private CucumberEngineExecutionContext ifChildren(
6564
return context;
6665
}
6766

68-
void mergeFeature(FeatureDescriptor descriptor) {
69-
recursivelyMerge(descriptor, this);
70-
}
71-
72-
private static void recursivelyMerge(TestDescriptor descriptor, TestDescriptor parent) {
73-
Optional<? extends TestDescriptor> byUniqueId = parent.findByUniqueId(descriptor.getUniqueId());
74-
if (!byUniqueId.isPresent()) {
75-
parent.addChild(descriptor);
76-
} else {
77-
byUniqueId.ifPresent(
78-
existingParent -> descriptor.getChildren()
79-
.forEach(child -> recursivelyMerge(child, existingParent)));
80-
}
81-
}
8267
}

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.cucumber.core.plugin.NoPublishFormatter;
1212
import io.cucumber.core.plugin.PublishFormatter;
1313
import io.cucumber.core.snippets.SnippetType;
14+
import io.cucumber.junit.platform.engine.CucumberDiscoverySelectors.FeatureWithLinesSelector;
1415
import io.cucumber.tagexpressions.Expression;
1516
import io.cucumber.tagexpressions.TagExpressionParser;
1617
import org.junit.platform.engine.ConfigurationParameters;
@@ -19,9 +20,9 @@
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.Collections;
22-
import java.util.Comparator;
2323
import java.util.List;
2424
import java.util.Optional;
25+
import java.util.Set;
2526
import java.util.regex.Pattern;
2627
import java.util.stream.Collectors;
2728

@@ -41,6 +42,7 @@
4142
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME;
4243
import static io.cucumber.junit.platform.engine.Constants.SNIPPET_TYPE_PROPERTY_NAME;
4344
import static io.cucumber.junit.platform.engine.Constants.UUID_GENERATOR_PROPERTY_NAME;
45+
import static java.util.Objects.requireNonNull;
4446

4547
class CucumberEngineOptions implements
4648
io.cucumber.core.plugin.Options,
@@ -51,7 +53,7 @@ class CucumberEngineOptions implements
5153
private final ConfigurationParameters configurationParameters;
5254

5355
CucumberEngineOptions(ConfigurationParameters configurationParameters) {
54-
this.configurationParameters = configurationParameters;
56+
this.configurationParameters = requireNonNull(configurationParameters);
5557
}
5658

5759
@Override
@@ -176,14 +178,13 @@ NamingStrategy namingStrategy() {
176178
.orElse(DefaultNamingStrategy.SHORT);
177179
}
178180

179-
List<FeatureWithLines> featuresWithLines() {
181+
Set<FeatureWithLinesSelector> featuresWithLines() {
180182
return configurationParameters.get(FEATURES_PROPERTY_NAME,
181183
s -> Arrays.stream(s.split(","))
182184
.map(String::trim)
183185
.map(FeatureWithLines::parse)
184-
.sorted(Comparator.comparing(FeatureWithLines::uri))
185-
.distinct()
186-
.collect(Collectors.toList()))
187-
.orElse(Collections.emptyList());
186+
.map(FeatureWithLinesSelector::from)
187+
.collect(Collectors.toSet()))
188+
.orElse(Collections.emptySet());
188189
}
189190
}
Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
11
package io.cucumber.junit.platform.engine;
22

3-
import io.cucumber.core.feature.FeatureWithLines;
43
import io.cucumber.core.logging.Logger;
54
import io.cucumber.core.logging.LoggerFactory;
6-
import io.cucumber.junit.platform.engine.NodeDescriptor.PickleDescriptor;
75
import org.junit.platform.engine.ConfigurationParameters;
6+
import org.junit.platform.engine.DiscoveryFilter;
7+
import org.junit.platform.engine.DiscoverySelector;
88
import org.junit.platform.engine.EngineDiscoveryRequest;
9-
import org.junit.platform.engine.Filter;
109
import org.junit.platform.engine.TestDescriptor;
11-
import org.junit.platform.engine.discovery.ClassSelector;
12-
import org.junit.platform.engine.discovery.ClasspathResourceSelector;
13-
import org.junit.platform.engine.discovery.ClasspathRootSelector;
14-
import org.junit.platform.engine.discovery.DirectorySelector;
15-
import org.junit.platform.engine.discovery.FileSelector;
1610
import org.junit.platform.engine.discovery.PackageNameFilter;
17-
import org.junit.platform.engine.discovery.PackageSelector;
18-
import org.junit.platform.engine.discovery.UniqueIdSelector;
19-
import org.junit.platform.engine.discovery.UriSelector;
11+
import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver;
2012

2113
import java.util.List;
14+
import java.util.Objects;
15+
import java.util.Set;
2216
import java.util.function.Predicate;
2317

2418
import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME;
25-
import static io.cucumber.junit.platform.engine.FeatureResolver.create;
19+
import static java.util.stream.Collectors.toList;
2620
import static org.junit.platform.engine.Filter.composeFilters;
2721

2822
class DiscoverySelectorResolver {
2923

3024
private static final Logger log = LoggerFactory.getLogger(DiscoverySelectorResolver.class);
3125

26+
// @formatter:off
27+
private static final EngineDiscoveryRequestResolver<CucumberEngineDescriptor> resolver = EngineDiscoveryRequestResolver
28+
.<CucumberEngineDescriptor>builder()
29+
.addSelectorResolver(context -> new FeatureResolver(
30+
context.getDiscoveryRequest().getConfigurationParameters(),
31+
getPackageFilter(context.getDiscoveryRequest()))
32+
)
33+
.addTestDescriptorVisitor(context -> new PackageFilteringVisitor(getPackageFilter(context.getDiscoveryRequest())))
34+
.addTestDescriptorVisitor(context -> new FeatureOrderingVisitor())
35+
.addTestDescriptorVisitor(context -> new FeatureElementOrderingVisitor())
36+
.addTestDescriptorVisitor(context -> TestDescriptor::prune)
37+
.build();
38+
// @formatter:on
39+
3240
private static boolean warnedWhenCucumberFeaturesPropertyIsUsed = false;
3341

3442
private static void warnWhenCucumberFeaturesPropertyIsUsed() {
@@ -44,67 +52,49 @@ private static void warnWhenCucumberFeaturesPropertyIsUsed() {
4452
}
4553

4654
void resolveSelectors(EngineDiscoveryRequest request, CucumberEngineDescriptor engineDescriptor) {
47-
Predicate<String> packageFilter = buildPackageFilter(request);
48-
resolve(request, engineDescriptor, packageFilter);
49-
filter(engineDescriptor, packageFilter);
50-
pruneTree(engineDescriptor);
51-
}
52-
53-
private Predicate<String> buildPackageFilter(EngineDiscoveryRequest request) {
54-
Filter<String> packageFilter = composeFilters(request.getFiltersByType(PackageNameFilter.class));
55-
return packageFilter.toPredicate();
56-
}
57-
58-
private void resolve(
59-
EngineDiscoveryRequest request, CucumberEngineDescriptor engineDescriptor, Predicate<String> packageFilter
60-
) {
6155
ConfigurationParameters configuration = request.getConfigurationParameters();
62-
FeatureResolver featureResolver = create(
63-
configuration,
64-
engineDescriptor,
65-
packageFilter);
66-
6756
CucumberEngineOptions options = new CucumberEngineOptions(configuration);
68-
List<FeatureWithLines> featureWithLines = options.featuresWithLines();
57+
Set<CucumberDiscoverySelectors.FeatureWithLinesSelector> featureWithLines = options.featuresWithLines();
6958
if (!featureWithLines.isEmpty()) {
7059
warnWhenCucumberFeaturesPropertyIsUsed();
71-
featureWithLines.forEach(featureResolver::resolveFeatureWithLines);
72-
return;
60+
request = new CucumberFeaturesPropertyDiscoveryRequest(request, featureWithLines);
7361
}
74-
75-
request.getSelectorsByType(ClasspathRootSelector.class).forEach(featureResolver::resolveClasspathRoot);
76-
request.getSelectorsByType(ClasspathResourceSelector.class).forEach(featureResolver::resolveClasspathResource);
77-
request.getSelectorsByType(ClassSelector.class).forEach(featureResolver::resolveClass);
78-
request.getSelectorsByType(PackageSelector.class).forEach(featureResolver::resolvePackageResource);
79-
request.getSelectorsByType(FileSelector.class).forEach(featureResolver::resolveFile);
80-
request.getSelectorsByType(DirectorySelector.class).forEach(featureResolver::resolveDirectory);
81-
request.getSelectorsByType(UniqueIdSelector.class).forEach(featureResolver::resolveUniqueId);
82-
request.getSelectorsByType(UriSelector.class).forEach(featureResolver::resolveUri);
62+
resolver.resolve(request, engineDescriptor);
8363
}
8464

85-
private void filter(TestDescriptor engineDescriptor, Predicate<String> packageFilter) {
86-
applyPackagePredicate(packageFilter, engineDescriptor);
65+
private static Predicate<String> getPackageFilter(EngineDiscoveryRequest request) {
66+
// TODO: Move into JUnit.
67+
return composeFilters(request.getFiltersByType(PackageNameFilter.class)).toPredicate();
8768
}
8869

89-
private void pruneTree(TestDescriptor rootDescriptor) {
90-
rootDescriptor.accept(TestDescriptor::prune);
91-
}
70+
private static class CucumberFeaturesPropertyDiscoveryRequest implements EngineDiscoveryRequest {
9271

93-
private void applyPackagePredicate(Predicate<String> packageFilter, TestDescriptor engineDescriptor) {
94-
engineDescriptor.accept(descriptor -> {
95-
if (descriptor instanceof PickleDescriptor) {
96-
PickleDescriptor pickleDescriptor = (PickleDescriptor) descriptor;
97-
if (!includePickle(pickleDescriptor, packageFilter)) {
98-
descriptor.removeFromHierarchy();
99-
}
100-
}
101-
});
102-
}
72+
private final EngineDiscoveryRequest delegate;
73+
private final Set<? extends DiscoverySelector> selectors;
74+
75+
public CucumberFeaturesPropertyDiscoveryRequest(
76+
EngineDiscoveryRequest delegate,
77+
Set<? extends DiscoverySelector> selectors
78+
) {
79+
this.delegate = delegate;
80+
this.selectors = selectors;
81+
}
82+
83+
@Override
84+
public <T extends DiscoverySelector> List<T> getSelectorsByType(Class<T> selectorType) {
85+
Objects.requireNonNull(selectorType);
86+
return this.selectors.stream().filter(selectorType::isInstance).map(selectorType::cast).collect(toList());
87+
}
88+
89+
@Override
90+
public <T extends DiscoveryFilter<?>> List<T> getFiltersByType(Class<T> filterType) {
91+
return delegate.getFiltersByType(filterType);
92+
}
10393

104-
private boolean includePickle(PickleDescriptor pickleDescriptor, Predicate<String> packageFilter) {
105-
return pickleDescriptor.getPackage()
106-
.map(packageFilter::test)
107-
.orElse(true);
94+
@Override
95+
public ConfigurationParameters getConfigurationParameters() {
96+
return delegate.getConfigurationParameters();
97+
}
10898
}
10999

110100
}

0 commit comments

Comments
 (0)