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 org.apache.maven.doxia.markup.HtmlMarkup;
23  import org.apache.maven.doxia.sink.Sink;
24  import org.apache.maven.doxia.sink.SinkEventAttributeSet;
25  import org.apache.maven.doxia.sink.SinkEventAttributes;
26  import org.apache.maven.reporting.MavenReportException;
27  
28  import java.text.NumberFormat;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.ListIterator;
32  import java.util.Locale;
33  import java.util.Map;
34  import java.util.ResourceBundle;
35  import java.util.StringTokenizer;
36  
37  /**
38   * @version $Id: SurefireReportGenerator.java 1076592 2011-03-03 11:36:07Z ltheussl $
39   */
40  public class SurefireReportGenerator
41  {
42      private final SurefireReportParser report;
43  
44      private List testSuites;
45  
46      private final boolean showSuccess;
47  
48      private final String xrefLocation;
49  
50      public SurefireReportGenerator( List reportsDirectories, Locale locale, boolean showSuccess, String xrefLocation )
51      {
52          report = new SurefireReportParser( reportsDirectories, locale );
53  
54          this.xrefLocation = xrefLocation;
55  
56          this.showSuccess = showSuccess;
57      }
58  
59      public void doGenerateReport( ResourceBundle bundle, Sink sink )
60          throws MavenReportException
61      {
62          testSuites = report.parseXMLReportFiles();
63  
64          sink.head();
65  
66          sink.title();
67          sink.text( bundle.getString( "report.surefire.header" ) );
68          sink.title_();
69  
70          sink.head_();
71  
72          sink.body();
73  
74          SinkEventAttributeSet atts = new SinkEventAttributeSet();
75          atts.addAttribute( SinkEventAttributes.TYPE, "text/javascript" );
76          sink.unknown( "script", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
77          sink.unknown( "cdata", new Object[]{new Integer( HtmlMarkup.CDATA_TYPE ), javascriptToggleDisplayCode() }, null );
78          sink.unknown( "script", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
79  
80          sink.section1();
81          sink.sectionTitle1();
82          sink.text( bundle.getString( "report.surefire.header" ) );
83          sink.sectionTitle1_();
84          sink.section1_();
85  
86          constructSummarySection( bundle, sink );
87  
88          Map suitePackages = report.getSuitesGroupByPackage( testSuites );
89          if ( !suitePackages.isEmpty() )
90          {
91              constructPackagesSection( bundle, sink, suitePackages );
92          }
93  
94          if ( !testSuites.isEmpty() )
95          {
96              constructTestCasesSection( bundle, sink );
97          }
98  
99          List failureList = report.getFailureDetails( testSuites );
100         if ( !failureList.isEmpty() )
101         {
102             constructFailureDetails( sink, bundle, failureList );
103         }
104 
105         sink.body_();
106 
107         sink.flush();
108 
109         sink.close();
110     }
111 
112     private void constructSummarySection( ResourceBundle bundle, Sink sink )
113     {
114         Map summary = report.getSummary( testSuites );
115 
116         sink.section1();
117         sink.sectionTitle1();
118         sink.text( bundle.getString( "report.surefire.label.summary" ) );
119         sink.sectionTitle1_();
120 
121         sinkAnchor( sink, "Summary" );
122 
123         constructHotLinks( sink, bundle );
124 
125         sinkLineBreak( sink );
126 
127         sink.table();
128 
129         sink.tableRow();
130 
131         sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
132 
133         sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
134 
135         sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
136 
137         sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
138 
139         sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
140 
141         sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
142 
143         sink.tableRow_();
144 
145         sink.tableRow();
146 
147         sinkCell( sink, (String) summary.get( "totalTests" ) );
148 
149         sinkCell( sink, (String) summary.get( "totalErrors" ) );
150 
151         sinkCell( sink, (String) summary.get( "totalFailures" ) );
152 
153         sinkCell( sink, (String) summary.get( "totalSkipped" ) );
154 
155         sinkCell( sink, summary.get( "totalPercentage" ) + "%" );
156 
157         sinkCell( sink, (String) summary.get( "totalElapsedTime" ) );
158 
159         sink.tableRow_();
160 
161         sink.table_();
162 
163         sink.lineBreak();
164 
165         sink.paragraph();
166         sink.text( bundle.getString( "report.surefire.text.note1" ) );
167         sink.paragraph_();
168 
169         sinkLineBreak( sink );
170 
171         sink.section1_();
172     }
173 
174     private void constructPackagesSection( ResourceBundle bundle, Sink sink, Map suitePackages )
175     {
176         NumberFormat numberFormat = report.getNumberFormat();
177 
178         sink.section1();
179         sink.sectionTitle1();
180         sink.text( bundle.getString( "report.surefire.label.packagelist" ) );
181         sink.sectionTitle1_();
182 
183         sinkAnchor( sink, "Package_List" );
184 
185         constructHotLinks( sink, bundle );
186 
187         sinkLineBreak( sink );
188 
189         sink.table();
190 
191         sink.tableRow();
192 
193         sinkHeader( sink, bundle.getString( "report.surefire.label.package" ) );
194 
195         sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
196 
197         sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
198 
199         sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
200 
201         sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
202 
203         sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
204 
205         sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
206 
207         sink.tableRow_();
208 
209         Iterator packIter = suitePackages.keySet().iterator();
210 
211         while ( packIter.hasNext() )
212         {
213             sink.tableRow();
214 
215             String packageName = (String) packIter.next();
216 
217             List testSuiteList = (List) suitePackages.get( packageName );
218 
219             Map packageSummary = report.getSummary( testSuiteList );
220 
221             sinkCellLink( sink, packageName, "#" + packageName );
222 
223             sinkCell( sink, (String) packageSummary.get( "totalTests" ) );
224 
225             sinkCell( sink, (String) packageSummary.get( "totalErrors" ) );
226 
227             sinkCell( sink, (String) packageSummary.get( "totalFailures" ) );
228 
229             sinkCell( sink, (String) packageSummary.get( "totalSkipped" ) );
230 
231             sinkCell( sink, packageSummary.get( "totalPercentage" ) + "%" );
232 
233             sinkCell( sink, (String) packageSummary.get( "totalElapsedTime" ) );
234 
235             sink.tableRow_();
236         }
237 
238         sink.table_();
239 
240         sink.lineBreak();
241 
242         sink.paragraph();
243         sink.text( bundle.getString( "report.surefire.text.note2" ) );
244         sink.paragraph_();
245 
246         packIter = suitePackages.keySet().iterator();
247 
248         while ( packIter.hasNext() )
249         {
250             String packageName = (String) packIter.next();
251 
252             List testSuiteList = (List) suitePackages.get( packageName );
253 
254             Iterator suiteIterator = testSuiteList.iterator();
255 
256             sink.section2();
257             sink.sectionTitle2();
258             sink.text( packageName );
259             sink.sectionTitle2_();
260 
261             sinkAnchor( sink, packageName );
262 
263             sink.table();
264 
265             sink.tableRow();
266 
267             sinkHeader( sink, "" );
268 
269             sinkHeader( sink, bundle.getString( "report.surefire.label.class" ) );
270 
271             sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
272 
273             sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
274 
275             sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
276 
277             sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
278 
279             sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
280 
281             sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
282 
283             sink.tableRow_();
284 
285             while ( suiteIterator.hasNext() )
286             {
287                 ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
288 
289                 if ( showSuccess || suite.getNumberOfErrors() != 0 || suite.getNumberOfFailures() != 0 )
290                 {
291 
292                     sink.tableRow();
293 
294                     sink.tableCell();
295 
296                     sink.link( "#" + suite.getPackageName() + suite.getName() );
297 
298                     if ( suite.getNumberOfErrors() > 0 )
299                     {
300                         sinkIcon( "error", sink );
301                     }
302                     else if ( suite.getNumberOfFailures() > 0 )
303                     {
304                         sinkIcon( "junit.framework", sink );
305                     }
306                     else if ( suite.getNumberOfSkipped() > 0 )
307                     {
308                         sinkIcon( "skipped", sink );
309                     }
310                     else
311                     {
312                         sinkIcon( "success", sink );
313                     }
314 
315                     sink.link_();
316 
317                     sink.tableCell_();
318 
319                     sinkCellLink( sink, suite.getName(), "#" + suite.getPackageName() + suite.getName() );
320 
321                     sinkCell( sink, Integer.toString( suite.getNumberOfTests() ) );
322 
323                     sinkCell( sink, Integer.toString( suite.getNumberOfErrors() ) );
324 
325                     sinkCell( sink, Integer.toString( suite.getNumberOfFailures() ) );
326 
327                     sinkCell( sink, Integer.toString( suite.getNumberOfSkipped() ) );
328 
329                     String percentage = report.computePercentage( suite.getNumberOfTests(), suite.getNumberOfErrors(),
330                                                                   suite.getNumberOfFailures(), suite
331                         .getNumberOfSkipped() );
332                     sinkCell( sink, percentage + "%" );
333 
334                     sinkCell( sink, numberFormat.format( suite.getTimeElapsed() ) );
335 
336                     sink.tableRow_();
337                 }
338             }
339 
340             sink.table_();
341 
342             sink.section2_();
343         }
344 
345         sinkLineBreak( sink );
346 
347         sink.section1_();
348     }
349 
350     private void constructTestCasesSection( ResourceBundle bundle, Sink sink )
351     {
352         NumberFormat numberFormat = report.getNumberFormat();
353 
354         sink.section1();
355         sink.sectionTitle1();
356         sink.text( bundle.getString( "report.surefire.label.testcases" ) );
357         sink.sectionTitle1_();
358 
359         sinkAnchor( sink, "Test_Cases" );
360 
361         constructHotLinks( sink, bundle );
362 
363         ListIterator suiteIterator = testSuites.listIterator();
364 
365         while ( suiteIterator.hasNext() )
366         {
367             ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
368 
369             List testCases = suite.getTestCases();
370 
371             if ( testCases != null && !testCases.isEmpty() )
372             {
373                 ListIterator caseIterator = testCases.listIterator();
374 
375                 sink.section2();
376                 sink.sectionTitle2();
377                 sink.text( suite.getName() );
378                 sink.sectionTitle2_();
379 
380                 sinkAnchor( sink, suite.getPackageName() + suite.getName() );
381 
382                 sink.table();
383 
384                 while ( caseIterator.hasNext() )
385                 {
386                     ReportTestCase testCase = (ReportTestCase) caseIterator.next();
387 
388                     if ( testCase.getFailure() != null || showSuccess )
389                     {
390                         sink.tableRow();
391 
392                         sink.tableCell();
393 
394                         Map failure = testCase.getFailure();
395 
396                         if ( failure != null )
397                         {
398                             sink.link( "#" + testCase.getFullName() );
399 
400                             sinkIcon( (String) failure.get( "type" ), sink );
401 
402                             sink.link_();
403                         }
404                         else
405                         {
406                             sinkIcon( "success", sink );
407                         }
408 
409                         sink.tableCell_();
410 
411                         if ( failure != null )
412                         {
413                             sink.tableCell();
414 
415                             sinkLink( sink, testCase.getName(), "#" + testCase.getFullName() );
416 
417                             SinkEventAttributeSet atts = new SinkEventAttributeSet();
418                             atts.addAttribute( SinkEventAttributes.CLASS, "detailToggle" );
419                             atts.addAttribute( SinkEventAttributes.STYLE, "display:inline" );
420                             sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
421 
422                             sink.link( "javascript:toggleDisplay('" + toHtmlId( testCase.getFullName() ) + "');" );
423 
424                             atts = new SinkEventAttributeSet();
425                             atts.addAttribute( SinkEventAttributes.STYLE, "display:inline;" );
426                             atts.addAttribute( SinkEventAttributes.ID, toHtmlId( testCase.getFullName() ) + "off" );
427                             sink.unknown( "span", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
428                             sink.text( " + " );
429                             sink.unknown( "span", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
430 
431                             atts = new SinkEventAttributeSet();
432                             atts.addAttribute( SinkEventAttributes.STYLE, "display:none;" );
433                             atts.addAttribute( SinkEventAttributes.ID, toHtmlId( testCase.getFullName() ) + "on" );
434                             sink.unknown( "span", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
435                             sink.text( " - " );
436                             sink.unknown( "span", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
437 
438                             sink.text( "[ Detail ]" );
439                             sink.link_();
440 
441                             sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
442 
443                             sink.tableCell_();
444                         }
445                         else
446                         {
447                             sinkCell( sink, testCase.getName() );
448                         }
449 
450                         sinkCell( sink, numberFormat.format( testCase.getTime() ) );
451 
452                         sink.tableRow_();
453 
454                         if ( failure != null )
455                         {
456                             sink.tableRow();
457 
458                             sinkCell( sink, "" );
459                             sinkCell( sink, (String) failure.get( "message" ) );
460                             sinkCell( sink, "" );
461                             sink.tableRow_();
462 
463                             List detail = (List) failure.get( "detail" );
464                             if ( detail != null )
465                             {
466 
467                                 sink.tableRow();
468                                 sinkCell( sink, "" );
469 
470                                 sink.tableCell();
471                                 SinkEventAttributeSet atts = new SinkEventAttributeSet();
472                                 atts.addAttribute( SinkEventAttributes.ID, toHtmlId( testCase.getFullName() ) + "error" );
473                                 atts.addAttribute( SinkEventAttributes.STYLE, "display:none;" );
474                                 sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
475 
476                                 Iterator it = detail.iterator();
477 
478                                 sink.verbatim( null );
479                                 while ( it.hasNext() )
480                                 {
481                                     sink.text( it.next().toString() );
482                                     sink.lineBreak();
483                                 }
484                                 sink.verbatim_();
485 
486                                 sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
487                                 sink.tableCell_();
488 
489                                 sinkCell( sink, "" );
490 
491                                 sink.tableRow_();
492                             }
493                         }
494                     }
495                 }
496 
497                 sink.table_();
498 
499                 sink.section2_();
500             }
501         }
502 
503         sinkLineBreak( sink );
504 
505         sink.section1_();
506     }
507 
508 
509     private String toHtmlId(String id){
510         return id.replace(".", "_");
511     }
512     private void constructFailureDetails( Sink sink, ResourceBundle bundle, List failureList )
513     {
514         Iterator failIter = failureList.iterator();
515 
516         if ( failIter != null )
517         {
518             sink.section1();
519             sink.sectionTitle1();
520             sink.text( bundle.getString( "report.surefire.label.failuredetails" ) );
521             sink.sectionTitle1_();
522 
523             sinkAnchor( sink, "Failure_Details" );
524 
525             constructHotLinks( sink, bundle );
526 
527             sinkLineBreak( sink );
528 
529             sink.table();
530 
531             while ( failIter.hasNext() )
532             {
533                 ReportTestCase tCase = (ReportTestCase) failIter.next();
534 
535                 Map failure = tCase.getFailure();
536 
537                 sink.tableRow();
538 
539                 sink.tableCell();
540 
541                 String type = (String) failure.get( "type" );
542                 sinkIcon( type, sink );
543 
544                 sink.tableCell_();
545 
546                 sinkCellAnchor( sink, tCase.getName(), tCase.getFullName() );
547 
548                 sink.tableRow_();
549 
550                 String message = (String) failure.get( "message" );
551 
552                 sink.tableRow();
553 
554                 sinkCell( sink, "" );
555 
556                 StringBuffer sb = new StringBuffer();
557                 sb.append( type );
558 
559                 if ( message != null )
560                 {
561                     sb.append( ": " );
562                     sb.append( message );
563                 }
564 
565                 sinkCell( sink, sb.toString() );
566 
567                 sink.tableRow_();
568 
569                 List detail = (List) failure.get( "detail" );
570                 if ( detail != null )
571                 {
572                     Iterator it = detail.iterator();
573 
574                     boolean firstLine = true;
575 
576                     String techMessage = "";
577                     while ( it.hasNext() )
578                     {
579                         techMessage = it.next().toString();
580                         if ( firstLine )
581                         {
582                             firstLine = false;
583                         }
584                         else
585                         {
586                             sink.text( "    " );
587                         }
588                     }
589 
590                     sink.tableRow();
591 
592                     sinkCell( sink, "" );
593 
594                     sink.tableCell();
595                     SinkEventAttributeSet atts = new SinkEventAttributeSet();
596                     atts.addAttribute( SinkEventAttributes.ID, tCase.getName() + "error" );
597                     sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
598 
599                     if ( xrefLocation != null )
600                     {
601                         String path = tCase.getFullClassName().replace( '.', '/' );
602 
603                         sink.link( xrefLocation + "/" + path + ".html#" +
604                             getErrorLineNumber( tCase.getFullName(), techMessage ) );
605                     }
606                     sink.text(
607                         tCase.getFullClassName() + ":" + getErrorLineNumber( tCase.getFullName(), techMessage ) );
608 
609                     if ( xrefLocation != null )
610                     {
611                         sink.link_();
612                     }
613                     sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
614 
615                     sink.tableCell_();
616 
617                     sink.tableRow_();
618                 }
619             }
620 
621             sink.table_();
622         }
623 
624         sinkLineBreak( sink );
625 
626         sink.section1_();
627     }
628 
629     private String getErrorLineNumber( String className, String source )
630     {
631         StringTokenizer tokenizer = new StringTokenizer( source );
632 
633         String lineNo = "";
634 
635         while ( tokenizer.hasMoreTokens() )
636         {
637             String token = tokenizer.nextToken();
638             if ( token.startsWith( className ) )
639             {
640                 int idx = token.indexOf( ":" );
641                 lineNo = token.substring( idx + 1, token.indexOf( ")" ) );
642                 break;
643             }
644         }
645         return lineNo;
646     }
647 
648     private void constructHotLinks( Sink sink, ResourceBundle bundle )
649     {
650         if ( !testSuites.isEmpty() )
651         {
652             sink.paragraph();
653 
654             sink.text( "[" );
655             sinkLink( sink, bundle.getString( "report.surefire.label.summary" ), "#Summary" );
656             sink.text( "]" );
657 
658             sink.text( " [" );
659             sinkLink( sink, bundle.getString( "report.surefire.label.packagelist" ), "#Package_List" );
660             sink.text( "]" );
661 
662             sink.text( " [" );
663             sinkLink( sink, bundle.getString( "report.surefire.label.testcases" ), "#Test_Cases" );
664             sink.text( "]" );
665 
666             sink.paragraph_();
667         }
668     }
669 
670     private void sinkLineBreak( Sink sink )
671     {
672         sink.lineBreak();
673     }
674 
675     private void sinkIcon( String type, Sink sink )
676     {
677         sink.figure();
678 
679         if ( type.startsWith( "junit.framework" ) || "skipped".equals( type ) )
680         {
681             sink.figureGraphics( "images/icon_warning_sml.gif" );
682         }
683         else if ( type.startsWith( "success" ) )
684         {
685             sink.figureGraphics( "images/icon_success_sml.gif" );
686         }
687         else
688         {
689             sink.figureGraphics( "images/icon_error_sml.gif" );
690         }
691 
692         sink.figure_();
693     }
694 
695     private void sinkHeader( Sink sink, String header )
696     {
697         sink.tableHeaderCell();
698         sink.text( header );
699         sink.tableHeaderCell_();
700     }
701 
702     private void sinkCell( Sink sink, String text )
703     {
704         sink.tableCell();
705         sink.text( text );
706         sink.tableCell_();
707     }
708 
709     private void sinkLink( Sink sink, String text, String link )
710     {
711         sink.link( link );
712         sink.text( text );
713         sink.link_();
714     }
715 
716     private void sinkCellLink( Sink sink, String text, String link )
717     {
718         sink.tableCell();
719         sinkLink( sink, text, link );
720         sink.tableCell_();
721     }
722 
723     private void sinkCellAnchor( Sink sink, String text, String anchor )
724     {
725         sink.tableCell();
726         sinkAnchor( sink, anchor );
727         sink.text( text );
728         sink.tableCell_();
729     }
730 
731     private void sinkAnchor( Sink sink, String anchor )
732     {
733         sink.anchor( anchor );
734         sink.anchor_();
735     }
736 
737     private static String javascriptToggleDisplayCode()
738     {
739         final StringBuffer str = new StringBuffer( 64 );
740 
741         // the javascript code is emitted within a commented CDATA section
742         // so we have to start with a newline and comment the CDATA closing in the end
743         str.append( "\n" );
744         str.append( "function toggleDisplay(elementId) {\n" );
745         str.append( " var elm = document.getElementById(elementId + 'error');\n" );
746         str.append( " if (elm && typeof elm.style != \"undefined\") {\n" );
747         str.append( " if (elm.style.display == \"none\") {\n" );
748         str.append( " elm.style.display = \"\";\n" );
749         str.append( " document.getElementById(elementId + 'off').style.display = \"none\";\n" );
750         str.append( " document.getElementById(elementId + 'on').style.display = \"inline\";\n" );
751         str.append( " }" );
752         str.append( " else if (elm.style.display == \"\") {" );
753         str.append( " elm.style.display = \"none\";\n" );
754         str.append( " document.getElementById(elementId + 'off').style.display = \"inline\";\n" );
755         str.append( " document.getElementById(elementId + 'on').style.display = \"none\";\n" );
756         str.append( " } \n" );
757         str.append( " } \n" );
758         str.append( " }\n" );
759         str.append( "//" );
760 
761         return str.toString();
762     }
763 }