Index: src/main/java/org/sonar/plugins/cxx/CxxPlugin.java =================================================================== --- src/main/java/org/sonar/plugins/cxx/CxxPlugin.java (revision 4862) +++ src/main/java/org/sonar/plugins/cxx/CxxPlugin.java (working copy) @@ -27,6 +27,7 @@ import org.sonar.api.Property; import org.sonar.plugins.cxx.CxxLanguage; import org.sonar.plugins.cxx.xunit.CxxXunitSensor; +import org.sonar.plugins.cxx.bullseyecoverage.CxxBullseyeCoverageSensor; import org.sonar.plugins.cxx.gcovr.CxxGcovrSensor; import org.sonar.plugins.cxx.cppcheck.CxxCppCheckSensor; import org.sonar.plugins.cxx.cppcheck.CxxCppCheckRuleRepository; @@ -97,6 +98,7 @@ l.add(CxxCppCheckSensor.class); l.add(CxxCppCheckProfile.class); l.add(CxxCppNcssSensor.class); + l.add(CxxBullseyeCoverageSensor.class); l.add(CxxVeraxxRuleRepository.class); l.add(CxxVeraxxSensor.class); l.add(CxxVeraxxProfile.class); Index: src/main/java/org/sonar/plugins/cxx/bullseyecoverage/CxxBullseyeCoverageSensor.java =================================================================== --- src/main/java/org/sonar/plugins/cxx/bullseyecoverage/CxxBullseyeCoverageSensor.java (revision 0) +++ src/main/java/org/sonar/plugins/cxx/bullseyecoverage/CxxBullseyeCoverageSensor.java (revision 0) @@ -0,0 +1,405 @@ +/* + * 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.bullseyecoverage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.xml.stream.XMLStreamException; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.io.input.TeeInputStream; +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.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.PersistenceMode; +import org.sonar.api.measures.PropertiesBuilder; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.resources.Project; +import org.sonar.api.rules.RuleFinder; +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; + +public class CxxBullseyeCoverageSensor extends ReportsHelper implements Sensor { + + private String command="blabla "; + private RuleFinder ruleFinder; + private RulesProfile rulesProfile; + + private static Logger logger = LoggerFactory + .getLogger(CxxBullseyeCoverageSensor.class); + + + public CxxBullseyeCoverageSensor(RuleFinder ruleFinder, RulesProfile rulesProfile, Configuration conf/*, CxxCppNcssProfileExporter profileExporter*/) + { + this.ruleFinder = ruleFinder; + this.command = conf.getString("sonar.cxx.bullseyecoverage.command"); + this.rulesProfile = rulesProfile; + } + + public boolean shouldExecuteOnProject(Project project) { + return 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 = "bulleyescoverage"; + public static final String DEFAULT_GCOVR_REPORTS_DIR = "bullseyecoverage-reports"; + public static final String DEFAULT_REPORTS_FILE_PATTERN = "**/bullseyecoverage-result-*.xml"; + public static final String CPPNCSS_RULES_FILE = "bullseyecoverage_conf"; + + @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; + } + + public void analyse(Project project, SensorContext context) { + + Process p; + try { + if (this.command != null && !this.command.isEmpty()) { + File resultOutputFile = new File(project.getFileSystem().getSonarWorkingDirectory() + + "/bullseyecoverage-result.xml"); + logger.debug("Output result to " + resultOutputFile.getAbsolutePath()); + logger.debug(this.command); + p = Runtime.getRuntime().exec(this.command); + + FileOutputStream fos = new FileOutputStream(resultOutputFile); + TeeInputStream tis = new TeeInputStream(p.getInputStream(), fos, true); + + parseReport(project, tis, context); + } else { + File reportDirectory = getReportsDirectory(project); + if (reportDirectory != null) { + File reports[] = getReports(project, reportDirectory); + for (File report : reports) { + FileInputStream file = new FileInputStream(report); + parseReport(project, file, context); + } + } + } + } catch (IOException ex) { + logger.error("Error e=", ex); + } + } + + private class FileData { + FileData(String name) { + FileName = name; + } + + private class Probe { + Integer line; + String kind; + String event; + + public Integer geLine() { + return line; + } + + public String getKind() { + return kind; + } + + public String getEvent() { + return event; + } + + Probe(Integer line, String kind, String event) { + this.line = line; + this.kind = kind; + this.event = event; + } + } + + public void saveMetric(Project project, SensorContext context) { + + CxxFile file = CxxFile.fromFileName(project, FileName, getReportsIncludeSourcePath(project), false); + if (context.getResource(file) != null) { + Object t[] = { FileName }; + logger.info("File managed : {}", t); + + context.saveMeasure(file, CoreMetrics.LINES_TO_COVER, getCdTotal()); + context.saveMeasure(file, CoreMetrics.UNCOVERED_LINES, getCdTotal()); + + double num = getFnCov() + getCdCov(); + double den = getFnTotal() + getCdTotal(); + double coverage = (den == 0.0) ? 100.0 : (num / den) * 100.0; + + context.saveMeasure(file, CoreMetrics.COVERAGE, coverage); + + PropertiesBuilder lineHitsBuilder = new PropertiesBuilder( + CoreMetrics.COVERAGE_LINE_HITS_DATA); + + PropertiesBuilder branchHitsBuilder = new PropertiesBuilder( + CoreMetrics.BRANCH_COVERAGE_HITS_DATA); + for (Map.Entry> e : ComplexityPerMethod.entrySet()) { + Iterator i = e.getValue().iterator(); + while (i.hasNext()) { + Probe p = i.next(); + if (p.getKind().equalsIgnoreCase("decision")) + { + branchHitsBuilder.add(Integer.toString(p.geLine()), p.getEvent()); + + } else + if (!p.getEvent().equalsIgnoreCase("none")) + { + lineHitsBuilder.add(Integer.toString(p.geLine()), 1); + } else + { + lineHitsBuilder.add(Integer.toString(p.geLine()), 0); + } + } + } + context.saveMeasure(file, lineHitsBuilder.build().setPersistenceMode(PersistenceMode.DATABASE)); + + context.saveMeasure(file, CoreMetrics.CONDITIONS_TO_COVER, getCdTotal()); + context.saveMeasure(file, CoreMetrics.UNCOVERED_CONDITIONS, getCdTotal()-getCdCov()); + + context.saveMeasure(file, branchHitsBuilder.build().setPersistenceMode(PersistenceMode.DATABASE)); + } else { + Object t[] = { FileName }; + logger.warn("File not managed : {}", t); + } + } + + public void addProbe(String funcName, Integer line, String kind, String event) { + List data = ComplexityPerMethod.get(funcName); + if (data == null) { + data = new LinkedList(); + ComplexityPerMethod.put(funcName, data); + } + data.add(new Probe(line, kind, event)); + } + + public double getFnCov() { + return fn_cov; + } + + public void setFnCov(double fn_cov) { + this.fn_cov = fn_cov; + } + + public double getFnTotal() { + return fn_total; + } + + public void setFnTotal(double fn_total) { + this.fn_total = fn_total; + } + + public double getCdCov() { + return cd_cov; + } + + public void setCdCov(double cd_cov) { + this.cd_cov = cd_cov; + } + + public double getCdTotal() { + return cd_total; + } + + public void setCdTotal(double cd_total) { + this.cd_total = cd_total; + } + + private double fn_cov; + private double fn_total; + private double cd_cov; + private double cd_total; + private String FileName; + private Map> ComplexityPerMethod = new HashMap>(); + } + + /** + * Parse the stream of Bullseyes XML report + * + * @param project + * @param xmlStream + * - This stream will be closed at the end of this method + * @param context + */ + private void parseReport(final Project project, InputStream xmlStream, + final SensorContext context) { + try { + logger.info("parsing Bullseyes XML stream{}"); + StaxParser parser = new StaxParser( + new StaxParser.XmlStreamHandler() { + + public void stream(SMHierarchicCursor rootCursor) + throws XMLStreamException { + try { + Map fileDataPerFilename = new HashMap(); + rootCursor.advance(); + String refPath = rootCursor.getAttrValue("dir"); + collectCoverage(project, refPath, rootCursor.childElementCursor("folder"), fileDataPerFilename, context); + for (FileData d : fileDataPerFilename.values()) { + d.saveMetric(project, context); + } + } catch (ParseException e) { + throw new XMLStreamException(e); + } + } + }); + parser.parse(xmlStream); + } catch (XMLStreamException e) { + throw new XmlParserException(e); + } finally { + try { + if (xmlStream != null) { + xmlStream.close(); + } + } catch (IOException ex) { + logger.error("Can't close the xml stream", ex); + } + } + } + + private void collectCoverage(Project project, String refPath, SMInputCursor folder, Map fileDataPerFilename, SensorContext context) throws ParseException, XMLStreamException { + LinkedList path = new LinkedList(); + while (folder.getNext() != null) { + String folderName = folder.getAttrValue("name"); + path.add(folderName); + SMInputCursor child = folder.childElementCursor(); + while (child.getNext() != null) { + String folderChildName = child.getAttrValue("name"); + path.add(folderChildName); + recTreeWalk(project, refPath, child, fileDataPerFilename, context, path); + path.removeLast(); + } + path.removeLast(); + } + } + + private void probWalk(Project project, SMInputCursor prob, Map fileDataPerFilename, FileData data, SensorContext context, LinkedList path, String funcName) + throws ParseException, XMLStreamException { + String line = prob.getAttrValue("line"); + String kind = prob.getAttrValue("kind"); + String event = prob.getAttrValue("event"); + data.addProbe(funcName, Integer.parseInt(line), kind, event); + } + + private void funcWalk(Project project, SMInputCursor func, Map fileDataPerFilename, FileData data, SensorContext context, LinkedList path, String funcName) throws ParseException, XMLStreamException + { + SMInputCursor prob = func.childElementCursor(); + while (prob.getNext() != null) { + String probName = prob.getLocalName(); + if( probName.equalsIgnoreCase("probe") ) + { + probWalk(project, prob, fileDataPerFilename, data, context, path, funcName); + } + } + } + + private void fileWalk(Project project, SMInputCursor file, Map fileDataPerFilename, FileData data, SensorContext context, LinkedList path) throws ParseException, XMLStreamException + { + SMInputCursor func = file.childElementCursor(); + while (func.getNext() != null) { + String funcName = func.getLocalName(); + String name = func.getAttrValue("name"); + if( funcName.equalsIgnoreCase("fn") ) + { + funcWalk(project, func, fileDataPerFilename, data, context, path, name); + } + } + } + + private void recTreeWalk(Project project, String refPath, SMInputCursor folder, Map fileDataPerFilename, SensorContext context, LinkedList path) throws ParseException, XMLStreamException + { + SMInputCursor child = folder.childElementCursor(); + while (child.getNext() != null) { + String folderChildName = child.getLocalName(); + String name = child.getAttrValue("name"); + path.add(name); + if( folderChildName.equalsIgnoreCase("src") ) + { + String fileName = ""; + Iterator iterator = path.iterator(); + while (iterator.hasNext()) { + fileName += "/" + iterator.next(); + } + + String theFileName = refPath + fileName; + FileData data = fileDataPerFilename.get(theFileName); + if (data == null) { + data = new FileData(theFileName); + data.setFnCov(Double.parseDouble(child.getAttrValue("fn_cov"))); + data.setFnTotal(Double.parseDouble(child.getAttrValue("fn_total"))); + data.setCdCov(Double.parseDouble(child.getAttrValue("cd_cov"))); + data.setCdTotal(Double.parseDouble(child.getAttrValue("cd_total"))); + fileDataPerFilename.put(theFileName, data); + } + fileWalk(project , child, fileDataPerFilename, data, context, path); + } + else + { + recTreeWalk(project, refPath, child, fileDataPerFilename, context, path); + } + path.removeLast(); + } + } + + private boolean fileExist(SensorContext context, CxxFile file) { + return context.getResource(file) != null; + } +} +