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.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.Mojo;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.project.MavenProject;
39  import org.apache.maven.reporting.AbstractMavenReport;
40  import org.apache.maven.reporting.MavenReportException;
41  import org.codehaus.plexus.i18n.I18N;
42  import org.codehaus.plexus.util.ReaderFactory;
43  import org.codehaus.plexus.util.StringUtils;
44  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
45  
46  /**
47   * Generate a report based on the results of the Maven invocations. <strong>Note:</strong> This mojo doesn't fork any
48   * lifecycle, if you have a clean working copy, you have to use a command like
49   * <code>mvn clean integration-test site</code> to ensure the build results are present when this goal is invoked.
50   * 
51   * @author Olivier Lamy
52   * @since 1.4
53   */
54  @Mojo( name = "report", threadSafe = true )
55  public class InvokerReport
56      extends AbstractMavenReport
57  {
58      
59      /**
60       * The Maven Project.
61       */
62      @Component
63      protected MavenProject project;
64      
65      /**
66       * Doxia Site Renderer component.
67       */
68      @Component
69      protected Renderer siteRenderer;    
70      
71      /**
72       * Internationalization component.
73       */
74      @Component
75      protected I18N i18n;    
76      
77      /**
78       * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from
79       * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in
80       * the Maven Site Plugin is used instead.
81       */
82      @Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true )
83      protected File outputDirectory;    
84      
85      /**
86       * Base directory where all build reports have been written to.
87       */
88      @Parameter( defaultValue = "${project.build.directory}/invoker-reports", property = "invoker.reportsDirectory" )
89      private File reportsDirectory; 
90  
91      /**
92       * The number format used to print percent values in the report locale.
93       */
94      private NumberFormat percentFormat;
95  
96      /**
97       * The number format used to print time values in the report locale.
98       */
99      private NumberFormat secondsFormat;
100 
101     protected void executeReport( Locale locale )
102         throws MavenReportException
103     {
104         DecimalFormatSymbols symbols = new DecimalFormatSymbols( locale );
105         percentFormat = new DecimalFormat( getText( locale, "report.invoker.format.percent" ), symbols );
106         secondsFormat = new DecimalFormat( getText( locale, "report.invoker.format.seconds" ), symbols );
107 
108         Sink sink = getSink();
109 
110         sink.head();
111 
112         sink.title();
113         sink.text( getText( locale, "report.invoker.result.title" ) );
114         sink.title_();
115 
116         sink.head_();
117 
118         sink.body();
119 
120         sink.section1();
121         sink.sectionTitle1();
122         sink.text( getText( locale, "report.invoker.result.title" ) );
123         sink.sectionTitle1_();
124         sink.paragraph();
125         sink.text( getText( locale, "report.invoker.result.description" ) );
126         sink.paragraph_();
127         sink.section1_();
128 
129         // ----------------------------------
130         //  build buildJob beans
131         // ----------------------------------
132         File[] reportFiles = ReportUtils.getReportFiles( reportsDirectory );
133         if ( reportFiles.length <= 0 )
134         {
135             getLog().info( "no invoker report files found, skip report generation" );
136             return;
137         }
138 
139         List<BuildJob> buildJobs = new ArrayList<BuildJob>( reportFiles.length );
140         for ( File reportFile : reportFiles )
141         {
142             try
143             {
144                 BuildJobXpp3Reader reader = new BuildJobXpp3Reader();
145                 buildJobs.add( reader.read( ReaderFactory.newXmlReader( reportFile ) ) );
146             }
147             catch ( XmlPullParserException e )
148             {
149                 throw new MavenReportException( "Failed to parse report file: " + reportFile, e );
150             }
151             catch ( IOException e )
152             {
153                 throw new MavenReportException( "Failed to read report file: " + reportFile, e );
154             }
155         }
156 
157         // ----------------------------------
158         //  summary
159         // ----------------------------------
160         
161         constructSummarySection( buildJobs, locale );
162         
163         // ----------------------------------
164         //  per file/it detail
165         // ----------------------------------        
166 
167         sink.section2();
168         sink.sectionTitle2();
169 
170         sink.text( getText( locale, "report.invoker.detail.title" ) );
171 
172         sink.sectionTitle2_();
173 
174         sink.section2_();
175 
176         // detail tests table header
177         sink.table();
178 
179         sink.tableRow();
180         // -------------------------------------------
181         // name | Result | time | message
182         // -------------------------------------------
183         sinkTableHeader( sink, getText( locale, "report.invoker.detail.name" ) );
184         sinkTableHeader( sink, getText( locale, "report.invoker.detail.result" ) );
185         sinkTableHeader( sink, getText( locale, "report.invoker.detail.time" ) );
186         sinkTableHeader( sink, getText( locale, "report.invoker.detail.message" ) );
187 
188         sink.tableRow_();
189 
190         for ( BuildJob buildJob : buildJobs )
191         {
192             renderBuildJob( buildJob, locale );
193         }
194 
195         sink.table_();
196 
197         sink.body_();
198 
199         sink.flush();
200         sink.close();
201     }
202 
203     private void constructSummarySection( List<? extends BuildJob> buildJobs, Locale locale )
204     {
205         Sink sink = getSink();
206 
207         sink.section2();
208         sink.sectionTitle2();
209 
210         sink.text( getText( locale, "report.invoker.summary.title" ) );
211 
212         sink.sectionTitle2_();
213         sink.section2_();
214 
215         // ------------------------------------------------------------------------
216         // Building a table with
217         // it number | succes nb | failed nb | Success rate | total time | avg time
218         // ------------------------------------------------------------------------
219 
220         sink.table();
221         sink.tableRow();
222 
223         sinkTableHeader( sink, getText( locale, "report.invoker.summary.number" ) );
224         sinkTableHeader( sink, getText( locale, "report.invoker.summary.success" ) );
225         sinkTableHeader( sink, getText( locale, "report.invoker.summary.failed" ) );
226         sinkTableHeader( sink, getText( locale, "report.invoker.summary.skipped" ) );
227         sinkTableHeader( sink, getText( locale, "report.invoker.summary.success.rate" ) );
228         sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.total" ) );
229         sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.avg" ) );
230 
231         int number = buildJobs.size();
232         int success = 0;
233         int failed = 0;
234         int skipped = 0;
235         double totalTime = 0;
236 
237         for ( BuildJob buildJob : buildJobs )
238         {
239             if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) )
240             {
241                 success++;
242             }
243             else if ( BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) )
244             {
245                 skipped++;
246             }
247             else
248             {
249                 failed++;
250             }
251             totalTime += buildJob.getTime();
252         }
253 
254         sink.tableRow_();
255         sink.tableRow();
256 
257         sinkCell( sink, Integer.toString( number ) );
258         sinkCell( sink, Integer.toString( success ) );
259         sinkCell( sink, Integer.toString( failed ) );
260         sinkCell( sink, Integer.toString( skipped ) );
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         StringBuilder buffer = new StringBuilder();
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 }