11package 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 ;
49import io .cucumber .plugin .ConcurrentEventListener ;
510import 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
1014import java .io .File ;
1115import java .io .OutputStream ;
16+ import java .io .OutputStreamWriter ;
17+ import java .io .PrintWriter ;
1218import java .net .URI ;
1319import 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 */
2535public 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