From a9c7545bade08693186ec1c62f8c30a8a36df16f Mon Sep 17 00:00:00 2001 From: Nick Vance <704392+nickv2002@users.noreply.github.com> Date: Wed, 29 May 2024 10:28:07 -0700 Subject: [PATCH] Update cover2cover.py Add pretty formatted output and calculation for `lines-valid` and `lines-covered` --- cover2cover.py | 148 +++++++++++++++++++++++++++++-------------------- 1 file changed, 89 insertions(+), 59 deletions(-) diff --git a/cover2cover.py b/cover2cover.py index 7cb2d2f..e336a24 100755 --- a/cover2cover.py +++ b/cover2cover.py @@ -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 elements for a given source file in a package.""" lines = list() @@ -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 elements into Cobertura 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)) @@ -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('') - print(ET.tostring(into)) + outputXML = '' + 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)