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

Add support for Clover coverage xml reports #298

Merged
merged 2 commits into from
Dec 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -181,7 +184,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 @@ -388,6 +403,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>