Hello Sonarqube community!
As part of increasing our code quality standards and improve our testing culture we’ve recently introduced sonarqube to our ci/cd pipeline. Adding the different processes as projects has been pretty smooth so far. However, at the moment I’m struggling to add code coverage information for one of our java processes we build using ant.
These are the versions of the relevant software we’re using:
- The version we’re using is SonarQube 9.9 (build 65466) and it’s Deployed in Docker
- We’re using jacoco 0.8.8 to generate the test coverage report
- Our unit testing framework is testng and the version is 7.5
- The Ant task library is sonarqube-ant-task-2.7.1.1951.jar
- The Ant version is 1.10.13
What I’m observing is that the jacoco report is generated, sonar says in the logs it is reading and uploading it, but when I go to our sonarqube server and check on my branch overall view, it says the expected code coverage after merge is 0%. I’ve looked at the html, csv, and xml reports and I don’t think it should be 0%, but I will admit that I don’t know what sonarqube is expecting in terms of how it should be reported.
This is the ant build xml script. I’ve omitted what I consider to be irrelevant as it is quite a large build file:
<?xml version="1.0" encoding="UTF-8" ?>
<project name="blah" default="jar" basedir="." xmlns:unless="ant:unless" xmlns:sonar="antlib:org.sonar.ant">
<property name="test.src.root.dir" value="test" />
<property name="test.src.dir" value="test/unit" />
<property name="classes.dir" value="bin" />
<property name="build.dir" value="test_output" />
<property name="test.classes.dir" value="${build.dir}/classes" />
<property name="testng.reports.dir" value="${build.dir}/testng" />
<property name="jacoco.reports.dir" value="${build.dir}/jacoco" />
<target name="init_build" description="Create build directory">
<delete dir="${classes.dir}" />
<mkdir dir="${classes.dir}"/>
</target>
<target name="init_test" description="Create test and report build directories">
<mkdir dir="${test.classes.dir}"/>
<mkdir dir="${testng.reports.dir}"/>
<mkdir dir="${jacoco.reports.dir}"/>
</target>
<!-- SONARQUBE -->
<property name="sonar.host.url" value="https://sonarqube.XXX" />
<property name="sonar.projectKey" value="XXX" />
<property name="sonar.projectName" value="XXX" />
<property name="sonar.projectVersion" value="1.0" />
<property name="sonar.sources" value="source" />
<property name="sonar.inclusions" value="${sonar.sources}/**/*.java" />
<property name="sonar.java.binaries" value="bin" />
<property name="sonar.java.libraries" value="lib/*.jar" />
<property name="sonar.tests" value="${test.src.root.dir}" />
<property name="sonar.test.inclusions" value="${test.src.root.dir}/**/*.java" />
<property name="sonar.java.test.binaries" value="${test.classes.dir}" />
<!-- test coverage with jacoco -->
<property name="sonar.java.coveragePlugin" value="jacoco" />
<property name="sonar.coverage.jacoco.xmlReportPaths" value="${jacoco.reports.dir}/jacoco_report.xml" />
<!-- <property name="sonar.jacoco.reportPath" value="${jacoco.reports.dir}/jacoco.exec" /> -->
<path id="jacoco_libs">
<fileset dir="." includes="*.jar"/>
</path>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath refid="jacoco_libs" />
</taskdef>
<target name="report" depends="customer_code,run_testng">
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath refid="jacoco_libs" />
</taskdef>
<jacoco:report xmlns:jacoco="antlib:org.jacoco.ant">
<executiondata>
<file file="${jacoco.reports.dir}/jacoco.exec" />
</executiondata>
<structure name="jacoco report">
<classfiles>
<fileset dir="${test.classes.dir}" />
</classfiles>
<sourcefiles encoding="UTF-8">
<fileset dir="${test.src.dir}" />
</sourcefiles>
</structure>
<html destdir="${jacoco.reports.dir}" />
<csv destfile="${jacoco.reports.dir}/jacoco_report.csv" />
<xml destfile="${jacoco.reports.dir}/jacoco_report.xml" />
</jacoco:report>
</target>
<!-- End test coverage -->
<!-- Define SonarScanner for Ant Target -->
<target name="sonar" depends="report">
<taskdef uri="antlib:org.sonar.ant" resource="org/sonar/ant/antlib.xml">
<classpath path="sonarqube-ant-task-2.7.1.1951.jar" />
</taskdef>
<!-- Execute SonarScanner for Ant Analysis -->
<sonar:sonar />
</target>
<!-- /SONARQUBE -->
<path id="class.path">
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
</path>
<!-- Unit tests -->
<path id="classpath">
<fileset dir="lib" includes="*.jar"/>
<fileset dir="." includes="*.jar"/>
<pathelement location="${test.classes.dir}"/>
<pathelement location="source"/>
<pathelement location="bin"/>
</path>
<taskdef name="testng" classpathref="classpath" classname="org.testng.TestNGAntTask" />
<target name="compile_tests" depends="init_test,compile">
<javac srcdir="${test.src.dir}" classpathref="classpath" includeAntRuntime="No" destdir="${test.classes.dir}" debug="true" />
</target>
<target name="run_testng" depends="compile_tests">
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath refid="jacoco_libs" />
</taskdef>
<jacoco:coverage destfile="${jacoco.reports.dir}/jacoco.exec" xmlns:jacoco="antlib:org.jacoco.ant">
<testng outputdir="${testng.reports.dir}" classpathref="classpath" useDefaultListeners="true">
<classpath location="${test.classes.dir}"/>
<xmlfileset dir="${test.src.dir}" includes="XXX_suites.xml"/>
</testng>
</jacoco:coverage>
</target>
<!-- End Unit tests -->
These are the relevant logs after running with the -debug and -v flags:
[...]
report:
parsing buildfile jar:file:/PROJECT_PATH/org.jacoco.ant-0.8.8.jar!/org/jacoco/ant/antlib.xml with URI = jar:file:/PROJECT_PATH/org.jacoco.ant-0.8.8.jar!/org/jacoco/ant/antlib.xml from a zip file
Trying to override old definition of task antlib:org.jacoco.ant:coverage
Trying to override old definition of task antlib:org.jacoco.ant:agent
Trying to override old definition of task antlib:org.jacoco.ant:report
Trying to override old definition of task antlib:org.jacoco.ant:merge
Trying to override old definition of task antlib:org.jacoco.ant:dump
Trying to override old definition of task antlib:org.jacoco.ant:instrument
[jacoco:report] Loading execution data file /PROJECT_PATH/test_output/jacoco/jacoco.exec
[jacoco:report] Writing bundle 'logloader jacoco report' with 5 classes
[...]
[sonar:sonar] Sensor JaCoCo XML Report Importer [jacoco]
[sonar:sonar] Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list.
[sonar:sonar] Reading report '/PROJECT_PATH/test_output/jacoco/jacoco_report.xml'
[sonar:sonar] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=38ms
[...]
This is a screenshot of the jacoco html report:
And these are some of the lines from the XML report:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE report PUBLIC "-//JACOCO//DTD Report 1.1//EN" "report.dtd">
<report name="XXX jacoco report">
<sessioninfo id="XXX" /> [...]
<package name="unit">
<class name="unit/XXXTest" sourcefilename="XXXTest.java">
<method name="<init>" desc="()V" line="41">
<counter type="INSTRUCTION" missed="0" covered="3" />
<counter type="LINE" missed="0" covered="1" />
<counter type="COMPLEXITY" missed="0" covered="1" />
<counter type="METHOD" missed="0" covered="1" />
</method>
<method name="setUpOnce" desc="()V" line="56">
<counter type="INSTRUCTION" missed="5" covered="0" />
<counter type="LINE" missed="4" covered="0" />
<counter type="COMPLEXITY" missed="1" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="tearDownOnce" desc="()V" line="68">
<counter type="INSTRUCTION" missed="3" covered="0" />
<counter type="LINE" missed="2" covered="0" />
<counter type="COMPLEXITY" missed="1" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="setUp" desc="()V" line="78">
<counter type="INSTRUCTION" missed="7" covered="0" />
<counter type="LINE" missed="4" covered="0" />
<counter type="COMPLEXITY" missed="1" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="XXX" desc="()V" line="88">
<counter type="INSTRUCTION" missed="23" covered="0" />
<counter type="LINE" missed="7" covered="0" />
<counter type="COMPLEXITY" missed="1" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="tearDown" desc="()V" line="106">
<counter type="INSTRUCTION" missed="3" covered="0" />
<counter type="LINE" missed="2" covered="0" />
<counter type="COMPLEXITY" missed="1" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="XXX" desc="()V" line="111">
<counter type="INSTRUCTION" missed="43" covered="0" />
<counter type="BRANCH" missed="4" covered="0" />
<counter type="LINE" missed="11" covered="0" />
<counter type="COMPLEXITY" missed="3" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="XXX" desc="()V" line="132">
<counter type="INSTRUCTION" missed="49" covered="0" />
<counter type="BRANCH" missed="4" covered="0" />
<counter type="LINE" missed="9" covered="0" />
<counter type="COMPLEXITY" missed="3" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="XXX" desc="()V" line="152">
<counter type="INSTRUCTION" missed="40" covered="0" />
<counter type="BRANCH" missed="4" covered="0" />
<counter type="LINE" missed="9" covered="0" />
<counter type="COMPLEXITY" missed="3" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
<method name="XXX" desc="()V" line="174">
<counter type="INSTRUCTION" missed="17" covered="0" />
<counter type="LINE" missed="7" covered="0" />
<counter type="COMPLEXITY" missed="1" covered="0" />
<counter type="METHOD" missed="1" covered="0" />
</method>
[...]
<counter type="INSTRUCTION" missed="641" covered="9" />
<counter type="BRANCH" missed="42" covered="0" />
<counter type="LINE" missed="211" covered="3" />
<counter type="COMPLEXITY" missed="41" covered="2" />
<counter type="METHOD" missed="20" covered="2" />
<counter type="CLASS" missed="0" covered="1" />
</class>
<sourcefile name="XXX.java">
[...]
<counter type="INSTRUCTION" missed="0" covered="329" />
<counter type="LINE" missed="0" covered="73" />
<counter type="COMPLEXITY" missed="0" covered="14" />
<counter type="METHOD" missed="0" covered="14" />
<counter type="CLASS" missed="0" covered="2" />
</sourcefile>
<sourcefile name="XXX.java">
[...]
<counter type="INSTRUCTION" missed="175" covered="338" />
<counter type="BRANCH" missed="4" covered="4" />
<counter type="LINE" missed="63" covered="87" />
<counter type="COMPLEXITY" missed="5" covered="13" />
<counter type="METHOD" missed="3" covered="11" />
<counter type="CLASS" missed="0" covered="1" />
</sourcefile>
<sourcefile name="XXX.java">
[...]
<counter type="INSTRUCTION" missed="641" covered="9" />
<counter type="BRANCH" missed="42" covered="0" />
<counter type="LINE" missed="211" covered="3" />
<counter type="COMPLEXITY" missed="41" covered="2" />
<counter type="METHOD" missed="20" covered="2" />
<counter type="CLASS" missed="0" covered="1" />
</sourcefile>
<sourcefile name="XXX.java">
[...]
<counter type="INSTRUCTION" missed="34" covered="140" />
<counter type="BRANCH" missed="2" covered="2" />
<counter type="LINE" missed="15" covered="40" />
<counter type="COMPLEXITY" missed="2" covered="9" />
<counter type="METHOD" missed="0" covered="9" />
<counter type="CLASS" missed="0" covered="1" />
</sourcefile>
<counter type="INSTRUCTION" missed="850" covered="816" />
<counter type="BRANCH" missed="48" covered="6" />
<counter type="LINE" missed="289" covered="203" />
<counter type="COMPLEXITY" missed="48" covered="38" />
<counter type="METHOD" missed="23" covered="36" />
<counter type="CLASS" missed="0" covered="5" />
</package>
<counter type="INSTRUCTION" missed="850" covered="816" />
<counter type="BRANCH" missed="48" covered="6" />
<counter type="LINE" missed="289" covered="203" />
<counter type="COMPLEXITY" missed="48" covered="38" />
<counter type="METHOD" missed="23" covered="36" />
<counter type="CLASS" missed="0" covered="5" />
</report>
However, this is what I see when I go to our sonarqube server and pick my feature branch:
I’ve been trying different combinations of parameters, such as sonar.jacoco.reportPath, and building the report in different ways with no luck. I think it should be working because the report is generated and it has data coverage information, but there’s obviously something wrong. Any help or a nudge in the right direction would be much appreciated.
Thanks in advance,
Michael.