1
1
package io .cucumber .core .plugin ;
2
2
3
- import io .cucumber .plugin .event .PickleStepTestStep ;
3
+ import io .cucumber .core .exception .CucumberException ;
4
+ import io .cucumber .plugin .EventListener ;
5
+ import io .cucumber .plugin .StrictAware ;
4
6
import io .cucumber .plugin .event .EventPublisher ;
7
+ import io .cucumber .plugin .event .PickleStepTestStep ;
5
8
import io .cucumber .plugin .event .Result ;
6
9
import io .cucumber .plugin .event .Status ;
7
10
import io .cucumber .plugin .event .TestCaseFinished ;
8
11
import io .cucumber .plugin .event .TestCaseStarted ;
9
12
import io .cucumber .plugin .event .TestRunFinished ;
13
+ import io .cucumber .plugin .event .TestRunStarted ;
10
14
import io .cucumber .plugin .event .TestSourceRead ;
11
15
import io .cucumber .plugin .event .TestStepFinished ;
12
- import io .cucumber .core .exception .CucumberException ;
13
-
14
- import io .cucumber .plugin .EventListener ;
15
- import io .cucumber .plugin .StrictAware ;
16
16
import org .w3c .dom .Document ;
17
17
import org .w3c .dom .Element ;
18
- import org .w3c .dom .NodeList ;
19
18
20
19
import javax .xml .XMLConstants ;
21
20
import javax .xml .parsers .DocumentBuilderFactory ;
26
25
import javax .xml .transform .TransformerFactory ;
27
26
import javax .xml .transform .dom .DOMSource ;
28
27
import javax .xml .transform .stream .StreamResult ;
29
-
30
- import static java .util .Locale .ROOT ;
31
- import static java .util .concurrent .TimeUnit .SECONDS ;
32
-
33
28
import java .io .Closeable ;
34
29
import java .io .IOException ;
35
30
import java .io .PrintWriter ;
38
33
import java .net .URL ;
39
34
import java .text .DecimalFormat ;
40
35
import java .text .NumberFormat ;
36
+ import java .time .Duration ;
37
+ import java .time .Instant ;
41
38
import java .util .ArrayList ;
42
39
import java .util .List ;
43
40
import java .util .Locale ;
44
41
42
+ import static java .util .Locale .ROOT ;
43
+ import static java .util .concurrent .TimeUnit .SECONDS ;
44
+
45
45
public final class JUnitFormatter implements EventListener , StrictAware {
46
46
47
- private static final long NANOS_PER_SECONDS = SECONDS .toNanos (1L );
47
+ private static final long MILLIS_PER_SECOND = SECONDS .toMillis (1L );
48
48
private final Writer writer ;
49
49
private final Document document ;
50
50
private final Element rootElement ;
@@ -55,6 +55,7 @@ public final class JUnitFormatter implements EventListener, StrictAware {
55
55
private String currentFeatureFile = null ;
56
56
private String previousTestCaseName ;
57
57
private int exampleNumber ;
58
+ private Instant started ;
58
59
59
60
@ SuppressWarnings ("WeakerAccess" ) // Used by plugin factory
60
61
public JUnitFormatter (URL writer ) throws IOException {
@@ -72,13 +73,24 @@ private static String getUniqueTestNameForScenarioExample(String testCaseName, i
72
73
return testCaseName + (testCaseName .contains (" " ) ? " " : "_" ) + exampleNumber ;
73
74
}
74
75
76
+ private static String calculateTotalDurationString (Duration result ) {
77
+ DecimalFormat numberFormat = (DecimalFormat ) NumberFormat .getNumberInstance (Locale .US );
78
+ double duration = (double ) result .toMillis () / MILLIS_PER_SECOND ;
79
+ return numberFormat .format (duration );
80
+ }
81
+
75
82
@ Override
76
83
public void setEventPublisher (EventPublisher publisher ) {
84
+ publisher .registerHandlerFor (TestRunStarted .class , this ::handleTestRunStarted );
77
85
publisher .registerHandlerFor (TestSourceRead .class , this ::handleTestSourceRead );
78
86
publisher .registerHandlerFor (TestCaseStarted .class , this ::handleTestCaseStarted );
79
87
publisher .registerHandlerFor (TestCaseFinished .class , this ::handleTestCaseFinished );
80
88
publisher .registerHandlerFor (TestStepFinished .class , this ::handleTestStepFinished );
81
- publisher .registerHandlerFor (TestRunFinished .class , event -> finishReport ());
89
+ publisher .registerHandlerFor (TestRunFinished .class , this ::handleTestRunFinished );
90
+ }
91
+
92
+ private void handleTestRunStarted (TestRunStarted event ) {
93
+ this .started = event .getInstant ();
82
94
}
83
95
84
96
@ Override
@@ -101,7 +113,7 @@ private void handleTestCaseStarted(TestCaseStarted event) {
101
113
testCase .writeElement (root );
102
114
rootElement .appendChild (root );
103
115
104
- increaseAttributeValue (rootElement , "tests" );
116
+ increaseTestCount (rootElement );
105
117
}
106
118
107
119
private void handleTestStepFinished (TestStepFinished event ) {
@@ -119,13 +131,15 @@ private void handleTestCaseFinished(TestCaseFinished event) {
119
131
}
120
132
}
121
133
122
- private void finishReport ( ) {
134
+ private void handleTestRunFinished ( TestRunFinished event ) {
123
135
try {
136
+ Instant finished = event .getInstant ();
124
137
// set up a transformer
125
138
rootElement .setAttribute ("name" , JUnitFormatter .class .getName ());
126
139
rootElement .setAttribute ("failures" , String .valueOf (rootElement .getElementsByTagName ("failure" ).getLength ()));
127
140
rootElement .setAttribute ("skipped" , String .valueOf (rootElement .getElementsByTagName ("skipped" ).getLength ()));
128
- rootElement .setAttribute ("time" , getTotalDuration (rootElement .getElementsByTagName ("testcase" )));
141
+ rootElement .setAttribute ("errors" , "0" );
142
+ rootElement .setAttribute ("time" , calculateTotalDurationString (Duration .between (started , finished )));
129
143
130
144
TransformerFactory factory = TransformerFactory .newInstance ();
131
145
factory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
@@ -148,28 +162,12 @@ private void closeQuietly(Closeable out) {
148
162
}
149
163
}
150
164
151
- private String getTotalDuration (NodeList testCaseNodes ) {
152
- double totalDurationSecondsForAllTimes = 0.0d ;
153
- for (int i = 0 ; i < testCaseNodes .getLength (); i ++) {
154
- try {
155
- double testCaseTime =
156
- Double .parseDouble (testCaseNodes .item (i ).getAttributes ().getNamedItem ("time" ).getNodeValue ());
157
- totalDurationSecondsForAllTimes += testCaseTime ;
158
- } catch (NumberFormatException | NullPointerException e ) {
159
- throw new CucumberException (e );
160
- }
161
- }
162
- DecimalFormat nfmt = (DecimalFormat ) NumberFormat .getNumberInstance (Locale .US );
163
- nfmt .applyPattern ("0.######" );
164
- return nfmt .format (totalDurationSecondsForAllTimes );
165
- }
166
-
167
- private void increaseAttributeValue (Element element , String attribute ) {
165
+ private void increaseTestCount (Element element ) {
168
166
int value = 0 ;
169
- if (element .hasAttribute (attribute )) {
170
- value = Integer .parseInt (element .getAttribute (attribute ));
167
+ if (element .hasAttribute ("tests" )) {
168
+ value = Integer .parseInt (element .getAttribute ("tests" ));
171
169
}
172
- element .setAttribute (attribute , String .valueOf (++value ));
170
+ element .setAttribute ("tests" , String .valueOf (++value ));
173
171
}
174
172
175
173
final class TestCase {
@@ -203,24 +201,25 @@ private String calculateElementName(io.cucumber.plugin.event.TestCase testCase)
203
201
}
204
202
205
203
void addTestCaseElement (Document doc , Element tc , Result result ) {
206
- tc .setAttribute ("time" , calculateTotalDurationString (result ));
204
+ tc .setAttribute ("time" , calculateTotalDurationString (result . getDuration () ));
207
205
208
206
StringBuilder sb = new StringBuilder ();
209
207
addStepAndResultListing (sb );
210
208
Element child ;
211
209
Status status = result .getStatus ();
212
210
if (status .is (Status .FAILED ) || status .is (Status .AMBIGUOUS )) {
213
211
addStackTrace (sb , result );
214
- child = createElementWithMessage (doc , sb , "failure" , printStackTrace ( result .getError ()));
212
+ child = createFailure (doc , sb , result . getError (). getMessage (), result .getError (). getClass ( ));
215
213
} else if (status .is (Status .PENDING ) || status .is (Status .UNDEFINED )) {
216
214
if (strict ) {
217
- child = createElementWithMessage (doc , sb , "failure" , "The scenario has pending or undefined step(s)" );
215
+ Throwable error = result .getError ();
216
+ child = createFailure (doc , sb , "The scenario has pending or undefined step(s)" , error == null ? Exception .class : error .getClass ());
218
217
} else {
219
218
child = createElement (doc , sb , "skipped" );
220
219
}
221
220
} else if (status .is (Status .SKIPPED ) && result .getError () != null ) {
222
221
addStackTrace (sb , result );
223
- child = createElementWithMessage (doc , sb , "skipped" , printStackTrace (result .getError ()));
222
+ child = createSkipped (doc , sb , printStackTrace (result .getError ()));
224
223
} else {
225
224
child = createElement (doc , sb , "system-out" );
226
225
}
@@ -229,20 +228,18 @@ void addTestCaseElement(Document doc, Element tc, Result result) {
229
228
}
230
229
231
230
void handleEmptyTestCase (Document doc , Element tc , Result result ) {
232
- tc .setAttribute ("time" , calculateTotalDurationString (result ));
231
+ tc .setAttribute ("time" , calculateTotalDurationString (result . getDuration () ));
233
232
234
- String resultType = strict ? "failure" : "skipped" ;
235
- Element child = createElementWithMessage (doc , new StringBuilder (), resultType , "The scenario has no steps" );
233
+ Element child ;
234
+ if (strict ) {
235
+ child = createFailure (doc , new StringBuilder (), "The scenario has no steps" , Exception .class );
236
+ } else {
237
+ child = createSkipped (doc , new StringBuilder (), "The scenario has no steps" );
238
+ }
236
239
237
240
tc .appendChild (child );
238
241
}
239
242
240
- private String calculateTotalDurationString (Result result ) {
241
- DecimalFormat numberFormat = (DecimalFormat ) NumberFormat .getNumberInstance (Locale .US );
242
- numberFormat .applyPattern ("0.######" );
243
- return numberFormat .format (((double ) result .getDuration ().toNanos () / NANOS_PER_SECONDS ));
244
- }
245
-
246
243
private void addStepAndResultListing (StringBuilder sb ) {
247
244
for (int i = 0 ; i < steps .size (); i ++) {
248
245
int length = sb .length ();
@@ -275,9 +272,16 @@ private String printStackTrace(Throwable error) {
275
272
return stringWriter .toString ();
276
273
}
277
274
278
- private Element createElementWithMessage (Document doc , StringBuilder sb , String elementType , String message ) {
279
- Element child = createElement (doc , sb , elementType );
275
+ private Element createSkipped (Document doc , StringBuilder sb , String message ) {
276
+ Element child = createElement (doc , sb , "skipped" );
277
+ child .setAttribute ("message" , message );
278
+ return child ;
279
+ }
280
+
281
+ private Element createFailure (Document doc , StringBuilder sb , String message , Class <? extends Throwable > type ) {
282
+ Element child = createElement (doc , sb , "failure" );
280
283
child .setAttribute ("message" , message );
284
+ child .setAttribute ("type" , type .getName ());
281
285
return child ;
282
286
}
283
287
@@ -290,6 +294,6 @@ private Element createElement(Document doc, StringBuilder sb, String elementType
290
294
child .appendChild (doc .createCDATASection (normalizedLineEndings ));
291
295
return child ;
292
296
}
293
-
294
297
}
298
+
295
299
}
0 commit comments