Coverage Report - org.apache.maven.plugin.surefire.report.StatelessXmlReporter
 
Classes in this File Line Coverage Branch Coverage Complexity
StatelessXmlReporter
83%
89/106
50%
31/62
3,692
 
 1  
 package org.apache.maven.plugin.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.FileOutputStream;
 24  
 import java.io.IOException;
 25  
 import java.io.OutputStreamWriter;
 26  
 import java.util.Enumeration;
 27  
 import java.util.Properties;
 28  
 import java.util.StringTokenizer;
 29  
 import org.apache.maven.shared.utils.io.IOUtil;
 30  
 import org.apache.maven.shared.utils.xml.XMLWriter;
 31  
 import org.apache.maven.surefire.report.ReportEntry;
 32  
 import org.apache.maven.surefire.report.ReporterException;
 33  
 import org.apache.maven.surefire.report.SafeThrowable;
 34  
 
 35  
 /**
 36  
  * XML format reporter writing to <code>TEST-<i>reportName</i>[-<i>suffix</i>].xml</code> file like written and read
 37  
  * by Ant's <a href="http://ant.apache.org/manual/Tasks/junit.html"><code>&lt;junit&gt;</code></a> and
 38  
  * <a href="http://ant.apache.org/manual/Tasks/junitreport.html"><code>&lt;junitreport&gt;</code></a> tasks,
 39  
  * then supported by many tools like CI servers.
 40  
  * <p/>
 41  
  * <pre>&lt;?xml version="1.0" encoding="UTF-8"?>
 42  
  * &lt;testsuite name="<i>suite name</i>" [group="<i>group</i>"] tests="<i>0</i>" failures="<i>0</i>" errors="<i>0</i>" skipped="<i>0</i>" time="<i>0,###.###</i>">
 43  
  *  &lt;properties>
 44  
  *    &lt;property name="<i>name</i>" value="<i>value</i>"/>
 45  
  *    [...]
 46  
  *  &lt;/properties>
 47  
  *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]"/>
 48  
  *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]">
 49  
  *    &lt;<b>error</b> message="<i>message</i>" type="<i>exception class name</i>"><i>stacktrace</i>&lt;/error>
 50  
  *    &lt;system-out><i>system out content (present only if not empty)</i>&lt;/system-out>
 51  
  *    &lt;system-err><i>system err content (present only if not empty)</i>&lt;/system-err>
 52  
  *  &lt;/testcase>
 53  
  *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]">
 54  
  *    &lt;<b>failure</b> message="<i>message</i>" type="<i>exception class name</i>"><i>stacktrace</i>&lt;/failure>
 55  
  *    &lt;system-out><i>system out content (present only if not empty)</i>&lt;/system-out>
 56  
  *    &lt;system-err><i>system err content (present only if not empty)</i>&lt;/system-err>
 57  
  *  &lt;/testcase>
 58  
  *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]">
 59  
  *    &lt;<b>skipped</b>/>
 60  
  *  &lt;/testcase>
 61  
  *  [...]</pre>
 62  
  *
 63  
  * @author Kristian Rosenvold
 64  
  * @see <a href="http://wiki.apache.org/ant/Proposals/EnhancedTestReports">Ant's format enhancement proposal</a>
 65  
  *      (not yet implemented by Ant 1.8.2)
 66  
  */
 67  
 public class StatelessXmlReporter
 68  
 {
 69  
 
 70  
     private final File reportsDirectory;
 71  
 
 72  
     private final String reportNameSuffix;
 73  
 
 74  
     private final boolean trimStackTrace;
 75  
 
 76  3
     private final String encoding = "UTF-8";
 77  
 
 78  
     public StatelessXmlReporter( File reportsDirectory, String reportNameSuffix, boolean trimStackTrace )
 79  3
     {
 80  3
         this.reportsDirectory = reportsDirectory;
 81  3
         this.reportNameSuffix = reportNameSuffix;
 82  3
         this.trimStackTrace = trimStackTrace;
 83  3
     }
 84  
 
 85  
     public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStats testSetStats )
 86  
         throws ReporterException
 87  
     {
 88  
 
 89  2
         OutputStreamWriter fw = getWriter( testSetReportEntry );
 90  
         try
 91  
         {
 92  
 
 93  2
             org.apache.maven.shared.utils.xml.XMLWriter ppw =
 94  
                 new org.apache.maven.shared.utils.xml.PrettyPrintXMLWriter( fw );
 95  2
             ppw.setEncoding( encoding );
 96  
 
 97  2
             createTestSuiteElement( ppw, testSetReportEntry, testSetStats, reportNameSuffix );
 98  
 
 99  2
             showProperties( ppw );
 100  
 
 101  2
             for ( WrappedReportEntry entry : testSetStats.getReportEntries() )
 102  
             {
 103  3
                 if ( ReportEntryType.success.equals( entry.getReportEntryType() ) )
 104  
                 {
 105  2
                     startTestElement( ppw, entry, reportNameSuffix );
 106  2
                     ppw.endElement();
 107  
                 }
 108  
                 else
 109  
                 {
 110  1
                     getTestProblems( ppw, entry, trimStackTrace, reportNameSuffix );
 111  
                 }
 112  
 
 113  
             }
 114  2
             ppw.endElement(); // TestSuite
 115  
 
 116  
         }
 117  
         finally
 118  
         {
 119  2
             IOUtil.close( fw );
 120  2
         }
 121  2
     }
 122  
 
 123  
     private OutputStreamWriter getWriter( WrappedReportEntry testSetReportEntry )
 124  
     {
 125  2
         File reportFile = getReportFile( testSetReportEntry, reportsDirectory, reportNameSuffix );
 126  
 
 127  2
         File reportDir = reportFile.getParentFile();
 128  
 
 129  
         //noinspection ResultOfMethodCallIgnored
 130  2
         reportDir.mkdirs();
 131  
 
 132  
         try
 133  
         {
 134  
 
 135  2
             FileOutputStream fos = new FileOutputStream( reportFile );
 136  
 
 137  2
             return new OutputStreamWriter( fos, encoding );
 138  
         }
 139  0
         catch ( IOException e )
 140  
         {
 141  0
             throw new ReporterException( "When writing report", e );
 142  
         }
 143  
     }
 144  
 
 145  
     private File getReportFile( ReportEntry report, File reportsDirectory, String reportNameSuffix )
 146  
     {
 147  
         File reportFile;
 148  
 
 149  2
         if ( reportNameSuffix != null && reportNameSuffix.length() > 0 )
 150  
         {
 151  0
             reportFile = new File( reportsDirectory, "TEST-" + report.getName() + "-" + reportNameSuffix + ".xml" );
 152  
         }
 153  
         else
 154  
         {
 155  2
             reportFile = new File( reportsDirectory, "TEST-" + report.getName() + ".xml" );
 156  
         }
 157  
 
 158  2
         return reportFile;
 159  
     }
 160  
 
 161  
     private static void startTestElement( XMLWriter ppw, WrappedReportEntry report, String reportNameSuffix )
 162  
     {
 163  3
         ppw.startElement( "testcase" );
 164  3
         ppw.addAttribute( "name", report.getReportName() );
 165  3
         if ( report.getGroup() != null )
 166  
         {
 167  0
             ppw.addAttribute( "group", report.getGroup() );
 168  
         }
 169  3
         if ( report.getSourceName() != null )
 170  
         {
 171  3
             if ( reportNameSuffix != null && reportNameSuffix.length() > 0 )
 172  
             {
 173  0
                 ppw.addAttribute( "classname", report.getSourceName() + "(" + reportNameSuffix + ")" );
 174  
             }
 175  
             else
 176  
             {
 177  3
                 ppw.addAttribute( "classname", report.getSourceName() );
 178  
             }
 179  
         }
 180  3
         ppw.addAttribute( "time", report.elapsedTimeAsString() );
 181  3
     }
 182  
 
 183  
     private static void createTestSuiteElement( XMLWriter ppw, WrappedReportEntry report, TestSetStats testSetStats,
 184  
                                                 String reportNameSuffix1 )
 185  
     {
 186  2
         ppw.startElement( "testsuite" );
 187  
 
 188  2
         ppw.addAttribute( "name", report.getReportName( reportNameSuffix1 ) );
 189  
 
 190  2
         if ( report.getGroup() != null )
 191  
         {
 192  0
             ppw.addAttribute( "group", report.getGroup() );
 193  
         }
 194  
 
 195  2
         ppw.addAttribute( "time", testSetStats.getElapsedForTestSet() );
 196  
 
 197  2
         ppw.addAttribute( "tests", String.valueOf( testSetStats.getCompletedCount() ) );
 198  
 
 199  2
         ppw.addAttribute( "errors", String.valueOf( testSetStats.getErrors() ) );
 200  
 
 201  2
         ppw.addAttribute( "skipped", String.valueOf( testSetStats.getSkipped() ) );
 202  
 
 203  2
         ppw.addAttribute( "failures", String.valueOf( testSetStats.getFailures() ) );
 204  
 
 205  2
     }
 206  
 
 207  
 
 208  
     private void getTestProblems( XMLWriter ppw, WrappedReportEntry report, boolean trimStackTrace,
 209  
                                   String reportNameSuffix )
 210  
     {
 211  
 
 212  1
         startTestElement( ppw, report, reportNameSuffix );
 213  
 
 214  1
         ppw.startElement( report.getReportEntryType().name() );
 215  
 
 216  1
         String stackTrace = report.getStackTrace( trimStackTrace );
 217  
 
 218  1
         if ( report.getMessage() != null && report.getMessage().length() > 0 )
 219  
         {
 220  1
             ppw.addAttribute( "message", extraEscape( report.getMessage(), true ) );
 221  
         }
 222  
 
 223  1
         if ( report.getStackTraceWriter() != null )
 224  
         {
 225  
             //noinspection ThrowableResultOfMethodCallIgnored
 226  1
             SafeThrowable t = report.getStackTraceWriter().getThrowable();
 227  1
             if ( t != null )
 228  
             {
 229  1
                 if ( t.getMessage() != null )
 230  
                 {
 231  1
                     ppw.addAttribute( "type", ( stackTrace.contains( ":" )
 232  
                         ? stackTrace.substring( 0, stackTrace.indexOf( ":" ) )
 233  
                         : stackTrace ) );
 234  
                 }
 235  
                 else
 236  
                 {
 237  0
                     ppw.addAttribute( "type", new StringTokenizer( stackTrace ).nextToken() );
 238  
                 }
 239  
             }
 240  
         }
 241  
 
 242  1
         if ( stackTrace != null )
 243  
         {
 244  1
             ppw.writeText( extraEscape( stackTrace, false ) );
 245  
         }
 246  
 
 247  1
         ppw.endElement(); // entry type
 248  
 
 249  1
         addOutputStreamElement( ppw, report.getStdout(), "system-out" );
 250  
 
 251  1
         addOutputStreamElement( ppw, report.getStdErr(), "system-err" );
 252  
 
 253  1
         ppw.endElement(); // test element
 254  1
     }
 255  
 
 256  
     private void addOutputStreamElement( XMLWriter xmlWriter, String stdOut, String name )
 257  
     {
 258  2
         if ( stdOut != null && stdOut.trim().length() > 0 )
 259  
         {
 260  2
             xmlWriter.startElement( name );
 261  2
             xmlWriter.writeText( extraEscape( stdOut, false ) );
 262  2
             xmlWriter.endElement();
 263  
         }
 264  2
     }
 265  
 
 266  
     /**
 267  
      * Adds system properties to the XML report.
 268  
      * <p/>
 269  
      *
 270  
      * @param xmlWriter The test suite to report to
 271  
      */
 272  
     private void showProperties( XMLWriter xmlWriter )
 273  
     {
 274  2
         xmlWriter.startElement( "properties" );
 275  
 
 276  2
         Properties systemProperties = System.getProperties();
 277  
 
 278  2
         if ( systemProperties != null )
 279  
         {
 280  2
             Enumeration<?> propertyKeys = systemProperties.propertyNames();
 281  
 
 282  118
             while ( propertyKeys.hasMoreElements() )
 283  
             {
 284  116
                 String key = (String) propertyKeys.nextElement();
 285  
 
 286  116
                 String value = systemProperties.getProperty( key );
 287  
 
 288  116
                 if ( value == null )
 289  
                 {
 290  0
                     value = "null";
 291  
                 }
 292  
 
 293  116
                 xmlWriter.startElement( "property" );
 294  
 
 295  116
                 xmlWriter.addAttribute( "name", key );
 296  
 
 297  116
                 xmlWriter.addAttribute( "value", extraEscape( value, true ));
 298  
 
 299  116
                 xmlWriter.endElement();
 300  
 
 301  116
             }
 302  
         }
 303  2
         xmlWriter.endElement();
 304  2
     }
 305  
 
 306  
     /**
 307  
      * Handle stuff that may pop up in java that is not legal in xml
 308  
      *
 309  
      * @param message   The string
 310  
      * @param attribute
 311  
      * @return The escaped string
 312  
      */
 313  
     private static String extraEscape( String message, boolean attribute )
 314  
     {
 315  
         // Someday convert to xml 1.1 which handles everything but 0 inside string
 316  120
         if ( !containsEscapesIllegalnXml10( message ) )
 317  
         {
 318  120
             return message;
 319  
         }
 320  0
         return escapeXml( message, attribute );
 321  
     }
 322  
 
 323  
     private static boolean containsEscapesIllegalnXml10( String message )
 324  
     {
 325  120
         int size = message.length();
 326  9976
         for ( int i = 0; i < size; i++ )
 327  
         {
 328  9856
             if ( isIllegalEscape( message.charAt( i ) ) )
 329  
             {
 330  0
                 return true;
 331  
             }
 332  
 
 333  
         }
 334  120
         return false;
 335  
     }
 336  
 
 337  
     private static boolean isIllegalEscape( char c )
 338  
     {
 339  9856
         return c < 32 && c != '\n' && c != '\r' && c != '\t';
 340  
     }
 341  
 
 342  
     private static String escapeXml( String text, boolean attribute )
 343  
     {
 344  0
         StringBuilder sb = new StringBuilder( text.length() * 2 );
 345  0
         for ( int i = 0; i < text.length(); i++ )
 346  
         {
 347  0
             char c = text.charAt( i );
 348  0
             if ( isIllegalEscape( c ) )
 349  
             {
 350  
                 // uh-oh!  This character is illegal in XML 1.0!
 351  
                 // http://www.w3.org/TR/1998/REC-xml-19980210#charsets
 352  
                 // we're going to deliberately doubly-XML escape it...
 353  
                 // there's nothing better we can do! :-(
 354  
                 // SUREFIRE-456
 355  0
                 sb.append( attribute ? "&#" : "&amp#" ).append( (int) c ).append(
 356  
                     ';' ); // & Will be encoded to amp inside xml encodingSHO
 357  
             }
 358  
             else
 359  
             {
 360  0
                 sb.append( c );
 361  
             }
 362  
         }
 363  0
         return sb.toString();
 364  
     }
 365  
 
 366  
 }