001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *  
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017     
018    package org.apache.geronimo.mavenplugins.testsuite.report;
019    
020    import org.apache.maven.reporting.MavenReportException;
021    import org.codehaus.doxia.sink.Sink;
022    
023    import java.io.File;
024    import java.text.NumberFormat;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.ListIterator;
028    import java.util.Locale;
029    import java.util.Map;
030    import java.util.ResourceBundle;
031    import java.util.StringTokenizer;
032    
033    public class SurefireReportGenerator
034    {
035        private SurefireReportParser report;
036    
037        private List testSuites;
038    
039        private boolean showSuccess;
040    
041        private String xrefLocation;
042    
043        public SurefireReportGenerator( File reportsDirectory, Locale locale, boolean showSuccess, String xrefLocation )
044        {
045            report = new SurefireReportParser( reportsDirectory, locale );
046    
047            this.xrefLocation = xrefLocation;
048    
049            this.showSuccess = showSuccess;
050        }
051    
052        public void doGenerateReport( ResourceBundle bundle, Sink sink )
053            throws MavenReportException
054        {
055            testSuites = report.parseXMLReportFiles();
056    
057            sink.head();
058    
059            sink.text( bundle.getString( "report.surefire.description" ) );
060    
061            StringBuffer str = new StringBuffer();
062            str.append( "<script type=\"text/javascript\">\n" );
063            str.append( "function toggleDisplay(elementId) {\n" );
064            str.append( " var elm = document.getElementById(elementId + 'error');\n" );
065            str.append( " if (elm && typeof elm.style != \"undefined\") {\n" );
066            str.append( " if (elm.style.display == \"none\") {\n" );
067            str.append( " elm.style.display = \"\";\n" );
068            str.append( " document.getElementById(elementId + 'off').style.display = \"none\";\n" );
069            str.append( " document.getElementById(elementId + 'on').style.display = \"inline\";\n" );
070            str.append( " }" );
071            str.append( " else if (elm.style.display == \"\") {" );
072            str.append( " elm.style.display = \"none\";\n" );
073            str.append( " document.getElementById(elementId + 'off').style.display = \"inline\";\n" );
074            str.append( " document.getElementById(elementId + 'on').style.display = \"none\";\n" );
075            str.append( " } \n" );
076            str.append( " } \n" );
077            str.append( " }\n" );
078            str.append( "</script>" );
079            sink.rawText( str.toString() );
080    
081            sink.head_();
082    
083            sink.body();
084    
085            constructSummarySection( bundle, sink );
086    
087            Map suitePackages = report.getSuitesGroupByPackage( testSuites );
088            if ( !suitePackages.isEmpty() )
089            {
090                constructPackagesSection( bundle, sink, suitePackages );
091            }
092    
093            if ( !testSuites.isEmpty() )
094            {
095                constructTestCasesSection( bundle, sink );
096            }
097    
098            List failureList = report.getFailureDetails( testSuites );
099            if ( !failureList.isEmpty() )
100            {
101                constructFailureDetails( sink, bundle, failureList );
102            }
103    
104            sinkLineBreak( sink );
105    
106            sink.body_();
107    
108            sink.flush();
109    
110            sink.close();
111        }
112    
113        private void constructSummarySection( ResourceBundle bundle, Sink sink )
114        {
115            Map summary = report.getSummary( testSuites );
116    
117            sink.sectionTitle1();
118    
119            sinkAnchor( sink, "Summary" );
120    
121            sink.text( bundle.getString( "report.surefire.label.summary" ) );
122    
123            sink.sectionTitle1_();
124    
125            constructHotLinks( sink, bundle );
126    
127            sinkLineBreak( sink );
128    
129            sink.table();
130    
131            sink.tableRow();
132    
133            sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
134    
135            sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
136    
137            sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
138            
139            sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
140    
141            sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
142    
143            sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
144    
145            sink.tableRow_();
146    
147            sink.tableRow();
148    
149            sinkCell( sink, (String) summary.get( "totalTests" ) );
150    
151            sinkCell( sink, (String) summary.get( "totalErrors" ) );
152    
153            sinkCell( sink, (String) summary.get( "totalFailures" ) );
154    
155            sinkCell( sink, (String) summary.get( "totalSkipped" ) );
156    
157            sinkCell( sink, summary.get( "totalPercentage" ) + "%" );
158    
159            sinkCell( sink, (String) summary.get( "totalElapsedTime" ) );
160    
161            sink.tableRow_();
162    
163            sink.table_();
164    
165            sink.lineBreak();
166    
167            sink.rawText( bundle.getString( "report.surefire.text.note1" ) );
168    
169            sinkLineBreak( sink );
170        }
171    
172        private void constructPackagesSection( ResourceBundle bundle, Sink sink, Map suitePackages )
173        {
174            NumberFormat numberFormat = report.getNumberFormat();
175    
176            sink.sectionTitle1();
177    
178            sinkAnchor( sink, "Package_List" );
179    
180            sink.text( bundle.getString( "report.surefire.label.packagelist" ) );
181    
182            sink.sectionTitle1_();
183    
184            constructHotLinks( sink, bundle );
185    
186            sinkLineBreak( sink );
187    
188            sink.table();
189    
190            sink.tableRow();
191    
192            sinkHeader( sink, bundle.getString( "report.surefire.label.package" ) );
193    
194            sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
195    
196            sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
197    
198            sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
199    
200            sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
201    
202            sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
203    
204            sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
205    
206            sink.tableRow_();
207    
208            Iterator packIter = suitePackages.keySet().iterator();
209    
210            while ( packIter.hasNext() )
211            {
212                sink.tableRow();
213    
214                String packageName = (String) packIter.next();
215    
216                List testSuiteList = (List) suitePackages.get( packageName );
217    
218                Map packageSummary = report.getSummary( testSuiteList );
219    
220                sinkCellLink( sink, packageName, getLink(packageName) );
221    
222                sinkCell( sink, (String) packageSummary.get( "totalTests" ) );
223    
224                sinkCell( sink, (String) packageSummary.get( "totalErrors" ) );
225    
226                sinkCell( sink, (String) packageSummary.get( "totalFailures" ) );
227    
228                sinkCell( sink, (String) packageSummary.get( "totalSkipped" ) );
229    
230                sinkCell( sink, packageSummary.get( "totalPercentage" ) + "%" );
231    
232                sinkCell( sink, (String) packageSummary.get( "totalElapsedTime" ) );
233    
234                sink.tableRow_();
235            }
236    
237            sink.table_();
238    
239            sink.lineBreak();
240    
241            sink.rawText( bundle.getString( "report.surefire.text.note2" ) );
242    
243            packIter = suitePackages.keySet().iterator();
244    
245            while ( packIter.hasNext() )
246            {
247                String packageName = (String) packIter.next();
248                
249                if (packageName.endsWith("#")) {
250                    continue;
251                }
252    
253                List testSuiteList = (List) suitePackages.get( packageName );
254    
255                Iterator suiteIterator = testSuiteList.iterator();
256    
257                sink.sectionTitle2();
258    
259                sinkAnchor( sink, packageName );
260    
261                sink.text( packageName );
262    
263                sink.sectionTitle2_();
264    
265                sink.table();
266    
267                sink.tableRow();
268    
269                sinkHeader( sink, "" );
270    
271                sinkHeader( sink, bundle.getString( "report.surefire.label.class" ) );
272    
273                sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
274    
275                sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
276    
277                sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
278    
279                sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
280    
281                sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
282    
283                sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
284    
285                sink.tableRow_();
286    
287                while ( suiteIterator.hasNext() )
288                {
289                    ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
290    
291                    if ( showSuccess || suite.getNumberOfErrors() != 0 || suite.getNumberOfFailures() != 0 )
292                    {
293    
294                        sink.tableRow();
295    
296                        sink.tableCell();
297    
298                        sink.link( "#" + suite.getPackageName() + suite.getName() );
299    
300                        if ( suite.getNumberOfErrors() > 0 )
301                        {
302                            sinkIcon( "error", sink );
303                        }
304                        else if ( suite.getNumberOfFailures() > 0 )
305                        {
306                            sinkIcon( "junit.framework", sink );
307                        }
308                        else
309                        {
310                            sinkIcon( "success", sink );
311                        }
312    
313                        sink.link_();
314    
315                        sink.tableCell_();
316    
317                        sinkCellLink( sink, suite.getName(), "#" + suite.getPackageName() + suite.getName() );
318    
319                        sinkCell( sink, Integer.toString( suite.getNumberOfTests() ) );
320    
321                        sinkCell( sink, Integer.toString( suite.getNumberOfErrors() ) );
322    
323                        sinkCell( sink, Integer.toString( suite.getNumberOfFailures() ) );
324    
325                        sinkCell( sink, Integer.toString( suite.getNumberOfSkipped() ) );
326    
327                        String percentage = report.computePercentage( suite.getNumberOfTests(), suite.getNumberOfErrors(),
328                                                                      suite.getNumberOfFailures(), suite
329                                                                          .getNumberOfSkipped() );
330                        sinkCell( sink, percentage + "%" );
331    
332                        sinkCell( sink, numberFormat.format( suite.getTimeElapsed() ) );
333    
334                        sink.tableRow_();
335                    }
336                }
337    
338                sink.table_();
339            }
340    
341            sinkLineBreak( sink );
342        }
343    
344        private void constructTestCasesSection( ResourceBundle bundle, Sink sink )
345        {
346            NumberFormat numberFormat = report.getNumberFormat();
347    
348            sink.sectionTitle1();
349    
350            sinkAnchor( sink, "Test_Cases" );
351    
352            sink.text( bundle.getString( "report.surefire.label.testcases" ) );
353    
354            sink.sectionTitle1_();
355    
356            constructHotLinks( sink, bundle );
357    
358            ListIterator suiteIterator = testSuites.listIterator();
359    
360            while ( suiteIterator.hasNext() )
361            {
362                ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
363                if ( suite.getPackageName().endsWith("#") ) {
364                    continue;
365                }
366    
367                List testCases = suite.getTestCases();
368    
369                if ( testCases != null )
370                {
371                    ListIterator caseIterator = testCases.listIterator();
372    
373                    sink.sectionTitle2();
374    
375                    sinkAnchor( sink, suite.getPackageName() + suite.getName() );
376    
377                    sink.text( suite.getName() );
378    
379                    sink.sectionTitle2_();
380    
381                    sink.table();
382    
383                    while ( caseIterator.hasNext() )
384                    {
385                        ReportTestCase testCase = (ReportTestCase) caseIterator.next();
386    
387                        if ( testCase.getFailure() != null || showSuccess )
388                        {
389                            sink.tableRow();
390    
391                            sink.tableCell();
392    
393                            Map failure = testCase.getFailure();
394    
395                            if ( failure != null )
396                            {
397                                sink.link( "#" + testCase.getFullName() );
398    
399                                sinkIcon( (String) failure.get( "type" ), sink );
400    
401                                sink.link_();
402                            }
403                            else
404                            {
405                                sinkIcon( "success", sink );
406                            }
407    
408                            sink.tableCell_();
409    
410                            if ( failure != null )
411                            {
412                                sink.tableCell();
413    
414                                sinkLink( sink, testCase.getName(), "#" + testCase.getFullName() );
415    
416                                sink.rawText( "  <div class=\"detailToggle\" style=\"display:inline\">" );
417    
418                                sink.link( "javascript:toggleDisplay('" + testCase.getName() + "');" );
419    
420                                sink.rawText( "<span style=\"display: inline;\" " + "id=\"" + testCase.getName() +
421                                    "off\">+</span><span id=\"" + testCase.getName() + "on\" " +
422                                    "style=\"display: none;\">-</span> " );
423                                sink.text( "[ Detail ]" );
424                                sink.link_();
425    
426                                sink.rawText( "</div>" );
427    
428                                sink.tableCell_();
429                            }
430                            else
431                            {
432                                sinkCell( sink, testCase.getName() );
433                            }
434    
435                            sinkCell( sink, numberFormat.format( testCase.getTime() ) );
436    
437                            sink.tableRow_();
438    
439                            if ( failure != null )
440                            {
441                                sink.tableRow();
442    
443                                sinkCell( sink, "" );
444                                sinkCell( sink, (String) failure.get( "message" ) );
445                                sinkCell( sink, "" );
446                                sink.tableRow_();
447    
448                                List detail = (List) failure.get( "detail" );
449                                if ( detail != null )
450                                {
451    
452                                    sink.tableRow();
453                                    sinkCell( sink, "" );
454    
455                                    sink.tableCell();
456                                    sink.rawText(
457                                        "  <div id=\"" + testCase.getName() + "error\" style=\"display:none;\">" );
458    
459                                    Iterator it = detail.iterator();
460    
461                                    sink.verbatim( true );
462                                    while ( it.hasNext() )
463                                    {
464                                        sink.text( it.next().toString() );
465                                        sink.lineBreak();
466                                    }
467                                    sink.verbatim_();
468    
469                                    sink.rawText( "</div>" );
470                                    sink.tableCell_();
471    
472                                    sinkCell( sink, "" );
473    
474                                    sink.tableRow_();
475                                }
476                            }
477                        }
478                    }
479    
480                    sink.table_();
481                }
482            }
483    
484            sinkLineBreak( sink );
485        }
486    
487        private void constructFailureDetails( Sink sink, ResourceBundle bundle, List failureList )
488        {
489            Iterator failIter = failureList.iterator();
490    
491            if ( failIter != null )
492            {
493                sink.sectionTitle1();
494    
495                sinkAnchor( sink, "Failure_Details" );
496    
497                sink.text( bundle.getString( "report.surefire.label.failuredetails" ) );
498    
499                sink.sectionTitle1_();
500    
501                constructHotLinks( sink, bundle );
502    
503                sinkLineBreak( sink );
504    
505                sink.table();
506    
507                while ( failIter.hasNext() )
508                {
509                    ReportTestCase tCase = (ReportTestCase) failIter.next();
510    
511                    Map failure = tCase.getFailure();
512    
513                    sink.tableRow();
514    
515                    sink.tableCell();
516    
517                    String type = (String) failure.get( "type" );
518                    sinkIcon( type, sink );
519    
520                    sink.tableCell_();
521    
522                    sinkCellAnchor( sink, tCase.getName(), tCase.getFullName() );
523    
524                    sink.tableRow_();
525    
526                    String message = (String) failure.get( "message" );
527    
528                    sink.tableRow();
529    
530                    sinkCell( sink, "" );
531    
532                    StringBuffer sb = new StringBuffer();
533                    sb.append( type );
534    
535                    if ( message != null )
536                    {
537                        sb.append( ": " );
538                        sb.append( message );
539                    }
540    
541                    sinkCell( sink, sb.toString() );
542    
543                    sink.tableRow_();
544    
545                    List detail = (List) failure.get( "detail" );
546                    if ( detail != null )
547                    {
548                        Iterator it = detail.iterator();
549    
550                        boolean firstLine = true;
551    
552                        String techMessage = "";
553                        while ( it.hasNext() )
554                        {
555                            techMessage = it.next().toString();
556                            if ( firstLine )
557                            {
558                                firstLine = false;
559                            }
560                            else
561                            {
562                                sink.text( "    " );
563                            }
564                        }
565    
566                        sink.tableRow();
567    
568                        sinkCell( sink, "" );
569    
570                        sink.tableCell();
571                        sink.rawText( "  <div id=\"" + tCase.getName() + "error\" >" );
572    
573                        if ( xrefLocation != null )
574                        {
575                            String path = tCase.getFullClassName().replace( '.', '/' );
576    
577                            sink.link( xrefLocation + "/" + path + ".html#" +
578                                getErrorLineNumber( tCase.getFullName(), techMessage ) );
579                        }
580                        sink.text(
581                            tCase.getFullClassName() + ":" + getErrorLineNumber( tCase.getFullName(), techMessage ) );
582    
583                        if ( xrefLocation != null )
584                        {
585                            sink.link_();
586                        }
587                        sink.rawText( "</div>" );
588    
589                        sink.tableCell_();
590    
591                        sink.tableRow_();
592                    }
593                }
594    
595                sink.table_();
596            }
597    
598            sinkLineBreak( sink );
599        }
600    
601        private String getErrorLineNumber( String className, String source )
602        {
603            StringTokenizer tokenizer = new StringTokenizer( source );
604    
605            String lineNo = "";
606    
607            while ( tokenizer.hasMoreTokens() )
608            {
609                String token = tokenizer.nextToken();
610                if ( token.startsWith( className ) )
611                {
612                    int idx = token.indexOf( ":" );
613                    lineNo = token.substring( idx + 1, token.indexOf( ")" ) );
614                    break;
615                }
616            }
617            return lineNo;
618        }
619    
620        private void constructHotLinks( Sink sink, ResourceBundle bundle )
621        {
622            if ( !testSuites.isEmpty() )
623            {
624                sink.section2();
625    
626                sink.rawText( "[" );
627                sinkLink( sink, bundle.getString( "report.surefire.label.summary" ), "#Summary" );
628                sink.rawText( "]" );
629    
630                sink.rawText( "[" );
631                sinkLink( sink, bundle.getString( "report.surefire.label.packagelist" ), "#Package_List" );
632                sink.rawText( "]" );
633    
634                sink.rawText( "[" );
635                sinkLink( sink, bundle.getString( "report.surefire.label.testcases" ), "#Test_Cases" );
636                sink.rawText( "]" );
637                sink.section2_();
638            }
639        }
640    
641        private void sinkLineBreak( Sink sink )
642        {
643            sink.table();
644            sink.tableRow();
645            sink.tableRow_();
646            sink.tableRow();
647            sink.tableRow_();
648            sink.table_();
649        }
650    
651        private void sinkIcon( String type, Sink sink )
652        {
653            sink.figure();
654    
655            if ( type.startsWith( "junit.framework" ) )
656            {
657                sink.figureGraphics( "images/icon_warning_sml.gif" );
658            }
659            else if ( type.startsWith( "success" ) )
660            {
661                sink.figureGraphics( "images/icon_success_sml.gif" );
662            }
663            else
664            {
665                sink.figureGraphics( "images/icon_error_sml.gif" );
666            }
667    
668            sink.figure_();
669        }
670    
671        private void sinkHeader( Sink sink, String header )
672        {
673            sink.tableHeaderCell();
674            sink.text( header );
675            sink.tableHeaderCell_();
676        }
677    
678        private void sinkCell( Sink sink, String text )
679        {
680            sink.tableCell();
681            sink.text( text );
682            sink.tableCell_();
683        }
684    
685        private void sinkLink( Sink sink, String text, String link )
686        {
687            sink.link( link );
688            sink.text( text );
689            sink.link_();
690        }
691    
692        private void sinkCellLink( Sink sink, String text, String link )
693        {
694            sink.tableCell();
695            sinkLink( sink, text, link );
696            sink.tableCell_();
697        }
698    
699        private void sinkCellAnchor( Sink sink, String text, String anchor )
700        {
701            sink.tableCell();
702            sinkAnchor( sink, anchor );
703            sink.text( text );
704            sink.tableCell_();
705        }
706    
707        private void sinkAnchor( Sink sink, String anchor )
708        {
709            sink.anchor( anchor );
710            sink.anchor_();
711        }
712    
713        private String getLink(String link) {
714            if ( link.endsWith("#") ) {
715                link = link.replace("#", "/");
716                link = link.replace("@", "/");
717                return link + "surefire-report.html";
718            }
719            return "#" + link;
720        }
721    }