GitLab CI

The same three setups as the GitHub Actions guide, written for .gitlab-ci.yml.

1. Simplest: run

Install Gooze and run the whole suite. No registry, no caching.

# .gitlab-ci.yml
image: golang:1.27          # official Go image: go is already installed

mutation-testing:
  script:
    - go install gooze.dev/pkg/gooze@latest
    - gooze run ./...

2. Sharded: run in parallel

parallel: 3 fans the job out; each instance tests its slice with -s INDEX/TOTAL and saves its reports as an artifact. The merge job collects them all and runs gooze report merge.

image: golang:1.27

stages: [test, merge]

test:
  stage: test
  parallel: 3
  script:
    - go install gooze.dev/pkg/gooze@latest
    # CI_NODE_INDEX is 1-based; gooze shards are 0-based.
    - gooze run -s "$((CI_NODE_INDEX - 1))/$CI_NODE_TOTAL" ./...
  artifacts:
    paths:
      - .gooze-reports/

merge:
  stage: merge
  dependencies: [test]       # pulls every parallel shard's artifacts
  script:
    - go install gooze.dev/pkg/gooze@latest
    - gooze report merge

3. Sharded + cached baseline

Each shard pulls the :main baseline for the cache (incremental runs), then hands its reports to the merge job as an artifact — shards never push to the registry. The merge job combines them and, only on main, publishes the single merged baseline back to :main.

image: golang:1.27

stages: [test, merge]

variables:
  REPORTS_REF: "$CI_REGISTRY_IMAGE/gooze-reports:main"
  # CI_JOB_TOKEN can read/write this project's container registry.
  GOOZE_REGISTRY_USERNAME: "gitlab-ci-token"
  GOOZE_REGISTRY_PASSWORD: "$CI_JOB_TOKEN"

test:
  stage: test
  parallel: 3
  script:
    - go install gooze.dev/pkg/gooze@latest
    # Reuse the main baseline so unchanged files are skipped (the cache).
    - gooze report pull "$REPORTS_REF" || echo "no baseline yet"
    - gooze run -s "$((CI_NODE_INDEX - 1))/$CI_NODE_TOTAL" ./...
  artifacts:
    paths:
      - .gooze-reports/        # handed to the merge job; no registry push here

merge:
  stage: merge
  dependencies: [test]
  script:
    - go install gooze.dev/pkg/gooze@latest
    - gooze report merge
    # Only main publishes the merged baseline back to the registry.
    - |
      if [ "$CI_COMMIT_BRANCH" = "main" ]; then
        gooze report push "$REPORTS_REF"
      fi

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.

merge:
  # ...after gooze report merge (or in the single job, after gooze run):
  variables:
    MIN_SCORE: "60"          # fail below this percent
  script:
    - go install gooze.dev/pkg/gooze@latest
    - gooze report merge
    - |
      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

  • For the project's own container registry, use gitlab-ci-token with $CI_JOB_TOKEN (shown above), or $CI_REGISTRY_USER / $CI_REGISTRY_PASSWORD.
  • For another registry, set GOOZE_REGISTRY_USERNAME / GOOZE_REGISTRY_PASSWORD (the password may be a token/PAT), or rely on the Docker credential store.
  • For a non-TLS or self-signed registry add --plain-http or --insecure to push/pull.