Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
Add support for Clover coverage xml reports (#298)
Browse files Browse the repository at this point in the history
* Add support for Clover coverage xml reports

* Fixed the checkstyle issues
  • Loading branch information
d-claassen authored and kageiit committed Dec 4, 2018
1 parent 90da7cc commit d24c091
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package com.uber.jenkins.phabricator.coverage;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
Expand All @@ -39,6 +40,7 @@
import java.util.SortedMap;
import java.util.TreeMap;

import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
Expand All @@ -58,6 +60,7 @@ public XmlCoverageProvider(Set<File> coverageReports, Set<String> includeFiles)
super(includeFiles);
this.coverageReports = coverageReports;
this.xmlCoverageHandlers = Arrays.asList(new CoberturaXmlCoverageHandler(),
new CloverXmlCoverageHandler(),
new JacocoXmlCoverageHandler());

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Expand Down Expand Up @@ -194,7 +197,19 @@ public int getLength() {

@Override
boolean isApplicable(Document document) {
return document.getDocumentElement().getTagName().equals("coverage");
Element documentElement = document.getDocumentElement();
if (!documentElement.getTagName().equals("coverage")) {
return false;
}

NodeList children = documentElement.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (children.item(i).getNodeName().equals("packages")) {
return true;
}
}

return false;
}

@Override
Expand Down Expand Up @@ -430,6 +445,156 @@ void parseCoverage(
}
}

private static class CloverXmlCoverageHandler extends XmlCoverageHandler {

@Override
boolean isApplicable(Document document) {
Element documentElement = document.getDocumentElement();
if (!documentElement.getTagName().equals("coverage")) {
return false;
}

NodeList children = documentElement.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (children.item(i).getNodeName().equals("project")) {
return true;
}
}

return false;
}

@Override
void parseCoverage(
Document document, Set<String> includeFiles,
CoverageCounters cc,
Map<String, List<Integer>> lineCoverage) {
Map<String, SortedMap<Integer, Integer>> internalCounts = new HashMap<String, SortedMap<Integer, Integer>>();
NodeList packages = document.getElementsByTagName("package");

// Compute line coverage
for (int i = 0; i < packages.getLength(); i++) {
Node packageNode = packages.item(i);
NodeList fileNodes = packageNode.getChildNodes();
for (int j = 0; j < fileNodes.getLength(); j++) {
Node fileNode = fileNodes.item(j);
if (!fileNode.hasAttributes()) {
continue;
}

String fileName = fileNode.getAttributes().getNamedItem("name").getTextContent();
String finalFileName = getRelativePathFromProjectRoot(includeFiles, fileName);
if (finalFileName != null) {
SortedMap<Integer, Integer> hitCounts = internalCounts.computeIfAbsent(
finalFileName, it -> new TreeMap<>());
NodeList coverage = fileNode.getChildNodes();
for (int k = 0; k < coverage.getLength(); k++) {
Node coverageNode = coverage.item(k);
if (coverageNode != null && "line".equals(coverageNode.getNodeName())) {
NamedNodeMap attrs = coverageNode.getAttributes();
if ("stmt".equals(attrs.getNamedItem("type").getTextContent())) {
long hitCount = getIntValue(attrs, "count");
int lineNumber = getIntValue(attrs, "num");
hitCounts.put(lineNumber, hitCount > 0 ? 1 : 0);
}
}
}
}
}
}
computeLineCoverage(internalCounts, lineCoverage);

// Update Counters
for (int i = 0; i < packages.getLength(); i++) {
Node packageNode = packages.item(i);
NodeList packageChildren = packageNode.getChildNodes();
boolean packageCovered = false;
for (int j = 0; j < packageChildren.getLength(); j++) {
Node fileNode = packageChildren.item(j);
if (!fileNode.getNodeName().equals("file")) {
continue;
}

NodeList fileChildren = fileNode.getChildNodes();
boolean fileCovered = false;
for (int k = 0; k < fileChildren.getLength(); k++) {
Node fileChild = fileChildren.item(k);

if (fileChild.getNodeName().equals("line")) {
Node lineChild = fileChild;
NamedNodeMap lineAttributes = lineChild.getAttributes();
String typeAttributeText = lineAttributes.getNamedItem("type").getTextContent();
if (typeAttributeText.equals("stmt")) {
int lineHits = getIntValue(lineAttributes, "count");
if (lineHits > 0) {
fileCovered = true;
cc.line.covered += 1;
} else {
cc.line.missed += 1;
}
} else if (typeAttributeText.equals("method")) {
int methodHits = getIntValue(lineAttributes, "count");
if (methodHits > 0) {
fileCovered = true;
cc.method.covered += 1;
} else {
cc.method.missed += 1;
}
}
}

if (fileChild.getNodeName().equals("class")) {
Node classNode = fileChild;
NodeList classChildren = classNode.getChildNodes();
for (int l = 0; l < classChildren.getLength(); l++) {
Node metricNode = classChildren.item(l);
if (metricNode.getNodeName().equals("metrics")) {
Integer coveredstatements = getIntValue(metricNode.getAttributes(), "coveredstatements");
if (coveredstatements > 0) {
fileCovered = true;
cc.cls.covered += 1;
} else {
cc.cls.missed += 1;
}
}
}
}
}
if (fileCovered) {
packageCovered = true;
cc.file.covered += 1;
} else {
cc.file.missed += 1;
}
}
if (packageCovered) {
cc.pkg.covered += 1;
} else {
cc.pkg.missed += 1;
}
}
}

/**
* The coverage file is an absolute path, but the include files are relative paths. But the coverage file might
* have been generated on a different node, where the directory structure differs. So we try to match the
* coverageFile to the includeFile that seems the most related
*/
@Nullable
private static String getRelativePathFromProjectRoot(Set<String> includeFiles, String coverageFile) {
if (includeFiles == null || includeFiles.isEmpty()) {
return coverageFile;
} else {
for (String includedFile : includeFiles) {
if (coverageFile.contains(includedFile)) {
return includedFile;
}
}
return null;
}
}
}

private static class CoverageCounter {

long covered = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

Expand Down Expand Up @@ -64,6 +65,34 @@ public void jacoco() {
provider.getMetrics());
}

@Test
public void cloverPhpunit() {
CoverageProvider provider = new XmlCoverageProvider(getResources("clover-phpunit-coverage.xml"));

assertTrue(provider.hasCoverage());

Map<String, List<Integer>> lineCoverage = provider.getLineCoverage();
String expectedKey = "/home/ubuntu/example-php/src/Example/Example.php";

assertNull(lineCoverage.get(expectedKey).get(4));
assertEquals(1, lineCoverage.get(expectedKey).get(6).longValue());
assertEquals(0, lineCoverage.get(expectedKey).get(7).longValue());
assertEquals(1, lineCoverage.get(expectedKey).get(10).longValue());
assertEquals(new CodeCoverageMetrics(100.0f, 100.0f, 100.f, 100.0f, 66.66667f, 100.0f, 2, 3),
provider.getMetrics());
}

@Test public void cloverWithIncludeFiles() {
CoverageProvider provider = new XmlCoverageProvider(getResources("clover-phpunit-coverage.xml"),
Collections.singleton("src/Example/Example.php"));

assertTrue(provider.hasCoverage());

Map<String, List<Integer>> lineCoverage = provider.getLineCoverage();
List<Integer> exampleCoverage = lineCoverage.get("src/Example/Example.php");
assertNotNull(exampleCoverage);
}

@Test
public void lineCoverageAggregation() {
CoverageProvider provider = new XmlCoverageProvider(getResources(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1542744031">
<project timestamp="1542744031">
<package name="Example">
<file name="/home/ubuntu/example-php/src/Example/Example.php">
<class name="Example" namespace="Example">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="3" coveredstatements="2" elements="4" coveredelements="2"/>
</class>
<line num="5" type="method" name="go" crap="2.15" count="1"/>
<line num="7" type="stmt" count="1"/>
<line num="8" type="stmt" count="0"/>
<line num="11" type="stmt" count="1"/>
<metrics loc="14" ncloc="14" classes="1" methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="3" coveredstatements="2" elements="4" coveredelements="2"/>
</file>
</package>
<metrics files="1" loc="14" ncloc="14" classes="1" methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="3" coveredstatements="2" elements="4" coveredelements="2"/>
</project>
</coverage>

0 comments on commit d24c091

Please sign in to comment.