Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
44b4e27
revert directory stream to original and keep sensors consistent
bcolvin Oct 24, 2018
195533f
fix a space issue for lizardReport
bcolvin Oct 25, 2018
48c0761
LizardReportParser Unit Test exposing issue with space in folder and/…
Oct 31, 2018
a240b08
Added license header
Oct 31, 2018
f3c3b3c
merged pull request code from main
bcolvin Dec 10, 2018
98a1d69
Merge commit 'f3c3b3cb653349397e61c27de4f0fb79ec33ec59' into develop
Jan 10, 2019
2098a3e
Added -develop to version
Oct 29, 2018
7297abb
Fixed "rule does not exist" crash on Objective-C sensors
Oct 23, 2018
69a9b52
Remove use of internal class DefaultIssueLocation
Oct 24, 2018
e4a04b3
Use Metric interface rather than class
Oct 24, 2018
2e029d9
Reduced visibility as re-use is not needed outside package
Oct 24, 2018
bf853fc
POM cleanup
Oct 24, 2018
7b93b6f
Removed unneeded file
Oct 24, 2018
c89be20
Bumped sonar version
Oct 24, 2018
40a6615
Added swiftlint unused_setter_value rule
Feb 4, 2019
b727416
Set version
Feb 4, 2019
587a2b0
imports
bcolvin Feb 7, 2019
377695f
imports
bcolvin Feb 7, 2019
77815c3
thanks tim
bcolvin Feb 7, 2019
90d06f7
version change
bcolvin Feb 22, 2019
692a0bd
Added swiftlint unused_setter_value rule
Feb 4, 2019
4f20c33
LizardReportParser Unit Test exposing issue with space in folder and/…
Oct 31, 2018
90e048d
fix a space issue for lizardReport
bcolvin Oct 25, 2018
955e82a
Set version
Mar 4, 2019
b1fdbe3
thanks @timothy-volvo#207
bcolvin Mar 20, 2019
64e676c
update
bcolvin May 7, 2019
0eec423
update version
bcolvin May 7, 2019
1828c45
licensing format
bcolvin May 7, 2019
794c8f4
fix for cast exception
bcolvin May 13, 2019
33323d8
Fixed "ERROR: Metric 'files' should not be computed by a Sensor". Iss…
May 23, 2019
78982b7
Merge commit '794c8f4ef76ee559a1af5f34cbbbc5943ca65503' into 0.4.4-volvo
May 23, 2019
74bfd67
Set Version to 0.4.5-develop1
May 23, 2019
0f7afb4
Revert change removing guava
May 23, 2019
ff37db5
Fixed NPE
May 23, 2019
746b268
Merge branch 'master' of https://github.com/Backelite/sonar-swift int…
May 23, 2019
aaa8e3c
Merge commit '955e82a99f7cff28aac1d4ce2cdfee8433f2145d' into develop
May 23, 2019
324ff61
Merge branch '0.4.4-volvo' into develop
May 23, 2019
83439cb
Revert bad merge
May 23, 2019
cb6d6fa
Set version to 0.4.5-develop2
May 23, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ In SonarQube under Quality Profiles the used Linter can be specified by selectin
| Complexity |YES |Uses [Lizard](https://github.com/terryyin/lizard)| Uses [Lizard](https://github.com/terryyin/lizard)|
| Design |NO | | |
| Documentation |YES | | |
| Duplications |YES | | |
| Duplications |Only on Sonarqube < 7.3 | | |
| Issues |YES | Uses [SwiftLint](https://github.com/realm/SwiftLint) and/or [Tailor](https://github.com/sleekbyte/tailor) for Swift. [OCLint](http://oclint-docs.readthedocs.io/en/stable/) and [Faux Pas](http://fauxpasapp.com/) for Objective-C| Uses [Tailor](https://github.com/sleekbyte/tailor)|
| Size |YES | | |
| Tests |YES | Uses xcodebuild + xcpretty [xcpretty](https://github.com/supermarin/xcpretty) | Not Supported |
Expand Down
7 changes: 1 addition & 6 deletions commons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<parent>
<artifactId>backelite-swift</artifactId>
<groupId>com.backelite.sonarqube</groupId>
<version>0.4.4</version>
<version>0.4.5-develop2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down Expand Up @@ -90,11 +90,6 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
package com.backelite.sonarqube.commons;

import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.measures.Metric;

/**
* Created by gillesgrousset on 29/08/2018.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void addFinder(TestFileFinder finder) {
finders.add(finder);
}

public InputFile getUnitTestResource(FileSystem fileSystem, String classname) {
InputFile getUnitTestResource(FileSystem fileSystem, String classname) {
for (TestFileFinder finder : finders) {
InputFile result = finder.getUnitTestResource(fileSystem, classname);
if (result != null) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,24 @@
*/
package com.backelite.sonarqube.commons.surefire;

import com.backelite.sonarqube.commons.TestFileFinders;
import com.backelite.sonarqube.commons.MeasureUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.test.MutableTestPlan;
import org.sonar.api.test.TestCase;
import org.sonar.squidbridge.api.AnalysisException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.annotation.CheckForNull;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SurefireReportParser {
private static final Logger LOGGER = LoggerFactory.getLogger(SurefireReportParser.class);
Expand All @@ -52,110 +44,130 @@ public class SurefireReportParser {
protected final SensorContext context;
private final DocumentBuilderFactory dbfactory;
private final UnitTestIndex index;
private final ResourcePerspectives perspectives;
private final FileSystem fileSystem;

protected SurefireReportParser(FileSystem fileSystem, ResourcePerspectives perspectives, SensorContext context) {
this.fileSystem = fileSystem;
protected SurefireReportParser(SensorContext context) {
this.context = context;
this.perspectives = perspectives;
this.dbfactory = DocumentBuilderFactory.newInstance();
this.index = new UnitTestIndex();
}

public void collect(File reportsDir) {
List<File> xmlFiles = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(reportsDir.toPath(), "{TEST}*.{xml}")) {
for (Path p: stream) {
LOGGER.info("Processing Surefire report {}", p.getFileName());
xmlFiles.add(p.toFile());
}
} catch (IOException e) {
LOGGER.error( "Error while finding test files.", e);
public void parseReport(final File xmlFile) {
try {
DocumentBuilder builder = dbfactory.newDocumentBuilder();
Document document = builder.parse(xmlFile);
collectTestSuites(document.getElementsByTagName(TESTSUITE));
} catch (final FileNotFoundException e) {
LOGGER.error("Cobertura Report not found {}", xmlFile, e);
} catch (final IOException e) {
LOGGER.error("Error processing file named {}", xmlFile, e);
} catch (final ParserConfigurationException e) {
LOGGER.error("Error in parser config {}", e);
} catch (final SAXException e) {
LOGGER.error("Error processing file named {}", xmlFile, e);
}
}

if (!xmlFiles.isEmpty()) {
parseFiles(xmlFiles);
private void collectTestSuites(NodeList nodeList) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String testSuiteClassName = element.getAttribute("name");
if(testSuiteClassName != null && !testSuiteClassName.contains("$")){
NodeList nl = element.getElementsByTagName(TESTCASE);
collectTestCases(testSuiteClassName, nl);
}
}
}
}

private void parseFiles(List<File> reports) {
UnitTestIndex index = new UnitTestIndex();
parseFiles(reports, index);
save(index, context);
private void collectTestCases(String testSuiteName, NodeList nodeList) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;

String className = element.getAttribute("classname");
if(className != null && className.endsWith(")") && className.indexOf("(") >= 0){
className = className.substring(0, className.indexOf("("));
}
if(className == null || className.trim().isEmpty())
className = testSuiteName;

String testName = element.getAttribute("name");
if(className != null && className.contains("$"))
testName = className.substring(className.indexOf("$")) + "/" + testName;

UnitTestClassReport classReport = index.index(className);
UnitTestResult detail = new UnitTestResult();
detail.setName(testName);

String status = UnitTestResult.STATUS_OK;
Long duration = parseDuration(element.getAttribute("time"));
if(element.hasChildNodes()){
Element item = (Element) element.getChildNodes().item(0);
if ("skipped".equals(item.getLocalName())) {
status = UnitTestResult.STATUS_SKIPPED;
duration = 0L;

} else if ("failure".equals(item.getLocalName())) {
status = UnitTestResult.STATUS_FAILURE;
detail.setMessage(item.getAttribute("message"));
detail.setStackTrace(item.getTextContent());

} else if ("error".equals(item.getLocalName())) {
status = UnitTestResult.STATUS_ERROR;
detail.setMessage(item.getAttribute("message"));
detail.setStackTrace(item.getTextContent());
}
}
detail.setDurationMilliseconds(duration);
detail.setStatus(status);
classReport.add(detail);
}
}
}

private static void parseFiles(List<File> reports, UnitTestIndex index) {
StaxParser parser = new StaxParser(index);
for (File report : reports) {
try {
parser.parse(report);
} catch (XMLStreamException e) {
throw new AnalysisException("Fail to parse the Surefire report: " + report, e);
}
private Long parseDuration(String value) {
Long duration = null;
if(value != null && !value.isEmpty()){
Double time = Double.parseDouble(value);
duration = (long)(time * 1000);
}
return duration;
}

private void save(UnitTestIndex index, SensorContext context) {
public void save() {
long negativeTimeTestNumber = 0;
Map<InputFile, UnitTestClassReport> indexByInputFile = mapToInputFile(index.getIndexByClassname());
for (Map.Entry<InputFile, UnitTestClassReport> entry : indexByInputFile.entrySet()) {
UnitTestClassReport report = entry.getValue();
int testsCount = 0;
int testsSkipped = 0;
int testsErrors = 0;
int testsFailures = 0;
long testsTime = 0;

for (UnitTestClassReport report : index.getIndexByClassname().values()) {
testsCount += report.getTests() - report.getSkipped();
testsSkipped += report.getSkipped();
testsErrors += report.getErrors();
testsFailures += report.getFailures();

if (report.getTests() > 0) {
negativeTimeTestNumber += report.getNegativeTimeTestNumber();
save(report, entry.getKey(), context);
}
}
if (negativeTimeTestNumber > 0) {
LOGGER.warn("There is {} test(s) reported with negative time by surefire, total duration may not be accurate.", negativeTimeTestNumber);
}
}

private Map<InputFile, UnitTestClassReport> mapToInputFile(Map<String, UnitTestClassReport> indexByClassname) {
Map<InputFile, UnitTestClassReport> result = new HashMap<>();
indexByClassname.forEach((className, index) -> {
InputFile resource = getUnitTestResource(className, index);
if (resource != null) {
UnitTestClassReport report = result.computeIfAbsent(resource, r -> new UnitTestClassReport());
// in case of repeated/parameterized tests (JUnit 5.x) we may end up with tests having the same name
index.getResults().forEach(report::add);
} else {
LOGGER.debug("Resource not found: {}", className);
}
});
return result;
}

private void save(UnitTestClassReport report, InputFile inputFile, SensorContext context) {
int testsCount = report.getTests() - report.getSkipped();
saveMeasure(context, inputFile, CoreMetrics.SKIPPED_TESTS, report.getSkipped());
saveMeasure(context, inputFile, CoreMetrics.TESTS, testsCount);
saveMeasure(context, inputFile, CoreMetrics.TEST_ERRORS, report.getErrors());
saveMeasure(context, inputFile, CoreMetrics.TEST_FAILURES, report.getFailures());
saveMeasure(context, inputFile, CoreMetrics.TEST_EXECUTION_TIME, report.getDurationMilliseconds());
saveResults(inputFile, report);
}

protected void saveResults(InputFile testFile, UnitTestClassReport report) {
for (UnitTestResult unitTestResult : report.getResults()) {
MutableTestPlan testPlan = perspectives.as(MutableTestPlan.class, testFile);
if (testPlan != null) {
testPlan.addTestCase(unitTestResult.getName())
.setDurationInMs(Math.max(unitTestResult.getDurationMilliseconds(), 0))
.setStatus(TestCase.Status.of(unitTestResult.getStatus()))
.setMessage(unitTestResult.getMessage())
.setStackTrace(unitTestResult.getStackTrace());
}
if (negativeTimeTestNumber > 0) {
LOGGER.warn("There is {} test(s) reported with negative time by data, total duration may not be accurate.", negativeTimeTestNumber);
}
}

@CheckForNull
private InputFile getUnitTestResource(String className, UnitTestClassReport unitTestClassReport) {
return TestFileFinders.getInstance().getUnitTestResource(fileSystem, className);
}

private static <T extends Serializable> void saveMeasure(SensorContext context, InputFile inputFile, Metric<T> metric, T value) {
context.<T>newMeasure().forMetric(metric).on(inputFile).withValue(value).save();
if (testsCount > 0) {
InputComponent module = context.module();
MeasureUtil.saveMeasure(context, module, CoreMetrics.TESTS, testsCount);
MeasureUtil.saveMeasure(context, module, CoreMetrics.SKIPPED_TESTS, testsSkipped);
MeasureUtil.saveMeasure(context, module, CoreMetrics.TEST_ERRORS, testsErrors);
MeasureUtil.saveMeasure(context, module, CoreMetrics.TEST_FAILURES, testsFailures);
MeasureUtil.saveMeasure(context, module, CoreMetrics.TEST_EXECUTION_TIME, testsTime);
}
}

}
Loading