Skip to content

Uploading coverage from CI

Bitbucket Pipelines

Use the gocov pipe (commit, branch, repo and PR id are auto-detected):

- step:
    script:
      - go test ./... -covermode=atomic -coverprofile=coverage.out
      - pipe: docker://gocov/upload-pipe:0
        variables:
          FILES: coverage.out
          TOKEN: $GOCOV_TOKEN

with GOCOV_TOKEN set as a secured repository variable (add SERVER: $GOCOV_SERVER when self-hosting). On runners without Docker, run the CLI directly instead:

- step:
    script:
      - go test ./... -covermode=atomic -coverprofile=coverage.out
      - go run github.com/gocov/gocov/cmd/gocov@latest upload coverage.out

with GOCOV_SERVER and GOCOV_TOKEN set as repository variables.

GitHub Actions

Use gocov/gocov-action. It downloads the pinned CLI binary for the runner and verifies its checksum, so no Go toolchain is needed — the same three lines work whatever your tests are written in. Commit, branch, repo and PR number are auto-detected, including the PR head SHA on pull_request runs.

# Go
- run: go test ./... -covermode=atomic -coverprofile=coverage.out
- uses: gocov/gocov-action@v1
  with:
    files: coverage.out
    token: ${{ secrets.GOCOV_TOKEN }}
# JavaScript/TypeScript — Jest, Vitest, nyc, c8
- run: npx jest --coverage
- uses: gocov/gocov-action@v1
  with:
    files: coverage/lcov.info
    token: ${{ secrets.GOCOV_TOKEN }}
# Java/Kotlin — Maven with the jacoco-maven-plugin
- run: mvn verify
- uses: gocov/gocov-action@v1
  with:
    files: target/site/jacoco/jacoco.xml
    token: ${{ secrets.GOCOV_TOKEN }}

files takes a comma-separated list and globs. When self-hosting, add server: https://gocov.example; the default is the hosted service. part: labels one slice of a matrix build — see Parts.

On a runner that already has Go, the CLI also runs straight from source, no action involved:

- run: go run github.com/gocov/gocov/cmd/gocov@latest upload coverage.out
  env:
    GOCOV_SERVER: ${{ vars.GOCOV_SERVER }}
    GOCOV_TOKEN: ${{ secrets.GOCOV_TOKEN }}

GitLab CI

Project path, commit, branch and MR iid are auto-detected, including the real head SHA on merged-results pipelines:

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

coverage:
  image: golang:1.23
  script:
    - go test ./... -covermode=atomic -coverprofile=coverage.out
    - curl -fsSLO https://github.com/gocov/gocov/releases/download/v0.12.0/gocov-linux-amd64
    - curl -fsSL https://github.com/gocov/gocov/releases/download/v0.12.0/checksums.txt
      | grep ' gocov-linux-amd64$' | sha256sum -c -
    - chmod +x gocov-linux-amd64
    - ./gocov-linux-amd64 upload coverage.out

with GOCOV_TOKEN (masked, but not protected — GitLab checks "Protect variable" by default, and protected variables never reach merge request pipelines) and, when self-hosting, GOCOV_SERVER set as CI/CD variables under Settings → CI/CD → Variables on the group or project. The workflow rules run the job on merge requests and the default branch without duplicate pipelines. Note that gitlab.com's free tier requires a verified account (credit card) before shared runners pick up jobs.

Prebuilt binaries

On runners without a Go toolchain, use the prebuilt binaries from GitHub Releases instead (linux/darwin/windows, amd64 + arm64, checksums included). Pin a version and cache the download on self-hosted runners:

ver=v0.12.0
arch=$(uname -m); case "$arch" in x86_64) arch=amd64;; aarch64|arm64) arch=arm64;; esac
bin="$HOME/.cache/gocov/gocov-$ver-linux-$arch"
if [ ! -x "$bin" ]; then
  mkdir -p "$(dirname "$bin")"
  curl -fsSL "https://github.com/gocov/gocov/releases/download/$ver/gocov-linux-$arch" -o "$bin"
  chmod +x "$bin"
fi
"$bin" upload coverage.out

Outside CI

Values fall back to git or can be passed explicitly:

gocov upload -server https://gocov.example -token $TOKEN \
  -repo myworkspace/myrepo -commit $(git rev-parse HEAD) -branch main \
  coverage.out

Other ecosystems

Other ecosystems upload their reports the same way — the format is detected from the content, no flag needed:

npx jest --coverage             # or vitest run --coverage, nyc, c8 ...
gocov upload coverage/lcov.info

mvn verify                      # with the jacoco-maven-plugin
gocov upload target/site/jacoco/jacoco.xml

gradle test jacocoTestReport    # xml.required = true
gocov upload build/reports/jacoco/test/jacocoTestReport.xml

pytest --cov --cov-report=xml   # coverage.py / pytest-cov
gocov upload coverage.xml

phpunit --coverage-clover clover.xml
gocov upload clover.xml

bundle exec rspec               # with simplecov enabled
gocov upload coverage/.resultset.json

JaCoCo paths are package-qualified (com/example/Foo.java); diff coverage matches them against repo paths by suffix, so source roots like src/main/java need no configuration.

See also

  • Parts — combining several uploads (backend, frontend, e2e, matrix jobs) into one report per commit
  • API & badge — the raw upload endpoint, for anything the CLI doesn't cover