Skip to content

Commit a8470cb

Browse files
authored
[JUnit Platform Engine] Use FileSource.withPosition (#3084)
Creating a `FileSource` involves a call to `File.getCanonicalFile`. This call can be slow because it involves the file system. Especially if the file system is networked. By using `FileSource.withPosition` we can reuse the canonical file from the feature for its nodes. Fixes: #3010
1 parent 6593e91 commit a8470cb

File tree

10 files changed

+209
-145
lines changed

10 files changed

+209
-145
lines changed

.github/workflows/test-java.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on:
1717
jobs:
1818
build:
1919
strategy:
20+
fail-fast: true
2021
matrix:
2122
os: [ ubuntu-latest, windows-latest ]
2223
version: [ 17, 21 ]

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Fixed
14+
- [JUnit Platform Engine] Use `FileSource.withPosition` ([#3084](https://github.com/cucumber/cucumber-jvm/pull/3084) M.P. Korstanje)
15+
1316
### Changed
1417
- [JUnit Platform Engine] Use JUnit Platform 1.14.0 (JUnit Jupiter 5.14.0)
1518

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

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

33
import io.cucumber.core.feature.FeatureWithLines;
4-
import io.cucumber.core.gherkin.Feature;
54
import io.cucumber.plugin.event.Node;
65
import org.junit.platform.commons.util.ToStringBuilder;
76
import org.junit.platform.engine.DiscoveryIssue;
@@ -44,7 +43,7 @@ static FeatureWithLinesSelector from(FeatureWithLines featureWithLines) {
4443
static Set<FeatureWithLinesSelector> from(UniqueId uniqueId) {
4544
return uniqueId.getSegments()
4645
.stream()
47-
.filter(FeatureOrigin::isFeatureSegment)
46+
.filter(CucumberTestDescriptor::isFeatureSegment)
4847
.map(featureSegment -> {
4948
URI uri = URI.create(featureSegment.getValue());
5049
Set<FilePosition> filePosition = getFilePosition(uniqueId.getLastSegment());
@@ -69,7 +68,7 @@ private static URI stripQuery(URI uri) {
6968
}
7069

7170
private static Set<FilePosition> getFilePosition(UniqueId.Segment segment) {
72-
if (FeatureOrigin.isFeatureSegment(segment)) {
71+
if (CucumberTestDescriptor.isFeatureSegment(segment)) {
7372
return Collections.emptySet();
7473
}
7574

@@ -111,28 +110,28 @@ public String toString() {
111110

112111
static class FeatureElementSelector implements DiscoverySelector {
113112

114-
private final Feature feature;
113+
private final FeatureWithSource feature;
115114
private final Node element;
116115

117-
private FeatureElementSelector(Feature feature) {
118-
this(feature, feature);
116+
private FeatureElementSelector(FeatureWithSource feature) {
117+
this(feature, feature.getFeature());
119118
}
120119

121-
private FeatureElementSelector(Feature feature, Node element) {
120+
private FeatureElementSelector(FeatureWithSource feature, Node element) {
122121
this.feature = requireNonNull(feature);
123122
this.element = requireNonNull(element);
124123
}
125124

126-
static FeatureElementSelector selectFeature(Feature feature) {
125+
static FeatureElementSelector selectFeature(FeatureWithSource feature) {
127126
return new FeatureElementSelector(feature);
128127
}
129128

130-
static FeatureElementSelector selectElement(Feature feature, Node element) {
129+
static FeatureElementSelector selectElement(FeatureWithSource feature, Node element) {
131130
return new FeatureElementSelector(feature, element);
132131
}
133132

134133
static Stream<FeatureElementSelector> selectElementsAt(
135-
Feature feature, Supplier<Optional<Set<FilePosition>>> filePositions,
134+
FeatureWithSource feature, Supplier<Optional<Set<FilePosition>>> filePositions,
136135
DiscoveryIssueReporter issueReporter
137136
) {
138137
return filePositions.get()
@@ -141,23 +140,25 @@ static Stream<FeatureElementSelector> selectElementsAt(
141140
}
142141

143142
private static Stream<FeatureElementSelector> selectElementsAt(
144-
Feature feature, Set<FilePosition> filePositions, DiscoveryIssueReporter issueReporter
143+
FeatureWithSource feature, Set<FilePosition> filePositions, DiscoveryIssueReporter issueReporter
145144
) {
146145
return filePositions.stream().map(filePosition -> selectElementAt(feature, filePosition, issueReporter));
147146
}
148147

149148
static FeatureElementSelector selectElementAt(
150-
Feature feature, Supplier<Optional<FilePosition>> filePosition, DiscoveryIssueReporter issueReporter
149+
FeatureWithSource feature, Supplier<Optional<FilePosition>> filePosition,
150+
DiscoveryIssueReporter issueReporter
151151
) {
152152
return filePosition.get()
153153
.map(position -> selectElementAt(feature, position, issueReporter))
154154
.orElseGet(() -> selectFeature(feature));
155155
}
156156

157157
static FeatureElementSelector selectElementAt(
158-
Feature feature, FilePosition filePosition, DiscoveryIssueReporter issueReporter
158+
FeatureWithSource feature, FilePosition filePosition, DiscoveryIssueReporter issueReporter
159159
) {
160-
return feature.findPathTo(candidate -> candidate.getLocation().getLine() == filePosition.getLine())
160+
return feature.getFeature()
161+
.findPathTo(candidate -> candidate.getLocation().getLine() == filePosition.getLine())
161162
.map(nodes -> nodes.get(nodes.size() - 1))
162163
.map(node -> new FeatureElementSelector(feature, node))
163164
.orElseGet(() -> {
@@ -167,7 +168,7 @@ static FeatureElementSelector selectElementAt(
167168
}
168169

169170
private static void reportInvalidFilePosition(
170-
Feature feature, FilePosition filePosition, DiscoveryIssueReporter issueReporter
171+
FeatureWithSource feature, FilePosition filePosition, DiscoveryIssueReporter issueReporter
171172
) {
172173
issueReporter.reportIssue(DiscoveryIssue.create(WARNING,
173174
"Feature file " + feature.getUri()
@@ -176,7 +177,7 @@ private static void reportInvalidFilePosition(
176177
". Selecting the whole feature instead"));
177178
}
178179

179-
static Set<FeatureElementSelector> selectElementsOf(Feature feature, Node selected) {
180+
static Set<FeatureElementSelector> selectElementsOf(FeatureWithSource feature, Node selected) {
180181
if (selected instanceof Node.Container) {
181182
Node.Container<?> container = (Node.Container<?>) selected;
182183
return container.elements().stream()
@@ -186,7 +187,7 @@ static Set<FeatureElementSelector> selectElementsOf(Feature feature, Node select
186187
return Collections.emptySet();
187188
}
188189

189-
Feature getFeature() {
190+
FeatureWithSource getFeature() {
190191
return feature;
191192
}
192193

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

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import io.cucumber.core.gherkin.Feature;
44
import io.cucumber.core.gherkin.Pickle;
5+
import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureElementDescriptor.ExamplesDescriptor;
6+
import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureElementDescriptor.RuleDescriptor;
7+
import io.cucumber.junit.platform.engine.CucumberTestDescriptor.FeatureElementDescriptor.ScenarioOutlineDescriptor;
58
import io.cucumber.plugin.event.Location;
9+
import org.junit.platform.engine.TestDescriptor;
610
import org.junit.platform.engine.TestSource;
711
import org.junit.platform.engine.TestTag;
812
import org.junit.platform.engine.UniqueId;
@@ -17,16 +21,113 @@
1721
import java.util.Set;
1822
import java.util.stream.Stream;
1923

24+
import static java.util.Objects.requireNonNull;
2025
import static java.util.stream.Collectors.collectingAndThen;
2126
import static java.util.stream.Collectors.toCollection;
2227
import static java.util.stream.Collectors.toSet;
2328

2429
abstract class CucumberTestDescriptor extends AbstractTestDescriptor {
2530

26-
protected CucumberTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) {
31+
private static final String RULE_SEGMENT_TYPE = "rule";
32+
private static final String FEATURE_SEGMENT_TYPE = "feature";
33+
private static final String SCENARIO_SEGMENT_TYPE = "scenario";
34+
private static final String EXAMPLES_SEGMENT_TYPE = "examples";
35+
private static final String EXAMPLE_SEGMENT_TYPE = "example";
36+
37+
private CucumberTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) {
2738
super(uniqueId, displayName, source);
2839
}
2940

41+
static Builder builder(CucumberConfiguration configuration) {
42+
return new Builder(configuration, configuration.namingStrategy());
43+
}
44+
45+
static class Builder {
46+
private final CucumberConfiguration configuration;
47+
private final NamingStrategy namingStrategy;
48+
49+
Builder(CucumberConfiguration configuration, NamingStrategy namingStrategy) {
50+
this.configuration = requireNonNull(configuration);
51+
this.namingStrategy = requireNonNull(namingStrategy);
52+
}
53+
54+
Optional<TestDescriptor> build(
55+
TestDescriptor parent, FeatureWithSource feature, io.cucumber.plugin.event.Node node
56+
) {
57+
requireNonNull(parent);
58+
requireNonNull(feature);
59+
requireNonNull(node);
60+
61+
FeatureSource source = feature.getSource();
62+
if (node instanceof io.cucumber.plugin.event.Node.Feature) {
63+
return Optional.of(new FeatureDescriptor(
64+
createUniqueId(parent, FEATURE_SEGMENT_TYPE, feature.getUri()),
65+
namingStrategy.name(node),
66+
source.nodeSource(node),
67+
feature.getFeature()));
68+
}
69+
70+
if (node instanceof io.cucumber.plugin.event.Node.Rule) {
71+
return Optional.of(new RuleDescriptor(
72+
configuration,
73+
createUniqueId(parent, RULE_SEGMENT_TYPE, node.getLocation()),
74+
namingStrategy.name(node),
75+
source.nodeSource(node),
76+
node));
77+
}
78+
79+
if (node instanceof io.cucumber.plugin.event.Node.Scenario) {
80+
return Optional.of(new PickleDescriptor(
81+
configuration,
82+
createUniqueId(parent, SCENARIO_SEGMENT_TYPE, node.getLocation()),
83+
namingStrategy.name(node),
84+
source.nodeSource(node),
85+
feature.getFeature().getPickleAt(node)));
86+
}
87+
88+
if (node instanceof io.cucumber.plugin.event.Node.ScenarioOutline) {
89+
return Optional.of(new ScenarioOutlineDescriptor(
90+
configuration,
91+
createUniqueId(parent, SCENARIO_SEGMENT_TYPE, node.getLocation()),
92+
namingStrategy.name(node),
93+
source.nodeSource(node),
94+
node));
95+
}
96+
97+
if (node instanceof io.cucumber.plugin.event.Node.Examples) {
98+
return Optional.of(new ExamplesDescriptor(
99+
configuration,
100+
createUniqueId(parent, EXAMPLES_SEGMENT_TYPE, node.getLocation()),
101+
namingStrategy.name(node),
102+
source.nodeSource(node),
103+
node));
104+
}
105+
106+
if (node instanceof io.cucumber.plugin.event.Node.Example) {
107+
Pickle pickle = feature.getFeature().getPickleAt(node);
108+
return Optional.of(new PickleDescriptor(
109+
configuration,
110+
createUniqueId(parent, EXAMPLE_SEGMENT_TYPE, node.getLocation()),
111+
namingStrategy.nameExample(node, pickle),
112+
source.nodeSource(node),
113+
pickle));
114+
}
115+
throw new IllegalStateException("Got a " + node.getClass() + " but didn't have a case to handle it");
116+
}
117+
118+
private static UniqueId createUniqueId(TestDescriptor parent, String segmentType, Location line) {
119+
return createUniqueId(parent, segmentType, String.valueOf(line.getLine()));
120+
}
121+
122+
private static UniqueId createUniqueId(TestDescriptor parent, String segmentType, String line) {
123+
return parent.getUniqueId().append(segmentType, line);
124+
}
125+
}
126+
127+
static boolean isFeatureSegment(UniqueId.Segment segment) {
128+
return FEATURE_SEGMENT_TYPE.equals(segment.getType());
129+
}
130+
30131
protected abstract URI getUri();
31132

32133
protected abstract Location getLocation();

0 commit comments

Comments
 (0)