Metadata-Version: 2.4
Name: gdmutant
Version: 0.1.2
Summary: Mutation testing for GDScript and Godot: find the bugs your green tests would miss.
Project-URL: Homepage, https://github.com/kphutt/gdmutant
Project-URL: Repository, https://github.com/kphutt/gdmutant
Project-URL: Issues, https://github.com/kphutt/gdmutant/issues
Project-URL: Changelog, https://github.com/kphutt/gdmutant/blob/main/CHANGELOG.md
Author: kphutt
License-Expression: MIT
License-File: LICENSE
Keywords: gdscript,godot,mutation-testing,test-quality,testing
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: gdtoolkit<5,>=4.5
Requires-Dist: lark<2,>=1.2
Description-Content-Type: text/markdown

<h1 align="center">
  <img src="https://raw.githubusercontent.com/kphutt/gdmutant/v0.1.2/.github/assets/banner.png" alt="gdmutant: banner with Frank the Mutant, the project mascot" width="1200">
</h1>

<p align="center"><strong>Mutation testing for GDScript and Godot: find the bugs your green tests would miss.</strong></p>

<p align="center">
  <a href="https://github.com/kphutt/gdmutant/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/kphutt/gdmutant/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://pypi.org/project/gdmutant/"><img alt="PyPI" src="https://img.shields.io/pypi/v/gdmutant?cacheSeconds=300"></a>
  <a href="#compatibility"><img alt="Godot 4.3+" src="https://img.shields.io/badge/Godot-4.3%2B-478cbf?logo=godot-engine&logoColor=white"></a>
  <a href="https://github.com/bitwes/Gut"><img alt="GUT 9.x" src="https://img.shields.io/badge/GUT-9.x-478cbf"></a>
  <a href="https://github.com/godot-gdunit-labs/gdUnit4"><img alt="gdUnit4 6.x" src="https://img.shields.io/badge/gdUnit4-6.x-478cbf"></a>
  <a href="https://www.python.org/downloads/"><img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-blue?logo=python&logoColor=white"></a>
  <a href="https://github.com/kphutt/gdmutant/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green"></a>
</p>

<p align="center"><sub>A community tool, not affiliated with or endorsed by the Godot Foundation.</sub></p>

## What it is

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](https://github.com/boxed/mutmut) for Python, [Stryker](https://stryker-mutator.io/) for JS/TS, [PIT](https://pitest.org/) 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:

<p align="center">
  <img src="https://raw.githubusercontent.com/kphutt/gdmutant/v0.1.2/.github/assets/html-report-survivor-code.png" alt="turn_order.gd:27, return alive and not stunned, with and marked as a survivor" width="580">
</p>

and its detail card: what it means, why it's risky, and how to close it.

<p align="center">
  <img src="https://raw.githubusercontent.com/kphutt/gdmutant/v0.1.2/.github/assets/html-report-survivor-card.png" alt="The detail card: survived, boolean, turn_order.gd:27, func can_act. Gap: tests pass whether this needs both sides or just one. Risk: a change that loosens or tightens the guard would pass every test. Start: add a test where exactly one side is true and the other false, and assert the outcome" width="760">
</p>

## Prerequisites

- [Godot](https://godotengine.org/) 4.3+ (see [Compatibility](#compatibility) for exact versions),
  on your PATH so a plain `godot --version` works, or pass `--godot <full-path>` on every gdmutant
  command instead.
- [GUT](https://github.com/bitwes/Gut) or [gdUnit4](https://github.com/godot-gdunit-labs/gdUnit4),
  already installed and enabled in your project, if you use either. A different test runner works
  too, via `--runner command`: any headless command that exits non-zero on failure.
- [Python](https://www.python.org/downloads/) 3.12+ (check with `python --version`).

## Quickstart

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.

```sh
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

gdmutant run corpus/turn_order.gd --project corpus --runner gdunit4 --html
# mutates one file, reruns corpus/'s real GdUnit4 tests against each mutant
```

Output:

```
...

  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.
```

## Point it at your own project

```sh
pip install 'gdmutant==0.1.*'   # gdmutant is 0.x: pin the minor so a new one is a move you make on purpose
```

Pick your test runner with `--runner` (required). [GUT](https://github.com/bitwes/Gut) or
[gdUnit4](https://github.com/godot-gdunit-labs/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.

```sh
# GUT
gdmutant run ../my-project/src/module.gd --project ../my-project --runner gut --tests res://test/unit --json
```

```sh
# gdUnit4
gdmutant run ../my-project/src/module.gd --project ../my-project --runner gdunit4 --json
```

`--json` follows the
[`mutation-testing-elements`](https://github.com/stryker-mutator/mutation-testing-elements) schema

See the [survivor reference](https://github.com/kphutt/gdmutant/blob/main/docs/survivors/README.md) for what
`ignored`, `invalid` and `error` mean.

## Killing Survivors

Kill each survivor with a real test, or mark a genuine equivalent with `# gdmutant: ignore` and a
reason ([details](https://github.com/kphutt/gdmutant/blob/main/docs/survivors/README.md)). 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.

## GitHub Actions

gdmutant also ships as a GitHub Action, so a pull request can report its own survivors. Save this as
`.github/workflows/mutation.yml`:

```yaml
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@05728864a1c9330d632e2aab2348ff4442f3d61d # v0.1.0
        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](https://github.com/kphutt/gdmutant/blob/main/docs/gdmutant-guide.md#github-actions).

## Compatibility

| | 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 |

## Documentation

- [Survivor reference](https://github.com/kphutt/gdmutant/blob/main/docs/survivors/README.md): every operator explained, the score formula, how to kill or justify each.
- [Design & architecture](https://github.com/kphutt/gdmutant/blob/main/docs/design/DESIGN.md): the engine and the "Saboteur & the Jury" design.
- [The gdmutant guide](https://github.com/kphutt/gdmutant/blob/main/docs/gdmutant-guide.md): the CLI, troubleshooting, and the GitHub Action, in one place.
- [Contributing](https://github.com/kphutt/gdmutant/blob/main/CONTRIBUTING.md) · [Changelog](https://github.com/kphutt/gdmutant/blob/main/CHANGELOG.md) · [Credits](https://github.com/kphutt/gdmutant/blob/main/docs/credits.md)

## License

[MIT](https://github.com/kphutt/gdmutant/blob/main/LICENSE), © 2026 kphutt.
