How to track code coverage with SonarQube and Buddy

How to track code coverage with SonarQube and Buddy

SonarQube is a server that allows to test coverage, find bugs in your code and more. It is language-agnostic and can be installed on premises, and you can integrate it easily with Buddy.

Hint
For the sake of example, in this article we will use JavaScript as a sample code language.

This article will guide you step by step through the configuration process. If you already have a repository with tests/coverage set up, you can skip to the SonarQube configuration part. If you have SonarQube installed as well – you may skip to the integration part.

Hint

Actions used in this guide:

Set up code and tests with SonarQube code coverage

First, let's create the repository on Buddy. It will be a simple Git repo.

Image loading...Create a new Buddy repository

Now let's clone it:

bash
git clone https://app.buddy.works/yourname/yourrepo cd yourrepo$$

Let's quickly set up something testable to have something for SonarQube code coverage to work with. We will use jest as our test command:

bash
npm init # use 'jest --coverage' as test command and 'src/index.js' as entry point$

Now, let's install some dependencies:

bash
npm install jest @types/jest sonar-scanner --dev$

Jest is a test/coverage tool, and Sonar Scanner is a tool that uploads the coverage. Also, we installed Jest types for better code completion as all major IDEs support it.

Now let's make a sample code & test:

bash
mkdir src$

Create a file src/index.js with the following code:

js
// src/index.js exports.fn = arg => { if (arg < 0) return 0; return arg + 1; };

Create a test file src/index.test.js:

js
// src/index.test.js const {fn} = require('.'); describe('fn', () => { test('adds 1', () => { expect(isCovered(1)).toEqual(2); }); });

Now, if you run the following command:

bash
npm test$

You will get an output like this:

default
> buddy-sonar@1.0.0 test /buddy-sonar > jest --coverage PASS src/index.test.js isCovered ✓ adds 1 (3ms) ----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 75 | 100 | 50 | 100 | | index.js | 75 | 100 | 50 | 100 | | ----------|----------|----------|----------|----------|-------------------| Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.331s Ran all test suites.

Also, note that the coverage directory has been created in the root of your cloned repo.

Now let's create the Sonar Scanner config file sonar-project.properties:

default
sonar.projectKey=buddy sonar.projectName=Buddy sonar.sourceEncoding=UTF-8 sonar.sources=src sonar.exclusions=**/*.test.ts sonar.tests=src sonar.test.inclusions=**/*.test.js sonar.javascript.coveragePlugin=lcov sonar.javascript.lcov.reportPaths=coverage/lcov.info

Let's add the coverage folder to .gitignore along with Node modules:

default
coverage node_modules

The next step is adding our files to Git and pushing them to master:

bash
git add .gitignore src/index.js src/index.test.js package.json package-lock.json git commit -m Init git push origin:master$$$

Install SonarQube

For the sake of simplicity, we will use a local installation of SonarQube using Docker and put it online using Ngrok service. This kind of installation can be easily repeated elsewhere if you have a Docker instance deployed somewhere.

Thie first thing is installing Docker if you haven't done that already. Docker is a virtual machine manager that allows running virtual images with specific software installed as if it is a physical computer. Installation is very simple – just follow the docs on the site.

The next step is to run the SonarQube Docker image:

bash
docker run -d --name sonarqube -p 9000:9000 sonarqube$

You will see the following output:

bash
$ docker run -d --name sonarqube -p 9000:9000 sonarqube Unable to find image 'sonarqube:latest' locally latest: Pulling from library/sonarqube 8d691f585fa8: Pull complete 3da6fe7ff2ef: Pull complete e22147996cc0: Pull complete 8df48a2d4467: Pull complete 06eb74af83c0: Pull complete a642409dc81e: Pull complete 778617ae58c7: Pull complete 78e3d611ddbb: Pull complete ec0d78b01f70: Pull complete Digest: sha256:03681e6bb9de5ca4192e9c9b5035e0cc84404dbc107bb7069ca95152dca5f945 Status: Downloaded newer image for sonarqube:latest 854ae293f9003011fae39b757b8bf6f4d0fbbb7f7eb6a0a30f53d1aa1dfd0d19$$$$$$$$$$$$$$$

If you see no errors it means that the server is up and running.

Once this is done, follow these steps:

  1. Open http://localhost:9000 in your browser
  2. Click Log In and use admin as the username and password
  3. Click + in the upper right corner → Create new project
  4. Enter the project key buddy and the project name Buddy and click Set Up to proceed
  5. Give a name to the token: buddy-token and click Generate
  6. Copy the created token, it will look like this: buddy-token: xxxxxxxxxx, you will need it later
  7. Click Continue to proceed

Now, we need to install Ngrok. It will expose your 9000 port. You might need to sign up for the account if you haven't done that already.

Warning
Use it with care, it makes your local installation accessible. It is recommended to change the admin's password at the very least.

Now we can expose the Sonar Server by running this command:

bash
ngrok http 9000$

Your console will produce an output like this:

bash
ngrok by @inconshreveable Session Status online Account Kirill Konshin (Plan: Free) Version 2.3.29 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://yyyy.ngrok.io -> http://localhost:9000 Forwarding https://yyyy.ngrok.io -> http://localhost:9000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00$$$$$$$$$$

Copy the URL https://xxxx.ngrok.io – you will need it later.

Integrate SonarQube code coverage with Buddy

Now we're ready to configure a pipeline that will automatically run the tests on every push to the repository.

Hint
You can skip to the summary if you don't want to configure the pipeline via the GUI or would rather use a ready-to-use config file instead.

Buddy will automatically detect the type of files in the repository and label the project with corresponding logos.

Image loading...Repository overview

Click Add a new pipeline to begin. Enter the name of the pipeline, select the branch that you want to test, set it to run on push:

Image loading...Pipeline configuration

Configure the action

The pipeline is ready, now we need to add some actions to it. Click Node.js from the action roster:

Image loading...Adding the Node.js action

A modal will launch with configuration details. Add npm run coverage and set up SonarQube credentials in addition to the default scripts :

bash
npm install npm test npm run coverage -- -Dsonar.login=$SONAR_LOGIN \ -Dsonar.host.url=$SONAR_HOST_URL \ -Dsonar.links.homepage=$SONAR_LINKS_HOMEPAGE \ -Dsonar.links.ci=$SONAR_LINKS_CI -Dsonar.links.scm=$SONAR_LINKS_SCM$$$$$$

The action should look like this:

Image loading...Action configuration

Hint
To use the "sonar.branch.name" property and analyze branches, the "Developer Edition" or above is required. See https://redirect.sonarsource.com/doc/branches.html for more information. You can omit it to only use the master branch. If you want to enable it add -Dsonar.branch.name=$BUDDY_EXECUTION_BRANCH.

Set up environment

SonarQube code coverage Scanner needs Java to run, so we will have to use a custom Docker image with both Node.js and Java. To do so, switch the tab to Environment and set the image to ringcentral/web-tools and the image version to alpine:

Image loading...Action environment configuration

Hint
You may remove everything from the Customize environment section as we won't be needing that.

Set up cache

Sonar Scanner's performance can be improved by enabling the action cache. Switch to the Cache tab and add the following paths to the Additional cache section.

bash
/buddy/sonar/.scannerwork /root/.sonar/cache$$

The tab should look like this:

Image loading...Managing cache

Set up variables

Now we need to configure the ENV variables needed for the coverage to upload. Switch to the Variables tab and add the following variables:

  • SONAR_LOGIN — the SonarQube token (buddy-token's value xxxxxxxxxx) that you've obtained earlier
  • SONAR_HOST_URL — the Ngrok URL https://yyyy.ngrok.io that you've obtained earlier
  • SONAR_LINKS_HOMEPAGE — the URL of your Git repo at Buddy: https://app.buddy.works/yourname/yourrepo
  • SONAR_LINKS_SCM — the URL of your Git repo at Buddy: https://app.buddy.works/yourname/yourrepo
  • SONAR_LINKS_CI — the URL to the pipelines section of your Buddy project: https://app.buddy.works/yourname/yourrepo/pipelines

Image loading...Adding a variable

Warning
For security reasons, you should encrypt sensitive data like SONAR_TOKEN.

The complete page should look like this:

Image loading...Buddy variables page

Add npm script

Now let's go back to the code editor and add a few things to make the setup work. In order to upload the coverage, we need to create an npm script. Add the following to your package.json:

json
{ "scripts": { "test": "jest --coverage", "coverage": "sonar-scanner" } }

Running Pipeline

With everything in place, we're ready to give the pipeline a test ride. Make a push to the associated branch or click the Run button to initiate the pipeline. A progress bar will appear:

Image loading...Pipeline progress

You can click the actions within to take a look at how the execution is going, as well as browse execution logs once it's over:

Image loading...Action logs

A proper console output should look like this:

bash
npm run coverage -- -Dsonar.login=******ENCRYPTED****** -Dsonar.host.url=https://b4170e1e.ngrok.io -Dsonar.links.homepage=https://app.buddy.works/kirillkonshin/sonar -Dsonar.links.ci=https://app.buddy.works/kirillkonshin/sonar/pipelines -Dsonar.links.scm=https://app.buddy.works/kirillkonshin/sonar > buddy-sonar@1.0.0 coverage /buddy/sonar > sonar-scanner "-Dsonar.login=******ENCRYPTED******" "-Dsonar.host.url=https://b4170e1e.ngrok.io" "-Dsonar.links.homepage=https://app.buddy.works/kirillkonshin/sonar" "-Dsonar.links.ci=https://app.buddy.works/kirillkonshin/sonar/pipelines" "-Dsonar.links.scm=https://app.buddy.works/kirillkonshin/sonar" INFO: Scanner configuration file: /buddy/sonar/node_modules/sonar-scanner/conf/sonar-scanner.properties INFO: Project root configuration file: /buddy/sonar/sonar-project.properties INFO: SonarQube Scanner 3.1.0.1141 INFO: Java 1.8.0_202 Oracle Corporation (64-bit) INFO: Linux 4.15.0-1045-aws amd64 INFO: User cache: /root/.sonar/cache INFO: SonarQube server 7.9.1 INFO: Default locale: "en_US", source code encoding: "UTF-8" WARN: SonarScanner will require Java 11+ to run starting in SonarQube 8.x INFO: Load global settings INFO: Load global settings (done) | time=173ms INFO: Server id: BF41A1F2-AW4ZVHr_lBig5s92hKuf INFO: User cache: /root/.sonar/cache INFO: Load/download plugins INFO: Load plugins index INFO: Load plugins index (done) | time=107ms INFO: Load/download plugins (done) | time=79244ms INFO: Process project properties INFO: Execute project builders INFO: Execute project builders (done) | time=2ms INFO: Project key: buddy INFO: Base dir: /buddy/sonar INFO: Working dir: /buddy/sonar/.scannerwork INFO: Load project settings for component key: 'buddy' INFO: Load project settings for component key: 'buddy' (done) | time=125ms INFO: Load quality profiles INFO: Load quality profiles (done) | time=186ms INFO: Load active rules INFO: Load active rules (done) | time=4885ms INFO: Indexing files... INFO: Project configuration: INFO: Excluded sources: **/*.test.ts, **/*.spec.js INFO: Included tests: **/*.spec.js INFO: Load project repositories INFO: Load project repositories (done) | time=100ms INFO: 2 files indexed INFO: 0 files ignored because of inclusion/exclusion patterns INFO: 0 files ignored because of scm ignore settings INFO: Quality profile for js: Sonar way INFO: ------------- Run sensors on module Buddy INFO: Load metrics repository INFO: Load metrics repository (done) | time=149ms INFO: Sensor JaCoCo XML Report Importer [jacoco] INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=2ms INFO: Sensor SonarJS [javascript] INFO: 2 source files to be analyzed INFO: Sensor SonarJS [javascript] (done) | time=141ms INFO: Sensor ESLint-based SonarJS [javascript] INFO: 2/2 source files have been analyzed INFO: 2 source files to be analyzed INFO: Sensor ESLint-based SonarJS [javascript] (done) | time=5539ms INFO: Sensor SonarJS Coverage [javascript] INFO: 2/2 source files have been analyzed INFO: Analysing [/buddy/sonar/coverage/lcov.info] INFO: Sensor SonarJS Coverage [javascript] (done) | time=9ms INFO: Sensor JavaXmlSensor [java] INFO: Sensor JavaXmlSensor [java] (done) | time=0ms INFO: Sensor HTML [web] INFO: Sensor HTML [web] (done) | time=8ms INFO: ------------- Run sensors on project INFO: Sensor Zero Coverage Sensor INFO: Sensor Zero Coverage Sensor (done) | time=6ms INFO: SCM provider for this project is: git INFO: 2 files to be analyzed WARN: Shallow clone detected, no blame information will be provided. You can convert to non-shallow with 'git fetch --unshallow'. INFO: 0/2 files analyzed WARN: Missing blame information for the following files: WARN: * src/index.js WARN: * src/index.test.js WARN: This may lead to missing/broken features in SonarQube INFO: 2 files had no CPD blocks INFO: Calculating CPD for 0 files INFO: CPD calculation finished INFO: Analysis report generated in 42ms, dir size=73 KB INFO: Analysis report compressed in 6ms, zip size=12 KB INFO: Analysis report uploaded in 240ms INFO: ANALYSIS SUCCESSFUL, you can browse https://yyyy.ngrok.io/dashboard?id=buddy INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report INFO: More about the report processing at https://yyyy.ngrok.io/api/ce/task?id=zzz INFO: Analysis total time: 13.147 s INFO: ------------------------------------------------------------------------ INFO: EXECUTION SUCCESS INFO: ------------------------------------------------------------------------ INFO: Total time: 1:48.966s INFO: Final Memory: 14M/104M INFO: ------------------------------------------------------------------------$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

Checking SonarQube code coverage

If the pipeline has finished successfully, you can open http://localhost:9000 and then fire up your project. You will see that the coverage report has been properly collected:

Image loading...Sonarqube overview page

You can drill down to src/index.js stats to see which lines were covered:

Image loading...Sonarqube coverage page

Thanks for reading and good luck with setting up the pipeline and reading through coverage and execution reports! 🙌

Bonus: YAML configuration

If you prefer, you can flick the YAML switch and commit the following as buddy.yml file to avoid setting everything up in GUI:

yaml
- pipeline: "Sonar" trigger_mode: "ON_EVERY_PUSH" ref_name: "master" ref_type: "BRANCH" clone_depth: 1 trigger_condition: "ALWAYS" actions: - action: "Execute: npm run coverage" type: "BUILD" working_directory: "/buddy/sonar" docker_image_name: "ringcentral/web-tools" docker_image_tag: "alpine" execute_commands: - "npm install" - "npm test" - "npm run coverage -- -Dsonar.login=$SONAR_LOGIN -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.links.homepage=$SONAR_LINKS_HOMEPAGE -Dsonar.links.ci=$SONAR_LINKS_CI -Dsonar.links.scm=$SONAR_LINKS_SCM" cached_dirs: - "/buddy/sonar/.scannerwork" - "/root/.sonar/cache" mount_filesystem_path: "/buddy/sonar" shell: "BASH" trigger_condition: "ALWAYS"
Hint
To learn more about configuration-as-code in Buddy, check out the section on YAML configuration
Kirill Konshin

Kirill Konshin

Principal Software Developer

Kirill Konshin is the principal software developer at RingCentral, the world's leading Cloud communications provider. He is a highly experienced professional in full-stack web engineering with more than 10 years of experience, proficient in all the most recent web technologies. He is also an active open source contributor to React-related projects. You can follow him on [Medium](https://medium.com/@kirill.konshin).