Skip to content

Commit 6b5d978

Browse files
authored
Use a message based RerunFormatter (#3075)
Replace the RerunFormatter with the message based implementation. This achieves several goals: * Move the internal code base away from the events from the plugin module. * Extract common parts of Cucumber into modules that can be shared. Partially implement: #3001
1 parent 3c7979e commit 6b5d978

File tree

2 files changed

+111
-43
lines changed

2 files changed

+111
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- [Core] Emit StepMatchArgumentsList for ambiguous steps ([#3066](https://github.com/cucumber/cucumber-jvm/pull/3066) M.P. Korstanje)
2020

2121
### Changed
22+
- [Core] Use a message based `RerunFormatter` ([#3075](https://github.com/cucumber/cucumber-jvm/pull/3075) M.P. Korstanje)
2223
- [Core] Update dependency io.cucumber:cucumber-json-formatter to v0.2.0
2324
- [Core] Update dependency io.cucumber:gherkin to v35.0.0
2425
- [Core] Update dependency io.cucumber:html-formatter to v21.15.0
Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,54 @@
11
package io.cucumber.core.plugin;
22

3-
import io.cucumber.core.feature.FeatureWithLines;
3+
import io.cucumber.messages.types.Envelope;
4+
import io.cucumber.messages.types.Location;
5+
import io.cucumber.messages.types.Pickle;
6+
import io.cucumber.messages.types.TestCaseStarted;
7+
import io.cucumber.messages.types.TestStepResult;
8+
import io.cucumber.messages.types.TestStepResultStatus;
49
import io.cucumber.plugin.ConcurrentEventListener;
510
import io.cucumber.plugin.event.EventPublisher;
6-
import io.cucumber.plugin.event.TestCase;
7-
import io.cucumber.plugin.event.TestCaseFinished;
8-
import io.cucumber.plugin.event.TestRunFinished;
11+
import io.cucumber.query.Query;
12+
import io.cucumber.query.Repository;
913

1014
import java.io.File;
1115
import java.io.OutputStream;
16+
import java.io.OutputStreamWriter;
17+
import java.io.PrintWriter;
1218
import java.net.URI;
1319
import java.net.URISyntaxException;
14-
import java.util.ArrayList;
15-
import java.util.Collection;
16-
import java.util.LinkedHashMap;
17-
import java.util.Map;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.Optional;
22+
import java.util.TreeMap;
23+
import java.util.TreeSet;
24+
import java.util.stream.Collector;
1825

19-
import static io.cucumber.core.feature.FeatureWithLines.create;
26+
import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS;
27+
import static java.util.Objects.requireNonNull;
28+
import static java.util.stream.Collectors.groupingBy;
29+
import static java.util.stream.Collectors.mapping;
30+
import static java.util.stream.Collectors.toCollection;
2031

2132
/**
22-
* Formatter for reporting all failed test cases and print their locations
23-
* Failed means: results that make the exit code non-zero.
33+
* Formatter for reporting all failed test cases and print their locations.
2434
*/
2535
public final class RerunFormatter implements ConcurrentEventListener {
2636

27-
private final UTF8PrintWriter out;
28-
private final Map<URI, Collection<Integer>> featureAndFailedLinesMapping = new LinkedHashMap<>();
37+
private final PrintWriter writer;
38+
private final Repository repository = Repository.builder()
39+
.feature(INCLUDE_GHERKIN_DOCUMENTS, true)
40+
.build();
41+
private final Query query = new Query(repository);
2942

3043
public RerunFormatter(OutputStream out) {
31-
this.out = new UTF8PrintWriter(out);
44+
this.writer = createPrintWriter(out);
3245
}
3346

34-
@Override
35-
public void setEventPublisher(EventPublisher publisher) {
36-
publisher.registerHandlerFor(TestCaseFinished.class, this::handleTestCaseFinished);
37-
publisher.registerHandlerFor(TestRunFinished.class, event -> finishReport());
38-
}
39-
40-
private void handleTestCaseFinished(TestCaseFinished event) {
41-
if (!event.getResult().getStatus().isOk()) {
42-
recordTestFailed(event.getTestCase());
43-
}
44-
}
45-
46-
private void finishReport() {
47-
for (Map.Entry<URI, Collection<Integer>> entry : featureAndFailedLinesMapping.entrySet()) {
48-
FeatureWithLines featureWithLines = create(relativize(entry.getKey()), entry.getValue());
49-
out.println(featureWithLines.toString());
50-
}
51-
52-
out.close();
53-
}
54-
55-
private void recordTestFailed(TestCase testCase) {
56-
URI uri = testCase.getUri();
57-
Collection<Integer> failedTestCaseLines = getFailedTestCaseLines(uri);
58-
failedTestCaseLines.add(testCase.getLocation().getLine());
59-
}
60-
61-
private Collection<Integer> getFailedTestCaseLines(URI uri) {
62-
return featureAndFailedLinesMapping.computeIfAbsent(uri, k -> new ArrayList<>());
47+
private static PrintWriter createPrintWriter(OutputStream out) {
48+
return new PrintWriter(
49+
new OutputStreamWriter(
50+
requireNonNull(out),
51+
StandardCharsets.UTF_8));
6352
}
6453

6554
static URI relativize(URI uri) {
@@ -79,4 +68,82 @@ static URI relativize(URI uri) {
7968
throw new IllegalArgumentException(e.getMessage(), e);
8069
}
8170
}
71+
72+
@Override
73+
public void setEventPublisher(EventPublisher publisher) {
74+
publisher.registerHandlerFor(Envelope.class, event -> {
75+
repository.update(event);
76+
event.getTestRunFinished().ifPresent(testRunFinished -> finishReport());
77+
});
78+
}
79+
80+
private static final class UriAndLine {
81+
private final String uri;
82+
private final Long line;
83+
84+
private UriAndLine(String uri, Long line) {
85+
this.uri = uri;
86+
this.line = line;
87+
}
88+
89+
public String getUri() {
90+
return uri;
91+
}
92+
93+
public Long getLine() {
94+
return line;
95+
}
96+
}
97+
98+
private void finishReport() {
99+
query.findAllTestCaseStarted().stream()
100+
.filter(this::isNotPassingOrSkipped)
101+
.map(query::findPickleBy)
102+
.filter(Optional::isPresent)
103+
.map(Optional::get)
104+
.map(this::createUriAndLine)
105+
.collect(groupByUriAndThenCollectLines())
106+
.forEach(this::printUriWithLines);
107+
writer.close();
108+
}
109+
110+
private void printUriWithLines(String uri, TreeSet<Long> lines) {
111+
writer.println(renderFeatureWithLines(uri, lines));
112+
}
113+
114+
private static Collector<UriAndLine, ?, TreeMap<String, TreeSet<Long>>> groupByUriAndThenCollectLines() {
115+
return groupingBy(
116+
UriAndLine::getUri,
117+
// Sort URIs
118+
TreeMap::new,
119+
mapping(
120+
UriAndLine::getLine,
121+
// Sort lines
122+
toCollection(TreeSet::new)));
123+
}
124+
125+
private static StringBuilder renderFeatureWithLines(String uri, TreeSet<Long> lines) {
126+
String path = relativize(URI.create(uri)).toString();
127+
StringBuilder builder = new StringBuilder(path);
128+
for (Long line : lines) {
129+
builder.append(':');
130+
builder.append(line);
131+
}
132+
return builder;
133+
}
134+
135+
private UriAndLine createUriAndLine(Pickle pickle) {
136+
String uri = pickle.getUri();
137+
Long line = query.findLocationOf(pickle).map(Location::getLine).orElse(null);
138+
return new UriAndLine(uri, line);
139+
}
140+
141+
private boolean isNotPassingOrSkipped(TestCaseStarted event) {
142+
return query.findMostSevereTestStepResultBy(event)
143+
.map(TestStepResult::getStatus)
144+
.filter(status -> status != TestStepResultStatus.PASSED)
145+
.filter(status -> status != TestStepResultStatus.SKIPPED)
146+
.isPresent();
147+
}
148+
82149
}

0 commit comments

Comments
 (0)