In this tutorial I’m going to show how to analyze multiple Eclipse plug-in projects with Sonarqube. In particular, I’m going to focus on peculiarities that have to be taken care of due to the standard way Sonarqube analyzes sources and to the structure of typical Eclipse plug-in projects (concerning tests and code coverage).
The code of this example is available on Github: https://github.com/LorenzoBettini/tycho-sonarqube-example
This can be seen as a follow-up of my previous post on “Jacoco code coverage and report of multiple Eclipse plug-in projects. I’ll basically reuse almost the same structure of that example and a few things. The part of Jacoco report is not related to Sonarqube but I’ll leave it there.
The structure of the projects is as follows:
Each project’s code is tested in a specific .tests project. The code consists of simple Java classes doing nothing interesting, and tests just call that code.
The project example.tests.parent contains all the common configurations for test projects (and test report, please refer to my previous post on “Jacoco code coverage and report of multiple Eclipse plug-in projects for the details of this report project, which is not strictly required for Sonarqube).
This is its pom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>example</groupId> <artifactId>example.parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>example.tests.parent</artifactId> <packaging>pom</packaging> <properties> <!-- this will be overridden in jacoco profile with tycho.testArgLine, which, in turn, will be set by Jacoco prepare-agent goal. This property can then be used in argLine for custom tycho-surefire configuration, both when Jacoco is used and when it is not used. --> <additionalTestArgLine></additionalTestArgLine> <jacoco-version>0.7.9</jacoco-version> </properties> <profiles> <profile> <id>jacoco</id> <activation> <activeByDefault>false</activeByDefault> </activation> <properties> <!-- This will be set by Jacoco prepare-agent goal --> <additionalTestArgLine>${tycho.testArgLine}</additionalTestArgLine> </properties> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco-version}</version> <configuration> <excludes> <exclude>**/plugin1/Main.class</exclude> </excludes> </configuration> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> </project> |
Note that this also shows a possible way of dealing with custom argLine for tycho-surefire configuration: tycho.testArgLine will be automatically set the jacoco:prepare-agent goal, with the path of jacoco agent (needed for code coverage); the property tycho.testArgLine is automatically used by tycho-surefire. But if you have a custom configuration of tycho-surefire with additional arguments you want to pass in argLine, you must be careful not to overwrite the value set by jacoco. If you simply refer to tycho.testArgLine in the custom tycho-surefire configuration’s argLine, it will work when the jacoco profile is active but it will fail when it is not active since that property will not exist. Don’t try to define it as an empty property by default, since when tycho-surefire runs it will use that empty value, ignoring the one set by jacoco:prepare-agent (argLine’s properties are resolved before jacoco:prepare-agent is executed). Instead, use another level of indirection: refer to a new property, e.g., additionalTestArgLine, which by default is empty. In the jacoco profile, set additionalTestArgLine referring to tycho.testArgLine (in that profile, that property is surely set by jacoco:prepare-agent). Then, in the custom argLine, refer to additionalTestArgLine. An example is shown in the project example.plugin2.tests pom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>example</groupId> <artifactId>example.tests.parent</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../example.tests.parent</relativePath> </parent> <artifactId>example.plugin2.tests</artifactId> <packaging>eclipse-test-plugin</packaging> <build> <plugins> <plugin> <groupId>org.eclipse.tycho</groupId> <artifactId>tycho-surefire-plugin</artifactId> <version>${tycho-version}</version> <configuration> <!-- additionalTestArgLine is set in the parent project with the Jacoco agent when the jacoco profile is active (and it will be empty when jacoco profile is not active). This way, you can customize argLine without overwriting the jacoco agent set by jacoco:prepare-agent. --> <argLine>${additionalTestArgLine} -DbuildingWithTycho=true</argLine> </configuration> </plugin> </plugins> </build> </project> |
You can check that code coverage works as expected by running (it’s important to verify that jacoco has been configured correctly in your projects before running Sonarqube analysis: if it’s not working in Sonarqube then it’s something wrong in the configuration for Sonarqube, not in the jacoco configuration, as we’ll see in a minute):
1 |
mvn clean verify -Pjacoco |
Mare sure that example.tests.report/target/site/jacoco-aggregate/index.html reports some code coverage (in this example, example.plugin1 has some code uncovered by intention).
Now I assume you already have Sonarqube installed.
Let’s run a first Sonarqube analysis with
1 |
mvn clean verify -Pjacoco sonar:sonar |
This is the result:
So Unit Tests are correctly collected! What about Code Coverage? Something is shown, but if you click on that you see some bad surprises:
Code coverage only on tests (which is irrelevant) and no coverage for our SUT (Software Under Test) classes!
That’s because jacoco .exec files are by default generated in the target folder of the tests project, now when Sonarqube analyzes the projects:
- it finds the jacoco.exec file when it analyzes a tests project but can only see the sources of the tests project (not the ones of the SUT)
- when it analyzes a SUT project it cannot find any jacoco.exec file.
We could fix this by configuring the maven jacoco plugin to generate jacoco.exec in the SUT project, but then the aggregate report configuration should be updated accordingly (while it works out of the box with the defaults). Another way of fixing the problem is to use the Sonarqube maven property sonar.jacoco.reportPaths and “trick” Sonarqube like that (we do that in the parent pom properties section):
1 2 3 4 5 6 7 |
<!-- Always refer to the corresponding tests project (if it exists) otherwise Sonarqube won't be able to collect code coverage. For example, when analyzing project foo it wouldn't find code coverage information if it doesn't use foo.tests jacoco.exec. --> <sonar.jacoco.reportPaths> ../${project.artifactId}.tests/target/jacoco.exec </sonar.jacoco.reportPaths> |
This way, when it analyzes example.plugin1 it will use the jacoco.exec found in example.plugin1.tests project (if you follow the convention foo and foo.tests this works out of the box, otherwise, you have to list all the jacoco.exec paths in all the projects in that property, separated by comma).
Let’s run the analysis again:
OK, now code coverage is collected on the SUT classes as we wanted. Of course, now test classes appear as uncovered (remember, when it analyzes example.plugin1.tests it now searchs for jacoco.exec in example.plugin1.tests.tests, which does not even exist).
This leads us to another problem: test classes should be excluded from Sonarqube analysis. This works out of the box in standard Maven projects because source folders of SUT and source folders of test classes are separate and in the same project (that’s also why code coverage for pure Maven projects works out of the box in Sonarqube); this is not the case for Eclipse projects, where SUT and tests are in separate projects.
In fact, issues are reported also on test classes:
We can fix both problems by putting in the tests.parent pom properties these two Sonarqube properties (note the link to the Eclipse bug about this behavior)
1 2 3 |
<!-- Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=397015 --> <sonar.sources></sonar.sources> <sonar.tests>src</sonar.tests> |
This will be inherited by our tests projects and for those projects, Sonarqube will not analyze test classes.
Run the analysis again and see the results: code coverage only on SUT and issues only on SUT (remember that in this example MyClass1 is not uncovered completely by intention):
You might be tempted to use the property sonar.skip set to true for test projects, but you will use JUnit test reports collection.
The final bit of customization is to exclude the Main.java class from code coverage. We have already configured the jacoco maven plugin to do so, but this won’t be picked up by Sonarqube (that configuration only tells jacoco to skip that class when it generates the HTML report).
We have to repeat that exclusion with a Sonarqube maven property, in the parent pom:
1 2 3 4 |
<!-- Example of skipping code coverage (comma separated Java files). --> <sonar.coverage.exclusions> **/plugin1/Main.java </sonar.coverage.exclusions> |
Note that in the jacoco maven configuration we had excluded a .class file, while here we exclude Java files.
Run the analysis again and Main is not considered in code coverage:
Now you can have fun in fixing Sonarqube issues, which is out of the scope of this tutorial 🙂
This example is also analyzed from Travis using Sonarcloud (https://sonarcloud.io/dashboard?id=example%3Aexample.parent).
Hope you enjoyed this tutorial and Happy new year! 🙂
yes! thank you, for the clear explanation with examples!