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.InputStream;
26  import java.io.InputStreamReader;
27  import java.text.NumberFormat;
28  import java.text.ParseException;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.StringTokenizer;
36  
37  import javax.xml.parsers.ParserConfigurationException;
38  import javax.xml.parsers.SAXParser;
39  import javax.xml.parsers.SAXParserFactory;
40  
41  import org.xml.sax.Attributes;
42  import org.xml.sax.InputSource;
43  import org.xml.sax.SAXException;
44  import org.xml.sax.helpers.DefaultHandler;
45  
46  /**
47   *
48   */
49  public class TestSuiteXmlParser
50      extends DefaultHandler
51  {
52      private ReportTestSuite defaultSuite;
53  
54      private ReportTestSuite currentSuite;
55  
56      private Map<String, ReportTestSuite> classesToSuites;
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 Collection<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 Collection<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          classesToSuites = new HashMap<String, ReportTestSuite>();
100 
101         saxParser.parse( new InputSource( stream ), this );
102 
103         if ( currentSuite != defaultSuite )
104         { // omit the defaultSuite if it's empty and there are alternatives
105             if ( defaultSuite.getNumberOfTests() == 0 )
106             {
107                 classesToSuites.remove( defaultSuite.getFullClassName() );
108             }
109         }
110 
111         return classesToSuites.values();
112     }
113 
114     /**
115      * {@inheritDoc}
116      */
117     public void startElement( String uri, String localName, String qName, Attributes attributes )
118         throws SAXException
119     {
120         if ( !valid )
121         {
122             return;
123         }
124         try
125         {
126             if ( "testsuite".equals( qName ) )
127             {
128                 currentSuite = defaultSuite = new ReportTestSuite();
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 }