#!/usr/bin/env python import sys import xml.etree.ElementTree as ET import re import os.path # 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() sourcefiles = j_package.findall("sourcefile") for sourcefile in sourcefiles: if sourcefile.attrib.get("name") == os.path.basename(filename): lines = lines + sourcefile.findall("line") return lines def line_is_after(jm, 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 for jline in jlines: 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') for jline in j_lines: 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 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) else: 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' 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)) 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']) return str(operation(covered, missed)) else: 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']) 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'])) 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')) 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)) add_counters(j_class, c_class) convert_lines(all_j_lines, c_class) return c_class def convert_package(j_package): 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.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)) sources = ET.SubElement(target, 'sources') for s in source_roots: ET.SubElement(sources, 'source').text = s 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 == '-': root = ET.fromstring(sys.stdin.read()) else: tree = ET.parse(filename) root = tree.getroot() into = ET.Element('coverage') convert_root(root, into, source_roots) print('') print(ET.tostring(into).decode('utf-8')) 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 '.' jacoco2cobertura(filename, source_roots)