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