View Javadoc

1   package org.apache.maven.plugin.invoker;
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.IOException;
24  import java.text.DecimalFormat;
25  import java.text.DecimalFormatSymbols;
26  import java.text.NumberFormat;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Locale;
31  
32  import org.apache.maven.doxia.sink.Sink;
33  import org.apache.maven.doxia.siterenderer.Renderer;
34  import org.apache.maven.plugin.invoker.model.BuildJob;
35  import org.apache.maven.plugin.invoker.model.io.xpp3.BuildJobXpp3Reader;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.reporting.AbstractMavenReport;
38  import org.apache.maven.reporting.MavenReportException;
39  import org.codehaus.plexus.i18n.I18N;
40  import org.codehaus.plexus.util.ReaderFactory;
41  import org.codehaus.plexus.util.StringUtils;
42  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
43  
44  /**
45   * Generate a report based on the results of the Maven invocations. <strong>Note:</strong> This mojo doesn't fork any
46   * lifecycle, if you have a clean working copy, you have to use a command like
47   * <code>mvn clean integration-test site</code> to ensure the build results are present when this goal is invoked.
48   * 
49   * @goal report
50   * @author <a href="mailto:olamy@apache.org">olamy</a>
51   * @since 1.4
52   */
53  public class InvokerReport
54      extends AbstractMavenReport
55  {
56      
57      /**
58       * The Maven Project.
59       *
60       * @parameter default-value="${project}"
61       * @required
62       * @readonly
63       */
64      protected MavenProject project;
65      
66      /**
67       * Doxia Site Renderer component.
68       *
69       * @component
70       */
71      protected Renderer siteRenderer;    
72      
73      /**
74       * Internationalization component.
75       *
76       * @component
77       */
78      protected I18N i18n;    
79      
80      /**
81       * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from
82       * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in
83       * the Maven Site Plugin is used instead.
84       *
85       * @parameter default-value="${project.reporting.outputDirectory}"
86       * @required
87       */
88      protected File outputDirectory;    
89      
90      /**
91       * Base directory where all build reports have been written to.
92       *
93       * @parameter expression="${invoker.reportsDirectory}" default-value="${project.build.directory}/invoker-reports"
94       */
95      private File reportsDirectory; 
96  
97      /**
98       * The number format used to print percent values in the report locale.
99       */
100     private NumberFormat percentFormat;
101 
102     /**
103      * The number format used to print time values in the report locale.
104      */
105     private NumberFormat secondsFormat;
106 
107     protected void executeReport( Locale locale )
108         throws MavenReportException
109     {
110         DecimalFormatSymbols symbols = new DecimalFormatSymbols( locale );
111         percentFormat = new DecimalFormat( getText( locale, "report.invoker.format.percent" ), symbols );
112         secondsFormat = new DecimalFormat( getText( locale, "report.invoker.format.seconds" ), symbols );
113 
114         Sink sink = getSink();
115 
116         sink.head();
117 
118         sink.title();
119         sink.text( getText( locale, "report.invoker.result.title" ) );
120         sink.title_();
121 
122         sink.head_();
123 
124         sink.body();
125 
126         sink.section1();
127         sink.sectionTitle1();
128         sink.text( getText( locale, "report.invoker.result.title" ) );
129         sink.sectionTitle1_();
130         sink.paragraph();
131         sink.text( getText( locale, "report.invoker.result.description" ) );
132         sink.paragraph_();
133         sink.section1_();
134 
135         // ----------------------------------
136         //  build buildJob beans
137         // ----------------------------------
138         File[] reportFiles = ReportUtils.getReportFiles( reportsDirectory );
139         if ( reportFiles.length <= 0 )
140         {
141             getLog().info( "no invoker report files found, skip report generation" );
142             return;
143         }
144 
145         List buildJobs = new ArrayList( reportFiles.length );
146         for ( int i = 0, size = reportFiles.length; i < size; i++ )
147         {
148             File reportFile = reportFiles[i];
149             try
150             {
151                 BuildJobXpp3Reader reader = new BuildJobXpp3Reader();
152                 buildJobs.add( reader.read( ReaderFactory.newXmlReader( reportFile ) ) );
153             }
154             catch ( XmlPullParserException e )
155             {
156                 throw new MavenReportException( "Failed to parse report file: " + reportFile, e );
157             }
158             catch ( IOException e )
159             {
160                 throw new MavenReportException( "Failed to read report file: " + reportFile, e );
161             }
162         }
163 
164         // ----------------------------------
165         //  summary
166         // ----------------------------------
167         
168         constructSummarySection( buildJobs, locale );
169         
170         // ----------------------------------
171         //  per file/it detail
172         // ----------------------------------        
173 
174         sink.section2();
175         sink.sectionTitle2();
176 
177         sink.text( getText( locale, "report.invoker.detail.title" ) );
178 
179         sink.sectionTitle2_();
180 
181         sink.section2_();
182 
183         // detail tests table header
184         sink.table();
185 
186         sink.tableRow();
187         // -------------------------------------------
188         // name | Result | time | message
189         // -------------------------------------------
190         sinkTableHeader( sink, getText( locale, "report.invoker.detail.name" ) );
191         sinkTableHeader( sink, getText( locale, "report.invoker.detail.result" ) );
192         sinkTableHeader( sink, getText( locale, "report.invoker.detail.time" ) );
193         sinkTableHeader( sink, getText( locale, "report.invoker.detail.message" ) );
194 
195         sink.tableRow_();
196 
197         for ( Iterator iterator = buildJobs.iterator(); iterator.hasNext(); )
198         {
199             BuildJob buildJob = (BuildJob) iterator.next();
200             renderBuildJob( buildJob, locale );
201         }
202 
203         sink.table_();
204 
205         sink.body_();
206 
207         sink.flush();
208         sink.close();
209     }
210 
211     private void constructSummarySection( List /* BuildJob */buildJobs, Locale locale )
212     {
213         Sink sink = getSink();
214 
215         sink.section2();
216         sink.sectionTitle2();
217 
218         sink.text( getText( locale, "report.invoker.summary.title" ) );
219 
220         sink.sectionTitle2_();
221         sink.section2_();
222 
223         // ------------------------------------------------------------------------
224         // Building a table with
225         // it number | succes nb | failed nb | Success rate | total time | avg time
226         // ------------------------------------------------------------------------
227 
228         sink.table();
229         sink.tableRow();
230 
231         sinkTableHeader( sink, getText( locale, "report.invoker.summary.number" ) );
232         sinkTableHeader( sink, getText( locale, "report.invoker.summary.success" ) );
233         sinkTableHeader( sink, getText( locale, "report.invoker.summary.failed" ) );
234         sinkTableHeader( sink, getText( locale, "report.invoker.summary.success.rate" ) );
235         sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.total" ) );
236         sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.avg" ) );
237 
238         int number = buildJobs.size();
239         int success = 0;
240         int failed = 0;
241         double totalTime = 0;
242 
243         for ( Iterator iterator = buildJobs.iterator(); iterator.hasNext(); )
244         {
245             BuildJob buildJob = (BuildJob) iterator.next();
246             if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) )
247             {
248                 success++;
249             }
250             else if ( !BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) )
251             {
252                 failed++;
253             }
254             totalTime += buildJob.getTime();
255         }
256 
257         sink.tableRow_();
258         sink.tableRow();
259 
260         sinkCell( sink, Integer.toString( number ) );
261         sinkCell( sink, Integer.toString( success ) );
262         sinkCell( sink, Integer.toString( failed ) );
263 
264         if ( success + failed > 0 )
265         {
266             sinkCell( sink, percentFormat.format( (double) success / ( success + failed ) ) );
267         }
268         else
269         {
270             sinkCell( sink, "" );
271         }
272 
273         sinkCell( sink, secondsFormat.format( totalTime ) );
274 
275         sinkCell( sink, secondsFormat.format( totalTime / number ) );
276 
277         sink.tableRow_();
278         sink.table_();
279 
280     }
281 
282     private void renderBuildJob( BuildJob buildJob, Locale locale )
283     {
284         Sink sink = getSink();
285         sink.tableRow();
286         StringBuffer buffer = new StringBuffer();
287         if ( !StringUtils.isEmpty( buildJob.getName() ) && !StringUtils.isEmpty( buildJob.getDescription() ) )
288         {
289             buffer.append( buildJob.getName() );
290             buffer.append( " : " );
291             buffer.append( buildJob.getDescription() );
292         }
293         else
294         {
295             buffer.append( buildJob.getProject() );
296         }
297         sinkCell( sink, buffer.toString() );
298         // FIXME image
299         sinkCell( sink, buildJob.getResult() );
300         sinkCell( sink, secondsFormat.format( buildJob.getTime() ) );
301         sinkCell( sink, buildJob.getFailureMessage() );
302         sink.tableRow_();
303     }
304 
305     protected String getOutputDirectory()
306     {
307         return outputDirectory.getAbsolutePath();
308     }
309 
310     protected MavenProject getProject()
311     {
312         return project;
313     }
314 
315     protected Renderer getSiteRenderer()
316     {
317         return siteRenderer;
318     }
319 
320     public String getDescription( Locale locale )
321     {
322         return getText( locale, "report.invoker.result.description" );
323     }
324 
325     public String getName( Locale locale )
326     {
327         return getText( locale, "report.invoker.result.name" );
328     }
329 
330     public String getOutputName()
331     {
332         return "invoker-report";
333     }
334 
335     public boolean canGenerateReport()
336     {
337         return ReportUtils.getReportFiles( reportsDirectory ).length > 0;
338     }
339 
340     private String getText( Locale locale, String key )
341     {
342         return i18n.getString( "invoker-report", locale, key );
343     }
344 
345     private void sinkTableHeader( Sink sink, String header )
346     {
347         sink.tableHeaderCell();
348         sink.text( header );
349         sink.tableHeaderCell_();
350     }
351 
352     private void sinkCell( Sink sink, String text )
353     {
354         sink.tableCell();
355         sink.text( text );
356         sink.tableCell_();
357     }
358 
359 }