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