-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCloverCoverageParser.java
More file actions
90 lines (81 loc) · 3.57 KB
/
CloverCoverageParser.java
File metadata and controls
90 lines (81 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package io.github.randomcodespace.sonarpredict.cli.coverage;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
/**
* Parses a Clover XML coverage report — the format PHPUnit emits for PHP.
*
* <p>Clover nests {@code <file>} elements under {@code <project>}; each
* {@code <line>} carries a {@code type} ({@code stmt}, {@code cond},
* {@code method}) and a {@code count}. Line coverage counts only
* {@code type="stmt"} lines: each is a coverable line, covered when
* {@code count > 0}. The file path is the {@code path} attribute, falling back
* to {@code name} when {@code path} is absent.
*/
public final class CloverCoverageParser implements CoverageParser {
@Override
public CoverageReport parse(Path path) {
Element root = CoverageXml.parse(path).getDocumentElement();
if (root == null || !"coverage".equals(root.getNodeName())) {
throw new CoverageException(
"not a Clover report: " + path + " (expected a <coverage> root)");
}
// Collect statement line numbers per file path.
var coverableByFile = new java.util.LinkedHashMap<String,
java.util.NavigableSet<Integer>>();
var coveredByFile = new java.util.LinkedHashMap<String,
java.util.NavigableSet<Integer>>();
for (Element file : CoverageXml.elementsByTag(root, "file")) {
collectFile(file, coverableByFile, coveredByFile);
}
List<FileCoverage> files = new ArrayList<>();
coverableByFile.forEach((file, coverable) ->
files.add(new FileCoverage(file, coveredByFile.get(file), coverable)));
return new CoverageReport(files);
}
/**
* Records the statement-coverage line numbers for one {@code <file>}
* element into the accumulating maps, keyed by the file's path attribute.
*/
private static void collectFile(
Element file,
java.util.Map<String, java.util.NavigableSet<Integer>> coverableByFile,
java.util.Map<String, java.util.NavigableSet<Integer>> coveredByFile) {
String filePath = filePathOf(file);
if (filePath == null) {
return;
}
var coverable = coverableByFile.computeIfAbsent(
filePath, k -> new java.util.TreeSet<>());
var covered = coveredByFile.computeIfAbsent(
filePath, k -> new java.util.TreeSet<>());
for (Element line : CoverageXml.children(file, "line")) {
recordStatementLine(line, coverable, covered);
}
}
/** Returns the file's path attribute, or {@code name} as fallback, or {@code null}. */
private static String filePathOf(Element file) {
String filePath = file.getAttribute("path");
if (filePath == null || filePath.isBlank()) {
filePath = file.getAttribute("name");
}
if (filePath == null || filePath.isBlank()) {
return null;
}
return filePath;
}
/** Adds a {@code <line type="stmt">} entry to coverable/covered as appropriate. */
private static void recordStatementLine(Element line,
java.util.NavigableSet<Integer> coverable,
java.util.NavigableSet<Integer> covered) {
if (!"stmt".equals(line.getAttribute("type"))) {
return;
}
int number = CoverageXml.intAttr(line, "num");
coverable.add(number);
if (CoverageXml.intAttr(line, "count") > 0) {
covered.add(number);
}
}
}