DEV Community

Anden Acitelli
Anden Acitelli

Posted on • Updated on • Originally published at

Github Actions - Use Maven + Jacoco to Gate PR Merging by Code Coverage

I work for Akkio, where I'm building out a no-code predictive AI platform. If you're looking to harness the power of AI without needing a data scientist, give us a try!

Had some difficulty finding an up-to-date way to do this, so I figure I save anybody looking for this in the future some time.

Without future ado, here's the .github/workflows/.yml:

name: Code Coverage

    branches: [ "master" ]
    branches: [ "master" ]

    runs-on: ubuntu-latest
      - uses: actions/checkout@v2
      - name: Set up JDK 17
        uses: actions/setup-java@v3
          java-version: 17
          distribution: 'temurin'
          cache: maven

      - name: Generate Coverage Report
        run: |
          mvn -B package --file pom.xml

      - name: Upload Report
        uses: 'actions/upload-artifact@v2'
          name: jacoco-report
          path: ${{ github.workspace }}/target/site/jacoco/jacoco.xml

      - name: Add coverage to PR
        id: jacoco
        uses: madrapps/jacoco-report@v1.2
          paths: ${{ github.workspace }}/target/site/jacoco/jacoco.xml
          token: ${{ secrets.GITHUB_TOKEN }}
          min-coverage-overall: 80
          min-coverage-changed-files: 80
          title: Code Coverage

      - name: Save Coverage To Environment Variable
        run: |
          echo "TOTAL_COVERAGE=${{ steps.jacoco.outputs.coverage-overall }}" >> $GITHUB_ENV
          echo "CHANGED_FILES_COVERAGE=${{ steps.jacoco.outputs.coverage-changed-files }}" >> $GITHUB_ENV

      - name: Print & Check Coverage Info
        run: |
          import os
          import sys
          print("Total Coverage: " + str(os.environ["TOTAL_COVERAGE"]))
          print("Changed Files Coverage: " + str(os.environ["CHANGED_FILES_COVERAGE"]))
          if float(os.environ["TOTAL_COVERAGE"]) < 80 or float(os.environ["CHANGED_FILES_COVERAGE"]) < 80:
            print("Insufficient Coverage!")
            sys.exit(-1) # Cause Status Check Failure due to noncompliant coverage
        shell: python
Enter fullscreen mode Exit fullscreen mode

The Python script at the end technically returns a 255 for some reason in case of insufficient coverage, but it still returns a nonzero exit code, which is what we need.

Some notes:

  • Maven dependencies are cached. Subsequent runs should be much faster than the initial one.
  • Python is necessary because I couldn't find a convenient way to parse the coverage number jacoco-report gives us (which ends up as a string) into a float in Bash.
  • madrapps/jacoco-report is not optional, though you can disable the comment it adds onto the PR if you like. It's necessary because it outputs the steps.jacoco.outputs.coverage-overall and steps.jacoco.outputs.coverage-changed-files variables.

Top comments (5)

cicirello profile image
Vincent A. Cicirello

Another way to do this is with:

GitHub logo cicirello / jacoco-badge-generator

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


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

Check out all of our GitHub Actions:


GitHub Actions GitHub release (latest by date) Count of Action Users
Command-Line Utility PyPI
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…

It supports checking for min coverage as well as min branches coverage. It also can check for decreased coverage.

However, it doesn't currently check just the changed files. It also doesn't currently comment on PRs, but that is easy enough to do with a step in workflow using GitHub CLI. I have a DEV post with a sample workflow showing how to do that:

aacitelli profile image
Anden Acitelli • Edited

Ah, I tried out your plugin and ran into an issue. I forget why, sorry. Would probably work if I took another shot at it again.

I just happened to gravitate to a slightly custom solution because this is for a private project where I don't really need badges and all that fancy stuff. Just wanted a simple way to gate merging behind code coverage requirements.

cicirello profile image
Vincent A. Cicirello

If all you want to do is fail the workflow run if coverage is below some target, you can configure jacoco to do that without using any special github actions. Check out the jacoco:check goal and its rules parameter. If you use that and coverage is below what you set, the build will halt with a non-zero code, which in turn will fail the github workflow run.

cicirello profile image
Vincent A. Cicirello

The sys.exit(-1) is giving you an actual exit code of 255 because on unix systems Python exit codes are unsigned bytes, so your -1 is an underflow.

aacitelli profile image
Anden Acitelli

Ah, good to know. Makes sense!