Skip to content

Commit

Permalink
Implement Surefire directly into the plugin remove old Surefire depen…
Browse files Browse the repository at this point in the history
…dency
  • Loading branch information
miklein committed Nov 10, 2014
1 parent 70a60c1 commit 9a98ba2
Show file tree
Hide file tree
Showing 9 changed files with 618 additions and 60 deletions.
6 changes: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@
<artifactId>sonar-plugin-api</artifactId>
<version>${sonar.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.sonar-plugins.java</groupId>
<artifactId>sonar-surefire-plugin</artifactId>
<version>2.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar-plugins.java</groupId>
<artifactId>sonar-java-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2011 - 2014 All contributors
* [email protected]
*
* 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.scala.surefire;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Map;

import javax.xml.stream.XMLStreamException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
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.utils.ParsingUtils;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.StaxParser;

public class ScalaSurefireParser {

private static final Logger LOG = LoggerFactory.getLogger(ScalaSurefireParser.class);
private final FileSystem fileSystem;

public ScalaSurefireParser(FileSystem fileSystem){
this.fileSystem = fileSystem;
}

public void collect(Project project, SensorContext context, File reportsDir) {
File[] xmlFiles = getReports(reportsDir);

if (xmlFiles.length == 0) {
// See http://jira.codehaus.org/browse/SONAR-2371
if (project.getModules().isEmpty()) {
context.saveMeasure(CoreMetrics.TESTS, 0.0);
}
} else {
parseFiles(context, xmlFiles);
}
}

private File[] getReports(File dir) {
if (dir == null) {
return new File[0];
} else if (!dir.isDirectory()) {
LOG.warn("Reports path not found: " + dir.getAbsolutePath());
return new File[0];
}
File[] unitTestResultFiles = findXMLFilesStartingWith(dir, "TEST-");
if (unitTestResultFiles.length == 0) {
// maybe there's only a test suite result file
unitTestResultFiles = findXMLFilesStartingWith(dir, "TESTS-");
}
return unitTestResultFiles;
}

private File[] findXMLFilesStartingWith(File dir, final String fileNameStart) {
return dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(fileNameStart) && name.endsWith(".xml");
}
});
}

private void parseFiles(SensorContext context, File[] reports) {
UnitTestIndex index = new UnitTestIndex();
parseFiles(reports, index);
save(index, context);
}

private void parseFiles(File[] reports, UnitTestIndex index) {
SurefireStaxHandler staxParser = new SurefireStaxHandler(index);
StaxParser parser = new StaxParser(staxParser, false);
for (File report : reports) {
try {
parser.parse(report);
} catch (XMLStreamException e) {
throw new SonarException("Fail to parse the Surefire report: " + report, e);
}
}
}

private void save(UnitTestIndex index, SensorContext context) {
long negativeTimeTestNumber = 0;
for (Map.Entry<String, UnitTestClassReport> entry : index.getIndexByClassname().entrySet()) {
UnitTestClassReport report = entry.getValue();
if (report.getTests() > 0) {
negativeTimeTestNumber += report.getNegativeTimeTestNumber();
InputFile resource = getUnitTestResource(entry.getKey());
if (resource != null) {
save(report, resource, context);
} else {
LOG.warn("Resource not found: {}", entry.getKey());
}
}
}
if (negativeTimeTestNumber > 0) {
LOG.warn("There is {} test(s) reported with negative time by surefire, total duration may not be accurate.", negativeTimeTestNumber);
}
}

private void save(UnitTestClassReport report, InputFile resource, SensorContext context) {
double testsCount = report.getTests() - report.getSkipped();
saveMeasure(context, resource, CoreMetrics.SKIPPED_TESTS, report.getSkipped());
saveMeasure(context, resource, CoreMetrics.TESTS, testsCount);
saveMeasure(context, resource, CoreMetrics.TEST_ERRORS, report.getErrors());
saveMeasure(context, resource, CoreMetrics.TEST_FAILURES, report.getFailures());
saveMeasure(context, resource, CoreMetrics.TEST_EXECUTION_TIME, report.getDurationMilliseconds());
double passedTests = testsCount - report.getErrors() - report.getFailures();
if (testsCount > 0) {
double percentage = passedTests * 100d / testsCount;
saveMeasure(context, resource, CoreMetrics.TEST_SUCCESS_DENSITY, ParsingUtils.scaleValue(percentage));
}
saveResults(context, resource, report);
}

private void saveMeasure(SensorContext context, InputFile resource, Metric metric, double value) {
if (!Double.isNaN(value)) {
context.saveMeasure(resource, metric, value);
}
}

private void saveResults(SensorContext context, InputFile resource, UnitTestClassReport report) {
context.saveMeasure(resource, new Measure(CoreMetrics.TEST_DATA, report.toXml()));
}

private InputFile getUnitTestResource(String classKey){
String filename = classKey.replace('.', '/') + ".scala";
FilePredicates filePredicates = fileSystem.predicates();

return fileSystem.inputFile(filePredicates. matchesPathPattern("**/*" + filename));
}

// for(InputFile inputFile : inputFiles){
////System.out.println("inputFile: " + inputFile.absolutePath());
////System.out.println("filename: " + filename);
//if (inputFile.absolutePath().endsWith(filename)){
// return inputFile;
//}
//}

}
28 changes: 8 additions & 20 deletions src/main/java/org/sonar/plugins/scala/surefire/SurefireSensor.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,37 @@
*/
package org.sonar.plugins.scala.surefire;

import java.io.File;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.CoverageExtension;
import org.sonar.api.batch.DependsUpon;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Resource;
import org.sonar.plugins.scala.language.Scala;
import org.sonar.plugins.surefire.api.AbstractSurefireParser;
import org.sonar.plugins.surefire.api.SurefireUtils;

import java.io.File;

public class SurefireSensor implements Sensor {

private static final Logger LOG = LoggerFactory.getLogger(SurefireSensor.class);
private final Settings settings;
private final FileSystem fileSystem;

@DependsUpon
public Class<?> dependsUponCoverageSensors() {
return CoverageExtension.class;
}

public SurefireSensor (Settings settings){
public SurefireSensor (Settings settings, FileSystem fileSystem){
this.settings = settings;
this.fileSystem = fileSystem;
}

public boolean shouldExecuteOnProject(Project project) {
return project.getAnalysisType().isDynamic(true) && Scala.KEY.equals(project.getLanguageKey());
return fileSystem.hasFiles(fileSystem.predicates().hasLanguage(Scala.KEY));
}

public void analyse(Project project, SensorContext context) {
Expand All @@ -61,19 +59,9 @@ public void analyse(Project project, SensorContext context) {

protected void collect(Project project, SensorContext context, File reportsDir) {
LOG.info("parsing {}", reportsDir);
SUREFIRE_PARSER.collect(project, context, reportsDir);
new ScalaSurefireParser(fileSystem).collect(project, context, reportsDir);
}

private static final AbstractSurefireParser SUREFIRE_PARSER = new AbstractSurefireParser() {
@Override
protected Resource getUnitTestResource(String classKey) {
String filename = classKey.replace('.', '/') + ".scala";
org.sonar.api.resources.File sonarFile = new org.sonar.api.resources.File(filename);
sonarFile.setQualifier(Qualifiers.UNIT_TEST_FILE);
return sonarFile;
}
};

@Override
public String toString() {
return "Scala SurefireSensor";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Sonar Scala Plugin
* Copyright (C) 2011 - 2014 All contributors
* [email protected]
*
* 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.scala.surefire;

import org.codehaus.staxmate.in.ElementFilter;
import org.codehaus.staxmate.in.SMEvent;
import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.utils.ParsingUtils;
import org.sonar.api.utils.StaxParser;

import javax.xml.stream.XMLStreamException;
import java.text.ParseException;
import java.util.Locale;

public class SurefireStaxHandler implements StaxParser.XmlStreamHandler {

private UnitTestIndex index;

public SurefireStaxHandler(UnitTestIndex index) {
this.index = index;
}

@Override
public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException {
SMInputCursor testSuite = rootCursor.constructDescendantCursor(new ElementFilter("testsuite"));
SMEvent testSuiteEvent;
while ((testSuiteEvent = testSuite.getNext()) != null) {
if (testSuiteEvent.compareTo(SMEvent.START_ELEMENT) == 0) {
String testSuiteClassName = testSuite.getAttrValue("name");
SMInputCursor testCase = testSuite.childCursor(new ElementFilter("testcase"));
SMEvent event;
while ((event = testCase.getNext()) != null) {
if (event.compareTo(SMEvent.START_ELEMENT) == 0) {
UnitTestClassReport classReport = index.index(testSuiteClassName);
parseTestCase(testCase, classReport);
}
}
}
}
}

private void parseTestCase(SMInputCursor testCaseCursor, UnitTestClassReport report) throws XMLStreamException {
report.add(parseTestResult(testCaseCursor));
}

private void setStackAndMessage(UnitTestResult result, SMInputCursor stackAndMessageCursor) throws XMLStreamException {
result.setMessage(stackAndMessageCursor.getAttrValue("message"));
String stack = stackAndMessageCursor.collectDescendantText();
result.setStackTrace(stack);
}

private UnitTestResult parseTestResult(SMInputCursor testCaseCursor) throws XMLStreamException {
UnitTestResult detail = new UnitTestResult();
detail.setName(testCaseCursor.getAttrValue("name"));

String status = UnitTestResult.STATUS_OK;
long duration = getTimeAttributeInMS(testCaseCursor);

SMInputCursor childNode = testCaseCursor.descendantElementCursor();
if (childNode.getNext() != null) {
String elementName = childNode.getLocalName();
if ("skipped".equals(elementName)) {
status = UnitTestResult.STATUS_SKIPPED;
// bug with surefire reporting wrong time for skipped tests
duration = 0L;

} else if ("failure".equals(elementName)) {
status = UnitTestResult.STATUS_FAILURE;
setStackAndMessage(detail, childNode);

} else if ("error".equals(elementName)) {
status = UnitTestResult.STATUS_ERROR;
setStackAndMessage(detail, childNode);
}
}
while (childNode.getNext() != null) {
// make sure we loop till the end of the elements cursor
}
detail.setDurationMilliseconds(duration);
detail.setStatus(status);
return detail;
}

private long getTimeAttributeInMS(SMInputCursor testCaseCursor) throws XMLStreamException {
// hardcoded to Locale.ENGLISH see http://jira.codehaus.org/browse/SONAR-602
try {
Double time = ParsingUtils.parseNumber(testCaseCursor.getAttrValue("time"), Locale.ENGLISH);
return !Double.isNaN(time) ? new Double(ParsingUtils.scaleValue(time * 1000, 3)).longValue() : 0L;
} catch (ParseException e) {
throw new XMLStreamException(e);
}
}

}
Loading

0 comments on commit 9a98ba2

Please sign in to comment.