11package io .cucumber .core .plugin ;
22
3- import io .cucumber .core .feature .FeatureWithLines ;
43import io .cucumber .messages .types .Envelope ;
4+ import io .cucumber .messages .types .Location ;
55import io .cucumber .messages .types .Pickle ;
6- import io .cucumber .messages .types .TestCase ;
76import io .cucumber .messages .types .TestCaseFinished ;
8- import io .cucumber .messages .types .TestCaseStarted ;
97import io .cucumber .messages .types .TestRunFinished ;
10- import io .cucumber .messages .types .TestStepFinished ;
8+ import io .cucumber .messages .types .TestStepResult ;
119import io .cucumber .messages .types .TestStepResultStatus ;
1210import io .cucumber .plugin .ConcurrentEventListener ;
1311import io .cucumber .plugin .event .EventPublisher ;
12+ import io .cucumber .query .Query ;
1413
1514import java .io .File ;
1615import java .io .OutputStream ;
1918import java .net .URI ;
2019import java .net .URISyntaxException ;
2120import 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 ;
2923import java .util .Optional ;
30- import java .util .Set ;
24+ import java .util .TreeSet ;
3125
32- import static io .cucumber .core .feature .FeatureWithLines .create ;
3326import static io .cucumber .messages .types .TestStepResultStatus .PASSED ;
3427import static io .cucumber .messages .types .TestStepResultStatus .SKIPPED ;
35- import static java .util .Collections .emptyList ;
36- import static java .util .Comparator .comparing ;
3728import 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 */
4236public 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