diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5f44da8..f8c8280 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,12 +33,16 @@ test_job: - build_job script: - mvn $MAVEN_CLI_OPTS test + - mkdir -p target/site/cobertura && ./cover2cover.py target/site/jacoco/jacoco.xml src/io/scenarium/logger/ > target/site/cobertura/coverage.xml - awk -F, '{ instructions += $4 + $5; covered += $5 } END { print "===============================================\n== Coverage Results:\n==\n== ", covered, " / ", instructions, " instructions covered"; print "== ", 100*covered/instructions, "% covered\n==\n===============================================" }' target/site/jacoco/jacoco.csv artifacts: - reports: - junit: target/surefire-reports/TEST-*.xml + when: on_success + expire_in: 3 day path: - target/site/jacoco + reports: + junit: target/surefire-reports/TEST-*.xml + cobertura: target/site/cobertura/coverage.xml package_job: stage: package @@ -48,6 +52,6 @@ package_job: - mvn $MAVEN_CLI_OPTS package artifacts: when: on_success - expire_in: 1 day + expire_in: 3 day paths: - target/*.jar \ No newline at end of file diff --git a/cover2cover.py b/cover2cover.py new file mode 100755 index 0000000..ec5e105 --- /dev/null +++ b/cover2cover.py @@ -0,0 +1,156 @@ +#!/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)