GitHub Actions

Three workflows, from a one-liner to a sharded, cached pipeline, plus a step that fails the build below a mutation score. Pick the one that matches how much you want to optimize.

1. Simplest: checkout and run

Install Gooze and run the whole suite. No registry, no caching — a good starting point.

name: Mutation testing

on: [push, pull_request]

jobs:
  gooze:
    runs-on: ubuntu-latest
    container: golang:1.27       # official Go image: go is already installed
    steps:
      - uses: actions/checkout@v4
      - run: go install gooze.dev/pkg/gooze@latest
      - run: gooze run ./...

2. Sharded: run in parallel

Split the run across jobs with -s INDEX/TOTAL to cut wall-clock time. Each job tests its own slice and uploads its reports; a final merge job downloads them all and combines them with gooze report merge.

jobs:
  test:
    runs-on: ubuntu-latest
    container: golang:1.27
    strategy:
      matrix:
        shard: [0, 1, 2]
    steps:
      - uses: actions/checkout@v4
      - run: go install gooze.dev/pkg/gooze@latest

      # Each job tests one third of the mutations, in parallel.
      # Sharded runs write to .gooze-reports/shard_${{ matrix.shard }}/.
      - name: Run shard ${{ matrix.shard }}
        run: gooze run -s ${{ matrix.shard }}/3 ./...

      - name: Upload shard reports
        uses: actions/upload-artifact@v4
        with:
          name: gooze-shard-${{ matrix.shard }}
          path: .gooze-reports/

  merge:
    needs: test
    runs-on: ubuntu-latest
    container: golang:1.27
    steps:
      - run: go install gooze.dev/pkg/gooze@latest

      # Collect every shard's reports back into .gooze-reports/.
      - name: Download shard reports
        uses: actions/download-artifact@v4
        with:
          path: .gooze-reports
          pattern: gooze-shard-*
          merge-multiple: true

      # Merge shard_* subdirs into one combined report set.
      - name: Merge shard reports
        run: gooze report merge

3. Sharded + cached baseline

Add an OCI baseline so each run only re-tests changed files (incremental runs). Every shard pulls the :main baseline for the cache, then hands its reports to the merge job as an artifact — shards never push to the registry. The merge job combines them with gooze report merge and, only on main, publishes the single merged baseline back to :main.

permissions:
  contents: read
  packages: write          # push the merged baseline to GHCR

env:
  REPORTS_REF: ghcr.io/${{ github.repository }}/gooze-reports:main
  GOOZE_REGISTRY_USERNAME: ${{ github.actor }}
  GOOZE_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

jobs:
  test:
    runs-on: ubuntu-latest
    container: golang:1.27
    strategy:
      matrix:
        shard: [0, 1, 2]
    steps:
      - uses: actions/checkout@v4
      - run: go install gooze.dev/pkg/gooze@latest

      # Reuse the main baseline so unchanged files are skipped (the cache).
      - name: Restore baseline from main
        run: gooze report pull "$REPORTS_REF" || echo "no baseline yet"

      - name: Run shard ${{ matrix.shard }}
        run: gooze run -s ${{ matrix.shard }}/3 ./...

      # Hand reports to the merge job via an artifact — no registry push here.
      - name: Upload shard reports
        uses: actions/upload-artifact@v4
        with:
          name: gooze-shard-${{ matrix.shard }}
          path: .gooze-reports/

  merge:
    needs: test
    runs-on: ubuntu-latest
    container: golang:1.27
    steps:
      - run: go install gooze.dev/pkg/gooze@latest

      - name: Collect shard reports
        uses: actions/download-artifact@v4
        with:
          path: .gooze-reports
          pattern: gooze-shard-*
          merge-multiple: true

      - name: Merge shards
        run: gooze report merge

      # Only main publishes the merged baseline back to the registry.
      - name: Publish merged baseline (main only)
        if: ${{ github.ref == 'refs/heads/main' }}
        run: gooze report push "$REPORTS_REF"

Fail the build below a score (quality gate)

Gooze has no threshold flag yet: survivors never change its exit code. Add this step %s. It reads the mutation score from the non-interactive output and fails when it is below MIN_SCORE.

      # Fail the job when the mutation score is below MIN_SCORE (percent).
      - name: Mutation score gate
        env:
          MIN_SCORE: "60"
        run: |
          score=$(gooze report view | sed -n 's/^Mutation score: \([0-9.]*\)%$/\1/p' | tail -1)
          echo "Mutation score: ${score}% (minimum ${MIN_SCORE}%)"
          awk -v s="$score" -v m="$MIN_SCORE" 'BEGIN { exit !(s != "" && s >= m) }'

With sharding, gate only in the merge job: a single shard's score covers only its slice. A missing score (no reports) also fails the step.

Authentication

  • GOOZE_REGISTRY_USERNAME / GOOZE_REGISTRY_PASSWORD — the password may be a token/PAT (for GHCR use GITHUB_TOKEN).
  • If unset, Gooze falls back to the Docker credential store, so a prior docker/login-action step also works.
  • For a non-TLS or self-signed registry add --plain-http or --insecure to push/pull.