---
title: "Continuous integration"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Continuous integration}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

This vignette explains how to run mutator on a package in GitHub Actions using
the reusable workflow that ships with the package, how to gate a pull request on
a minimum mutation score, and how to publish a badge.

## The reusable workflow

mutator provides a reusable workflow at
`PRL-PRG/mutator/.github/workflows/mutation-testing.yaml`. A repository adopts it
with a short caller workflow rather than by copying any script. The workflow
checks out your package, installs it together with mutator and its
dependencies, runs `mutate_package()`, uploads a results artifact, and writes a
Markdown summary to the run's Summary tab.

Add `.github/workflows/mutation-testing.yaml` to your repository:

```yaml
on:
  pull_request:
  push:
    branches: [main, master]
  workflow_dispatch:

name: mutation-testing

jobs:
  mutation:
    uses: PRL-PRG/mutator/.github/workflows/mutation-testing.yaml@v0.2.0
    with:
      target-margin: "0.10"
```

Pin to a released tag such as `@v0.2.0`. The reusable workflow lives in the
mutator package repository and is versioned with the package, so the tag you
pin matches a mutator release. Bump the tag in your caller when you want to move
to a newer release. You can also pin to a branch (for example `@main`) to track
the latest changes, at the cost of reproducibility.

## Inputs

All inputs are optional.

| Input | Default | Meaning |
| --- | --- | --- |
| `target-margin` | `"0.10"` | Sample mutants to this plus or minus CI half-width, as a proportion. Empty tests every mutant. |
| `max-mutants` | `""` | Fixed cap on sampled mutants. Takes precedence over `target-margin`. |
| `fail-under` | `""` | Fail the job when the score is below this percentage. |
| `show-confidence-interval` | `true` | Show the confidence interval with a sampled mutation score in the badge and job summary. |
| `cores` | `"0"` | Parallel workers. `"0"` auto-detects. |
| `timeout-seconds` | `""` | Per-mutant timeout. Empty derives it from the baseline run. |
| `isolate` | `true` | Deep-copy `src/` and `tests/` per mutant, for non-hermetic suites. |
| `coverage-guided` | `true` | Run only the tests covering each mutated line. Disabled automatically, with a warning, for installed-tests packages. |
| `coverage-backend` | `record_tests` | How coverage is attributed to tests. `"per_file"` gives exact file-level attribution (no helper-file fallback), at the cost of relying on covr internals. |
| `exclude-files` | `""` | Glob patterns of `R/` files to skip. |
| `badge-label` | `mutator` | Label shown on the shields.io badge. |
| `deploy-badge` | `false` | Publish a badge. Requires `contents: write`. |
| `badge-branch` | `gh-pages` | Branch the badge JSON is deployed to. |
| `mutator-spec` | `github::PRL-PRG/mutator` | pak spec used to install mutator, for example `any::mutator` once it is on CRAN. |
| `install-imputesrcref` | `true` | Install the optional imputesrcref package to refine mutant locations. |
| `r-version` | `release` | R version passed to `setup-r`. |

## Enforcing a minimum mutation score

Set `fail-under` to the percentage below which the job should fail. The workflow
computes the mutation score, and if it is below the threshold the job exits
non-zero, which blocks the pull request:

```yaml
jobs:
  mutation:
    uses: PRL-PRG/mutator/.github/workflows/mutation-testing.yaml@v0.2.0
    with:
      fail-under: "75"
      target-margin: "0.05"
```

Two points make a threshold gate reliable:

1. Reduce sampling noise. The score is estimated from a sample of mutants, so a
   run-to-run variation exists. Use a tighter `target-margin` (for example
   `"0.05"`) or a large `max-mutants` when you gate on `fail-under`, so the
   estimate does not cross the threshold by chance. The half-width you choose is
   roughly how far the reported score can sit from the true score.
2. Ratchet the threshold. Start `fail-under` at, or slightly below, your current
   score, then raise it as you add tests. This locks in progress without failing
   the build on the day you adopt it.

Leave `fail-under` empty to report the score on every run without failing.

The measured score, its confidence interval, and the killed, survived, and
hanged counts appear in the run's Summary tab and in the uploaded artifact
regardless of whether a threshold is set.

## Badge

Set `deploy-badge: true` and grant the caller job `contents: write`. The
workflow writes a [shields.io endpoint](https://shields.io/endpoint) JSON file
and deploys it to `gh-pages` (or `badge-branch`):

```yaml
jobs:
  mutation:
    permissions:
      contents: write
    uses: PRL-PRG/mutator/.github/workflows/mutation-testing.yaml@v0.2.0
    with:
      deploy-badge: true
```

Reference the badge in your README, replacing `OWNER/REPO`:

```markdown
![mutator](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/gh-pages/mutation-score.json)
```

This reads the JSON straight from `badge-branch` via
`raw.githubusercontent.com`, so no GitHub Pages setup is needed. If you instead
fetch it through a pkgdown Pages site
(`https://OWNER.github.io/REPO/mutation-score.json`), GitHub Pages must be
activated and serving from `badge-branch`, or the badge will not appear.

The badge is labelled `mutator`; its message shows the score and, when the run
sampled mutants, a compact asymmetric confidence interval such as
`83.2% -5.2/+4.7% (95% CI)`. The badge colour tracks the score: green above 80%,
yellow above 60%, orange above 40%, and red below.

Set `show-confidence-interval: false` to show only the mutation score in both
the badge and the job summary. The confidence interval remains part of the
uploaded mutation results.

## Permissions and private dependencies

The workflow reads `GITHUB_TOKEN` to install packages from GitHub, which is
provided automatically. You only need to set `permissions: contents: write` on
the caller job when `deploy-badge` is true, because publishing the badge pushes
to a branch. Pull request runs never deploy the badge.

If your package depends on packages that are not on CRAN, pass them through
`extra-packages` as pak specs:

```yaml
    with:
      extra-packages: "github::owner/dependency, any::somePackage"
```

## Tuning the run

Mutation testing runs the test suite once per mutant, so a large package can
take a while. To keep CI fast:

- Cap work with `max-mutants`, or let `target-margin` size the sample for you.
- `coverage-guided` is on by default and runs only the tests that cover each
  mutated line. It applies to packages tested with testthat; for installed-tests
  packages the workflow warns and runs the full suite.
- Use `exclude-files` to skip generated or vendored files under `R/`.
- Raise `cores` on a larger runner, or leave it at `"0"` to use every core.

See the [Configuration article](configuration.html) for how these options
behave in detail.
