Skip to main content

Fast mutation testing for Python

Project description

irradiate

PyPI License: MIT

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

  1. Parse Python source with tree-sitter (40 mutation operator categories including regex patterns)
  2. Generate trampolined mutants: each function gets an original, N mutated variants, and a runtime dispatcher
  3. Collect test coverage and timing in a single pytest run
  4. Fork a child process per mutant inside pre-warmed workers (no pytest restart)
  5. 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

irradiate-0.3.0.tar.gz (324.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl (1.8 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl (1.7 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl (1.7 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

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

Hashes for irradiate-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5f6917e1b6f4e2c18bbdb30365680eb458f4dd975306a76661774296c9567c8a
MD5 1f93af68d9382f2d290949abcbbf0721
BLAKE2b-256 955b65dfa5f238dbfabb30f6a365b4dbcd6f4f1a59cffe5f20edcc734b9b1506

See more details on using hashes here.

Provenance

The following attestation bundles were made for irradiate-0.3.0.tar.gz:

Publisher: release.yml on nwyin/irradiate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for irradiate-0.3.0-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c8a218f79ce3cc149a23badaab7fe4dfb21cf7868c81ab598598443d7b4b832a
MD5 4c93ea0a0b5f8b7fff97eec72bf1b218
BLAKE2b-256 1dea60569404fa1b72fc8574963701935fe93602db42879c77740303a45d8a9b

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for irradiate-0.3.0-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 8bdbf4493e535aef08a99ce4a2fb6f84880976fbfa7f064745c30be6752ab873
MD5 58cb11153d7ffe6444ecca540ee7d7f9
BLAKE2b-256 5c9691a02420d0aa783575617b838f164116cab393c198b999ee474d57f1557c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for irradiate-0.3.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 021ceff1ec41948cc19fd947cae9aecdc7040b2651de6afe2d09a5520f6fd925
MD5 4fdda9fa30293ed64f55446824df83f8
BLAKE2b-256 1f0bdbe39d7ab91895ff08e1251d8ee5009f8d0f7defa291b7db480dc43359fe

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for irradiate-0.3.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 0621080e03d37c5688048a434aee76717736d5cc094f1850a042c0ba374a7361
MD5 0baa735eb5ea7b815e1e96957a7fa6d2
BLAKE2b-256 49e54b14725aaeb086949671ddc7ddd9ce2a1375263f702e0c3e6e451d76a90d

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page