From 392488ad8e5973ea5de6db4b43cfc8401a0803aa Mon Sep 17 00:00:00 2001 From: Arne Keller Date: Sun, 14 Mar 2021 11:08:33 +0100 Subject: [PATCH] Scripts used in CI --- calcCoverage.sh | 2 + cover2cover.py | 165 +++++++++++++++++++++++++++++++++++++++++++++ source2filename.py | 49 ++++++++++++++ 3 files changed, 216 insertions(+) create mode 100755 calcCoverage.sh create mode 100644 cover2cover.py create mode 100644 source2filename.py diff --git a/calcCoverage.sh b/calcCoverage.sh new file mode 100755 index 0000000..552ba08 --- /dev/null +++ b/calcCoverage.sh @@ -0,0 +1,2 @@ +#!/bin/sh +rg edu.kit.typicalc.model | rg --only-matching -r '$1' 'line-rate="([0-9.]+)"' | head -n 1 | awk '{ print "Test Coverage: " ($1 * 100) "%" }' diff --git a/cover2cover.py b/cover2cover.py new file mode 100644 index 0000000..748d8ab --- /dev/null +++ b/cover2cover.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +import sys +import xml.etree.ElementTree as ET +import re +import os.path +import time + +# source: https://gitlab.com/haynes/jacoco2cobertura/ + +# 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 path_to_filepath(path_to_class, sourcefilename): + return path_to_class[0: path_to_class.rfind("/") + 1] + sourcefilename + +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', path_to_filepath(j_class.attrib['name'], j_class.attrib['sourcefilename'])) + + 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): + try: + target.set('timestamp', str(int(source.find('sessioninfo').attrib['start']) / 1000)) + except AttributeError as e: + target.set('timestamp', str(int(time.time() / 1000))) + sources = ET.SubElement(target, 'sources') + for s in source_roots: + ET.SubElement(sources, 'source').text = s + + packages = ET.SubElement(target, 'packages') + + for group in source.findall('group'): + for package in group.findall('package'): + packages.append(convert_package(package)) + + 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, encoding='unicode')) + +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) diff --git a/source2filename.py b/source2filename.py new file mode 100644 index 0000000..39a70b1 --- /dev/null +++ b/source2filename.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +import sys +import xml.etree.ElementTree as ET +import lxml.etree +import re +import os.path + +# source: https://gitlab.com/haynes/jacoco2cobertura/ + +def convert_source(filename): + #read input file + fin = open(filename, "rt") + #read file contents to string + data = fin.read() + # xmlstr is your xml in a string + root = lxml.etree.fromstring(data) + sources = root.find('sources') + packages = root.find('packages') + for package in packages: + classes = package.find('classes') + for clazz in classes: + file_not_found = True + for source in sources: + full_filename = source.text + '/' + clazz.attrib['filename'] + if os.path.isfile(full_filename): + clazz.attrib['filename'] = full_filename + file_not_found = False + if file_not_found: + print("Warning: File {} not found in all sources; removing from sources.".format(clazz.attrib['filename'])) + clazz.getparent().remove(clazz) + + data = lxml.etree.tostring(root, pretty_print=True) + #close the input file + fin.close() + #open the input file in write mode + fin = open(filename, "wb") + #overrite the input file with the resulting data + fin.write(data) + #close the file + fin.close() + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: source2filename.py FILENAME") + sys.exit(1) + + filename = sys.argv[1] + + convert_source(filename)