Skip to content

Commit 2733aff

Browse files
committed
Update Surefire sensor with code from sonar-java
1 parent 0316a6a commit 2733aff

File tree

9 files changed

+530
-80
lines changed

9 files changed

+530
-80
lines changed

pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@
142142
<artifactId>sslr-squid-bridge</artifactId>
143143
<version>2.5.3</version>
144144
</dependency>
145-
<dependency>
146-
<groupId>org.codehaus.sonar.plugins</groupId>
147-
<artifactId>sonar-surefire-plugin</artifactId>
148-
<version>2.7</version>
149-
</dependency>
150145
<dependency>
151146
<groupId>com.googlecode.plist</groupId>
152147
<artifactId>dd-plist</artifactId>

src/main/java/org/sonar/plugins/objectivec/surefire/SurefireParser.java

Lines changed: 85 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,44 +21,49 @@
2121
package org.sonar.plugins.objectivec.surefire;
2222

2323
import com.google.common.collect.ImmutableList;
24-
import org.apache.commons.lang.StringEscapeUtils;
24+
import org.apache.commons.lang.StringUtils;
2525
import org.slf4j.Logger;
2626
import org.slf4j.LoggerFactory;
2727
import org.sonar.api.batch.SensorContext;
2828
import org.sonar.api.batch.fs.FileSystem;
2929
import org.sonar.api.batch.fs.InputFile;
30+
import org.sonar.api.component.ResourcePerspectives;
3031
import org.sonar.api.measures.CoreMetrics;
31-
import org.sonar.api.measures.Measure;
3232
import org.sonar.api.measures.Metric;
3333
import org.sonar.api.resources.Project;
3434
import org.sonar.api.resources.Qualifiers;
3535
import org.sonar.api.resources.Resource;
36+
import org.sonar.api.test.MutableTestPlan;
37+
import org.sonar.api.test.TestCase;
3638
import org.sonar.api.utils.ParsingUtils;
39+
import org.sonar.api.utils.SonarException;
3740
import org.sonar.api.utils.StaxParser;
38-
import org.sonar.api.utils.XmlParserException;
3941
import org.sonar.plugins.objectivec.ObjectiveC;
40-
import org.sonar.plugins.surefire.TestCaseDetails;
41-
import org.sonar.plugins.surefire.TestSuiteParser;
42-
import org.sonar.plugins.surefire.TestSuiteReport;
42+
import org.sonar.plugins.objectivec.surefire.data.SurefireStaxHandler;
43+
import org.sonar.plugins.objectivec.surefire.data.UnitTestClassReport;
44+
import org.sonar.plugins.objectivec.surefire.data.UnitTestIndex;
45+
import org.sonar.plugins.objectivec.surefire.data.UnitTestResult;
4346

44-
import javax.xml.transform.TransformerException;
47+
import javax.xml.stream.XMLStreamException;
4548
import java.io.File;
4649
import java.io.FilenameFilter;
47-
import java.util.HashSet;
4850
import java.util.List;
49-
import java.util.Set;
51+
import java.util.Map;
5052

5153
public final class SurefireParser {
5254
private static final Logger LOGGER = LoggerFactory.getLogger(SurefireParser.class);
5355

56+
private final FileSystem fileSystem;
5457
private final Project project;
5558
private final SensorContext context;
56-
private final FileSystem fileSystem;
59+
private final ResourcePerspectives perspectives;
5760

58-
public SurefireParser(Project project, SensorContext context, FileSystem fileSystem) {
61+
public SurefireParser(FileSystem fileSystem, Project project, ResourcePerspectives perspectives,
62+
SensorContext context) {
63+
this.fileSystem = fileSystem;
5964
this.project = project;
65+
this.perspectives = perspectives;
6066
this.context = context;
61-
this.fileSystem = fileSystem;
6267
}
6368

6469
public void collect(File reportsDir) {
@@ -88,75 +93,78 @@ private void insertZeroWhenNoReports() {
8893
}
8994

9095
private void parseFiles(File[] reports) {
91-
Set<TestSuiteReport> analyzedReports = new HashSet<TestSuiteReport>();
92-
try {
93-
for (File report : reports) {
94-
TestSuiteParser parserHandler = new TestSuiteParser();
95-
StaxParser parser = new StaxParser(parserHandler, false);
96-
parser.parse(report);
96+
UnitTestIndex index = new UnitTestIndex();
97+
parseFiles(reports, index);
98+
sanitize(index);
99+
save(index);
100+
}
97101

98-
for (TestSuiteReport fileReport : parserHandler.getParsedReports()) {
99-
if (!fileReport.isValid() || analyzedReports.contains(fileReport)) {
100-
continue;
101-
}
102-
if (fileReport.getTests() > 0) {
103-
double testsCount = fileReport.getTests() - fileReport.getSkipped();
104-
saveClassMeasure(fileReport, CoreMetrics.SKIPPED_TESTS, fileReport.getSkipped());
105-
saveClassMeasure(fileReport, CoreMetrics.TESTS, testsCount);
106-
saveClassMeasure(fileReport, CoreMetrics.TEST_ERRORS, fileReport.getErrors());
107-
saveClassMeasure(fileReport, CoreMetrics.TEST_FAILURES, fileReport.getFailures());
108-
saveClassMeasure(fileReport, CoreMetrics.TEST_EXECUTION_TIME, fileReport.getTimeMS());
109-
double passedTests = testsCount - fileReport.getErrors() - fileReport.getFailures();
110-
if (testsCount > 0) {
111-
double percentage = passedTests * 100d / testsCount;
112-
saveClassMeasure(fileReport, CoreMetrics.TEST_SUCCESS_DENSITY, ParsingUtils.scaleValue(percentage));
113-
}
114-
saveTestsDetails(fileReport);
115-
analyzedReports.add(fileReport);
116-
}
117-
}
102+
private static void parseFiles(File[] reports, UnitTestIndex index) {
103+
SurefireStaxHandler staxParser = new SurefireStaxHandler(index);
104+
StaxParser parser = new StaxParser(staxParser, false);
105+
for (File report : reports) {
106+
try {
107+
parser.parse(report);
108+
} catch (XMLStreamException e) {
109+
throw new SonarException("Fail to parse the Surefire report: " + report, e);
118110
}
119-
120-
} catch (Exception e) {
121-
throw new XmlParserException("Can not parse surefire reports", e);
122111
}
123112
}
124113

125-
private void saveTestsDetails(TestSuiteReport fileReport) throws TransformerException {
126-
StringBuilder testCaseDetails = new StringBuilder(256);
127-
testCaseDetails.append("<tests-details>");
128-
List<TestCaseDetails> details = fileReport.getDetails();
129-
for (TestCaseDetails detail : details) {
130-
testCaseDetails.append("<testcase status=\"").append(detail.getStatus())
131-
.append("\" time=\"").append(detail.getTimeMS())
132-
.append("\" name=\"").append(detail.getName()).append("\"");
133-
boolean isError = detail.getStatus().equals(TestCaseDetails.STATUS_ERROR);
134-
if (isError || detail.getStatus().equals(TestCaseDetails.STATUS_FAILURE)) {
135-
testCaseDetails.append(">")
136-
.append(isError ? "<error message=\"" : "<failure message=\"")
137-
.append(StringEscapeUtils.escapeXml(detail.getErrorMessage())).append("\">")
138-
.append("<![CDATA[").append(StringEscapeUtils.escapeXml(detail.getStackTrace())).append("]]>")
139-
.append(isError ? "</error>" : "</failure>").append("</testcase>");
140-
} else {
141-
testCaseDetails.append("/>");
114+
private static void sanitize(UnitTestIndex index) {
115+
for (String classname : index.getClassnames()) {
116+
if (StringUtils.contains(classname, "$")) {
117+
// Surefire reports classes whereas sonar supports files
118+
String parentClassName = StringUtils.substringBefore(classname, "$");
119+
index.merge(classname, parentClassName);
142120
}
143121
}
144-
testCaseDetails.append("</tests-details>");
145-
context.saveMeasure(getUnitTestResource(fileReport.getClassKey()), new Measure(CoreMetrics.TEST_DATA, testCaseDetails.toString()));
146122
}
147123

148-
private void saveClassMeasure(TestSuiteReport fileReport, Metric metric, double value) {
149-
if (!Double.isNaN(value)) {
150-
151-
String className = fileReport.getClassKey();
124+
private void save(UnitTestIndex index) {
125+
long negativeTimeTestNumber = 0;
126+
for (Map.Entry<String, UnitTestClassReport> entry : index.getIndexByClassname().entrySet()) {
127+
UnitTestClassReport report = entry.getValue();
128+
if (report.getTests() > 0) {
129+
negativeTimeTestNumber += report.getNegativeTimeTestNumber();
130+
Resource resource = getUnitTestResource(entry.getKey());
131+
if (resource != null) {
132+
save(report, resource);
133+
} else {
134+
LOGGER.warn("Resource not found: {}", entry.getKey());
135+
}
136+
}
137+
}
138+
if (negativeTimeTestNumber > 0) {
139+
LOGGER.warn("There is {} test(s) reported with negative time by surefire, total duration may not be accurate.", negativeTimeTestNumber);
140+
}
141+
}
152142

153-
context.saveMeasure(getUnitTestResource(className), metric, value);
143+
private void save(UnitTestClassReport report, Resource resource) {
144+
double testsCount = report.getTests() - report.getSkipped();
145+
saveMeasure(resource, CoreMetrics.SKIPPED_TESTS, report.getSkipped());
146+
saveMeasure(resource, CoreMetrics.TESTS, testsCount);
147+
saveMeasure(resource, CoreMetrics.TEST_ERRORS, report.getErrors());
148+
saveMeasure(resource, CoreMetrics.TEST_FAILURES, report.getFailures());
149+
saveMeasure(resource, CoreMetrics.TEST_EXECUTION_TIME, report.getDurationMilliseconds());
150+
double passedTests = testsCount - report.getErrors() - report.getFailures();
151+
if (testsCount > 0) {
152+
double percentage = passedTests * 100d / testsCount;
153+
saveMeasure(resource, CoreMetrics.TEST_SUCCESS_DENSITY, ParsingUtils.scaleValue(percentage));
154+
}
155+
saveResults(resource, report);
156+
}
154157

155-
// Try with + in name
156-
try {
157-
context.saveMeasure(getUnitTestResource(className.replace('_', '+')), metric, value);
158-
} catch (Exception e) {
159-
// no-op - file was probably already registered successfully
158+
protected void saveResults(Resource testFile, UnitTestClassReport report) {
159+
for (UnitTestResult unitTestResult : report.getResults()) {
160+
MutableTestPlan testPlan = perspectives.as(MutableTestPlan.class, testFile);
161+
if (testPlan != null) {
162+
testPlan.addTestCase(unitTestResult.getName())
163+
.setDurationInMs(Math.max(unitTestResult.getDurationMilliseconds(), 0))
164+
.setStatus(TestCase.Status.of(unitTestResult.getStatus()))
165+
.setMessage(unitTestResult.getMessage())
166+
.setType(TestCase.TYPE_UNIT)
167+
.setStackTrace(unitTestResult.getStackTrace());
160168
}
161169
}
162170
}
@@ -170,12 +178,11 @@ public Resource getUnitTestResource(String classname) {
170178
}
171179

172180
/*
173-
* Most xcodebuild JUnit parsers don't include the path to the class in the class field, so search for if it
181+
* Most xcodebuild JUnit parsers don't include the path to the class in the class field, so search for it if it
174182
* wasn't found in the root.
175183
*/
176184
if (!file.isFile() || !file.exists()) {
177185
List<File> files = ImmutableList.copyOf(fileSystem.files(fileSystem.predicates().and(
178-
fileSystem.predicates().hasLanguage(ObjectiveC.KEY),
179186
fileSystem.predicates().hasType(InputFile.Type.TEST),
180187
fileSystem.predicates().matchesPathPattern("**/" + fileName))));
181188

@@ -194,4 +201,10 @@ public Resource getUnitTestResource(String classname) {
194201
sonarFile.setQualifier(Qualifiers.UNIT_TEST_FILE);
195202
return sonarFile;
196203
}
204+
205+
private void saveMeasure(Resource resource, Metric metric, double value) {
206+
if (!Double.isNaN(value)) {
207+
context.saveMeasure(resource, metric, value);
208+
}
209+
}
197210
}

src/main/java/org/sonar/plugins/objectivec/surefire/SurefireSensor.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.sonar.api.batch.Sensor;
2727
import org.sonar.api.batch.SensorContext;
2828
import org.sonar.api.batch.fs.FileSystem;
29+
import org.sonar.api.component.ResourcePerspectives;
2930
import org.sonar.api.config.Settings;
3031
import org.sonar.api.resources.Project;
3132
import org.sonar.api.scan.filesystem.PathResolver;
@@ -39,11 +40,14 @@ public final class SurefireSensor implements Sensor {
3940

4041
private final FileSystem fileSystem;
4142
private final PathResolver pathResolver;
43+
private final ResourcePerspectives resourcePerspectives;
4244
private final Settings settings;
4345

44-
public SurefireSensor(FileSystem fileSystem, PathResolver pathResolver, Settings settings) {
46+
public SurefireSensor(FileSystem fileSystem, PathResolver pathResolver, ResourcePerspectives resourcePerspectives,
47+
Settings settings) {
4548
this.fileSystem = fileSystem;
4649
this.pathResolver = pathResolver;
50+
this.resourcePerspectives = resourcePerspectives;
4751
this.settings = settings;
4852
}
4953

@@ -67,7 +71,7 @@ public void analyse(Project project, SensorContext context) {
6771

6872
protected void collect(Project project, SensorContext context, File reportsDir) {
6973
LOGGER.info("parsing {}", reportsDir);
70-
new SurefireParser(project, context, fileSystem).collect(reportsDir);
74+
new SurefireParser(fileSystem, project, resourcePerspectives, context).collect(reportsDir);
7175
}
7276

7377
@Override

0 commit comments

Comments
 (0)