Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pretty formatted output and calculation for lines-valid and lines-covered #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
148 changes: 89 additions & 59 deletions cover2cover.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#!/usr/bin/env python
import sys
import xml.etree.ElementTree as ET
import re
import os.path
import xml.etree.ElementTree as ET
from xml.dom.minidom import parseString

# branch-rate="0.0" complexity="0.0" line-rate="1.0"
# branch="true" hits="1" number="86"


def find_lines(j_package, filename):
"""Return all <line> elements for a given source file in a package."""
lines = list()
Expand All @@ -16,89 +18,113 @@ def find_lines(j_package, filename):
lines = lines + sourcefile.findall("line")
return lines


def line_is_after(jm, start_line):
return int(jm.attrib.get('line', 0)) > start_line
return int(jm.attrib.get("line", 0)) > start_line


def method_lines(jmethod, jmethods, jlines):
"""Filter the lines from the given set of jlines that apply to the given jmethod."""
start_line = int(jmethod.attrib.get('line', 0))
larger = list(int(jm.attrib.get('line', 0)) for jm in jmethods if line_is_after(jm, start_line))
end_line = min(larger) if len(larger) else 99999999
start_line = int(jmethod.attrib.get("line", 0))
larger = list(
int(jm.attrib.get("line", 0))
for jm in jmethods
if line_is_after(jm, start_line)
)
end_line = min(larger) if len(larger) else 99999999

for jline in jlines:
if start_line <= int(jline.attrib['nr']) < end_line:
if start_line <= int(jline.attrib["nr"]) < end_line:
yield jline


def convert_lines(j_lines, into):
"""Convert the JaCoCo <line> elements into Cobertura <line> elements, add them under the given element."""
c_lines = ET.SubElement(into, 'lines')
c_lines = ET.SubElement(into, "lines")
for jline in j_lines:
mb = int(jline.attrib['mb'])
cb = int(jline.attrib['cb'])
ci = int(jline.attrib['ci'])
mb = int(jline.attrib["mb"])
cb = int(jline.attrib["cb"])
ci = int(jline.attrib["ci"])

cline = ET.SubElement(c_lines, 'line')
cline.set('number', jline.attrib['nr'])
cline.set('hits', '1' if ci > 0 else '0') # Probably not true but no way to know from JaCoCo XML file
cline = ET.SubElement(c_lines, "line")
cline.set("number", jline.attrib["nr"])
cline.set(
"hits", "1" if ci > 0 else "0"
) # Probably not true but no way to know from JaCoCo XML file

if mb + cb > 0:
percentage = str(int(100 * (float(cb) / (float(cb) + float(mb))))) + '%'
cline.set('branch', 'true')
cline.set('condition-coverage', percentage + ' (' + str(cb) + '/' + str(cb + mb) + ')')

cond = ET.SubElement(ET.SubElement(cline, 'conditions'), 'condition')
cond.set('number', '0')
cond.set('type', 'jump')
cond.set('coverage', percentage)
percentage = str(int(100 * (float(cb) / (float(cb) + float(mb))))) + "%"
cline.set("branch", "true")
cline.set(
"condition-coverage",
percentage + " (" + str(cb) + "/" + str(cb + mb) + ")",
)

cond = ET.SubElement(ET.SubElement(cline, "conditions"), "condition")
cond.set("number", "0")
cond.set("type", "jump")
cond.set("coverage", percentage)
else:
cline.set('branch', 'false')
cline.set("branch", "false")


def guess_filename(path_to_class):
m = re.match('([^$]*)', path_to_class)
return (m.group(1) if m else path_to_class) + '.java'
m = re.match("([^$]*)", path_to_class)
return (m.group(1) if m else path_to_class) + ".java"


def add_counters(source, target):
target.set('line-rate', counter(source, 'LINE'))
target.set('branch-rate', counter(source, 'BRANCH'))
target.set('complexity', counter(source, 'COMPLEXITY', sum))
target.set("line-rate", counter(source, "LINE"))
target.set("branch-rate", counter(source, "BRANCH"))
target.set("complexity", counter(source, "COMPLEXITY", sum))
target.set("lines-valid", counter(source, "LINE", sum, mode="lines-valid"))
target.set("lines-covered", counter(source, "LINE", sum, mode="lines-covered"))


def fraction(covered, missed):
return covered / (covered + missed)


def sum(covered, missed):
return covered + missed

def counter(source, type, operation=fraction):
cs = source.findall('counter')
c = next((ct for ct in cs if ct.attrib.get('type') == type), None)

if c is not None:
covered = float(c.attrib['covered'])
missed = float(c.attrib['missed'])
def counter(source, type, operation=fraction, mode="N/A"):
cs = source.findall("counter")
c = next((ct for ct in cs if ct.attrib.get("type") == type), None)

if c is not None:
covered = float(c.attrib["covered"])
missed = float(c.attrib["missed"])
if mode == "lines-valid":
return str(int(covered))
elif mode == "lines-covered":
return str(int(sum(covered, missed)))
return str(operation(covered, missed))
else:
return '0.0'
return "0.0"


def convert_method(j_method, j_lines):
c_method = ET.Element('method')
c_method.set('name', j_method.attrib['name'])
c_method.set('signature', j_method.attrib['desc'])
c_method = ET.Element("method")
c_method.set("name", j_method.attrib["name"])
c_method.set("signature", j_method.attrib["desc"])

add_counters(j_method, c_method)
convert_lines(j_lines, c_method)

return c_method


def convert_class(j_class, j_package):
c_class = ET.Element('class')
c_class.set('name', j_class.attrib['name'].replace('/', '.'))
c_class.set('filename', guess_filename(j_class.attrib['name']))
c_class = ET.Element("class")
c_class.set("name", j_class.attrib["name"].replace("/", "."))
c_class.set("filename", guess_filename(j_class.attrib["name"]))

all_j_lines = list(find_lines(j_package, c_class.attrib['filename']))
all_j_lines = list(find_lines(j_package, c_class.attrib["filename"]))

c_methods = ET.SubElement(c_class, 'methods')
all_j_methods = list(j_class.findall('method'))
c_methods = ET.SubElement(c_class, "methods")
all_j_methods = list(j_class.findall("method"))
for j_method in all_j_methods:
j_method_lines = method_lines(j_method, all_j_methods, all_j_lines)
c_methods.append(convert_method(j_method, j_method_lines))
Expand All @@ -108,49 +134,53 @@ def convert_class(j_class, j_package):

return c_class


def convert_package(j_package):
c_package = ET.Element('package')
c_package.attrib['name'] = j_package.attrib['name'].replace('/', '.')
c_package = ET.Element("package")
c_package.attrib["name"] = j_package.attrib["name"].replace("/", ".")

c_classes = ET.SubElement(c_package, 'classes')
for j_class in j_package.findall('class'):
c_classes = ET.SubElement(c_package, "classes")
for j_class in j_package.findall("class"):
c_classes.append(convert_class(j_class, j_package))

add_counters(j_package, c_package)

return c_package


def convert_root(source, target, source_roots):
target.set('timestamp', str(int(source.find('sessioninfo').attrib['start']) / 1000))
target.set("timestamp", str(int(source.find("sessioninfo").attrib["start"]) / 1000))

sources = ET.SubElement(target, 'sources')
sources = ET.SubElement(target, "sources")
for s in source_roots:
ET.SubElement(sources, 'source').text = s
ET.SubElement(sources, "source").text = s

packages = ET.SubElement(target, 'packages')
for package in source.findall('package'):
packages = ET.SubElement(target, "packages")
for package in source.findall("package"):
packages.append(convert_package(package))

add_counters(source, target)


def jacoco2cobertura(filename, source_roots):
if filename == '-':
if filename == "-":
root = ET.fromstring(sys.stdin.read())
else:
tree = ET.parse(filename)
root = tree.getroot()

into = ET.Element('coverage')
into = ET.Element("coverage")
convert_root(root, into, source_roots)
print('<?xml version="1.0" ?>')
print(ET.tostring(into))
outputXML = '<?xml version="1.0" ?>' + ET.tostring(into, encoding="unicode")
print(parseString(outputXML).toprettyxml())


if __name__ == '__main__':
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: cover2cover.py FILENAME [SOURCE_ROOTS]")
sys.exit(1)

filename = sys.argv[1]
source_roots = sys.argv[2:] if 2 < len(sys.argv) else '.'
filename = sys.argv[1]
source_roots = sys.argv[2:] if 2 < len(sys.argv) else "."

jacoco2cobertura(filename, source_roots)