Skip to content
Snippets Groups Projects
Verified Commit c643ac86 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Add coverage information to CI

parent a98cc493
No related branches found
No related tags found
1 merge request!2Draft: Jetpack compose rewrite
Pipeline #580 canceled
......@@ -36,7 +36,10 @@ test:
paths:
- "*/build/test-results/**/TEST-*.xml"
- "*/build/reports/*.xml"
- "*/build/reports/cobertura/*.xml"
reports:
cobertura:
- "*/build/reports/cobertura/*.xml"
junit:
- "*/build/test-results/**/TEST-*.xml"
- "*/build/reports/*.xml"
......
plugins {
kotlin("jvm")
id("jacoco")
id("de.kuschku.coverageconverter")
}
tasks.withType<Test> {
......
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.coverageconverter
import org.gradle.api.Action
import org.gradle.api.Task
import org.gradle.testing.jacoco.tasks.JacocoReport
import java.io.File
internal class ConvertCoverageReportsAction(
private val jacocoReportTask: JacocoReport
) : Action<Task> {
private fun findOutputFile(jacocoFile: File): File? {
val actualFile = jacocoFile.absoluteFile
if (actualFile.exists() && actualFile.parentFile.name == "jacoco") {
val folder = File(actualFile.parentFile.parentFile, "cobertura")
folder.mkdirs()
return File(folder, actualFile.name)
}
return null
}
private fun createPythonScript(name: String, temporaryDir: File): File {
val file = File(temporaryDir, name)
if (file.exists()) {
file.delete()
}
val source = CoverageConverterPlugin::class.java.getResourceAsStream("/coverageconverter/$name")
file.writeBytes(source.readAllBytes())
return file
}
override fun execute(t: Task) {
val cover2coverScript = createPythonScript("cover2cover.py", t.temporaryDir)
val source2filenameScript = createPythonScript("source2filename.py", t.temporaryDir)
fun cover2cover(reportFile: File, outputFile: File, sourceDirectories: Iterable<File>) {
t.project.exec {
commandLine("python3")
args(cover2coverScript.absolutePath)
args(reportFile.absolutePath)
args(sourceDirectories.map(File::getAbsolutePath))
standardOutput = outputFile.outputStream()
}
}
fun source2filename(reportFile: File) {
t.project.exec {
commandLine("python3")
args(source2filenameScript.absolutePath)
args(reportFile.absolutePath)
}
}
jacocoReportTask.reports.forEach {
if (it.isEnabled && it.destination.extension == "xml") {
val outputFile = findOutputFile(it.destination)
if (outputFile != null) {
cover2cover(it.destination, outputFile, jacocoReportTask.sourceDirectories)
source2filename(outputFile)
}
}
}
}
}
package de.kuschku.coverageconverter
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.getByName
import org.gradle.kotlin.dsl.register
import org.gradle.testing.jacoco.tasks.JacocoReport
class CoverageConverterPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.afterEvaluate {
val testTask = tasks.getByName<Test>("test")
val jacocoReportTask = tasks.getByName<JacocoReport>("jacocoTestReport"){
dependsOn(testTask)
}
tasks.register("coberturaTestReport") {
dependsOn(jacocoReportTask)
mustRunAfter(jacocoReportTask)
group = "verification"
doLast(ConvertCoverageReportsAction(jacocoReportTask))
}
}
}
}
implementation-class=de.kuschku.coverageconverter.CoverageConverterPlugin
#!/usr/bin/env python
import sys
import xml.etree.ElementTree as ET
import re
import os.path
import time
# 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()
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 <line> elements into Cobertura <line> 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('<?xml version="1.0" ?>')
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)
#!/usr/bin/env python
import sys
import xml.etree.ElementTree as ET
import lxml.etree
import re
import os.path
def convert_source(filename):
#read input file
root = lxml.etree.parse(filename)
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)
#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)
plugins {
kotlin("jvm")
jacoco
id("de.kuschku.coverageconverter")
}
tasks.withType<Test> {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment