Skip to content

Commit 1e9b7ab

Browse files
committed
Implement
1 parent da57dc7 commit 1e9b7ab

File tree

2 files changed

+57
-118
lines changed

2 files changed

+57
-118
lines changed

cucumber-bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<junit-xml-formatter.version>0.8.1</junit-xml-formatter.version>
2121
<messages.version>28.1.0</messages.version>
2222
<pretty-formatter.version>2.1.0</pretty-formatter.version>
23-
<query.version>13.6.0</query.version>
23+
<query.version>13.6.1-SNAPSHOT</query.version>
2424
<tag-expressions.version>6.1.2</tag-expressions.version>
2525
<testng-xml-formatter.version>0.5.0</testng-xml-formatter.version>
2626
</properties>
Lines changed: 56 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package io.cucumber.core.plugin;
22

3-
import io.cucumber.core.feature.FeatureWithLines;
43
import io.cucumber.messages.types.Envelope;
4+
import io.cucumber.messages.types.Location;
55
import io.cucumber.messages.types.Pickle;
6-
import io.cucumber.messages.types.TestCase;
76
import io.cucumber.messages.types.TestCaseFinished;
8-
import io.cucumber.messages.types.TestCaseStarted;
97
import io.cucumber.messages.types.TestRunFinished;
10-
import io.cucumber.messages.types.TestStepFinished;
8+
import io.cucumber.messages.types.TestStepResult;
119
import io.cucumber.messages.types.TestStepResultStatus;
1210
import io.cucumber.plugin.ConcurrentEventListener;
1311
import io.cucumber.plugin.event.EventPublisher;
12+
import io.cucumber.query.Query;
1413

1514
import java.io.File;
1615
import java.io.OutputStream;
@@ -19,30 +18,24 @@
1918
import java.net.URI;
2019
import java.net.URISyntaxException;
2120
import java.nio.charset.StandardCharsets;
22-
import java.util.ArrayList;
23-
import java.util.Collections;
24-
import java.util.Comparator;
25-
import java.util.HashMap;
26-
import java.util.HashSet;
27-
import java.util.List;
28-
import java.util.Map;
21+
import java.util.AbstractMap.SimpleEntry;
22+
import java.util.Map.Entry;
2923
import java.util.Optional;
30-
import java.util.Set;
24+
import java.util.TreeSet;
3125

32-
import static io.cucumber.core.feature.FeatureWithLines.create;
3326
import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
3427
import static io.cucumber.messages.types.TestStepResultStatus.SKIPPED;
35-
import static java.util.Collections.emptyList;
36-
import static java.util.Comparator.comparing;
3728
import static java.util.Objects.requireNonNull;
29+
import static java.util.stream.Collectors.groupingBy;
30+
import static java.util.stream.Collectors.mapping;
31+
import static java.util.stream.Collectors.toCollection;
3832

3933
/**
4034
* Formatter for reporting all failed test cases and print their locations.
4135
*/
4236
public final class RerunFormatter implements ConcurrentEventListener {
4337

4438
private final Query query = new Query();
45-
private final Map<String, Set<Integer>> featureAndFailedLinesMapping = new HashMap<>();
4639
private final PrintWriter writer;
4740

4841
public RerunFormatter(OutputStream out) {
@@ -51,127 +44,73 @@ public RerunFormatter(OutputStream out) {
5144

5245
private static PrintWriter createPrintWriter(OutputStream out) {
5346
return new PrintWriter(
54-
new OutputStreamWriter(
55-
requireNonNull(out),
56-
StandardCharsets.UTF_8));
57-
}
58-
59-
static URI relativize(URI uri) {
60-
if (!"file".equals(uri.getScheme())) {
61-
return uri;
62-
}
63-
if (!uri.isAbsolute()) {
64-
return uri;
65-
}
66-
67-
try {
68-
URI root = new File("").toURI();
69-
URI relative = root.relativize(uri);
70-
// Scheme is lost by relativize
71-
return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment());
72-
} catch (URISyntaxException e) {
73-
throw new IllegalArgumentException(e.getMessage(), e);
74-
}
47+
new OutputStreamWriter(
48+
requireNonNull(out),
49+
StandardCharsets.UTF_8));
7550
}
7651

7752
@Override
7853
public void setEventPublisher(EventPublisher publisher) {
7954
publisher.registerHandlerFor(Envelope.class, event -> {
8055
query.update(event);
81-
event.getTestCaseFinished().ifPresent(this::handleTestCaseFinished);
8256
event.getTestRunFinished().ifPresent(this::handleTestRunFinished);
8357
});
8458
}
8559

86-
87-
private void handleTestCaseFinished(TestCaseFinished event) {
88-
TestStepResultStatus status = query.findMostSevereTestStepResultBy(event)
89-
// By definition
90-
.orElse(PASSED);
91-
if (status == PASSED || status == SKIPPED) {
92-
return;
93-
}
94-
query.findPickleBy(event).ifPresent(pickle -> {
95-
// Adds the entire feature for rerunning
96-
Set<Integer> lines = featureAndFailedLinesMapping.computeIfAbsent(pickle.getUri(), s -> new HashSet<>());
97-
pickle.getLocation().ifPresent(location -> {
98-
// Adds the specific scenarios
99-
// TODO: Messages are silly
100-
lines.add((int) (long) location.getLine());
101-
});
102-
});
103-
}
104-
10560
private void handleTestRunFinished(TestRunFinished testRunFinished) {
106-
for (Map.Entry<String, Set<Integer>> entry : featureAndFailedLinesMapping.entrySet()) {
107-
String key = entry.getKey();
108-
// TODO: Should these be relative?
109-
FeatureWithLines featureWithLines = create(relativize(URI.create(key)), entry.getValue());
110-
writer.println(featureWithLines);
111-
}
112-
61+
query.findAllTestCaseFinished()
62+
.stream()
63+
.filter(this::shouldBeRerun)
64+
.map(query::findPickleBy)
65+
.filter(Optional::isPresent)
66+
.map(Optional::get)
67+
.map(this::createPickleWithLineEntry)
68+
// TreeSet makes the lines sorted and unique.
69+
.collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toCollection(TreeSet::new))))
70+
.forEach((uri, lines) -> writer.println(renderFeatureWithLines(uri, lines)));
11371
writer.close();
11472
}
11573

116-
/**
117-
* Miniaturized version of Cucumber Query.
118-
* <p>
119-
* The rerun plugin only needs a few things.
120-
*/
121-
private static class Query {
122-
123-
private final Map<String, TestCase> testCaseById = new HashMap<>();
124-
private final Map<String, List<TestStepResultStatus>> testStepsResultStatusByTestCaseStartedId = new HashMap<>();
125-
private final Map<String, TestCaseStarted> testCaseStartedById = new HashMap<>();
126-
private final Map<String, Pickle> pickleById = new HashMap<>();
127-
128-
void update(Envelope envelope) {
129-
envelope.getPickle().ifPresent(this::updatePickle);
130-
envelope.getTestCase().ifPresent(this::updateTestCase);
131-
envelope.getTestCaseStarted().ifPresent(this::updateTestCaseStarted);
132-
envelope.getTestStepFinished().ifPresent(this::updateTestStepFinished);
133-
}
134-
135-
private void updatePickle(Pickle event) {
136-
pickleById.put(event.getId(), event);
74+
private String renderFeatureWithLines(String feature, TreeSet<Long> lines) {
75+
URI uri = relativize(URI.create(feature));
76+
StringBuilder builder = new StringBuilder(uri.toString());
77+
for (Long line : lines) {
78+
builder.append(':');
79+
builder.append(line);
13780
}
81+
return builder.toString();
82+
}
13883

139-
private void updateTestCase(TestCase event) {
140-
testCaseById.put(event.getId(), event);
141-
}
84+
private Entry<String, Long> createPickleWithLineEntry(Pickle pickle) {
85+
String uri = pickle.getUri();
86+
Long line = query.findLocationOf(pickle)
87+
.map(Location::getLine)
88+
.orElse(null);
89+
return new SimpleEntry<>(uri, line);
90+
}
14291

143-
private void updateTestCaseStarted(TestCaseStarted testCaseStarted) {
144-
testCaseStartedById.put(testCaseStarted.getId(), testCaseStarted);
145-
}
92+
private boolean shouldBeRerun(TestCaseFinished testCaseFinished) {
93+
TestStepResultStatus status = query.findMostSevereTestStepResultBy(testCaseFinished)
94+
.map(TestStepResult::getStatus)
95+
// By definition
96+
.orElse(PASSED);
97+
return !(status == PASSED || status == SKIPPED);
98+
}
14699

147-
private void updateTestStepFinished(TestStepFinished event) {
148-
String testCaseStartedId = event.getTestCaseStartedId();
149-
testStepsResultStatusByTestCaseStartedId.computeIfAbsent(testCaseStartedId, s -> new ArrayList<>())
150-
.add(event.getTestStepResult().getStatus());
100+
static URI relativize(URI uri) {
101+
if (!"file".equals(uri.getScheme())) {
102+
return uri;
151103
}
152-
153-
public Optional<TestStepResultStatus> findMostSevereTestStepResultBy(TestCaseFinished testCaseFinished) {
154-
List<TestStepResultStatus> statuses = testStepsResultStatusByTestCaseStartedId
155-
.getOrDefault(testCaseFinished.getTestCaseStartedId(), emptyList());
156-
if (statuses.isEmpty()) {
157-
return Optional.empty();
158-
}
159-
return Optional.of(Collections.max(statuses, comparing(Enum::ordinal)));
104+
if (!uri.isAbsolute()) {
105+
return uri;
160106
}
161-
162-
public Optional<Pickle> findPickleBy(TestCaseFinished testCaseFinished) {
163-
String testCaseStartedId = testCaseFinished.getTestCaseStartedId();
164-
TestCaseStarted testCaseStarted = testCaseStartedById.get(testCaseStartedId);
165-
if (testCaseStarted == null) {
166-
return Optional.empty();
167-
}
168-
TestCase testCase = testCaseById.get(testCaseStarted.getTestCaseId());
169-
if (testCase == null) {
170-
return Optional.empty();
171-
}
172-
return Optional.ofNullable(pickleById.get(testCase.getPickleId()));
107+
try {
108+
URI root = new File("").toURI();
109+
URI relative = root.relativize(uri);
110+
// Scheme is lost by relativize
111+
return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment());
112+
} catch (URISyntaxException e) {
113+
throw new IllegalArgumentException(e.getMessage(), e);
173114
}
174-
175115
}
176-
177116
}

0 commit comments

Comments
 (0)