Skip to content

Commit

Permalink
Add support for Opencover format based on opencover plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
jonesbusy committed Nov 29, 2023
1 parent e8c5c6b commit a3daf0e
Show file tree
Hide file tree
Showing 7 changed files with 7,827 additions and 0 deletions.
168 changes: 168 additions & 0 deletions src/main/java/edu/hm/hafner/coverage/parser/OpenCoverParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package edu.hm.hafner.coverage.parser;

import java.io.Reader;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import edu.hm.hafner.coverage.CoverageParser;
import edu.hm.hafner.coverage.FileNode;
import edu.hm.hafner.coverage.ModuleNode;
import edu.hm.hafner.coverage.Node;
import edu.hm.hafner.coverage.PackageNode;
import edu.hm.hafner.util.FilteredLog;
import edu.hm.hafner.util.PathUtil;
import edu.hm.hafner.util.SecureXmlParserFactory;
import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException;

/**
* A parser which parses reports made by OpenCover into a Java Object Model.
*
*/
public class OpenCoverParser extends CoverageParser {

private static final long serialVersionUID = 1L;

private static final PathUtil PATH_UTIL = new PathUtil();

/** XML elements. */
private static final QName MODULE = new QName("Module");
private static final QName CLASS = new QName("Class");
private static final QName METHOD = new QName("Method");
private static final QName CLASS_NAME = new QName("FullName");
private static final QName METHOD_NAME = new QName("Name");
private static final QName MODULE_NAME = new QName("ModuleName");
private static final QName FILE = new QName("File");

private static final QName UID = new QName("uid");
private static final QName FULL_PATH = new QName("fullPath");
private static final QName CLASS_COMPLEXITY = new QName("maxCyclomaticComplexity");
private static final QName METHOD_COMPLEXITY = new QName("cyclomaticComplexity");

@Override
protected ModuleNode parseReport(final Reader reader, final FilteredLog log) {
try {
var eventReader = new SecureXmlParserFactory().createXmlEventReader(reader);
var root = new ModuleNode("-");
boolean isEmpty = true;
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
if (event.isStartElement()) {
var startElement = event.asStartElement();
var tagName = startElement.getName();
if (MODULE.equals(tagName)) {

Check warning on line 61 in src/main/java/edu/hm/hafner/coverage/parser/OpenCoverParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 61 is only partially covered, one branch is missing
readPackage(eventReader, root, startElement, log);
isEmpty = false;

Check warning on line 63 in src/main/java/edu/hm/hafner/coverage/parser/OpenCoverParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 62-63 are not covered by tests
}
}
}
if (isEmpty) {

Check warning on line 67 in src/main/java/edu/hm/hafner/coverage/parser/OpenCoverParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 67 is only partially covered, one branch is missing
throw new NoSuchElementException("No coverage information found in the specified file.");
}
return root;

Check warning on line 70 in src/main/java/edu/hm/hafner/coverage/parser/OpenCoverParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 70 is not covered by tests
}
catch (XMLStreamException exception) {
throw new ParsingException(exception);
}
}

private void readPackage(final XMLEventReader reader, final ModuleNode root,
final StartElement currentStartElement, final FilteredLog log) throws XMLStreamException {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'currentStartElement' is never used.

Map<String, String> files = new LinkedHashMap<>();
PackageNode packageNode = null;
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
var nextElement = event.asStartElement();
if (CLASS.equals(nextElement.getName())) {
readClass(reader, nextElement, log);
}
else if (FILE.equals(nextElement.getName())) {
var fileName = getValueOf(nextElement, FULL_PATH);
var uid = getValueOf(nextElement, UID);
var relativePath = PATH_UTIL.getRelativePath(fileName);
files.put(uid, relativePath);
}
else if (MODULE_NAME.equals(nextElement.getName())) {
String packageName = reader.nextEvent().asCharacters().getData();
packageNode = root.findOrCreatePackageNode(packageName);
}
}
}

// Creating all file nodes
for (var entry : files.entrySet()) {
packageNode.findOrCreateFileNode(getFileName(entry.getValue()), getTreeStringBuilder().intern(entry.getValue()));

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
packageNode
may be null at this access because of
this
assignment.
}

}

private void readFile(final XMLEventReader reader, final ModuleNode root,
final StartElement currentStartElement, final FilteredLog log) throws XMLStreamException {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'currentStartElement' is never used.

PackageNode packageNode = null;

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'PackageNode packageNode' is never read.
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
var nextElement = event.asStartElement();
if (CLASS.equals(nextElement.getName())) {
readClass(reader, nextElement, log);
}
else if (MODULE_NAME.equals(nextElement.getName())) {
String packageName = reader.nextEvent().asCharacters().getData();
packageNode = root.findOrCreatePackageNode(packageName);
}
}
}
}

private void readClass(final XMLEventReader reader, final StartElement parentElement, final FilteredLog log) throws XMLStreamException {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'parentElement' is never used.

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'log' is never used.
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
var nextElement = event.asStartElement();
if (CLASS_NAME.equals(nextElement.getName())) {
String className = reader.nextEvent().asCharacters().getData();

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'String className' is never read.
}
}
}
}

private int readComplexity(final String c) {
try {
return Math.round(Float.parseFloat(c)); // some reports use float values
}
catch (NumberFormatException ignore) {
return 0;
}
}

private Node createClassNode(final FileNode file, final StartElement parentElement) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'parentElement' is never used.
// Read summary has name
return file.createClassNode(UUID.randomUUID().toString());
}

protected static String getValueOf(final StartElement element, final QName attribute) {
return getOptionalValueOf(element, attribute).orElseThrow(
() -> new NoSuchElementException(String.format(
"Could not obtain attribute '%s' from element '%s'", attribute, element)));
}

private String getFileName(final String relativePath) {
var path = Paths.get(PATH_UTIL.getAbsolutePath(relativePath)).getFileName();
if (path == null) {
return relativePath;
}
return path.toString();

Check warning on line 165 in src/main/java/edu/hm/hafner/coverage/parser/OpenCoverParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 80-165 are not covered by tests
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import edu.hm.hafner.coverage.parser.CoberturaParser;
import edu.hm.hafner.coverage.parser.JacocoParser;
import edu.hm.hafner.coverage.parser.JunitParser;
import edu.hm.hafner.coverage.parser.OpenCoverParser;
import edu.hm.hafner.coverage.parser.PitestParser;

/**
Expand All @@ -18,6 +19,7 @@ public class ParserRegistry {
/** Supported parsers. */
public enum CoverageParserType {
COBERTURA,
OPENCOVER,
JACOCO,
PIT,
JUNIT
Expand Down Expand Up @@ -56,6 +58,8 @@ public CoverageParser getParser(final CoverageParserType parser, final Processin
switch (parser) {
case COBERTURA:
return new CoberturaParser(processingMode);
case OPENCOVER:
return new OpenCoverParser();
case JACOCO:
return new JacocoParser();
case PIT:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package edu.hm.hafner.coverage.parser;

import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DefaultLocale;

import edu.hm.hafner.coverage.CoverageParser;
import edu.hm.hafner.coverage.ModuleNode;

@DefaultLocale("en")
class OpenCoverParserTest extends AbstractParserTest {

@Override
CoverageParser createParser() {
return new OpenCoverParser();
}

@Test
void shouldReadReport() {
readExampleReport();
}

private ModuleNode readExampleReport() {
return readReport("opencover.xml", new OpenCoverParser());
}

@Override
protected String getFolder() {
return "opencover";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import edu.hm.hafner.coverage.parser.JacocoParser;
import edu.hm.hafner.coverage.parser.JunitParser;
import edu.hm.hafner.coverage.parser.PitestParser;
import edu.hm.hafner.coverage.parser.OpenCoverParser;
import edu.hm.hafner.coverage.registry.ParserRegistry.CoverageParserType;

import static org.assertj.core.api.Assertions.*;
Expand All @@ -24,6 +25,9 @@ void shouldCreateSomeParsers() {
.isInstanceOf(PitestParser.class);
assertThat(registry.getParser(CoverageParserType.JUNIT, ProcessingMode.IGNORE_ERRORS))
.isInstanceOf(JunitParser.class);
assertThat(registry.getParser(CoverageParserType.COBERTURA.name(), ProcessingMode.FAIL_FAST)).isInstanceOf(CoberturaParser.class);
assertThat(registry.getParser(CoverageParserType.JACOCO, ProcessingMode.IGNORE_ERRORS)).isInstanceOf(JacocoParser.class);
assertThat(registry.getParser(CoverageParserType.OPENCOVER, ProcessingMode.IGNORE_ERRORS)).isInstanceOf(OpenCoverParser.class);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<CoverageSession>
<Summary numSequencePoints="15" visitedSequencePoints="9" numBranchPoints="6" visitedBranchPoints="3" sequenceCoverage="60" branchCoverage="50" maxCyclomaticComplexity="6" minCyclomaticComplexity="6" visitedClasses="1" numClasses="1" visitedMethods="1" numMethods="1" />
<Modules>
<Module hash="31750BAB-054A-4C4B-8763-D76F95F0353A">
<ModulePath>ClassLibrary.dll</ModulePath>
<ModuleTime>2019-12-13T01:36:13</ModuleTime>
<ModuleName>ClassLibrary</ModuleName>
<Files>
<File uid="1" fullPath="[PLACEHOLDER]" />
</Files>
<Classes>
<Class>
<Summary numSequencePoints="15" visitedSequencePoints="9" numBranchPoints="6" visitedBranchPoints="3" sequenceCoverage="60" branchCoverage="50" maxCyclomaticComplexity="6" minCyclomaticComplexity="6" visitedClasses="1" numClasses="1" visitedMethods="1" numMethods="1" />
<FullName>ClassLibrary.LibraryClass</FullName>
<Methods>
<Method cyclomaticComplexity="6" nPathComplexity="0" sequenceCoverage="60" branchCoverage="50" isConstructor="False" isGetter="False" isSetter="False" isStatic="True">
<Summary numSequencePoints="15" visitedSequencePoints="9" numBranchPoints="6" visitedBranchPoints="3" sequenceCoverage="60" branchCoverage="50" maxCyclomaticComplexity="6" minCyclomaticComplexity="6" visitedClasses="0" numClasses="0" visitedMethods="1" numMethods="1" />
<MetadataToken />
<Name>System.Int32 ClassLibrary.LibraryClass::Sum(System.Int32,System.Int32)</Name>
<FileRef uid="1" />
<SequencePoints>
<SequencePoint vc="2" uspid="8" ordinal="0" sl="8" sc="1" el="8" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="2" uspid="9" ordinal="1" sl="9" sc="1" el="9" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="2" uspid="10" ordinal="2" sl="10" sc="1" el="10" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="0" uspid="13" ordinal="3" sl="13" sc="1" el="13" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="0" uspid="14" ordinal="4" sl="14" sc="1" el="14" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="0" uspid="16" ordinal="5" sl="16" sc="1" el="16" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="0" uspid="17" ordinal="6" sl="17" sc="1" el="17" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="0" uspid="19" ordinal="7" sl="19" sc="1" el="19" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="0" uspid="20" ordinal="8" sl="20" sc="1" el="20" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="1" uspid="22" ordinal="9" sl="22" sc="1" el="22" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="1" uspid="23" ordinal="10" sl="23" sc="1" el="23" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="1" uspid="25" ordinal="11" sl="25" sc="1" el="25" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="1" uspid="26" ordinal="12" sl="26" sc="1" el="26" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="2" uspid="28" ordinal="13" sl="28" sc="1" el="28" ec="2" bec="0" bev="0" fileid="1" />
<SequencePoint vc="2" uspid="29" ordinal="14" sl="29" sc="1" el="29" ec="2" bec="0" bev="0" fileid="1" />
</SequencePoints>
<BranchPoints>
<BranchPoint vc="0" uspid="10" ordinal="1" path="1" offset="8" offsetend="35" sl="10" fileid="1" />
<BranchPoint vc="0" uspid="10" ordinal="2" path="2" offset="8" offsetend="44" sl="10" fileid="1" />
<BranchPoint vc="0" uspid="10" ordinal="3" path="3" offset="8" offsetend="53" sl="10" fileid="1" />
<BranchPoint vc="1" uspid="10" ordinal="4" path="4" offset="8" offsetend="62" sl="10" fileid="1" />
<BranchPoint vc="1" uspid="10" ordinal="5" path="5" offset="8" offsetend="71" sl="10" fileid="1" />
<BranchPoint vc="2" uspid="10" ordinal="0" path="0" offset="8" offsetend="80" sl="10" fileid="1" />
</BranchPoints>
<MethodPoint vc="9" uspid="0" p8:type="SequencePoint" ordinal="0" offset="0" sc="0" sl="8" ec="1" el="29" bec="0" bev="0" fileid="1" xmlns:p8="xsi" />
</Method>
</Methods>
</Class>
</Classes>
</Module>
</Modules>
</CoverageSession>
Loading

0 comments on commit a3daf0e

Please sign in to comment.