irradiate 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. irradiate-0.1.0/.github/workflows/ci.yml +87 -0
  2. irradiate-0.1.0/.github/workflows/docs.yml +94 -0
  3. irradiate-0.1.0/.github/workflows/fuzz.yml +72 -0
  4. irradiate-0.1.0/.github/workflows/release.yml +79 -0
  5. irradiate-0.1.0/.gitignore +64 -0
  6. irradiate-0.1.0/.hive/.gitignore +3 -0
  7. irradiate-0.1.0/.hive/project-context.md +73 -0
  8. irradiate-0.1.0/.hive/queen-context.md +35 -0
  9. irradiate-0.1.0/.hive.toml +6 -0
  10. irradiate-0.1.0/.pre-commit-config.yaml +45 -0
  11. irradiate-0.1.0/CLAUDE.md +102 -0
  12. irradiate-0.1.0/Cargo.lock +1379 -0
  13. irradiate-0.1.0/Cargo.toml +33 -0
  14. irradiate-0.1.0/LICENSE +21 -0
  15. irradiate-0.1.0/PKG-INFO +155 -0
  16. irradiate-0.1.0/README.md +135 -0
  17. irradiate-0.1.0/bench/README.md +132 -0
  18. irradiate-0.1.0/bench/compare.sh +252 -0
  19. irradiate-0.1.0/bench/setup.sh +103 -0
  20. irradiate-0.1.0/bench/summarize.py +415 -0
  21. irradiate-0.1.0/bench/targets/my_lib.sh +13 -0
  22. irradiate-0.1.0/bench/targets/simple_project.sh +10 -0
  23. irradiate-0.1.0/bench/targets/synth/pyproject.toml +18 -0
  24. irradiate-0.1.0/bench/targets/synth/src/synth/__init__.py +107 -0
  25. irradiate-0.1.0/bench/targets/synth/src/synth/data_ops.py +99 -0
  26. irradiate-0.1.0/bench/targets/synth/src/synth/math_ops.py +116 -0
  27. irradiate-0.1.0/bench/targets/synth/src/synth/string_ops.py +106 -0
  28. irradiate-0.1.0/bench/targets/synth/src/synth/validators.py +103 -0
  29. irradiate-0.1.0/bench/targets/synth/tests/test_data.py +79 -0
  30. irradiate-0.1.0/bench/targets/synth/tests/test_math.py +104 -0
  31. irradiate-0.1.0/bench/targets/synth/tests/test_strings.py +96 -0
  32. irradiate-0.1.0/bench/targets/synth/tests/test_validators.py +99 -0
  33. irradiate-0.1.0/bench/targets/synth.sh +10 -0
  34. irradiate-0.1.0/docs/blog/.authors.yml +5 -0
  35. irradiate-0.1.0/docs/blog/.gitkeep +0 -0
  36. irradiate-0.1.0/docs/blog/index.md +1 -0
  37. irradiate-0.1.0/docs/blog/posts/what-is-mutation-testing.md +127 -0
  38. irradiate-0.1.0/docs/getting-started/configuration.md +84 -0
  39. irradiate-0.1.0/docs/getting-started/installation.md +54 -0
  40. irradiate-0.1.0/docs/getting-started/quickstart.md +109 -0
  41. irradiate-0.1.0/docs/guide/.gitkeep +0 -0
  42. irradiate-0.1.0/docs/guide/ci-integration.md +83 -0
  43. irradiate-0.1.0/docs/guide/comparison.md +49 -0
  44. irradiate-0.1.0/docs/guide/understanding-results.md +74 -0
  45. irradiate-0.1.0/docs/index.md +23 -0
  46. irradiate-0.1.0/docs/internals/.gitkeep +0 -0
  47. irradiate-0.1.0/docs/internals/architecture.md +405 -0
  48. irradiate-0.1.0/docs/internals/decorators.md +136 -0
  49. irradiate-0.1.0/docs/internals/import-hook.md +418 -0
  50. irradiate-0.1.0/docs/internals/markupsafe-benchmark-notes.md +373 -0
  51. irradiate-0.1.0/docs/internals/markupsafe-perf-analysis.md +168 -0
  52. irradiate-0.1.0/docs/internals/mutation-operators.md +357 -0
  53. irradiate-0.1.0/docs/internals/trampoline.md +176 -0
  54. irradiate-0.1.0/docs/reference/.gitkeep +0 -0
  55. irradiate-0.1.0/docs/reference/cli.md +112 -0
  56. irradiate-0.1.0/examples/mutate_file.rs +32 -0
  57. irradiate-0.1.0/examples/spike_libcst.rs +85 -0
  58. irradiate-0.1.0/harness/__init__.py +33 -0
  59. irradiate-0.1.0/harness/import_hook.py +107 -0
  60. irradiate-0.1.0/harness/stats_plugin.py +100 -0
  61. irradiate-0.1.0/harness/worker.py +336 -0
  62. irradiate-0.1.0/mkdocs.yml +81 -0
  63. irradiate-0.1.0/pyproject.toml +31 -0
  64. irradiate-0.1.0/requirements-docs.txt +2 -0
  65. irradiate-0.1.0/scripts/bootstrap-vendors.sh +34 -0
  66. irradiate-0.1.0/scripts/generate_report.py +693 -0
  67. irradiate-0.1.0/scripts/install-hooks.sh +7 -0
  68. irradiate-0.1.0/scripts/pre-commit +12 -0
  69. irradiate-0.1.0/scripts/run_vendor_tests.sh +251 -0
  70. irradiate-0.1.0/src/cache.rs +573 -0
  71. irradiate-0.1.0/src/codegen.rs +1659 -0
  72. irradiate-0.1.0/src/config.rs +262 -0
  73. irradiate-0.1.0/src/git_diff.rs +399 -0
  74. irradiate-0.1.0/src/harness.rs +83 -0
  75. irradiate-0.1.0/src/lib.rs +15 -0
  76. irradiate-0.1.0/src/main.rs +228 -0
  77. irradiate-0.1.0/src/mutation.rs +4950 -0
  78. irradiate-0.1.0/src/orchestrator.rs +987 -0
  79. irradiate-0.1.0/src/pipeline.rs +3237 -0
  80. irradiate-0.1.0/src/progress.rs +106 -0
  81. irradiate-0.1.0/src/protocol.rs +240 -0
  82. irradiate-0.1.0/src/report.rs +882 -0
  83. irradiate-0.1.0/src/stats.rs +252 -0
  84. irradiate-0.1.0/src/trace.rs +113 -0
  85. irradiate-0.1.0/src/trampoline.rs +1116 -0
  86. irradiate-0.1.0/src/tree_sitter_mutation.rs +2230 -0
  87. irradiate-0.1.0/tests/e2e.sh +342 -0
  88. irradiate-0.1.0/tests/fixtures/crash_worker_project/pyproject.toml +3 -0
  89. irradiate-0.1.0/tests/fixtures/crash_worker_project/src/crash_target/__init__.py +2 -0
  90. irradiate-0.1.0/tests/fixtures/crash_worker_project/tests/test_crash.py +21 -0
  91. irradiate-0.1.0/tests/fixtures/simple_project/pyproject.toml +7 -0
  92. irradiate-0.1.0/tests/fixtures/simple_project/src/simple_lib/__init__.py +16 -0
  93. irradiate-0.1.0/tests/fixtures/simple_project/tests/test_simple.py +16 -0
  94. irradiate-0.1.0/tests/harness_tests/conftest.py +5 -0
  95. irradiate-0.1.0/tests/harness_tests/test_import_hook.py +264 -0
  96. irradiate-0.1.0/tests/harness_tests/test_init.py +82 -0
  97. irradiate-0.1.0/tests/harness_tests/test_stats_plugin.py +281 -0
  98. irradiate-0.1.0/tests/harness_tests/test_worker.py +282 -0
  99. irradiate-0.1.0/tests/proptest_mutation.proptest-regressions +8 -0
  100. irradiate-0.1.0/tests/proptest_mutation.rs +1550 -0
  101. irradiate-0.1.0/tests/vendor_test.sh +219 -0
  102. irradiate-0.1.0/tests/worker_pool_integration.rs +1197 -0
@@ -0,0 +1,87 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ check:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+
12
+ - name: Install Rust stable
13
+ uses: dtolnay/rust-toolchain@stable
14
+
15
+ - name: Security audit
16
+ uses: rustsec/audit-check@v2.0.0
17
+ with:
18
+ token: ${{ secrets.GITHUB_TOKEN }}
19
+ continue-on-error: true
20
+
21
+ - name: Cache cargo target
22
+ uses: actions/cache@v4
23
+ with:
24
+ path: target
25
+ key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
26
+ restore-keys: |
27
+ ${{ runner.os }}-cargo-
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@v4
31
+
32
+ - name: Install Python 3.12
33
+ uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.12"
36
+
37
+ - name: Cache fixture venv
38
+ uses: actions/cache@v4
39
+ with:
40
+ path: tests/fixtures/simple_project/.venv
41
+ key: ${{ runner.os }}-venv-${{ hashFiles('tests/fixtures/simple_project/pyproject.toml', 'tests/fixtures/simple_project/requirements*.txt') }}
42
+ restore-keys: |
43
+ ${{ runner.os }}-venv-
44
+
45
+ - name: Set up fixture venv
46
+ run: cd tests/fixtures/simple_project && uv venv --python 3.12 --clear && uv pip install pytest
47
+
48
+ - name: cargo check + clippy
49
+ run: cargo check && cargo clippy -- -D warnings
50
+
51
+ - name: cargo test
52
+ run: cargo test
53
+
54
+ - name: Install cargo-llvm-cov
55
+ uses: taiki-e/install-action@cargo-llvm-cov
56
+
57
+ - name: Generate coverage report
58
+ run: cargo llvm-cov --lcov --output-path lcov.info
59
+
60
+ - name: Upload coverage artifact
61
+ uses: actions/upload-artifact@v4
62
+ with:
63
+ name: coverage-report
64
+ path: lcov.info
65
+
66
+ - name: Run e2e tests
67
+ run: bash tests/e2e.sh
68
+
69
+ - name: Dogfood mutation score
70
+ run: |
71
+ cd tests/harness_tests
72
+ # venv is created by e2e.sh; install to be safe
73
+ if [ ! -d .venv ]; then uv venv --python 3.12 && uv pip install pytest; fi
74
+ ../../target/debug/irradiate run \
75
+ --paths-to-mutate ../../harness \
76
+ --tests-dir . \
77
+ --python .venv/bin/python3 \
78
+ --timeout-multiplier 5 \
79
+ --isolate 2>&1 | tee dogfood.txt || true
80
+ killed=$(grep -oP 'Killed:\s+\K\d+' dogfood.txt || echo 0)
81
+ total=$(grep -oP 'complete \(\K\d+' dogfood.txt || echo 0)
82
+ echo "Mutation score: ${killed} / ${total} mutants killed"
83
+ if [ "$total" -gt 0 ]; then
84
+ pct=$(( killed * 100 / total ))
85
+ echo "Score: ${pct}%"
86
+ fi
87
+ rm -rf mutants .irradiate
@@ -0,0 +1,94 @@
1
+ name: Docs & Report
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ schedule:
7
+ - cron: "0 6 * * 1" # weekly Monday 6am UTC
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install Rust
26
+ uses: dtolnay/rust-toolchain@stable
27
+
28
+ - name: Cache cargo
29
+ uses: actions/cache@v4
30
+ with:
31
+ path: |
32
+ ~/.cargo/registry
33
+ ~/.cargo/git
34
+ target
35
+ key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
36
+ restore-keys: ${{ runner.os }}-cargo-
37
+
38
+ - name: Build release
39
+ run: cargo build --release
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@v4
43
+
44
+ - name: Set up Python
45
+ uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.12"
48
+
49
+ # --- Docs ---
50
+ - name: Install docs dependencies
51
+ run: uv pip install --system -r requirements-docs.txt
52
+
53
+ - name: Build docs
54
+ run: mkdocs build --strict || mkdocs build # --strict may fail until all pages exist
55
+
56
+ # --- Report ---
57
+ - name: Set up fixture venv
58
+ run: cd tests/fixtures/simple_project && uv venv --python 3.12 --clear && uv pip install pytest
59
+
60
+ # Benchmarks are slow — only run on schedule and workflow_dispatch, not on every push.
61
+ # The report still generates with placeholder data when bench results are absent.
62
+ - name: Set up benchmark environment
63
+ if: github.event_name != 'push'
64
+ run: bash bench/setup.sh
65
+
66
+ - name: Run benchmark (synth target, 1 run)
67
+ if: github.event_name != 'push'
68
+ run: bash bench/compare.sh synth --runs 1
69
+
70
+ # Vendor tests: run irradiate on real open-source Python libraries.
71
+ # Skipped on push to keep CI fast; report shows placeholders when file is absent.
72
+ - name: Run vendor tests
73
+ if: github.event_name != 'push'
74
+ run: bash scripts/run_vendor_tests.sh -o vendor_results.json
75
+
76
+ # Generate the report into site/report/ so it's a subpath of the docs site
77
+ - name: Generate report
78
+ run: uv run --python 3.12 scripts/generate_report.py -o site/report/index.html --vendor-results vendor_results.json
79
+
80
+ - name: Upload pages artifact
81
+ uses: actions/upload-pages-artifact@v3
82
+ with:
83
+ path: site
84
+
85
+ deploy:
86
+ needs: build
87
+ runs-on: ubuntu-latest
88
+ environment:
89
+ name: github-pages
90
+ url: ${{ steps.deployment.outputs.page_url }}
91
+ steps:
92
+ - name: Deploy to GitHub Pages
93
+ id: deployment
94
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,72 @@
1
+ name: Nightly Fuzz
2
+ on:
3
+ schedule:
4
+ - cron: '0 4 * * *' # 4am UTC daily
5
+ workflow_dispatch: {} # manual trigger
6
+
7
+ jobs:
8
+ fuzz:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ matrix:
12
+ target: [fuzz_mutate_file, fuzz_parse_param_names, fuzz_generate_trampoline]
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: dtolnay/rust-toolchain@nightly
16
+ - run: cargo install cargo-fuzz
17
+ - name: Seed corpus
18
+ run: |
19
+ mkdir -p fuzz/corpus/${{ matrix.target }}
20
+ # corpus seeds are committed in fuzz/corpus/<target>/
21
+ - name: Run fuzzer (5 minutes)
22
+ id: fuzz
23
+ continue-on-error: true
24
+ run: |
25
+ cargo fuzz run ${{ matrix.target }} fuzz/corpus/${{ matrix.target }} -- -max_total_time=300 2>&1 | tee fuzz_output.txt
26
+ echo "exit_code=$?" >> $GITHUB_OUTPUT
27
+ - name: Check for crash
28
+ if: steps.fuzz.outcome == 'failure'
29
+ run: |
30
+ CRASH_FILE=$(find fuzz/artifacts/${{ matrix.target }} -name "crash-*" -o -name "oom-*" 2>/dev/null | head -1)
31
+ if [ -n "$CRASH_FILE" ]; then
32
+ echo "CRASH_INPUT=$(base64 < "$CRASH_FILE")" >> $GITHUB_ENV
33
+ echo "CRASH_FILE=$CRASH_FILE" >> $GITHUB_ENV
34
+ fi
35
+ - name: File GH issue for crash
36
+ if: steps.fuzz.outcome == 'failure' && env.CRASH_FILE != ''
37
+ uses: actions/github-script@v7
38
+ with:
39
+ script: |
40
+ const fs = require('fs');
41
+ const output = fs.readFileSync('fuzz_output.txt', 'utf8').slice(-3000);
42
+ await github.rest.issues.create({
43
+ owner: context.repo.owner,
44
+ repo: context.repo.repo,
45
+ title: `Fuzz crash: ${{ matrix.target }}`,
46
+ body: [
47
+ '## Nightly fuzz found a crash',
48
+ '',
49
+ `**Target:** \`${{ matrix.target }}\``,
50
+ `**Date:** ${new Date().toISOString()}`,
51
+ `**Crash input (base64):** \`${process.env.CRASH_INPUT}\``,
52
+ '',
53
+ '### Output (last 3000 chars)',
54
+ '```',
55
+ output,
56
+ '```',
57
+ '',
58
+ '### Reproducing',
59
+ '```bash',
60
+ `echo "${process.env.CRASH_INPUT}" | base64 -d > crash_input`,
61
+ `cargo +nightly fuzz run ${{ matrix.target }} crash_input`,
62
+ '```',
63
+ ].join('\n'),
64
+ labels: ['fuzz', 'bug'],
65
+ });
66
+ - name: Upload corpus
67
+ if: always()
68
+ uses: actions/upload-artifact@v4
69
+ with:
70
+ name: corpus-${{ matrix.target }}
71
+ path: fuzz/corpus/${{ matrix.target }}/
72
+ retention-days: 7
@@ -0,0 +1,79 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ name: Build wheels (${{ matrix.target }})
11
+ runs-on: ${{ matrix.runner }}
12
+ strategy:
13
+ matrix:
14
+ include:
15
+ - runner: ubuntu-latest
16
+ target: x86_64-unknown-linux-gnu
17
+ - runner: ubuntu-latest
18
+ target: aarch64-unknown-linux-gnu
19
+ - runner: macos-14
20
+ target: aarch64-apple-darwin
21
+ - runner: macos-14
22
+ target: x86_64-apple-darwin
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Install Rust stable
28
+ uses: dtolnay/rust-toolchain@stable
29
+ with:
30
+ targets: ${{ matrix.target }}
31
+
32
+ - name: Build wheels
33
+ uses: PyO3/maturin-action@v1
34
+ with:
35
+ target: ${{ matrix.target }}
36
+ args: --release --out dist
37
+ manylinux: "2_28"
38
+
39
+ - name: Upload wheels
40
+ uses: actions/upload-artifact@v4
41
+ with:
42
+ name: wheels-${{ matrix.target }}
43
+ path: dist/
44
+
45
+ sdist:
46
+ name: Build sdist
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Build sdist
52
+ uses: PyO3/maturin-action@v1
53
+ with:
54
+ command: sdist
55
+ args: --out dist
56
+
57
+ - name: Upload sdist
58
+ uses: actions/upload-artifact@v4
59
+ with:
60
+ name: sdist
61
+ path: dist/
62
+
63
+ publish:
64
+ name: Publish to PyPI
65
+ runs-on: ubuntu-latest
66
+ needs: [build, sdist]
67
+ environment:
68
+ name: pypi
69
+ permissions:
70
+ id-token: write
71
+ steps:
72
+ - name: Download all artifacts
73
+ uses: actions/download-artifact@v4
74
+ with:
75
+ merge-multiple: true
76
+ path: dist/
77
+
78
+ - name: Publish to PyPI
79
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,64 @@
1
+ # Rust
2
+ /target
3
+ **/*.rs.bk
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ .venv/
10
+ venv/
11
+
12
+ # irradiate runtime
13
+ .irradiate/
14
+
15
+ # Generated mutants in test fixtures
16
+ tests/fixtures/simple_project/mutants/
17
+ tests/fixtures/simple_project/.irradiate/
18
+ mutants*
19
+
20
+ # Vendor smoke test repos and results (downloaded at runtime)
21
+ tests/vendor_repos/
22
+
23
+ # Vendor (reference only, cloned separately)
24
+ /vendor/
25
+
26
+ # Hive
27
+ .worktrees/
28
+ .hive/queen-state.md
29
+
30
+ # OS
31
+ .DS_Store
32
+ *.swp
33
+ *.swo
34
+ watch_*
35
+
36
+ # IDE
37
+ .idea/
38
+ .vscode/
39
+ *.iml
40
+
41
+ # Fuzzing — ignore runtime-generated corpus (hex-named) and artifacts; keep seed_* files
42
+ fuzz/corpus/*/[0-9a-f]*
43
+ fuzz/corpus/*/*.options
44
+ fuzz/artifacts/
45
+ fuzz/target/
46
+
47
+ # Benchmarks
48
+ bench/results/
49
+ bench/.venv/
50
+ bench/corpora/
51
+ bench/targets/*/.mutmut-cache
52
+
53
+ # Generated docs artifacts
54
+ docs/artifacts/
55
+
56
+ # Docs build
57
+ .venv-docs/
58
+ site/
59
+
60
+ # Generated report
61
+ /report/
62
+
63
+ # uv
64
+ uv.lock
@@ -0,0 +1,3 @@
1
+ # Ephemeral queen session files (regenerated each session)
2
+ queen-state.md
3
+ queen-instructions.md
@@ -0,0 +1,73 @@
1
+ # Project Context — irradiate
2
+
3
+ ## Overview
4
+ Mutation testing tool for Python, written in Rust — spiritual successor to mutmut. Parses Python source with libcst, generates mutant variants via trampoline code injection, then runs pytest workers over Unix sockets to classify each mutant as killed/survived.
5
+
6
+ ## Architecture
7
+ - **mutation.rs** — Parse Python via libcst-native, walk the CST to collect mutation points (binop swap, compop swap, boolop swap, name/number/string/lambda mutations, method swaps, assignment mutations). Each `Mutation` carries byte-span offsets within the function source.
8
+ - **trampoline.rs** — Name-mangle functions (mutmut convention: `x_func` / `xǁClassǁmethod`), generate orig + variant defs + lookup dict + trampoline wrapper that dispatches via `irradiate_harness.active_mutant`.
9
+ - **codegen.rs** — File-level codegen: strip original function defs, prepend trampoline runtime, append all trampoline arrangements. Produces `MutatedFile` with source + mutant name list.
10
+ - **pipeline.rs** — Full pipeline orchestration: discover .py files → mutate (rayon parallel) → write to `mutants/` dir → optional stats collection → optional forced-fail validation → dispatch to worker pool → write `.meta` results → print report. Also implements `results` and `show` subcommands.
11
+ - **orchestrator.rs** — Tokio-based worker pool: spawn N Python processes, accept Unix socket connections, dispatch `Run` messages, collect `Result`/`Error` messages, recycle workers after N mutants, respawn crashed workers.
12
+ - **harness/** — Embedded Python package (`irradiate_harness`): `__init__.py` (active_mutant global, hit recording), `worker.py` (pytest plugin that intercepts `pytest_runtestloop` for IPC-driven test execution), `stats_plugin.py` (coverage collection via `--irradiate-stats`).
13
+
14
+ Data flow: CLI → `pipeline::run` → `codegen::mutate_file` (parallel via rayon) → write mutated sources to `mutants/` → `stats::collect_stats` (single pytest run in stats mode) → build `WorkItem` list with targeted test IDs → `orchestrator::run_worker_pool` (tokio, Unix sockets) → Python workers run pytest items directly → results written to `.meta` JSON files.
15
+
16
+ ## Key Files
17
+ - `src/main.rs` — CLI entrypoint (clap), subcommands: `run`, `results`, `show`
18
+ - `src/pipeline.rs` — Full pipeline: mutate → stats → validate → test → report (~600 lines, the big one)
19
+ - `src/mutation.rs` — Mutation engine: CST walking, operator tables, byte-span tracking (~1100 lines with tests)
20
+ - `src/trampoline.rs` — Trampoline codegen: name mangling, wrapper generation, runtime dispatch code
21
+ - `src/codegen.rs` — File-level mutation: strip originals, inject trampolines
22
+ - `src/orchestrator.rs` — Tokio worker pool: spawn, dispatch, recycle, respawn
23
+ - `src/protocol.rs` — IPC message types: `OrchestratorMessage`, `WorkerMessage`, `MutantResult`, `MutantStatus`
24
+ - `src/harness.rs` — Extract embedded Python harness to `.irradiate/harness/`
25
+ - `src/config.rs` — Load `[tool.mutmut]` from pyproject.toml
26
+ - `src/stats.rs` — Stats collection: run pytest in stats mode, load/query results
27
+ - `harness/__init__.py` — Python runtime: `active_mutant` global, `ProgrammaticFailException`, hit recording
28
+ - `harness/worker.py` — Pytest worker: Unix socket IPC, direct item execution via `runtestprotocol`
29
+ - `harness/stats_plugin.py` — Pytest plugin for per-test function coverage
30
+ - `tests/e2e.sh` — End-to-end test: build, run on fixture, verify killed/survived counts, test `--isolate` flag
31
+
32
+ ## Build & Test
33
+ - **Language**: Rust 2021 edition + Python 3.12 (harness)
34
+ - **Package manager**: Cargo (Rust), uv (Python fixtures)
35
+ - **Build**: `cargo build`
36
+ - **Test**: `cargo check && cargo clippy -- -D warnings && cargo test && bash tests/e2e.sh`
37
+ - **Lint**: `cargo clippy -- -D warnings`
38
+ - **Format**: `cargo fmt` (Rust), `uvx ruff format` with line-length=144 (Python)
39
+ - **Type check**: N/A (no mypy/pyright configured for harness)
40
+ - **Pre-commit**: N/A (manual: must run check + clippy + test before every commit per CLAUDE.md)
41
+ - **Quirks**: E2E tests require `cd tests/fixtures && uv venv && uv pip install pytest`. The e2e.sh creates venvs automatically if missing. Python harness files are embedded via `include_str!` at compile time — edits to `harness/*.py` require `cargo build` to take effect. Unix socket paths use `/tmp` to avoid macOS 104-byte path limit.
42
+
43
+ ## Conventions
44
+ - Mutation naming follows mutmut: `x_func` (top-level), `xǁClassǁmethod` (class methods), `__mutmut_orig`/`__mutmut_N` suffixes
45
+ - Mutant keys are module-qualified: `module.x_func__mutmut_1`
46
+ - Results stored as JSON `.meta` files in `mutants/<module_path>.py.meta`
47
+ - Tests are inline `#[cfg(test)] mod tests` in each Rust source file — no separate test directory for Rust
48
+ - Python harness uses `# pragma: no mutate` to skip self-mutation of critical functions
49
+ - Error handling via `anyhow::Result` throughout; `tracing` for structured logging
50
+ - IPC protocol is newline-delimited JSON over Unix domain sockets
51
+ - snake_case everywhere (Rust convention); serde `rename_all = "snake_case"` for JSON
52
+ - Decorated Python functions are skipped (not mutated)
53
+ - `NEVER_MUTATE_FUNCTIONS`: `__getattribute__`, `__setattr__`, `__new__`
54
+
55
+ ## Dependencies & Integration
56
+ - **libcst (1.8.6)** — Rust port of Python's libcst; used for parsing Python source into CST for mutation point discovery. No-default-features (native parser only).
57
+ - **tokio** — Async runtime for worker pool orchestration, Unix socket communication
58
+ - **rayon** — Parallel mutation generation across source files
59
+ - **clap 4** — CLI argument parsing with derive macros
60
+ - **serde/serde_json** — JSON serialization for IPC protocol and .meta result files
61
+ - **toml** — Parse pyproject.toml for `[tool.mutmut]` config
62
+ - **pytest** — Test runner (invoked as subprocess); worker.py uses `_pytest.runner.runtestprotocol` for direct item execution
63
+ - **proptest** (dev) — Property-based testing for mutation engine
64
+
65
+ ## Gotchas
66
+ - Byte-span offsets in `Mutation` are relative to the function source string, not the full file. The cursor advances monotonically to handle duplicate tokens (e.g., two `+` operators).
67
+ - `codegen.rs` strips original functions by indent level — if a function has inner functions at the same indent, they may be incorrectly stripped.
68
+ - Worker recycling (`worker_recycle_after`) is critical for long runs: pytest accumulates state that eventually causes failures if workers aren't respawned.
69
+ - The `--isolate` flag runs each mutant in a fresh subprocess (slower but avoids all state leakage); e2e tests verify it produces identical results to worker pool mode.
70
+ - Config reads `[tool.mutmut]` (not `[tool.irradiate]`) for backward compatibility with mutmut.
71
+ - `PYTHONPATH` construction in `pipeline.rs` must include harness dir, mutants dir, and project source parent for correct import resolution.
72
+ - Stats mode (`active_mutant = "stats"`) records which functions each test calls; this is used to build targeted test lists per mutant, dramatically reducing run time.
73
+ - Forced-fail validation runs a single mutant with `active_mutant = "fail"` to verify the trampoline wiring works before the full run.
@@ -0,0 +1,35 @@
1
+ # Queen Context
2
+
3
+ Persistent project knowledge accumulated across queen sessions.
4
+ Update this file with architectural decisions, gotchas, and patterns.
5
+
6
+ ## Architecture Decisions
7
+
8
+ ### Import model (2025-03-18)
9
+ PYTHONPATH shadowing replaced with MutantFinder import hook (sys.meta_path). The hook runs before sys.path is consulted, eliminating all three prior bugs (partial mutation, pytest config interference, sys.path[0] cwd shadowing). PYTHONPATH is now just `harness_dir:source_parent`. The hook is installed in harness/__init__.py when IRRADIATE_MUTANTS_DIR env var is set.
10
+
11
+ ### Class method trampolining (2025-03-18)
12
+ codegen.rs now tracks class context during line walk. For class methods, the wrapper stays inside the class body (indented), while mangled orig/variants/dict go to module level. generate_trampoline() in trampoline.rs returns TrampolineOutput { module_code, wrapper_code, mutant_keys }.
13
+
14
+ ### conftest.py skip (2025-03-18)
15
+ conftest.py is never mutated (is_mutatable_python_file() rejects it) and the import hook skips it too. conftest contains test configuration/fixtures, not application logic.
16
+
17
+ ## Infrastructure State
18
+
19
+ ### What exists
20
+ - CI: .github/workflows/ci.yml (audit, build, clippy, test, coverage, e2e)
21
+ - Forced-fail validation in pipeline.rs (runs after clean validation)
22
+ - Bench infrastructure: bench/compare.sh (5 configs), bench/summarize.py, bench/setup.sh
23
+ - Bench targets: simple_project (~10 mutants), my_lib (~30 mutants) — both too small for meaningful perf comparison
24
+ - Import hook Python tests: 47 tests in tests/harness_tests/ (all pass with correct venv)
25
+
26
+ ### Vendor repo pattern
27
+ Follow pycfg-rs convention: scripts/bootstrap-vendors.sh does shallow clones into bench/corpora/ (gitignored). Each vendor needs its own venv. Bench target configs in bench/targets/<name>.sh export PROJECT_DIR, PATHS_TO_MUTATE, TESTS_DIR, PYTHON.
28
+
29
+ ## Gotchas for Workers
30
+
31
+ - Python harness files are embedded via include_str! — edits to harness/*.py require cargo build to take effect
32
+ - bench/.venv has mutmut installed from vendor/mutmut (editable install)
33
+ - /usr/bin/time -l is macOS-specific; Linux uses different format
34
+ - irradiate reads [tool.mutmut] from pyproject.toml (not [tool.irradiate]) for backward compat
35
+ - Bench compare.sh uses process substitution for /usr/bin/time capture — bash-specific
@@ -0,0 +1,6 @@
1
+ [project]
2
+ name = "irradiate"
3
+
4
+ [hive]
5
+ backend = "claude"
6
+ merge_queue_enabled = true
@@ -0,0 +1,45 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: cargo-check
5
+ name: cargo check
6
+ entry: cargo check
7
+ language: system
8
+ pass_filenames: false
9
+ types: [rust]
10
+
11
+ - id: cargo-clippy
12
+ name: cargo clippy
13
+ entry: cargo clippy -- -D warnings
14
+ language: system
15
+ pass_filenames: false
16
+ types: [rust]
17
+
18
+ - id: cargo-fmt
19
+ name: cargo fmt
20
+ entry: cargo fmt -- --check
21
+ language: system
22
+ pass_filenames: false
23
+ types: [rust]
24
+
25
+ - id: ruff-check
26
+ name: ruff check
27
+ entry: uvx ruff check harness/
28
+ language: system
29
+ pass_filenames: false
30
+ types: [python]
31
+
32
+ - id: ruff-format
33
+ name: ruff format
34
+ entry: uvx ruff format --check harness/
35
+ language: system
36
+ pass_filenames: false
37
+ types: [python]
38
+
39
+ - id: cargo-test
40
+ name: cargo test
41
+ entry: cargo test
42
+ language: system
43
+ pass_filenames: false
44
+ types: [rust]
45
+ stages: [pre-push]
@@ -0,0 +1,102 @@
1
+ # irradiate
2
+
3
+ Mutation testing for Python, written in Rust. Spiritual successor to mutmut.
4
+
5
+ ## Reference implementation
6
+
7
+ mutmut source is at `vendor/mutmut/` for reference. Key files:
8
+
9
+ - `vendor/mutmut/src/mutmut/__main__.py` — runner, config, orchestration (66KB, the big one)
10
+ - `vendor/mutmut/src/mutmut/file_mutation.py` — mutation generation engine (libcst-based)
11
+ - `vendor/mutmut/src/mutmut/node_mutation.py` — 18+ mutation operators
12
+ - `vendor/mutmut/src/mutmut/trampoline_templates.py` — trampoline code generation and runtime dispatch
13
+ - `vendor/mutmut/src/mutmut/code_coverage.py` — coverage integration
14
+ - `vendor/mutmut/src/mutmut/type_checking.py` — type checker integration
15
+ - `vendor/mutmut/e2e_projects/` — test projects (my_lib, config, type_checking, etc.)
16
+
17
+ ### mutmut naming conventions (we follow loosely)
18
+
19
+ - Top-level function `foo()` → mangled as `x_foo`
20
+ - Class method `Class.foo()` → mangled as `xǁClassǁfoo` (Unicode separator `ǁ` U+01C1)
21
+ - Mutant variants: `x_foo__mutmut_orig`, `x_foo__mutmut_1`, `x_foo__mutmut_2`, ...
22
+ - Mutant keys: `module.x_foo__mutmut_1`
23
+ - Metadata files: `mutants/path/to/file.py.meta`
24
+ - Runtime control: `MUTANT_UNDER_TEST` env var (mutmut) / `irradiate_harness.active_mutant` global (irradiate)
25
+
26
+ ### mutmut exit codes
27
+
28
+ - `0` → survived, `1`/`3` → killed, `5`/`33` → no tests, `34` → skipped
29
+ - `36`/`24`/`152`/`255` → timeout, `37` → type check caught, `-11`/`-9` → segfault
30
+
31
+ ## Building and testing
32
+
33
+ ```bash
34
+ # Build
35
+ cargo build
36
+
37
+ # Check + lint (must pass before every commit)
38
+ cargo check && cargo clippy -- -D warnings
39
+
40
+ # Run unit tests
41
+ cargo test
42
+
43
+ # Run e2e tests (once e2e.sh exists)
44
+ bash tests/e2e.sh
45
+
46
+ # Full verification
47
+ cargo check && cargo clippy -- -D warnings && cargo test && bash tests/e2e.sh
48
+ ```
49
+
50
+ ## Python environment
51
+
52
+ The integration tests need Python with pytest. Set up with:
53
+ ```bash
54
+ cd tests/fixtures && uv venv && uv pip install pytest
55
+ ```
56
+
57
+ ## Git hooks
58
+
59
+ Install pre-commit hooks with:
60
+ ```bash
61
+ bash scripts/install-hooks.sh
62
+ ```
63
+
64
+ The hook runs `cargo fmt --check`, `cargo clippy -- -D warnings`, and `cargo test` before every commit.
65
+
66
+ ## Commit guidelines
67
+
68
+ - Run `cargo check && cargo clippy -- -D warnings && cargo test` before every commit
69
+ - One logical change per commit
70
+ - Keep commits small and incremental — commit after completing each module/feature
71
+ - Do not note that PRs were authored by Claude Code
72
+
73
+ ## Project structure
74
+
75
+ ```
76
+ irradiate/
77
+ ├── Cargo.toml
78
+ ├── CLAUDE.md
79
+ ├── src/
80
+ │ ├── main.rs # binary entrypoint
81
+ │ ├── lib.rs # library root
82
+ │ ├── harness.rs # extract embedded Python harness at runtime
83
+ │ ├── orchestrator.rs # tokio worker pool manager
84
+ │ ├── protocol.rs # IPC message types
85
+ │ └── stats.rs # stats collection
86
+ ├── harness/ # Python harness files (embedded via include_str!)
87
+ │ ├── __init__.py # irradiate_harness package (active_mutant global, etc.)
88
+ │ ├── worker.py # pytest worker process
89
+ │ └── stats_plugin.py # pytest plugin for stats collection
90
+ ├── tests/
91
+ │ ├── worker_pool_integration.rs # integration tests (real pytest workers)
92
+ │ ├── fixtures/ # minimal Python projects for testing
93
+ │ └── e2e.sh # end-to-end test script
94
+ ├── vendor/
95
+ │ └── mutmut/ # reference implementation (git clone, not modified)
96
+ └── docs/
97
+ └── design.md # architecture, design rationale, and execution model
98
+ ```
99
+
100
+ ## Design docs
101
+
102
+ - `docs/design.md` — full architecture and design rationale