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 walks you through my workflow one step at a time.
- Triggering the workflow
- Checking out the repository
- Checking out the badges branch
- Setup Java
- Build including generating a code coverage report
- Generate coverage badges
- Upload the full coverage report as a workflow artifact
- Commit and push the coverage badges
- Comment the coverage percentages on pull requests
- Complete Workflow.
-
Repositories Used in This Post.
- Live Example from one of my projects on GitHub.
- The jacoco-badge-generator GitHub Action.
- Where You Can Find Me.
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:
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
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
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'
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
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:
The jacoco-badge-generator GitHub Action is now also available as a CLI tool from PyPI
Vincent A. Cicirello ・ Jul 8 '22
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 }}"
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/
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
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 }}
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 }}
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:
cicirello / Chips-n-Salsa
A Java library of Customizable, Hybridizable, Iterative, Parallel, Stochastic, and Self-Adaptive Local Search Algorithms
Copyright (C) 2002-2024 Vincent A. Cicirello.
Website: https://chips-n-salsa.cicirello.org/
API documentation: https://chips-n-salsa.cicirello.org/api/
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…
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:
cicirello / jacoco-badge-generator
Coverage badges, and pull request coverage checks, from JaCoCo reports in GitHub Actions
jacoco-badge-generator
Check out all of our GitHub Actions: https://actions.cicirello.org/
About
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:
Vincent A Cicirello
View My Detailed 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:
Top comments (0)