Mergestro

What's new in 0.6

0.6 makes the gate faster and puts its findings where you already look. The faster run, the since-last-run summary and the weakened-test lane are on by default; the rest are one input each. Every Action input has a CLI flag of the same name (budget ↔ --budget).

You want to…Use
Nothing — just a faster gateon by default
Keep the gate inside a CI time limitbudget
Stop paying a cold build on every runin-place
See what changed since the last pushon with comment
Survivors on the line they are aboutcomment-inline
Findings in the Security tabsarif
Catch a PR that deletes or weakens testson, advisory
Split a large run across a CI matrix--shard

Faster by default

Before the first mutant the gate now runs your suite once (it was up to three times), and each mutant runs only the changed crate's tests instead of the whole workspace's. Two knobs for when the defaults don't fit:

A time budget

      - uses: lucheeseng827/mergestro-gate@v0.6.1
        with:
          budget: "10m"                # 600, 90s, 10m, 1h
          # block-on-budget: "true"   # untested mutants block instead of warning

When the budget runs out the mutation run stops. Finished mutants count as usual; the rest are reported as not tested — never as caught or surviving — in the log and at the top of the pull-request comment. That is a warning unless you set block-on-budget. The budget covers the mutation run, including its first build, not the pre-flight.

No second cold build

      - uses: Swatinem/rust-cache@v2
      - uses: lucheeseng827/mergestro-gate@v0.6.1
        with:
          in-place: "true"

By default mutants are built in a scratch copy without target/, so every run pays a cold build even right after the pre-flight built the same tree. in-place mutates the checkout instead and reuses that build — on a small crate the first mutant's build went from 4 s to 0.2 s. Mutants then run one at a time. It edits files while it runs and puts them back, so use it on CI checkouts, not on a working tree you are editing.

Since the last run

With comment: "true" (the default) the pull-request comment says what changed since the previous push:

Since the last run: 1 new · 2 still open · 3 resolved

New survivors are marked, and fixed ones are listed as resolved. A run that did not re-test everything — the suite was red, or the budget ran out — never claims anything was fixed.

Inline review comments

    permissions: { contents: read, pull-requests: write }
    # …
      - uses: lucheeseng827/mergestro-gate@v0.6.1
        with:
          comment-inline: "true"

Each surviving mutant is also posted as a review comment on its line, once: reruns do not repeat it. Only lines the pull request's diff shows get one; the rest stay in the summary comment. If posting the review fails, that is a warning and it is retried next run. GitHub only.

Code scanning (SARIF)

    permissions: { contents: read, pull-requests: write, security-events: write }
    # …
      - uses: lucheeseng827/mergestro-gate@v0.6.1
        with:
          sarif: mergestro.sarif
          upload-sarif: "true"

Survivors, assertion-free tests and the pattern lanes' findings go to GitHub code scanning as SARIF 2.1.0: they appear in the pull request's Files changed tab and the Security tab, with code scanning's own dismiss and reopen. Critical and high survivors are errors, medium are warnings, low are notes. The upload runs even when the gate blocks. From the CLI: slop-gate --sarif out.sarif.

Weakened tests

Mutation testing cannot see a pull request that only deletes a test or removes assertions: there is no changed code to mutate, so it used to pass silently. The weakened-tests lane compares each changed file's tests before and after and reports:

It is advisory. To make it block:

          block-on-pattern: "weakened-tests"   # or one rule, e.g. test-removed

Sharding a large run

For a change with more mutants than one job should run, split the Rust mutants across a CI matrix and merge the results into one verdict:

# in each matrix job, k = 1..4
$ slop-gate --base origin/main --shard k/4 --format json --advisory > shard.json

# in one job after them all
$ slop-gate merge-reports --base origin/main --comment shard-*/shard.json

merge-reports adds the shards up, decides the verdict on the total (so max-survivors applies to the whole run) and posts one comment. It refuses a missing or duplicated shard, and shards run on different commits or settings: a missing shard is untested mutants, not a clean result. A complete GitHub Actions matrix is in the operations guide.

Putting it together

on: pull_request
permissions: { contents: read, pull-requests: write, security-events: write }
jobs:
  gate:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - uses: lucheeseng827/mergestro-gate@v0.6.1
        with:
          in-place: "true"
          budget: "12m"
          comment-inline: "true"
          sarif: mergestro.sarif
          upload-sarif: "true"
          block-on-pattern: "weakened-tests"

0.6.1: a smaller image

The Docker image went from 1.08 GB to 804 MB (359 to 278 MB compressed). The runtime is plain Alpine plus the Rust toolchain without the parts mutants never use — the rust-lld linker, the WebAssembly component linker, the nightly-only sanitizer runtimes and the docs — with gate results unchanged. The full list of changes is in the changelog.

Next: Inputs & troubleshooting →