Collecting Code Coverage for a Jenkins Shared Library

Part Four of my series on Jenkins Shared Libraries

Jake Wernette
ITNEXT

--

https://jenkins.io/

In my previous post, we walked through how you can write unit tests for a Jenkins Shared Library. In the fourth and final entry into my Jenkins Shared Library series I am going to show you how you can make a few small changes to your shared library which would allow you to collect code coverage and publish the results to Sonarqube.

Prerequisites

For this tutorial we are going to need to have Sonarqube installed locally. You can follow the instructions that are provided here. Look for the instructions under “Get Started in Two Minutes Guide”. This should give you a running Sonarqube instance at http://localhost:9000. You can login with the default username admin and password admin.

The next thing we need to do is install the SonarQube Groovy plugin. Once you have logged in, navigate to http://localhost:9000/admin/marketplace. You will see a screen like this:

In the search box under plugins, search for groovy and then click the install button to the right of the plugin.

When you click Install, you will see a message telling you to restart Sonarqube.

Click Restart.

Sonarqube will restart and then you will be asked to login again. Use the default username and password from earlier. Once that is done, Sonarqube is now ready to go!

Getting Started

This tutorial picks up where part three of the series left off. If you have not been following along I recommend going back to part one of the series or you can clone the shared library from https://github.com/werne2j/jenkins-shared-library. We are going to be continuing to work off of the tests branch. Run git checkout tests and that will put you right where we are starting from.

Collecting Code Coverage

We are going to be using JaCoCo to collect code coverage for our shared library. This is going to require a few changes to our pom.xml file.

The first thing we are going to add is some properties that are needed for Sonarqube. Under the properties tag we will add:

<properties>...<sonar.projectName>jenkins-shared-library</sonar.projectName>
<sonar.projectKey>jenkins-shared-library</sonar.projectKey>
<sonar.sources>src,vars</sonar.sources>
<sonar.tests>test</sonar.test>
<sonar.groovy.jacoco.reportPath>target/jacoco.exec</sonar.groovy.jacoco.reportPath>
</properties>

This tells Sonarqube what project to report to with projectName and projectKey. Where the code is to analyze and where the tests are located. And lastly, where to look for the code coverage analysis.

Next, we need to add the JaCoCo plugin under the pluginManagement —> plugin tag.

<build>
<pluginManagement>
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.3</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

This makes the plugin available to be used and tells the plugin to run during the test phase. So when we run mvn test, a coverage report will be created.

Thats it!

Lets tests collecting code coverage and see what we get!

We are going to run mvn clean test to generate a JaCoCo report.

We should get an output like this:

If you look down under the output of the tests, you will see the jacoco-maven-plugin generating a coverage report. And now we will send this report to Sonarqube and see what the results are.

Now, in your terminal, run mvn sonar:sonar. This will analyze the project and report its results to Sonarqube. When the command finishes you can navigate to http://localhost:9000/dashboard?id=jenkins-shared-library and see the results from the analysis.

But wait, something doesn’t look right — we don’t see any code coverage. This is actually expected. If you look at this comment in a Jenkins-Spock issue, it talks about how collecting coverage is not easily done.

However, through my trials and tribulations of trying to collect code coverage, I was able to come up with a solution to this issue.

Restructuring the shared library

In order to collect code coverage for our shared library, we are going to move the logic of our global variables into the src directory.

We are going to be creating two classes BuildJavascriptApp and Notify inside src/org/example. And to match that we are going to create two new test files BuildJavascriptAppSpec and NotifySpec, these are going to be located in test/src/org/example. The directory structure should now look like:

├── jenkins-shared-library
│ ├── src
│ │ ├── org
│ │ │ ├── example
│ │ │ │ ├── BuildJavascriptApp.groovy
│ │ │ │ ├── Constants.groovy
│ │ │ │ ├── Notify.groovy
│ ├── test
│ │ ├── resources
| │ ├── src
│ │ | ├── org
│ │ │ | ├── example
│ │ │ │ | ├── BuildJavascriptAppSpec.groovy
│ │ │ │ | ├── NotifySpec.groovy
│ │ ├── vars
│ ├── vars
│ │ ├── buildJavascriptApp.groovy
│ │ ├── notify.groovy

Taking a look at our classes, they are going to look very similar to what the global variables look like. For example, here is what BuildJavascriptApp.groovy will look like:

package org.exampleclass BuildJavascriptApp {
def exec(Map config=[:], Closure body={}) {
node {
git url: "https://github.com/werne2j/sample-nodejs"
stage("Install") {
sh "npm install"
}
stage("Test") {
sh "npm test"
}
stage("Deploy") {
if (config.deploy) {
sh "npm publish"
}
}
body()
}
}
}

We are taking the global variable and wrapping it in an exec method inside of a class.

Now for our global variable, we are going to update it to call our new class.

import org.example.BuildJavascriptAppdef call(Map config=[:], Closure body={}) {
def buildJavascriptApp = new BuildJavascriptApp()
buildJavascriptApp.exec(config, body)
}

And now our global variable is very simple and is only calling our new class.

The last thing to do is update the test. We are going to copy the tests from test/vars/BuildJavascriptAppSpec.groovy to the new test file we created at test/src/org/example/BuildJavascriptAppSpec.groovy. The only thing we have to do is make a couple small adjustments. We will be loading in the class instead of the variable, since we moved all the logic to the class. And then we will be calling the exec method instead of calling the variable. The code should look like this:

import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
import org.example.BuildJavascriptApp
class BuildJavascriptAppSpec extends JenkinsPipelineSpecification {
def buildJavascriptApp = null
def setup() {
buildJavascriptApp = new BuildJavascriptApp()
}
def "[buildJavascriptApp] will run npm publish if deploy is true"() {
when:
buildJavascriptApp.exec(deploy: true)
then:
1 * getPipelineMock("sh")("npm publish")
}
def "[buildJavascriptApp] will not npm publish if deploy is false"() {
when:
buildJavascriptApp.exec(deploy: false)
then:
0 * getPipelineMock("sh")("npm publish")
}
def "[buildJavascriptApp] will call closure if passed"() {
setup:
def body = Mock(Closure)
when:
buildJavascriptApp.exec(deploy: false, body)
then:
1 * body()
}
}

You can now delete the previous test residing in the test/vars directory. That is the last thing we need to do to convert the code to be able to collect code coverage. I will let you try your hand at converting the notify variable to have the logic in a class like we did with buildJavascriptApp.

There is a complete example at https://github.com/werne2j/jenkins-shared-library/tree/sonarqube if you would like to see the final results.

Once the library has be restructured, we are going to re-run the tests and try to reanalyze the library with Sonarqube.

Again, we will run mvn clean test.

We see that the tests have succeeded and our new report has been generated.

Now let’s run mvn sonar:sonar one more time. When the command finishes running navigate back to http://localhost:9000/dashboard?id=jenkins-shared-library. Or refresh the page if you still have it up. And we should see something new.

And there we have it, coverage!

Conclusion

With this strategy, we can now collect code coverage on a Jenkins Shared Library. This is great for managers or team leads that want to see whether or not code coverage is increasing or decreasing or being collected at all.

Thank you for reading part four of my Jenkins Shared Library series.

I really hope that you have enjoyed my series on Jenkins Shared Libraries. We have gone from explaining what the libraries are and how to use them, to writing tests and then collecting code coverage.

Hopefully you can take the knowledge that you’ve learned from this series and apply it to your work or personal endeavors.

Thank you again and good luck with all your Jenkins Shared Library adventures!

--

--