/*
* Sonar C# Plugin :: Gallio
* Copyright (C) 2010 Jose Chillan, Alexandre Victoor and SonarSource
* dev@sonar.codehaus.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.csharp.gallio;
import java.io.File;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.DependsUpon;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.measures.PropertiesBuilder;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.utils.ParsingUtils;
import org.sonar.dotnet.tools.commons.utils.FileFinder;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioProject;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioSolution;
import org.sonar.plugins.csharp.api.CSharpConfiguration;
import org.sonar.plugins.csharp.api.MicrosoftWindowsEnvironment;
import org.sonar.plugins.csharp.api.sensor.AbstractRegularCSharpSensor;
import org.sonar.plugins.csharp.gallio.results.coverage.CoverageResultParser;
import org.sonar.plugins.csharp.gallio.results.coverage.model.Coverable;
import org.sonar.plugins.csharp.gallio.results.coverage.model.FileCoverage;
import org.sonar.plugins.csharp.gallio.results.coverage.model.ParserResult;
import org.sonar.plugins.csharp.gallio.results.coverage.model.ProjectCoverage;
import org.sonar.plugins.csharp.gallio.results.coverage.model.SourceLine;
/**
* Gets the coverage test report and pushes data from it into sonar.
*/
@DependsUpon(GallioConstants.BARRIER_GALLIO_EXECUTED)
public class CoverageReportSensor extends AbstractRegularCSharpSensor {
private static final Logger LOG = LoggerFactory.getLogger(CoverageReportSensor.class);
private final PropertiesBuilder<String, Integer> lineHitsBuilder = new PropertiesBuilder<String, Integer>(
CoreMetrics.COVERAGE_LINE_HITS_DATA);
private CoverageResultParser parser;
private CSharpConfiguration configuration;
private String executionMode;
/**
* Constructs a {@link CoverageReportSensor}.
*
* @param fileSystem
* @param configuration
* @param microsoftWindowsEnvironment
*/
public CoverageReportSensor(CSharpConfiguration configuration, MicrosoftWindowsEnvironment microsoftWindowsEnvironment, CoverageResultParser parser) {
super(microsoftWindowsEnvironment);
this.configuration = configuration;
this.executionMode = configuration.getString(GallioConstants.MODE, "");
this.parser = parser;
}
/**
* {@inheritDoc}
*/
public boolean shouldExecuteOnProject(Project project) {
boolean skipMode = GallioConstants.MODE_SKIP.equalsIgnoreCase(executionMode);
if (skipMode) {
LOG.info("Coverage report analysis won't execute as it is set to 'skip' mode.");
}
return super.shouldExecuteOnProject(project) && !skipMode;
}
@Override
public void analyse(Project project, SensorContext context) {
File workDir = new File(getMicrosoftWindowsEnvironment().getWorkingDirectory());
final File reportFile;
if (GallioConstants.MODE_REUSE_REPORT.equals(executionMode)) {
String reportPath = configuration.getString(GallioConstants.REPORTS_COVERAGE_PATH_KEY, GallioConstants.GALLIO_COVERAGE_REPORT_XML);
reportFile = FileFinder.browse(workDir, reportPath);
LOG.info("Reusing Gallio coverage report: " + reportFile);
} else {
if ( !getMicrosoftWindowsEnvironment().isTestExecutionDone()) {
// This means that we are not in REUSE or SKIP mode, but for some reasons execution has not been done => skip the analysis
LOG.info("Coverage report analysis won't execute as Gallio was not executed.");
return;
}
reportFile = new File(workDir, GallioConstants.GALLIO_COVERAGE_REPORT_XML);
}
if ( !reportFile.isFile()) {
LOG.warn("No Gallio coverage report file found for: " + reportFile.getAbsolutePath());
return;
}
// We parse the file and save the results
parseAndSaveCoverageResults(project, context, reportFile);
}
protected void parseAndSaveCoverageResults(Project project, SensorContext context, File reportFile) {
ParserResult result = parser.parse(project, reportFile);
VisualStudioSolution solution
= getMicrosoftWindowsEnvironment().getCurrentSolution();
ProjectCoverage currentProjectCoverage = null;
for (ProjectCoverage projectCoverage : result.getProjects()) {
VisualStudioProject vsProject
= solution.getProject(projectCoverage.getAssemblyName());
String branch = project.getBranch();
final String vsProjectName;
if (StringUtils.isEmpty(branch)) {
vsProjectName = vsProject.getName();
} else {
vsProjectName = vsProject.getName() + " " +project.getBranch();
}
if (project.getName().equals(vsProjectName)) {
currentProjectCoverage = projectCoverage;
break;
}
}
if (currentProjectCoverage != null) {
// Save data for each file
for (FileCoverage fileCoverage : currentProjectCoverage.getFileCoverageCollection()) {
org.sonar.api.resources.File sonarFile = org.sonar.api.resources.File.fromIOFile(fileCoverage.getFile(), project);
if (context.isIndexed(sonarFile, false)) {
LOG.debug("- Saving coverage information for file {}", sonarFile.getKey());
saveCoverageMeasures(context, fileCoverage, sonarFile);
context.saveMeasure(sonarFile, getHitData(fileCoverage));
}
}
}
}
protected void saveCoverageMeasures(SensorContext context, Coverable coverageData, Resource<?> resource) {
double coverage = coverageData.getCoverage();
context.saveMeasure(resource, TestMetrics.ELOC, (double) coverageData.getCountLines());
context.saveMeasure(resource, CoreMetrics.LINES_TO_COVER, (double) coverageData.getCountLines());
context.saveMeasure(resource, CoreMetrics.UNCOVERED_LINES, (double) coverageData.getCountLines() - coverageData.getCoveredLines());
context.saveMeasure(resource, CoreMetrics.COVERAGE, convertPercentage(coverage));
// LINE_COVERAGE & COVERAGE should not be the same: need to have BRANCH_COVERAGE
context.saveMeasure(resource, CoreMetrics.LINE_COVERAGE, convertPercentage(coverage));
}
/*
* Generates a measure that contains the visits of each line of the source file.
*/
protected Measure getHitData(FileCoverage coverable) {
lineHitsBuilder.clear();
Map<Integer, SourceLine> lines = coverable.getLines();
for (SourceLine line : lines.values()) {
int lineNumber = line.getLineNumber();
int countVisits = line.getCountVisits();
lineHitsBuilder.add(Integer.toString(lineNumber), countVisits);
}
return lineHitsBuilder.build().setPersistenceMode(PersistenceMode.DATABASE);
}
protected double convertPercentage(Number percentage) {
return ParsingUtils.scaleValue(percentage.doubleValue() * 100.0);
}
}