1 package org.apache.maven.plugins.surefire.report;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.text.NumberFormat;
27 import java.text.ParseException;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.StringTokenizer;
34
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.parsers.SAXParser;
37 import javax.xml.parsers.SAXParserFactory;
38
39 import org.xml.sax.Attributes;
40 import org.xml.sax.InputSource;
41 import org.xml.sax.SAXException;
42 import org.xml.sax.helpers.DefaultHandler;
43
44
45
46
47 public class TestSuiteXmlParser
48 extends DefaultHandler
49 {
50 private ReportTestSuite defaultSuite;
51
52 private ReportTestSuite currentSuite;
53
54 private Map<String, Integer> classesToSuitesIndex;
55
56 private List<ReportTestSuite> suites;
57
58 private final NumberFormat numberFormat = NumberFormat.getInstance( Locale.ENGLISH );
59
60
61
62
63 private StringBuffer currentElement;
64
65 private ReportTestCase testCase;
66
67 private boolean valid;
68
69 public List<ReportTestSuite> parse( String xmlPath )
70 throws ParserConfigurationException, SAXException, IOException
71 {
72
73 File f = new File( xmlPath );
74
75 FileInputStream fileInputStream = new FileInputStream( f );
76
77 InputStreamReader inputStreamReader = new InputStreamReader( fileInputStream, "UTF-8" );
78
79 try
80 {
81 return parse( inputStreamReader );
82 }
83 finally
84 {
85 inputStreamReader.close();
86 fileInputStream.close();
87 }
88 }
89
90 public List<ReportTestSuite> parse( InputStreamReader stream )
91 throws ParserConfigurationException, SAXException, IOException
92 {
93 SAXParserFactory factory = SAXParserFactory.newInstance();
94
95 SAXParser saxParser = factory.newSAXParser();
96
97 valid = true;
98
99 classesToSuitesIndex = new HashMap<String, Integer>();
100 suites = new ArrayList<ReportTestSuite>();
101
102 saxParser.parse( new InputSource( stream ), this );
103
104 if ( currentSuite != defaultSuite )
105 {
106 if ( defaultSuite.getNumberOfTests() == 0 )
107 {
108 suites.remove( classesToSuitesIndex.get( defaultSuite.getFullClassName() ).intValue() );
109 }
110 }
111
112 return suites;
113 }
114
115
116
117
118 public void startElement( String uri, String localName, String qName, Attributes attributes )
119 throws SAXException
120 {
121 if ( !valid )
122 {
123 return;
124 }
125 try
126 {
127 if ( "testsuite".equals( qName ) )
128 {
129 defaultSuite = new ReportTestSuite();
130 currentSuite = defaultSuite;
131
132 try
133 {
134 Number time = numberFormat.parse( attributes.getValue( "time" ) );
135
136 defaultSuite.setTimeElapsed( time.floatValue() );
137 }
138 catch ( NullPointerException npe )
139 {
140 System.err.println( "WARNING: no time attribute found on testsuite element" );
141 }
142
143
144 if ( attributes.getValue( "group" ) != null && !"".equals( attributes.getValue( "group" ) ) )
145 {
146 String packageName = attributes.getValue( "group" );
147 String name = attributes.getValue( "name" );
148
149 defaultSuite.setFullClassName( packageName + "." + name );
150 }
151 else
152 {
153 String fullClassName = attributes.getValue( "name" );
154 defaultSuite.setFullClassName( fullClassName );
155 }
156
157 suites.add( defaultSuite );
158 classesToSuitesIndex.put( defaultSuite.getFullClassName(), suites.size() - 1 );
159 }
160 else if ( "testcase".equals( qName ) )
161 {
162 currentElement = new StringBuffer();
163
164 testCase = new ReportTestCase();
165
166 testCase.setName( attributes.getValue( "name" ) );
167
168 String fullClassName = attributes.getValue( "classname" );
169
170
171 if ( fullClassName != null )
172 {
173 Integer currentSuiteIndex = classesToSuitesIndex.get( fullClassName );
174 if ( currentSuiteIndex == null )
175 {
176 currentSuite = new ReportTestSuite();
177 currentSuite.setFullClassName( fullClassName );
178 suites.add( currentSuite );
179 classesToSuitesIndex.put( fullClassName, suites.size() - 1 );
180 }
181 else
182 {
183 currentSuite = suites.get( currentSuiteIndex );
184 }
185 }
186
187 testCase.setFullClassName( currentSuite.getFullClassName() );
188 testCase.setClassName( currentSuite.getName() );
189 testCase.setFullName( currentSuite.getFullClassName() + "." + testCase.getName() );
190
191 String timeAsString = attributes.getValue( "time" );
192
193 Number time = 0;
194
195 if ( timeAsString != null )
196 {
197 time = numberFormat.parse( timeAsString );
198 }
199
200 testCase.setTime( time.floatValue() );
201
202 if ( currentSuite != defaultSuite )
203 {
204 currentSuite.setTimeElapsed( time.floatValue() + currentSuite.getTimeElapsed() );
205 }
206 }
207 else if ( "failure".equals( qName ) )
208 {
209 testCase.addFailure( attributes.getValue( "message" ), attributes.getValue( "type" ) );
210 currentSuite.setNumberOfFailures( 1 + currentSuite.getNumberOfFailures() );
211 }
212 else if ( "error".equals( qName ) )
213 {
214 testCase.addFailure( attributes.getValue( "message" ), attributes.getValue( "type" ) );
215 currentSuite.setNumberOfErrors( 1 + currentSuite.getNumberOfErrors() );
216 }
217 else if ( "skipped".equals( qName ) )
218 {
219 final String message = attributes.getValue( "message" );
220 testCase.addFailure( message != null ? message : "skipped", "skipped" );
221 currentSuite.setNumberOfSkipped( 1 + currentSuite.getNumberOfSkipped() );
222 }
223 else if ( "flakyFailure".equals( qName ) || "flakyError".equals( qName ) )
224 {
225 currentSuite.setNumberOfFlakes( 1 + currentSuite.getNumberOfFlakes() );
226 }
227 else if ( "failsafe-summary".equals( qName ) )
228 {
229 valid = false;
230 }
231 }
232 catch ( ParseException e )
233 {
234 throw new SAXException( e.getMessage(), e );
235 }
236 }
237
238
239
240
241 public void endElement( String uri, String localName, String qName )
242 throws SAXException
243 {
244 if ( "testcase".equals( qName ) )
245 {
246 currentSuite.getTestCases().add( testCase );
247 }
248 else if ( "failure".equals( qName ) )
249 {
250 Map<String, Object> failure = testCase.getFailure();
251
252 failure.put( "detail", parseCause( currentElement.toString() ) );
253 }
254 else if ( "error".equals( qName ) )
255 {
256 Map<String, Object> error = testCase.getFailure();
257
258 error.put( "detail", parseCause( currentElement.toString() ) );
259 }
260 else if ( "time".equals( qName ) )
261 {
262 try
263 {
264 Number time = numberFormat.parse( currentElement.toString() );
265 defaultSuite.setTimeElapsed( time.floatValue() );
266 }
267 catch ( ParseException e )
268 {
269 throw new SAXException( e.getMessage(), e );
270 }
271 }
272
273 }
274
275
276
277
278 public void characters( char[] ch, int start, int length )
279 throws SAXException
280 {
281 if ( !valid )
282 {
283 return;
284 }
285 String s = new String( ch, start, length );
286
287 if ( !"".equals( s.trim() ) )
288 {
289 currentElement.append( s );
290 }
291 }
292
293 private List<String> parseCause( String detail )
294 {
295 String fullName = testCase.getFullName();
296 String name = fullName.substring( fullName.lastIndexOf( "." ) + 1 );
297 return parseCause( detail, name );
298 }
299
300 private List<String> parseCause( String detail, String compareTo )
301 {
302 StringTokenizer stringTokenizer = new StringTokenizer( detail, "\n" );
303 List<String> parsedDetail = new ArrayList<String>( stringTokenizer.countTokens() );
304
305 while ( stringTokenizer.hasMoreTokens() )
306 {
307 String lineString = stringTokenizer.nextToken().trim();
308 parsedDetail.add( lineString );
309 if ( lineString.contains( compareTo ) )
310 {
311 break;
312 }
313 }
314
315 return parsedDetail;
316 }
317
318 public boolean isValid()
319 {
320 return valid;
321 }
322 }