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.List;
29  import java.util.Locale;
30  
31  import org.apache.maven.doxia.sink.Sink;
32  import org.apache.maven.doxia.siterenderer.Renderer;
33  import org.apache.maven.plugin.invoker.model.BuildJob;
34  import org.apache.maven.plugin.invoker.model.io.xpp3.BuildJobXpp3Reader;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.reporting.AbstractMavenReport;
37  import org.apache.maven.reporting.MavenReportException;
38  import org.codehaus.plexus.i18n.I18N;
39  import org.codehaus.plexus.util.ReaderFactory;
40  import org.codehaus.plexus.util.StringUtils;
41  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
42  
43  /**
44   * Generate a report based on the results of the Maven invocations. <strong>Note:</strong> This mojo doesn't fork any
45   * lifecycle, if you have a clean working copy, you have to use a command like
46   * <code>mvn clean integration-test site</code> to ensure the build results are present when this goal is invoked.
47   * 
48   * @goal report
49   * @author <a href="mailto:olamy@apache.org">olamy</a>
50   * @threadSafe
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<BuildJob> buildJobs = new ArrayList<BuildJob>( 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 ( BuildJob buildJob : buildJobs )
198         {
199             renderBuildJob( buildJob, locale );
200         }
201 
202         sink.table_();
203 
204         sink.body_();
205 
206         sink.flush();
207         sink.close();
208     }
209 
210     private void constructSummarySection( List<? extends BuildJob> buildJobs, Locale locale )
211     {
212         Sink sink = getSink();
213 
214         sink.section2();
215         sink.sectionTitle2();
216 
217         sink.text( getText( locale, "report.invoker.summary.title" ) );
218 
219         sink.sectionTitle2_();
220         sink.section2_();
221 
222         // ------------------------------------------------------------------------
223         // Building a table with
224         // it number | succes nb | failed nb | Success rate | total time | avg time
225         // ------------------------------------------------------------------------
226 
227         sink.table();
228         sink.tableRow();
229 
230         sinkTableHeader( sink, getText( locale, "report.invoker.summary.number" ) );
231         sinkTableHeader( sink, getText( locale, "report.invoker.summary.success" ) );
232         sinkTableHeader( sink, getText( locale, "report.invoker.summary.failed" ) );
233         sinkTableHeader( sink, getText( locale, "report.invoker.summary.success.rate" ) );
234         sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.total" ) );
235         sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.avg" ) );
236 
237         int number = buildJobs.size();
238         int success = 0;
239         int failed = 0;
240         double totalTime = 0;
241 
242         for ( BuildJob buildJob : buildJobs )
243         {
244             if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) )
245             {
246                 success++;
247             }
248             else if ( !BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) )
249             {
250                 failed++;
251             }
252             totalTime += buildJob.getTime();
253         }
254 
255         sink.tableRow_();
256         sink.tableRow();
257 
258         sinkCell( sink, Integer.toString( number ) );
259         sinkCell( sink, Integer.toString( success ) );
260         sinkCell( sink, Integer.toString( failed ) );
261 
262         if ( success + failed > 0 )
263         {
264             sinkCell( sink, percentFormat.format( (double) success / ( success + failed ) ) );
265         }
266         else
267         {
268             sinkCell( sink, "" );
269         }
270 
271         sinkCell( sink, secondsFormat.format( totalTime ) );
272 
273         sinkCell( sink, secondsFormat.format( totalTime / number ) );
274 
275         sink.tableRow_();
276         sink.table_();
277 
278     }
279 
280     private void renderBuildJob( BuildJob buildJob, Locale locale )
281     {
282         Sink sink = getSink();
283         sink.tableRow();
284         StringBuffer buffer = new StringBuffer();
285         if ( !StringUtils.isEmpty( buildJob.getName() ) && !StringUtils.isEmpty( buildJob.getDescription() ) )
286         {
287             buffer.append( buildJob.getName() );
288             buffer.append( " : " );
289             buffer.append( buildJob.getDescription() );
290         }
291         else
292         {
293             buffer.append( buildJob.getProject() );
294         }
295         sinkCell( sink, buffer.toString() );
296         // FIXME image
297         sinkCell( sink, buildJob.getResult() );
298         sinkCell( sink, secondsFormat.format( buildJob.getTime() ) );
299         sinkCell( sink, buildJob.getFailureMessage() );
300         sink.tableRow_();
301     }
302 
303     protected String getOutputDirectory()
304     {
305         return outputDirectory.getAbsolutePath();
306     }
307 
308     protected MavenProject getProject()
309     {
310         return project;
311     }
312 
313     protected Renderer getSiteRenderer()
314     {
315         return siteRenderer;
316     }
317 
318     public String getDescription( Locale locale )
319     {
320         return getText( locale, "report.invoker.result.description" );
321     }
322 
323     public String getName( Locale locale )
324     {
325         return getText( locale, "report.invoker.result.name" );
326     }
327 
328     public String getOutputName()
329     {
330         return "invoker-report";
331     }
332 
333     public boolean canGenerateReport()
334     {
335         return ReportUtils.getReportFiles( reportsDirectory ).length > 0;
336     }
337 
338     private String getText( Locale locale, String key )
339     {
340         return i18n.getString( "invoker-report", locale, key );
341     }
342 
343     private void sinkTableHeader( Sink sink, String header )
344     {
345         sink.tableHeaderCell();
346         sink.text( header );
347         sink.tableHeaderCell_();
348     }
349 
350     private void sinkCell( Sink sink, String text )
351     {
352         sink.tableCell();
353         sink.text( text );
354         sink.tableCell_();
355     }
356 
357 }