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.
- irradiate-0.1.0/.github/workflows/ci.yml +87 -0
- irradiate-0.1.0/.github/workflows/docs.yml +94 -0
- irradiate-0.1.0/.github/workflows/fuzz.yml +72 -0
- irradiate-0.1.0/.github/workflows/release.yml +79 -0
- irradiate-0.1.0/.gitignore +64 -0
- irradiate-0.1.0/.hive/.gitignore +3 -0
- irradiate-0.1.0/.hive/project-context.md +73 -0
- irradiate-0.1.0/.hive/queen-context.md +35 -0
- irradiate-0.1.0/.hive.toml +6 -0
- irradiate-0.1.0/.pre-commit-config.yaml +45 -0
- irradiate-0.1.0/CLAUDE.md +102 -0
- irradiate-0.1.0/Cargo.lock +1379 -0
- irradiate-0.1.0/Cargo.toml +33 -0
- irradiate-0.1.0/LICENSE +21 -0
- irradiate-0.1.0/PKG-INFO +155 -0
- irradiate-0.1.0/README.md +135 -0
- irradiate-0.1.0/bench/README.md +132 -0
- irradiate-0.1.0/bench/compare.sh +252 -0
- irradiate-0.1.0/bench/setup.sh +103 -0
- irradiate-0.1.0/bench/summarize.py +415 -0
- irradiate-0.1.0/bench/targets/my_lib.sh +13 -0
- irradiate-0.1.0/bench/targets/simple_project.sh +10 -0
- irradiate-0.1.0/bench/targets/synth/pyproject.toml +18 -0
- irradiate-0.1.0/bench/targets/synth/src/synth/__init__.py +107 -0
- irradiate-0.1.0/bench/targets/synth/src/synth/data_ops.py +99 -0
- irradiate-0.1.0/bench/targets/synth/src/synth/math_ops.py +116 -0
- irradiate-0.1.0/bench/targets/synth/src/synth/string_ops.py +106 -0
- irradiate-0.1.0/bench/targets/synth/src/synth/validators.py +103 -0
- irradiate-0.1.0/bench/targets/synth/tests/test_data.py +79 -0
- irradiate-0.1.0/bench/targets/synth/tests/test_math.py +104 -0
- irradiate-0.1.0/bench/targets/synth/tests/test_strings.py +96 -0
- irradiate-0.1.0/bench/targets/synth/tests/test_validators.py +99 -0
- irradiate-0.1.0/bench/targets/synth.sh +10 -0
- irradiate-0.1.0/docs/blog/.authors.yml +5 -0
- irradiate-0.1.0/docs/blog/.gitkeep +0 -0
- irradiate-0.1.0/docs/blog/index.md +1 -0
- irradiate-0.1.0/docs/blog/posts/what-is-mutation-testing.md +127 -0
- irradiate-0.1.0/docs/getting-started/configuration.md +84 -0
- irradiate-0.1.0/docs/getting-started/installation.md +54 -0
- irradiate-0.1.0/docs/getting-started/quickstart.md +109 -0
- irradiate-0.1.0/docs/guide/.gitkeep +0 -0
- irradiate-0.1.0/docs/guide/ci-integration.md +83 -0
- irradiate-0.1.0/docs/guide/comparison.md +49 -0
- irradiate-0.1.0/docs/guide/understanding-results.md +74 -0
- irradiate-0.1.0/docs/index.md +23 -0
- irradiate-0.1.0/docs/internals/.gitkeep +0 -0
- irradiate-0.1.0/docs/internals/architecture.md +405 -0
- irradiate-0.1.0/docs/internals/decorators.md +136 -0
- irradiate-0.1.0/docs/internals/import-hook.md +418 -0
- irradiate-0.1.0/docs/internals/markupsafe-benchmark-notes.md +373 -0
- irradiate-0.1.0/docs/internals/markupsafe-perf-analysis.md +168 -0
- irradiate-0.1.0/docs/internals/mutation-operators.md +357 -0
- irradiate-0.1.0/docs/internals/trampoline.md +176 -0
- irradiate-0.1.0/docs/reference/.gitkeep +0 -0
- irradiate-0.1.0/docs/reference/cli.md +112 -0
- irradiate-0.1.0/examples/mutate_file.rs +32 -0
- irradiate-0.1.0/examples/spike_libcst.rs +85 -0
- irradiate-0.1.0/harness/__init__.py +33 -0
- irradiate-0.1.0/harness/import_hook.py +107 -0
- irradiate-0.1.0/harness/stats_plugin.py +100 -0
- irradiate-0.1.0/harness/worker.py +336 -0
- irradiate-0.1.0/mkdocs.yml +81 -0
- irradiate-0.1.0/pyproject.toml +31 -0
- irradiate-0.1.0/requirements-docs.txt +2 -0
- irradiate-0.1.0/scripts/bootstrap-vendors.sh +34 -0
- irradiate-0.1.0/scripts/generate_report.py +693 -0
- irradiate-0.1.0/scripts/install-hooks.sh +7 -0
- irradiate-0.1.0/scripts/pre-commit +12 -0
- irradiate-0.1.0/scripts/run_vendor_tests.sh +251 -0
- irradiate-0.1.0/src/cache.rs +573 -0
- irradiate-0.1.0/src/codegen.rs +1659 -0
- irradiate-0.1.0/src/config.rs +262 -0
- irradiate-0.1.0/src/git_diff.rs +399 -0
- irradiate-0.1.0/src/harness.rs +83 -0
- irradiate-0.1.0/src/lib.rs +15 -0
- irradiate-0.1.0/src/main.rs +228 -0
- irradiate-0.1.0/src/mutation.rs +4950 -0
- irradiate-0.1.0/src/orchestrator.rs +987 -0
- irradiate-0.1.0/src/pipeline.rs +3237 -0
- irradiate-0.1.0/src/progress.rs +106 -0
- irradiate-0.1.0/src/protocol.rs +240 -0
- irradiate-0.1.0/src/report.rs +882 -0
- irradiate-0.1.0/src/stats.rs +252 -0
- irradiate-0.1.0/src/trace.rs +113 -0
- irradiate-0.1.0/src/trampoline.rs +1116 -0
- irradiate-0.1.0/src/tree_sitter_mutation.rs +2230 -0
- irradiate-0.1.0/tests/e2e.sh +342 -0
- irradiate-0.1.0/tests/fixtures/crash_worker_project/pyproject.toml +3 -0
- irradiate-0.1.0/tests/fixtures/crash_worker_project/src/crash_target/__init__.py +2 -0
- irradiate-0.1.0/tests/fixtures/crash_worker_project/tests/test_crash.py +21 -0
- irradiate-0.1.0/tests/fixtures/simple_project/pyproject.toml +7 -0
- irradiate-0.1.0/tests/fixtures/simple_project/src/simple_lib/__init__.py +16 -0
- irradiate-0.1.0/tests/fixtures/simple_project/tests/test_simple.py +16 -0
- irradiate-0.1.0/tests/harness_tests/conftest.py +5 -0
- irradiate-0.1.0/tests/harness_tests/test_import_hook.py +264 -0
- irradiate-0.1.0/tests/harness_tests/test_init.py +82 -0
- irradiate-0.1.0/tests/harness_tests/test_stats_plugin.py +281 -0
- irradiate-0.1.0/tests/harness_tests/test_worker.py +282 -0
- irradiate-0.1.0/tests/proptest_mutation.proptest-regressions +8 -0
- irradiate-0.1.0/tests/proptest_mutation.rs +1550 -0
- irradiate-0.1.0/tests/vendor_test.sh +219 -0
- 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,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,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
|