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                 currentSuite = defaultSuite = new ReportTestSuite();
130 
131                 try
132                 {
133                     Number time = numberFormat.parse( attributes.getValue( "time" ) );
134 
135                     defaultSuite.setTimeElapsed( time.floatValue() );
136                 }
137                 catch ( NullPointerException npe )
138                 {
139                     System.err.println( "WARNING: no time attribute found on testsuite element" );
140                 }
141 
142                 //check if group attribute is existing
143                 if ( attributes.getValue( "group" ) != null && !"".equals( attributes.getValue( "group" ) ) )
144                 {
145                     String packageName = attributes.getValue( "group" );
146                     String name = attributes.getValue( "name" );
147 
148                     defaultSuite.setFullClassName( packageName + "." + name );
149                 }
150                 else
151                 {
152                     String fullClassName = attributes.getValue( "name" );
153                     defaultSuite.setFullClassName( fullClassName );
154                 }
155 
156                 suites.add( defaultSuite );
157                 classesToSuitesIndex.put( defaultSuite.getFullClassName(), suites.size() - 1 );
158             }
159             else if ( "testcase".equals( qName ) )
160             {
161                 currentElement = new StringBuffer();
162 
163                 testCase = new ReportTestCase();
164 
165                 testCase.setName( attributes.getValue( "name" ) );
166 
167                 String fullClassName = attributes.getValue( "classname" );
168 
169                 // if the testcase declares its own classname, it may need to belong to its own suite
170                 if ( fullClassName != null )
171                 {
172                     Integer currentSuiteIndex = classesToSuitesIndex.get( fullClassName );
173                     if ( currentSuiteIndex == null )
174                     {
175                         currentSuite = new ReportTestSuite();
176                         currentSuite.setFullClassName( fullClassName );
177                         suites.add( currentSuite );
178                         classesToSuitesIndex.put( fullClassName, suites.size() - 1 );
179                     } else
180                     {
181                         currentSuite = suites.get( currentSuiteIndex );
182                     }
183                 }
184 
185                 testCase.setFullClassName( currentSuite.getFullClassName() );
186                 testCase.setClassName( currentSuite.getName() );
187                 testCase.setFullName( currentSuite.getFullClassName() + "." + testCase.getName() );
188 
189                 String timeAsString = attributes.getValue( "time" );
190 
191                 Number time = 0;
192 
193                 if ( timeAsString != null )
194                 {
195                     time = numberFormat.parse( timeAsString );
196                 }
197 
198                 testCase.setTime( time.floatValue() );
199 
200                 if ( currentSuite != defaultSuite )
201                 {
202                     currentSuite.setTimeElapsed( time.floatValue() + currentSuite.getTimeElapsed() );
203                 }
204             }
205             else if ( "failure".equals( qName ) )
206             {
207                 testCase.addFailure( attributes.getValue( "message" ), attributes.getValue( "type" ) );
208                 currentSuite.setNumberOfFailures( 1 + currentSuite.getNumberOfFailures() );
209             }
210             else if ( "error".equals( qName ) )
211             {
212                 testCase.addFailure( attributes.getValue( "message" ), attributes.getValue( "type" ) );
213                 currentSuite.setNumberOfErrors( 1 + currentSuite.getNumberOfErrors() );
214             }
215             else if ( "skipped".equals( qName ) )
216             {
217                 final String message = attributes.getValue( "message" );
218                 testCase.addFailure( message != null ? message : "skipped", "skipped" );
219                 currentSuite.setNumberOfSkipped( 1 + currentSuite.getNumberOfSkipped() );
220             }
221             else if ( "failsafe-summary".equals( qName ) )
222             {
223                 valid = false;
224             }
225         }
226         catch ( ParseException e )
227         {
228             throw new SAXException( e.getMessage(), e );
229         }
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     public void endElement( String uri, String localName, String qName )
236         throws SAXException
237     {
238         if ( "testcase".equals( qName ) )
239         {
240             currentSuite.getTestCases().add( testCase );
241         }
242         else if ( "failure".equals( qName ) )
243         {
244             Map<String, Object> failure = testCase.getFailure();
245 
246             failure.put( "detail", parseCause( currentElement.toString() ) );
247         }
248         else if ( "error".equals( qName ) )
249         {
250             Map<String, Object> error = testCase.getFailure();
251 
252             error.put( "detail", parseCause( currentElement.toString() ) );
253         }
254         else if ( "time".equals( qName ) )
255         {
256             try
257             {
258                 Number time = numberFormat.parse( currentElement.toString() );
259                 defaultSuite.setTimeElapsed( time.floatValue() );
260             }
261             catch ( ParseException e )
262             {
263                 throw new SAXException( e.getMessage(), e );
264             }
265         }
266         // TODO extract real skipped reasons
267     }
268 
269     /**
270      * {@inheritDoc}
271      */
272     public void characters( char[] ch, int start, int length )
273         throws SAXException
274     {
275         if ( !valid )
276         {
277             return;
278         }
279         String s = new String( ch, start, length );
280 
281         if ( !"".equals( s.trim() ) )
282         {
283             currentElement.append( s );
284         }
285     }
286 
287     private List<String> parseCause( String detail )
288     {
289         String fullName = testCase.getFullName();
290         String name = fullName.substring( fullName.lastIndexOf( "." ) + 1 );
291         return parseCause( detail, name );
292     }
293 
294     private List<String> parseCause( String detail, String compareTo )
295     {
296         StringTokenizer stringTokenizer = new StringTokenizer( detail, "\n" );
297         List<String> parsedDetail = new ArrayList<String>( stringTokenizer.countTokens() );
298 
299         while ( stringTokenizer.hasMoreTokens() )
300         {
301             String lineString = stringTokenizer.nextToken().trim();
302             parsedDetail.add( lineString );
303             if ( lineString.contains( compareTo ) )
304             {
305                 break;
306             }
307         }
308 
309         return parsedDetail;
310     }
311 
312     public boolean isValid()
313     {
314         return valid;
315     }
316 }