DEV Community

Vincent A. Cicirello
Vincent A. Cicirello

Posted on

Using GitHub Actions to Build a Java Project With Pull Request Coverage Commenting and Coverage Badges

This post is the second in a series of GitHub Actions workflows for Java projects. In it, I explain the GitHub Actions workflow that I run on pushes and pull requests, for building, testing, generating coverage badges, and commenting the coverage percentages on pull requests. The status of this workflow is also used as a required pull request check (e.g., the build including unit tests must pass in order to allow merging).

Table of Contents:

Workflow Step by Step

I'm going to walk through the full workflow from beginning to end.

Triggering the workflow

My build workflow is set up to run based on multiple events. This includes the pull_request event to run on all pull requests, and workflow_dispatch events, enabling me to run it manually if desired. I also have it configured to run on push events, but only when the push includes changes to either java files, the Maven pom.xml for the project, or when the build workflow itself is changed. The reason I restrict when it runs on pushes is so that I am not wasting cycles on the runner if I'm just making edits to the readme or other similar minor changes that don't impact the build itself. I don't make this restriction on pull requests because I'm using this workflow as a required pull request check, so I need it to run even if the PR doesn't include source code changes to prevent blocking the PR.

name: build

on:
  push:
    branches: [ master ]
    paths: [ '**.java', '.github/workflows/build.yml', 'pom.xml' ]
  pull_request:
    branches: [ master ]
  workflow_dispatch:
Enter fullscreen mode Exit fullscreen mode

Checking out the repository

The workflow has a single job build, runs on Ubuntu, and begins with the usual checkout step, using the actions/checkout action.

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3
Enter fullscreen mode Exit fullscreen mode

Checking out the badges branch

One of the later steps of the workflow generates coverage badges that are then stored within the repository. However, the badges are stored in a separate branch that I've named badges, rather than the default branch. I do it this way because my default branch is protected. In order to push the badges to the default branch, I'd need to use a personal access token with permissions elevated above the default permissions of the GITHUB_TOKEN. I don't want to do that for security reasons. I also don't want to use any of the other approaches I've seen to circumventing branch protection rules in a GitHub Actions workflow.

Instead, I use a dedicated branch whose only purpose is to store coverage badges, which I can still embed in the readme in the default branch. So my workflow has a second actions/checkout step to checkout the badges branch to a badges directory that I've nested within the other, as seen below:

    - name: Checkout badges branch to a badges directory nested inside first checkout
      uses: actions/checkout@v3
      with:
        ref: badges
        path: badges
Enter fullscreen mode Exit fullscreen mode

Setup Java

Next, I use the actions/setup-java action to setup the Adoptium distribution of Java 17.

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        distribution: 'adopt'
        java-version: '17'
Enter fullscreen mode Exit fullscreen mode

Build including generating a code coverage report

Next, we build the library with Maven. I use JaCoCo for code coverage. I've configured the JaCoCo Maven plugin within a Maven profile with id coverage within my pom.xml. See my earlier post on using Maven profiles, which included an example of how I configure code coverage within a Maven profile. To activate that profile and generate the code coverage report, I use the command line option -Pcoverage.

    - name: Build with Maven
      run: mvn -B package -Pcoverage
Enter fullscreen mode Exit fullscreen mode

Generate coverage badges

To generate coverage badges, I use a GitHub Action that I develop and maintain, the jacoco-badge-generator. I've written about that GitHub Action here on DEV in the past. Here is one such post:

In configuring the use of this action within this workflow, I've used its badges-directory input to change the directory of where the badges are created to correspond to the directory that contains my earlier nested checkout of the badges branch. Additionally, the action's default only generates the instructions coverage badge, so I additionally use generate-branches-badge: true to also generate a branches coverage badge. This action can also optionally generate a simple JSON file containing the coverage percentages, so I activate that with generate-summary: true. One of my later steps uses that JSON summary when commenting on pull requests. After the jacoco-badge-generator step below, I'm also showing a step that I use to log the coverage percentages to the workflow run log.

    - name: Generate JaCoCo badge
      id: jacoco
      uses: cicirello/jacoco-badge-generator@v2
      with:
        badges-directory: badges
        generate-branches-badge: true
        generate-summary: true

    - name: Log coverage percentages to workflow output
      run: |
        echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
        echo "branches = ${{ steps.jacoco.outputs.branches }}"
Enter fullscreen mode Exit fullscreen mode

Upload the full coverage report as a workflow artifact

I then use the actions/upload-artifact action to upload the full JaCoCo coverage report as a workflow artifact. This way I can download it from the workflow run via the Actions tab if I need to see the details. I'm using Maven, along with the default location of the JaCoCo reports, so they are found in the directory target/site/jacoco/. The actions/upload-artifact action creates a zip file with the entire contents of that directory, which in this case will include all of JaCoCo's various reports, such as the detailed HTML report, as well as the XML, and CSV versions.

    - name: Upload JaCoCo coverage report as a workflow artifact
      uses: actions/upload-artifact@v3
      with:
        name: jacoco-report
        path: target/site/jacoco/
Enter fullscreen mode Exit fullscreen mode

Commit and push the coverage badges

The jacoco-badge-generator generates badges, but does not commit them. In this step, I just use a simple shell script to commit and push the badges. This step is conditional and runs only if the event that started the workflow is not a pull request (see the if: ${{ github.event_name != 'pull_request' }}). In other words, it runs on push and workflow_dispatch events. The coverage badges should be consistent with the state of the default branch, so committing badges that correspond to the coverage of a pull request that may or may not be merged doesn't make sense. If it is merged, the push event will then cause the workflow to run again, at which point the coverage badges will be committed. This step begins by changing the current directory to the directory where the badges branch was checked out. And it commits and pushes only if an svg or json file changed. The badges are SVGs, and recall the earlier step where I configured the jacoco-badge-generator to additionally generate a simple JSON file containing the coverage percentages.

    - name: Commit and push the coverage badges and summary file
      if: ${{ github.event_name != 'pull_request' }}
      run: |
        cd badges
        if [[ `git status --porcelain *.svg *.json` ]]; then
          git config --global user.name 'github-actions'
          git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
          git add *.svg *.json
          git commit -m "Autogenerated JaCoCo coverage badges" *.svg *.json
          git push
        fi
Enter fullscreen mode Exit fullscreen mode

Comment the coverage percentages on pull requests

In this last step, I use the GitHub CLI to comment the coverage percentages on pull requests. This step is conditional, like the previous, but this time it only runs on pull requests (see the if: ${{ github.event_name == 'pull_request' }}). It is a simple shell script. The first few lines uses jq to parse the coverage-summary.json produced by jacoco-badge-generator, and then generates a string with the content for a pull request comment. The last line then uses the GitHub CLI to comment on the pull request. The GitHub CLI requires authentication via the GITHUB_TOKEN passed as an environment variable.

    - name: Comment on PR with coverage percentages
      if: ${{ github.event_name == 'pull_request' }}
      run: |
        REPORT=$(<badges/coverage-summary.json)
        COVERAGE=$(jq -r '.coverage' <<< "$REPORT")%
        BRANCHES=$(jq -r '.branches' <<< "$REPORT")%
        NEWLINE=$'\n'
        BODY="## JaCoCo Test Coverage Summary Statistics${NEWLINE}* __Coverage:__ ${COVERAGE}${NEWLINE}* __Branches:__ ${BRANCHES}"
        gh pr comment ${{github.event.pull_request.number}} -b "${BODY}"
      continue-on-error: true
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

At the present time, I've set up this last step with continue-on-error: true, which will prevent this step from causing the workflow run to fail. The default permissions of the GITHUB_TOKEN are not sufficient for commenting on pull requests coming from forks. I need to elevate the permissions granted the GITHUB_TOKEN in order to do so, which is on my to-do list. Even without the commenting, the full coverage reports are uploaded as a workflow artifact during one of the earlier steps.

Complete Workflow

Here's my full workflow.

name: build

on:
  push:
    branches: [ master ]
    paths: [ '**.java', '.github/workflows/build.yml', 'pom.xml' ]
  pull_request:
    branches: [ master ]
  workflow_dispatch:

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Checkout badges branch to a badges directory nested inside first checkout
      uses: actions/checkout@v3
      with:
        ref: badges
        path: badges

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        distribution: 'adopt'
        java-version: '17'

    - name: Build with Maven
      run: mvn -B package -Pcoverage

    - name: Generate JaCoCo badge
      id: jacoco
      uses: cicirello/jacoco-badge-generator@v2
      with:
        badges-directory: badges
        generate-branches-badge: true
        generate-summary: true

    - name: Log coverage percentages to workflow output
      run: |
        echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
        echo "branches = ${{ steps.jacoco.outputs.branches }}"

    - name: Upload JaCoCo coverage report as a workflow artifact
      uses: actions/upload-artifact@v3
      with:
        name: jacoco-report
        path: target/site/jacoco/

    - name: Commit and push the coverage badges and summary file
      if: ${{ github.event_name != 'pull_request' }}
      run: |
        cd badges
        if [[ `git status --porcelain *.svg *.json` ]]; then
          git config --global user.name 'github-actions'
          git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
          git add *.svg *.json
          git commit -m "Autogenerated JaCoCo coverage badges" *.svg *.json
          git push
        fi

    - name: Comment on PR with coverage percentages
      if: ${{ github.event_name == 'pull_request' }}
      run: |
        REPORT=$(<badges/coverage-summary.json)
        COVERAGE=$(jq -r '.coverage' <<< "$REPORT")%
        BRANCHES=$(jq -r '.branches' <<< "$REPORT")%
        NEWLINE=$'\n'
        BODY="## JaCoCo Test Coverage Summary Statistics${NEWLINE}* __Coverage:__ ${COVERAGE}${NEWLINE}* __Branches:__ ${BRANCHES}"
        gh pr comment ${{github.event.pull_request.number}} -b "${BODY}"
      continue-on-error: true
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Repositories Used in this Post

Live Example

To see a live example, consult the build.yml workflow of one of my projects. Here is the GitHub repository:

GitHub logo cicirello / Chips-n-Salsa

A Java library of Customizable, Hybridizable, Iterative, Parallel, Stochastic, and Self-Adaptive Local Search Algorithms

Chips-n-Salsa - A Java library of customizable, hybridizable, iterative, parallel, stochastic, and self-adaptive local search algorithms

Chips-n-Salsa Mentioned in Awesome Machine Learning

Copyright (C) 2002-2022 Vincent A. Cicirello.

Website: https://chips-n-salsa.cicirello.org/

API documentation: https://chips-n-salsa.cicirello.org/api/

Publications About the Library DOI
Packages and Releases Maven Central GitHub release (latest by date) JitPack
Build Status build docs CodeQL
JaCoCo Test Coverage coverage branches coverage
Security Snyk security score Snyk Known Vulnerabilities
DOI DOI
License GitHub
Support GitHub Sponsors Liberapay Ko-Fi

How to Cite

If you use this library in your research, please cite the following paper:

Cicirello, V. A., (2020). Chips-n-Salsa: A Java Library of Customizable, Hybridizable, Iterative, Parallel, Stochastic, and Self-Adaptive Local Search Algorithms. Journal of Open Source Software, 5(52), 2448, https://doi.org/10.21105/joss.02448 .

Overview

Chips-n-Salsa is a Java library of customizable, hybridizable, iterative, parallel, stochastic, and self-adaptive local search algorithms. The library includes implementations of several stochastic local search algorithms, including simulated annealing, hill climbers, as well as constructive search algorithms such as stochastic sampling. Chips-n-Salsa now also includes genetic algorithms as well as evolutionary algorithms more generally. The library very extensively supports simulated annealing. It includes several classes for representing solutions to a variety of optimization problems. For…

The jacoco-badge-generator GitHub Action

One of the steps of my workflow uses the jacoco-badge-generator to parse and summarize the JaCoCo coverage report, producing coverage badges and a JSON summary file. Here is the repository for that action itself:

GitHub logo cicirello / jacoco-badge-generator

Coverage badges, and pull request coverage checks, from JaCoCo reports in GitHub Actions

jacoco-badge-generator

cicirello/jacoco-badge-generator - Coverage badges, and pull request coverage checks, from JaCoCo reports in GitHub Actions

Check out all of our GitHub Actions: https://actions.cicirello.org/

About

GitHub Actions GitHub release (latest by date) Count of Action Users
Command-Line Utility PyPI PyPI Downloads/month PyPI Downloads/week
Build Status build CodeQL
Security Snyk security score
Source Info License GitHub top language
Support GitHub Sponsors Liberapay Ko-Fi

The jacoco-badge-generator can be used in one of two ways: as a GitHub Action or as a command-line utility (e.g., such as part of a local build script). The jacoco-badge-generator parses a jacoco.csv from a JaCoCo coverage report, computes coverage percentages from JaCoCo's Instructions and Branches counters, and generates badges for one or both of these (user configurable) to provide an easy to read visual summary of the code coverage of your test cases. The default behavior directly generates the badges internally with no external calls, but the action also provides an option to instead generate Shields JSON endpoints. It supports both the basic case of a single jacoco.csv, as well as multi-module projects in which case the action can produce coverage badges from the combination of…

Where You Can Find Me

Follow me here on DEV:

Follow me on GitHub:

GitHub logo cicirello / cicirello

My GitHub Profile

Vincent A Cicirello

Vincent A. Cicirello

Sites where you can find me or my work
Web and social media Personal Website LinkedIn DEV Profile
Software development Github Maven Central PyPI Docker Hub
Publications Google Scholar ORCID DBLP ACM Digital Library IEEE Xplore ResearchGate arXiv

My bibliometrics

My GitHub Activity

If you want to generate the equivalent to the above for your own GitHub profile, check out the cicirello/user-statistician GitHub Action.




Or visit my website:

Vincent A. Cicirello - Professor of Computer Science

Vincent A. Cicirello - Professor of Computer Science at Stockton University - is a researcher in artificial intelligence, evolutionary computation, swarm intelligence, and computational intelligence, with a Ph.D. in Robotics from Carnegie Mellon University. He is an ACM Senior Member, IEEE Senior Member, AAAI Life Member, EAI Distinguished Member, and SIAM Member.

favicon cicirello.org

Top comments (0)