Fast mutation testing for Python
Project description
irradiate
Fast mutation testing for Python, written in Rust.
Why
Mutation testing is slow. The bottleneck isn't generating mutants, it's running the test suite once per mutant. A typical pytest startup costs 200-500ms, and with hundreds of mutants that adds up to minutes of pure overhead.
irradiate keeps a pool of pre-warmed pytest workers. Pytest starts once, collects tests once, then forks a child process for each mutant. 30-60 mutants/sec on real codebases.
How it works
- Parse Python source with tree-sitter (40 mutation operator categories including regex patterns)
- Generate trampolined mutants: each function gets an original, N mutated variants, and a runtime dispatcher
- Collect test coverage and timing in a single pytest run
- Fork a child process per mutant inside pre-warmed workers (no pytest restart)
- Report results as terminal output, JSON (Stryker schema v2), HTML, or GitHub Actions annotations
Install
pip install irradiate
Or build from source:
cargo build --release
Requires Python 3.10+ with pytest installed.
Usage
# Run mutation testing (auto-detects src/ and tests/)
irradiate run
# Only test functions changed since main
irradiate run --diff main
# Test 10% of mutants (fast CI feedback)
irradiate run --sample 0.1
# Generate JSON report (Stryker mutation-testing-report-schema v2)
irradiate run --report json
# Generate self-contained HTML report
irradiate run --report html
# Fail CI if mutation score is below threshold
irradiate run --fail-under 80
# See cached results
irradiate results
# Show diff for a specific mutant
irradiate show module.x_func__irradiate_1
Example output
$ irradiate run
Generating mutants...
done in 3ms (14 mutants across 1 files)
Running stats + validation...
done in 195ms
Running mutation testing (14 mutants, 10 workers)...
Mutation testing complete (14 mutants in 0.1s, 175 mutants/sec)
Cache hits: 0
Cache misses: 12
Killed: 11
Survived: 1
No tests: 2
Score: 91.7%
Survived mutants:
number_mutation (1):
simple_lib/__init__.py:6 replaced `0` with `1` [simple_lib.x_add__irradiate_3]
Configuration
Configure via [tool.irradiate] in pyproject.toml:
[tool.irradiate]
paths_to_mutate = "src"
tests_dir = "tests"
do_not_mutate = ["**/generated/*", "**/vendor/*"]
pytest_add_cli_args = ["-x", "--tb=short"]
Source paths can also be passed as positional arguments: irradiate run src/mylib. All settings can be overridden via CLI flags. Run irradiate run --help for the full list.
Features
Mutation operators (40 categories)
Arithmetic, comparison, boolean, augmented assignment, unary, string mutation/emptying, number literals, constant replacement, lambda bodies, return values, assignments, default arguments, argument removal, method swaps, dict kwargs, exception types, match/case removal, condition negation, condition replacement, statement deletion, keyword swap, loop mutation, ternary swap, slice index removal, regex pattern mutations (11 operators: anchor removal, charclass negation, shorthand negation, quantifier removal/change, lookaround negation, alternation removal, and more).
Functions can be excluded with # pragma: no mutate.
Execution model
By default, workers fork after pytest collection. Each mutant runs in an isolated child process with no restart overhead. For projects with complex test infrastructure, --isolate runs each mutant in a fresh subprocess instead. --verify-survivors re-tests survivors in isolate mode after the main run to catch false negatives from warm-session state leakage.
Incremental mode (--diff)
Only mutate functions touched by a git diff. Uses git merge-base to compare against the divergence point, so --diff main does the right thing on feature branches.
Reporting
Terminal output groups survived mutants by operator. --report json writes Stryker mutation-testing-report-schema v2, compatible with the Stryker Dashboard. --report html generates a self-contained report using mutation-testing-elements. On GitHub Actions, irradiate auto-emits ::warning annotations on survived mutants and writes a Markdown step summary.
Caching
Content-addressable cache keyed on SHA-256 of function body, test IDs, and operator. Survives rebases, branch switches, and touch (mtime-based caches don't). Use --no-cache to force a full re-run.
Decorator support
@property, @classmethod, and @staticmethod are handled natively via a descriptor-aware trampoline. Other decorated functions are currently skipped; a source-patching fallback is planned (#13).
Sampling (--sample)
Test a random subset of mutants for fast CI feedback. Academic research shows 5-10% random sampling gives 99% R² correlation with the full mutation score.
--sample 0.1: test 10% of mutants--sample 100: test exactly 100 mutants--sample-seed 42: override RNG seed (default: 0 for reproducibility)
Sampling is operator-stratified, so every mutation category is proportionally represented.
CI integration
Drop-in GitHub Actions composite action:
- uses: nwyin/irradiate@v0
with:
diff: origin/main
fail-under: "80"
Auto-detects GitHub Actions and emits inline ::warning annotations on survived mutants, plus a Markdown step summary.
Performance tuning
Parallelism defaults to CPU count (--workers N to override). Workers are recycled when RSS exceeds --max-worker-memory N MB. --covered-only skips mutants with no test coverage. --no-stats skips coverage collection when you want to test all mutants against all tests. Per-mutant timeout defaults to 10x baseline (--timeout-multiplier N).
How it compares to mutmut
| mutmut | irradiate | |
|---|---|---|
| Speed | pytest.main() per mutant (~200ms each) |
Fork-per-mutant, pytest starts once |
| Parser | LibCST (Python) | tree-sitter (Rust, parallel) |
| Operators | ~20 categories | 40 categories (incl. 13 regex) |
| Cache | mtime-based | Content-addressable (SHA-256) |
| Orchestration | Python multiprocessing | Rust + tokio async |
| Incremental | no | --diff with merge-base |
| Reports | Terminal only | JSON, HTML, GitHub Actions annotations |
| Decorator support | Skip all | @property/@classmethod/@staticmethod handled |
| Sampling | no | --sample with operator stratification |
| CI integration | Manual | --fail-under, GitHub Actions action, annotations, step summary |
| Isolation | Fork only | Warm-session + --isolate + --verify-survivors |
Acknowledgments
irradiate's trampoline architecture and mutation operator design are informed by mutmut. The naming convention is partially compatible with mutmut to ease migration.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 irradiate-0.3.0.tar.gz.
File metadata
- Download URL: irradiate-0.3.0.tar.gz
- Upload date:
- Size: 324.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f6917e1b6f4e2c18bbdb30365680eb458f4dd975306a76661774296c9567c8a
|
|
| MD5 |
1f93af68d9382f2d290949abcbbf0721
|
|
| BLAKE2b-256 |
955b65dfa5f238dbfabb30f6a365b4dbcd6f4f1a59cffe5f20edcc734b9b1506
|
Provenance
The following attestation bundles were made for irradiate-0.3.0.tar.gz:
Publisher:
release.yml on nwyin/irradiate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
irradiate-0.3.0.tar.gz -
Subject digest:
5f6917e1b6f4e2c18bbdb30365680eb458f4dd975306a76661774296c9567c8a - Sigstore transparency entry: 1186541386
- Sigstore integration time:
-
Permalink:
nwyin/irradiate@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/nwyin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Trigger Event:
push
-
Statement type:
File details
Details for the file irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.8 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8a218f79ce3cc149a23badaab7fe4dfb21cf7868c81ab598598443d7b4b832a
|
|
| MD5 |
4c93ea0a0b5f8b7fff97eec72bf1b218
|
|
| BLAKE2b-256 |
1dea60569404fa1b72fc8574963701935fe93602db42879c77740303a45d8a9b
|
Provenance
The following attestation bundles were made for irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on nwyin/irradiate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
c8a218f79ce3cc149a23badaab7fe4dfb21cf7868c81ab598598443d7b4b832a - Sigstore transparency entry: 1186541421
- Sigstore integration time:
-
Permalink:
nwyin/irradiate@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/nwyin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Trigger Event:
push
-
Statement type:
File details
Details for the file irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bdbf4493e535aef08a99ce4a2fb6f84880976fbfa7f064745c30be6752ab873
|
|
| MD5 |
58cb11153d7ffe6444ecca540ee7d7f9
|
|
| BLAKE2b-256 |
5c9691a02420d0aa783575617b838f164116cab393c198b999ee474d57f1557c
|
Provenance
The following attestation bundles were made for irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl:
Publisher:
release.yml on nwyin/irradiate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl -
Subject digest:
8bdbf4493e535aef08a99ce4a2fb6f84880976fbfa7f064745c30be6752ab873 - Sigstore transparency entry: 1186541438
- Sigstore integration time:
-
Permalink:
nwyin/irradiate@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/nwyin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Trigger Event:
push
-
Statement type:
File details
Details for the file irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
021ceff1ec41948cc19fd947cae9aecdc7040b2651de6afe2d09a5520f6fd925
|
|
| MD5 |
4fdda9fa30293ed64f55446824df83f8
|
|
| BLAKE2b-256 |
1f0bdbe39d7ab91895ff08e1251d8ee5009f8d0f7defa291b7db480dc43359fe
|
Provenance
The following attestation bundles were made for irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on nwyin/irradiate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
021ceff1ec41948cc19fd947cae9aecdc7040b2651de6afe2d09a5520f6fd925 - Sigstore transparency entry: 1186541400
- Sigstore integration time:
-
Permalink:
nwyin/irradiate@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/nwyin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Trigger Event:
push
-
Statement type:
File details
Details for the file irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0621080e03d37c5688048a434aee76717736d5cc094f1850a042c0ba374a7361
|
|
| MD5 |
0baa735eb5ea7b815e1e96957a7fa6d2
|
|
| BLAKE2b-256 |
49e54b14725aaeb086949671ddc7ddd9ce2a1375263f702e0c3e6e451d76a90d
|
Provenance
The following attestation bundles were made for irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl:
Publisher:
release.yml on nwyin/irradiate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
0621080e03d37c5688048a434aee76717736d5cc094f1850a042c0ba374a7361 - Sigstore transparency entry: 1186541413
- Sigstore integration time:
-
Permalink:
nwyin/irradiate@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/nwyin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae64f691f70d2b7a6ed6ef376e1f69f5d54bfc43 -
Trigger Event:
push
-
Statement type: