moonbuggy
Fast, agent-first mutation testing for Python.
Mutation testing measures whether your tests would actually notice if the code
broke. It makes small changes to your source — flipping a < to a <=, a
True to a False — and reruns the tests. A change no test objects to is a
gap: a missing test, a weak assertion, or a line nothing exercises.
Two things make moonbuggy different:
- Speed. It runs only the tests that actually cover each mutated line,
applies mutations in memory rather than writing files, runs mutants in
parallel forked processes, does the work every mutant shares once in a warm
host rather than once per mutant, and caches results across runs. On a suite
where test execution dominates, that is 38x faster than the naive
approach of rerunning everything per mutant, and about 1.9x faster than
mutmut. Both figures come from
make bench, which measures all three tools in one session on one machine; see benchmark results for what they do and do not establish. - Output built for agents. Results are JSON Lines, with a derived plaintext
view whose every line starts with a fixed keyword, so
grep SURVIVEDworks with no knowledge of the schema.
Install
Full documentation is published at jhamon.github.io/moonbuggy.
Requires Python 3.12+ and pytest.
pip install .
Use
From your project root, with no flags and no configuration:
moonbuggy
It discovers your source layout, runs one instrumented pass to build a line→test map, then runs each mutant against only its covering tests.
Two files land in .moonbuggy/:
| file | role |
|---|---|
results.jsonl |
canonical, one JSON object per mutant |
results.txt |
plaintext view, derived from the JSONL |
Exit code is 0 when nothing survived, 1 when there are survivors, and 2
when the run could not start.
Reading the output
Every plaintext line begins with one of exactly five keywords:
| keyword | meaning |
|---|---|
KILLED |
a test failed under the mutation — the good outcome |
SURVIVED |
every selected test passed — a gap, or an equivalent mutant |
TIMEOUT |
the mutation caused a hang, killed by the time budget |
SUSPICIOUS |
pytest could not complete; needs a look |
SKIPPED |
suppressed, or filtered out by configuration |
So the thing you usually want is:
grep SURVIVED .moonbuggy/results.txt
Each line carries key=value tokens, including nearest_test= — the test to
extend to close that gap.
Lines are one per mutant and never contain the diff, so they stay grep- and awk-friendly. To see a mutant in full:
moonbuggy show 'shipping.py:5:comparison_swap:0'
Suppressing an equivalent mutant
Some mutants cannot be killed by any test because the mutated program is genuinely equivalent — changing a cache size, say. Detecting these automatically is undecidable, so moonbuggy does not try. Mark them in the source:
CACHE_SIZE = 128 # moonbuggy: skip -- tuning only, no observable behaviour
They are then reported SKIPPED rather than silently dropped, so the mutant
count stays honest.
Options
Nothing below is required.
--timeout SECONDS before a mutant is called TIMEOUT (default: 30)
--operators NAMES comma-separated subset, e.g. comparison_swap,boundary
--include FRAGMENT only mutate paths containing FRAGMENT (repeatable)
--exclude FRAGMENT skip paths containing FRAGMENT (repeatable)
--jobs N mutants to run concurrently (default: CPU count - 1)
-n, --workers N pytest-xdist workers per mutant run
--source DIR directory to mutate, if discovery guesses wrong
--no-cache ignore and do not update the cache
--clear-cache delete the cache, then run
--quiet summary line only
Development
python -m venv .venv && .venv/bin/pip install -e '.[dev,bench]'
| command | what it checks |
|---|---|
make test |
fast unit suite |
make check-oracle |
every mutant against the hand-written oracle |
make check-spike |
in-memory mutation, assert rewriting, xdist |
make check-mutmut |
advisory cross-check of the oracle against mutmut |
make bench |
moonbuggy vs mutmut vs naive |
make check-fresh-install |
clean install, zero-config run |
make check-all |
all of the above |
The project under tests/fixtures/sample_project is input data, not tests of
moonbuggy — a small pytest project whose 22 mutants have hand-written expected
outcomes in oracle.toml. Some of its tests hang or fail by design once
mutated, which is why the outer suite excludes it.
Status
Phase 0 and Phase 1 of the acceptance criteria are implemented and all criteria are met. Speed numbers and the four measured iterations behind them are in docs/benchmark-results.md.
Design notes: spike A (in-memory mutation, xdist), spike B (coverage mechanism).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moonbuggy-0.1.1.tar.gz.
File metadata
- Download URL: moonbuggy-0.1.1.tar.gz
- Upload date:
- Size: 296.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
204c2417971b83c54d236e69055b82c8d75cab13b78321d87a5dac2db6a50e9f
|
|
| MD5 |
ca19c626b58d6be5c4b29aa06b879f1e
|
|
| BLAKE2b-256 |
390b1db44169c6ba523a52ab4b3a93d1fc3e91799ff5dad532c6789bfe28aa39
|
Provenance
The following attestation bundles were made for moonbuggy-0.1.1.tar.gz:
Publisher:
release.yml on jhamon/moonbuggy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moonbuggy-0.1.1.tar.gz -
Subject digest:
204c2417971b83c54d236e69055b82c8d75cab13b78321d87a5dac2db6a50e9f - Sigstore transparency entry: 2499997268
- Sigstore integration time:
-
Permalink:
jhamon/moonbuggy@59a5c4828d097561b3bed6587c74aabb76617a6c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/jhamon
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@59a5c4828d097561b3bed6587c74aabb76617a6c -
Trigger Event:
push
-
Statement type:
File details
Details for the file moonbuggy-0.1.1-py3-none-any.whl.
File metadata
- Download URL: moonbuggy-0.1.1-py3-none-any.whl
- Upload date:
- Size: 79.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dde2378f31519987555989141b158ebbc7b8a599f86c7fc4640be82c27e4a27b
|
|
| MD5 |
5ba23b99137cc943ea4040f4664b012e
|
|
| BLAKE2b-256 |
1f8c1481056865c445f5984f53d4c9fda6d831e29aabb5cd150c573a661fa45a
|
Provenance
The following attestation bundles were made for moonbuggy-0.1.1-py3-none-any.whl:
Publisher:
release.yml on jhamon/moonbuggy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moonbuggy-0.1.1-py3-none-any.whl -
Subject digest:
dde2378f31519987555989141b158ebbc7b8a599f86c7fc4640be82c27e4a27b - Sigstore transparency entry: 2499997275
- Sigstore integration time:
-
Permalink:
jhamon/moonbuggy@59a5c4828d097561b3bed6587c74aabb76617a6c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/jhamon
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@59a5c4828d097561b3bed6587c74aabb76617a6c -
Trigger Event:
push
-
Statement type: