| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Mutation testing for GDScript and Godot: find the bugs your green tests would miss.
A community tool, not affiliated with or endorsed by the Godot Foundation.
Coverage tells you a line ran. Mutations tell you if a bug there would be caught: a killed mutant means yes, a survivor means no. A standalone CLI, no AI required.
Same idea, other languages: mutmut for Python, Stryker for JS/TS, PIT for Java.
gdmutant mutates your GDScript (flips >↔>=, and↔or, bumps a number, deletes a statement), reruns your tests once per change, and reports the survivors.
The --html report, open on turn_order.gd. A survivor in the source, and marked on line 27:
and its detail card: what it means, why it's risky, and how to close it.
This mutates corpus/, a small real Godot project bundled in this repo just for this: a real script and a real GUT/gdUnit4 suite to try gdmutant against before pointing it at your own.
git clone https://github.com/kphutt/gdmutant
cd gdmutant # corpus/ lives right here, at the repo root
pip install . # installs gdmutant and its own dependencies
python scripts/install_gdunit4.py # gdUnit4 is a Godot addon that isn't vendored in git. This fetches it
# The first run below goes quiet for a bit right after it starts: that's Godot importing every
# asset in the project, a one-time cost, not a hang.
gdmutant run corpus/turn_order.gd --project corpus --runner gdunit4 --html
# mutates one file, reruns corpus/'s real GdUnit4 tests against each mutantOutput:
...
corpus\turn_order.gd:27 func can_act
27 | return alive and not stunned
| ^ changed and to or: every test still passed
gap Your tests pass whether this needs both sides (`and`) or just one
(`or`). No test covers the case that tells them apart: the
operands disagreeing (one true, one false).
risk Your tests can't tell 'needs both' from 'needs either.' A change
that loosens or tightens this guard would pass every test.
start Add a test where exactly one side is true and the other false,
and assert the outcome.
more https://github.com/kphutt/gdmutant/blob/main/docs/survivors/README.md#boolean
──────────────────────────────────────────────────────────────────────────
Results
Mutation score: 61.1%
killed: 11
timeout: 0 (counted as killed)
survived: 7
ignored: 0 (suppressed, excluded from score)
invalid: 0
error: 0
Wrote HTML report to gdmutant-report-turn_order-<timestamp>.html. Open it in a browser.
pip install 'gdmutant==0.1.*' # gdmutant is 0.x: pin the minor so a new one is a move you make on purposePick your test runner with --runner (required): GUT or gdUnit4. Godot has no built-in test runner, so both ship as addons: a plugin folder (addons/gut/ or addons/gdUnit4/) that lives inside a Godot project. Whichever you pick needs to already be installed there, in the project you're pointing gdmutant at. Godot itself needs to be on PATH, or point at it with --godot <path>. gdUnit4's usual test layout matches gdmutant's default --tests res://test, so a gdUnit4 command needs no --tests flag. GUT needs it spelled out: its stock layout puts suites in test/unit/ instead, and GUT's own -gdir doesn't search subdirectories.
# GUT
gdmutant run ../my-project/src/module.gd --project ../my-project --runner gut --tests res://test/unit --json# gdUnit4
gdmutant run ../my-project/src/module.gd --project ../my-project --runner gdunit4 --json--json follows the mutation-testing-elements schema
See the survivor reference for what ignored, invalid and error mean.
Kill each survivor with a real test, or mark a genuine equivalent with # gdmutant: ignore and a reason (annotation syntax). Re-run until nothing survives. A mutation score isn't a target to hit, it's a direction to watch. There's no universal "good" number.
gdmutant also ships as a GitHub Action, so a pull request can report its own survivors. This is a complete, standalone workflow file. Save it as .github/workflows/mutation.yml. Already have a workflow and just want to add gdmutant as one more step in it? See the guide's GitHub Actions section instead, which shows the bare step on its own.
name: Mutation testing
on:
pull_request:
push:
branches: [main]
jobs:
gdmutant:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # `since` below needs the base commit in the clone
- uses: kphutt/gdmutant@284f185f1495f2d79150781cf2e6de618ed11327 # v0.1.2
with:
godot-version: "4.7.0" # the only required input
paths: src # what to mutate (default: the whole project)
runner: gdunit4 # or gut, or command
# A PR diffs against its base commit. A merge to main diffs against the commit
# it replaced. Either way, only the code that actually changed gets mutated.
since: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}It sets up Godot, installs gdmutant, and writes each survivor's gap / risk / start explanation into the job summary. The step only fails on a real error, like an already-red suite, not on survivors. Your project's existing GUT or gdUnit4 addon, and a suite that already passes, are all it needs.
The same since scoping works locally too. gdmutant run --since origin/main checks only your latest changes, for fast iteration before you commit. The Action runs it the same way in CI, so every PR gets checked automatically, even one you didn't run gdmutant against yourself.
Every input and output, and how to pin a version, is in the guide.
| Verified at every release | Expected to work | |
|---|---|---|
| Godot | 4.7.0 | 4.3+ |
| Runner | GUT 9.7.1, gdUnit4 6.1.3 | GUT 9.x, gdUnit4 6.x, any headless command |
MIT, © 2026 kphutt.
| Back | FazBrowse Home | New Git URL |