/* * Sonar Cxx Plugin, open source software quality management tool. * Copyright (C) 2010 - 2011, Neticoa SAS France - Tous droits réservés. * Author(s) : Franck Bonin, Neticoa SAS France. * * Sonar Cxx Plugin 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. * * Sonar Cxx Plugin 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 Sonar Cxx Plugin; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.plugins.cxx.gcovr; import org.apache.commons.lang.StringUtils; import org.codehaus.staxmate.in.SMHierarchicCursor; import org.codehaus.staxmate.in.SMInputCursor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.AbstractCoverageExtension; import org.sonar.api.batch.Sensor; 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.utils.StaxParser; import org.sonar.api.utils.XmlParserException; import org.sonar.plugins.cxx.CxxFile; import org.sonar.plugins.cxx.CxxPlugin; import org.sonar.plugins.cxx.utils.ReportsHelper; import java.io.File; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamException; import static java.util.Locale.ENGLISH; import static org.sonar.api.utils.ParsingUtils.parseNumber; import static org.sonar.api.utils.ParsingUtils.scaleValue; /** * TODO copied from sonar-cobertura-plugin with modifications: JavaFile replaced * by C++, fixed SONARPLUGINS-696 C++ collectFileMeasures use Project to locate * C++ File getReports use FileSetManager for smarter report select using new * now plugin configuration use Fileset (ex ** /coverage.xml) */ public class CxxGcovrSensor extends AbstractCoverageExtension implements Sensor { private static Logger logger = LoggerFactory .getLogger(CxxGcovrSensor.class); @Override public boolean shouldExecuteOnProject(Project project) { return super.shouldExecuteOnProject(project) && CxxPlugin.KEY.equals(project.getLanguageKey()); } public static final String GROUP_ID = "org.codehaus.mojo"; public static final String ARTIFACT_ID = "cxx-maven-plugin"; public static final String SENSOR_ID = "gcovr"; public static final String DEFAULT_GCOVR_REPORTS_DIR = "gcovr-reports"; public static final String DEFAULT_REPORTS_FILE_PATTERN = "**/gcovr-result-*.xml"; private class GcovrReportsHelper extends ReportsHelper { @Override protected String getArtifactId() { return ARTIFACT_ID; } @Override protected String getSensorId() { return SENSOR_ID; } @Override protected String getDefaultReportsDir() { return DEFAULT_GCOVR_REPORTS_DIR; } @Override protected String getDefaultReportsFilePattern() { return DEFAULT_REPORTS_FILE_PATTERN; } @Override protected String getGroupId() { return GROUP_ID; } @Override protected Logger getLogger() { return logger; } } GcovrReportsHelper reportHelper = new GcovrReportsHelper(); public void analyse(Project project, SensorContext context) { File reportDirectory = reportHelper.getReportsDirectory(project); if (reportDirectory != null) { File reports[] = reportHelper.getReports(project, reportDirectory); Map<String, FileData> fileDataPerFilename = new HashMap<String, FileData>(); for (File report : reports) { parseReport(project, report, context, fileDataPerFilename); } for (FileData cci : fileDataPerFilename.values()) { logger.debug("collectPackageMeasures fileKeyExist? {}", cci.getFile().getKey()); if (fileExist(context, cci.getFile())) { logger.debug("collectPackageMeasures file Exist {}", cci.getFile().getKey()); for (Measure measure : cci.getMeasures()) { logger.debug("collectPackageMeasures mesure value = {}", measure.toString()); context.saveMeasure(cci.getFile(), measure); } } else { logger.warn("collectPackageMeasures file {} IS NOT inventoried ", cci.getFile().getKey()); } } } } private void parseReport(final Project project, File xmlFile, final SensorContext context, final Map<String, FileData> dataPerFilename) { try { logger.info("parsing {}", xmlFile); StaxParser parser = new StaxParser( new StaxParser.XmlStreamHandler() { public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { try { rootCursor.advance(); collectPackageMeasures(project, rootCursor.descendantElementCursor("package"), context, dataPerFilename); } catch (ParseException e) { throw new XMLStreamException(e); } } }); parser.parse(xmlFile); } catch (XMLStreamException e) { throw new XmlParserException(e); } } private void collectPackageMeasures(Project project, SMInputCursor pack, SensorContext context, Map<String, FileData> dataPerFilename) throws ParseException, XMLStreamException { logger.debug("collectPackageMeasures"); while (pack.getNext() != null) { collectFileMeasures(project, pack.descendantElementCursor("class"), dataPerFilename); } } private boolean fileExist(SensorContext context, CxxFile file) { return context.getResource(file) != null; } private void collectFileMeasures(Project project, SMInputCursor clazz, Map<String, FileData> dataPerFilename) throws ParseException, XMLStreamException { logger.debug("collectFileMeasures"); while (clazz.getNext() != null) { String fileName = clazz.getAttrValue("filename"); CxxFile cxxfile = CxxFile.fromFileName(project, fileName, false); String FileKey = cxxfile.getKey(); FileData data = dataPerFilename.get(FileKey); if (data == null) { data = new FileData(cxxfile); dataPerFilename.put(FileKey, data); logger.debug("collectFileMeasures created CXXFILe", data.getFile().getKey()); } collectFileData(clazz, data); } } private void collectFileData(SMInputCursor clazz, FileData data) throws ParseException, XMLStreamException { logger.debug("collectFileData"); SMInputCursor line = clazz.childElementCursor("lines").advance() .childElementCursor("line"); while (line.getNext() != null) { String lineId = line.getAttrValue("number"); data.addLine(lineId, (int) parseNumber(line.getAttrValue("hits"), ENGLISH)); String isBranch = line.getAttrValue("branch"); String text = line.getAttrValue("condition-coverage"); if (StringUtils.equals(isBranch, "true") && StringUtils.isNotBlank(text)) { String[] conditions = StringUtils.split(StringUtils .substringBetween(text, "(", ")"), "/"); data.addConditionLine(lineId, Integer.parseInt(conditions[0]), Integer.parseInt(conditions[1]), StringUtils .substringBefore(text, " ")); } } } private class FileData { private int lines = 0; private int conditions = 0; private int coveredLines = 0; private int coveredConditions = 0; private CxxFile file; private PropertiesBuilder<String, Integer> lineHitsBuilder = new PropertiesBuilder<String, Integer>( CoreMetrics.COVERAGE_LINE_HITS_DATA); private PropertiesBuilder<String, String> branchHitsBuilder = new PropertiesBuilder<String, String>( CoreMetrics.BRANCH_COVERAGE_HITS_DATA); public void addLine(String lineId, int lineHits) { lines++; if (lineHits > 0) { coveredLines++; } Map<String, Integer> props = lineHitsBuilder.getProps(); if (props.containsKey(lineId)) { logger.debug("lineHitsBuilder find pre-existing line"); props.put(lineId, props.get(lineId) + lineHits); } else { lineHitsBuilder.add(lineId, lineHits); } } public void addConditionLine(String lineId, int coveredConditions, int conditions, String label) { this.conditions += conditions; this.coveredConditions += coveredConditions; Map<String, String> props = branchHitsBuilder.getProps(); if (props.containsKey(lineId)) { logger.debug("branchHitsBuilder find pre-existing line"); props.put(lineId, props.get(lineId) + ", " + label); } else { branchHitsBuilder.add(lineId, label); } } public FileData(CxxFile file) { this.file = file; } public List<Measure> getMeasures() { List<Measure> measures = new ArrayList<Measure>(); if (lines > 0) { measures.add(new Measure(CoreMetrics.LINES_TO_COVER, (double) lines)); measures.add(new Measure(CoreMetrics.UNCOVERED_LINES, (double) lines - coveredLines)); measures.add(lineHitsBuilder.build().setPersistenceMode( PersistenceMode.DATABASE)); if (conditions > 0) { measures.add(new Measure(CoreMetrics.CONDITIONS_TO_COVER, (double) conditions)); measures.add(new Measure(CoreMetrics.UNCOVERED_CONDITIONS, (double) conditions - coveredConditions)); measures.add(branchHitsBuilder.build().setPersistenceMode( PersistenceMode.DATABASE)); } } return measures; } public CxxFile getFile() { return file; } } @Override public String toString() { return getClass().getSimpleName(); } }