/* * Sonar Cxx Plugin, open source software quality management tool. * Copyright (C) 2010 SonarSource * mailto:contact AT sonarsource DOT com * 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.xunit; import java.io.File; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.transform.TransformerException; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.AbstractCoverageExtension; import org.sonar.api.batch.DependsUpon; 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.Metric; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.utils.ParsingUtils; 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; /** * Copied from sonar-surefire-plugin with modifications: JavaFile replaced by * C++ getUnitTestResource use Project to locate C++ File getReports use * FileSetManager for smarter report select using new now plugin configuration * use Fileset (ex ** /TEST*.xml) * */ public class CxxXunitSensor extends ReportsHelper implements Sensor { private static Logger logger = LoggerFactory .getLogger(CxxXunitSensor.class); @DependsUpon public Class<?> dependsUponCoverageSensors() { return AbstractCoverageExtension.class; } public boolean shouldExecuteOnProject(Project project) { return project.getAnalysisType().isDynamic(true) && 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 = "xunit"; public static final String DEFAULT_XUNIT_REPORTS_DIR = "xunit-reports"; public static final String DEFAULT_REPORTS_FILE_PATTERN = "**/xunit-result-*.xml"; @Override protected String getArtifactId() { return ARTIFACT_ID; } @Override protected String getSensorId() { return SENSOR_ID; } @Override protected String getDefaultReportsDir() { return DEFAULT_XUNIT_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) { File reportDirectory = getReportsDirectory(project); if (reportDirectory != null) { File[] reports = getReports(project, reportDirectory); if (reports.length == 0) { insertZeroWhenNoReports(project, context); } else { parseReport(project, reports, context); } } } private void insertZeroWhenNoReports(Project pom, SensorContext context) { if (!StringUtils.equalsIgnoreCase("pom", pom.getPackaging())) { context.saveMeasure(CoreMetrics.TESTS, 0.0); } } private void parseReport(Project project, File[] reports, SensorContext context) { Set<TestSuiteReport> analyzedReports = new HashSet<TestSuiteReport>(); try { for (File report : reports) { logger.info("parsing {}", report); TestSuiteParser parserHandler = new TestSuiteParser(); StaxParser parser = new StaxParser(parserHandler, false); parser.parse(report); for (TestSuiteReport fileReport : parserHandler .getParsedReports()) { if (!fileReport.isValid() || analyzedReports.contains(fileReport)) { continue; } if (fileReport.getTests() > 0) { double testsCount = fileReport.getTests() - fileReport.getSkipped(); saveClassMeasure(project, context, fileReport, CoreMetrics.SKIPPED_TESTS, fileReport .getSkipped()); saveClassMeasure(project, context, fileReport, CoreMetrics.TESTS, testsCount); saveClassMeasure(project, context, fileReport, CoreMetrics.TEST_ERRORS, fileReport.getErrors()); saveClassMeasure(project, context, fileReport, CoreMetrics.TEST_FAILURES, fileReport .getFailures()); saveClassMeasure(project, context, fileReport, CoreMetrics.TEST_EXECUTION_TIME, fileReport .getTimeMS()); double passedTests = testsCount - fileReport.getErrors() - fileReport.getFailures(); if (testsCount > 0) { double percentage = passedTests * 100d / testsCount; saveClassMeasure(project, context, fileReport, CoreMetrics.TEST_SUCCESS_DENSITY, ParsingUtils.scaleValue(percentage)); } saveTestsDetails(project, context, fileReport); analyzedReports.add(fileReport); } } } } catch (Exception e) { throw new XmlParserException("Can not parse xunit reports", e); } } private void saveTestsDetails(Project project, SensorContext context, TestSuiteReport fileReport) throws TransformerException { StringBuilder testCaseDetails = new StringBuilder(256); testCaseDetails.append("<tests-details>"); List<TestCaseDetails> details = fileReport.getDetails(); for (TestCaseDetails detail : details) { testCaseDetails.append("<testcase status=\"").append( detail.getStatus()).append("\" time=\"").append( detail.getTimeMS()).append("\" name=\"").append( detail.getName()).append("\""); boolean isError = detail.getStatus().equals( TestCaseDetails.STATUS_ERROR); if (isError || detail.getStatus() .equals(TestCaseDetails.STATUS_FAILURE)) { testCaseDetails.append(">").append( isError ? "<error message=\"" : "<failure message=\"") .append( StringEscapeUtils.escapeXml(detail .getErrorMessage())).append("\">") .append("<![CDATA[").append( StringEscapeUtils.escapeXml(detail .getStackTrace())).append("]]>") .append(isError ? "</error>" : "</failure>").append( "</testcase>"); } else { testCaseDetails.append("/>"); } } testCaseDetails.append("</tests-details>"); context.saveMeasure(getUnitTestResource(project, fileReport), new Measure(CoreMetrics.TEST_DATA, testCaseDetails.toString())); } private void saveClassMeasure(Project project, SensorContext context, TestSuiteReport fileReport, Metric metric, double value) { if (!Double.isNaN(value)) { context.saveMeasure(getUnitTestResource(project, fileReport), metric, value); } } private Resource<?> getUnitTestResource(Project project, TestSuiteReport fileReport) { logger.debug("Unit Test Resource key = {}", fileReport.getClassKey()); return CxxFile.fromFileName(project, fileReport.getClassKey(), true); } @Override public String toString() { return getClass().getSimpleName(); } }