Metadata-Version: 2.5
Name: moonbuggy
Version: 0.1.0
Summary: Fast, agent-first mutation testing for Python
Author: Jennifer Hamon
License: MIT
Requires-Python: >=3.12
Requires-Dist: coverage>=7.4
Requires-Dist: pytest-cov>=5.0
Provides-Extra: bench
Requires-Dist: mutmut>=3.0; extra == 'bench'
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: furo>=2024.1; extra == 'docs'
Requires-Dist: interrogate>=1.7; extra == 'docs'
Requires-Dist: myst-parser>=3.0; extra == 'docs'
Requires-Dist: pydoclint>=0.5; extra == 'docs'
Requires-Dist: sphinx>=7.3; extra == 'docs'
Provides-Extra: lint
Requires-Dist: mypy>=1.11; extra == 'lint'
Requires-Dist: ruff>=0.6; extra == 'lint'
Description-Content-Type: text/markdown

# 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, and caches results across runs. On a suite where
  test execution dominates, that is **17x faster than the naive approach** of
  rerunning everything per mutant, and modestly faster than mutmut.
- **Output built for agents.** Results are JSON Lines, with a derived plaintext
  view whose every line starts with a fixed keyword, so `grep SURVIVED` works
  with no knowledge of the schema.

## Install

Full documentation is published at
[jhamon.github.io/moonbuggy](https://jhamon.github.io/moonbuggy/).

Requires Python 3.12+ and pytest.

```bash
pip install .
```

## Use

From your project root, with no flags and no configuration:

```bash
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:

```bash
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:

```bash
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:

```python
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

```bash
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](docs/development/acceptance-criteria.md)
are implemented and all criteria are met. Speed numbers and the four measured
iterations behind them are in
[docs/benchmark-results.md](docs/benchmark-results.md).

Design notes: [spike A](docs/development/spike-a-findings.md) (in-memory mutation, xdist),
[spike B](docs/development/spike-b-findings.md) (coverage mechanism).
