View Javadoc
1   package org.apache.maven.plugins.surefire.report;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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       * @noinspection StringBufferField
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         { // omit the defaultSuite if it's empty and there are alternatives
106             if ( defaultSuite.getNumberOfTests() == 0 )
107             {
108                 suites.remove( classesToSuitesIndex.get( defaultSuite.getFullClassName() ).intValue() );
109             }
110         }
111 
112         return suites;
113     }
114 
115     /**
116      * {@inheritDoc}
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                 //check if group attribute is existing
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                 // if the testcase declares its own classname, it may need to belong to its own suite
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      * {@inheritDoc}
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         // TODO extract real skipped reasons
273     }
274 
275     /**
276      * {@inheritDoc}
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 }