blob: 7792974d018ea5bcd1c3a75306d66f9d74269542 [file] [log] [blame]
package org.apache.maven.plugin.invoker;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugin.invoker.model.BuildJob;
import org.apache.maven.plugin.invoker.model.io.xpp3.BuildJobXpp3Reader;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Generate a report based on the results of the Maven invocations. <strong>Note:</strong> This mojo doesn't fork any
* lifecycle, if you have a clean working copy, you have to use a command like
* <code>mvn clean integration-test site</code> to ensure the build results are present when this goal is invoked.
*
* @author Olivier Lamy
* @since 1.4
*/
@Mojo( name = "report", threadSafe = true )
public class InvokerReport
extends AbstractMavenReport
{
/**
* The Maven Project.
*/
@Parameter( defaultValue = "${project}", readonly = true, required = true )
protected MavenProject project;
/**
* Doxia Site Renderer component.
*/
@Component
protected Renderer siteRenderer;
/**
* Internationalization component.
*/
@Component
protected I18N i18n;
/**
* The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from
* the command line. If the goal is run indirectly as part of a site generation, the output directory configured in
* the Maven Site Plugin is used instead.
*/
@Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true )
protected File outputDirectory;
/**
* Base directory where all build reports have been written to.
*/
@Parameter( defaultValue = "${project.build.directory}/invoker-reports", property = "invoker.reportsDirectory" )
private File reportsDirectory;
/**
* The number format used to print percent values in the report locale.
*/
private NumberFormat percentFormat;
/**
* The number format used to print time values in the report locale.
*/
private NumberFormat secondsFormat;
protected void executeReport( Locale locale )
throws MavenReportException
{
DecimalFormatSymbols symbols = new DecimalFormatSymbols( locale );
percentFormat = new DecimalFormat( getText( locale, "report.invoker.format.percent" ), symbols );
secondsFormat = new DecimalFormat( getText( locale, "report.invoker.format.seconds" ), symbols );
Sink sink = getSink();
sink.head();
sink.title();
sink.text( getText( locale, "report.invoker.result.title" ) );
sink.title_();
sink.head_();
sink.body();
sink.section1();
sink.sectionTitle1();
sink.text( getText( locale, "report.invoker.result.title" ) );
sink.sectionTitle1_();
sink.paragraph();
sink.text( getText( locale, "report.invoker.result.description" ) );
sink.paragraph_();
sink.section1_();
// ----------------------------------
// build buildJob beans
// ----------------------------------
File[] reportFiles = ReportUtils.getReportFiles( reportsDirectory );
if ( reportFiles.length <= 0 )
{
getLog().info( "no invoker report files found, skip report generation" );
return;
}
List<BuildJob> buildJobs = new ArrayList<BuildJob>( reportFiles.length );
for ( File reportFile : reportFiles )
{
try
{
BuildJobXpp3Reader reader = new BuildJobXpp3Reader();
buildJobs.add( reader.read( ReaderFactory.newXmlReader( reportFile ) ) );
}
catch ( XmlPullParserException e )
{
throw new MavenReportException( "Failed to parse report file: " + reportFile, e );
}
catch ( IOException e )
{
throw new MavenReportException( "Failed to read report file: " + reportFile, e );
}
}
// ----------------------------------
// summary
// ----------------------------------
constructSummarySection( buildJobs, locale );
// ----------------------------------
// per file/it detail
// ----------------------------------
sink.section2();
sink.sectionTitle2();
sink.text( getText( locale, "report.invoker.detail.title" ) );
sink.sectionTitle2_();
sink.section2_();
// detail tests table header
sink.table();
sink.tableRow();
// -------------------------------------------
// name | Result | time | message
// -------------------------------------------
sinkTableHeader( sink, getText( locale, "report.invoker.detail.name" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.detail.result" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.detail.time" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.detail.message" ) );
sink.tableRow_();
for ( BuildJob buildJob : buildJobs )
{
renderBuildJob( buildJob, locale );
}
sink.table_();
sink.body_();
sink.flush();
sink.close();
}
private void constructSummarySection( List<? extends BuildJob> buildJobs, Locale locale )
{
Sink sink = getSink();
sink.section2();
sink.sectionTitle2();
sink.text( getText( locale, "report.invoker.summary.title" ) );
sink.sectionTitle2_();
sink.section2_();
// ------------------------------------------------------------------------
// Building a table with
// it number | succes nb | failed nb | Success rate | total time | avg time
// ------------------------------------------------------------------------
sink.table();
sink.tableRow();
sinkTableHeader( sink, getText( locale, "report.invoker.summary.number" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.summary.success" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.summary.failed" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.summary.skipped" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.summary.success.rate" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.total" ) );
sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.avg" ) );
int number = buildJobs.size();
int success = 0;
int failed = 0;
int skipped = 0;
double totalTime = 0;
for ( BuildJob buildJob : buildJobs )
{
if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) )
{
success++;
}
else if ( BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) )
{
skipped++;
}
else
{
failed++;
}
totalTime += buildJob.getTime();
}
sink.tableRow_();
sink.tableRow();
sinkCell( sink, Integer.toString( number ) );
sinkCell( sink, Integer.toString( success ) );
sinkCell( sink, Integer.toString( failed ) );
sinkCell( sink, Integer.toString( skipped ) );
if ( success + failed > 0 )
{
sinkCell( sink, percentFormat.format( (double) success / ( success + failed ) ) );
}
else
{
sinkCell( sink, "" );
}
sinkCell( sink, secondsFormat.format( totalTime ) );
sinkCell( sink, secondsFormat.format( totalTime / number ) );
sink.tableRow_();
sink.table_();
}
private void renderBuildJob( BuildJob buildJob, Locale locale )
{
Sink sink = getSink();
sink.tableRow();
StringBuilder buffer = new StringBuilder();
if ( !StringUtils.isEmpty( buildJob.getName() ) && !StringUtils.isEmpty( buildJob.getDescription() ) )
{
buffer.append( buildJob.getName() );
buffer.append( " : " );
buffer.append( buildJob.getDescription() );
}
else
{
buffer.append( buildJob.getProject() );
}
sinkCell( sink, buffer.toString() );
// FIXME image
sinkCell( sink, buildJob.getResult() );
sinkCell( sink, secondsFormat.format( buildJob.getTime() ) );
sinkCell( sink, buildJob.getFailureMessage() );
sink.tableRow_();
}
protected String getOutputDirectory()
{
return outputDirectory.getAbsolutePath();
}
protected MavenProject getProject()
{
return project;
}
protected Renderer getSiteRenderer()
{
return siteRenderer;
}
public String getDescription( Locale locale )
{
return getText( locale, "report.invoker.result.description" );
}
public String getName( Locale locale )
{
return getText( locale, "report.invoker.result.name" );
}
public String getOutputName()
{
return "invoker-report";
}
public boolean canGenerateReport()
{
return ReportUtils.getReportFiles( reportsDirectory ).length > 0;
}
private String getText( Locale locale, String key )
{
return i18n.getString( "invoker-report", locale, key );
}
private void sinkTableHeader( Sink sink, String header )
{
sink.tableHeaderCell();
sink.text( header );
sink.tableHeaderCell_();
}
private void sinkCell( Sink sink, String text )
{
sink.tableCell();
sink.text( text );
sink.tableCell_();
}
}