Skip to main content

mutgate

CI codecov CodeQL Release PyPI DOI Python License: MIT

Status: alpha (0.1.x). The verdict names, the Mutation fields and the exit-code contract are stable; the declaration file may gain fields (control= is planned).

Named mutations as contracts on a test suite. Each mutation is a deliberate, named change to the code under test, together with the list of tests it must turn red — or the statement that it must turn nothing red. mutgate applies each one in a sandbox copy, runs the suite, and judges the contract.

# tests/mutations.py
from mutgate import Mutation

MUTATIONS = [
    Mutation("keep-larger", file="pkg/engine.py",
             old="if (birth[a], -a) >= (birth[b], -b):",
             new="if size[a] >= size[b]:",
             fires=("TestElderRule",)),
    Mutation("skip-absolute-floor", file="pkg/engine.py",
             old="if u.birth < abs_floor:", new="if False:",
             fires=("TestAbsoluteFloor",)),
    Mutation("tie-to-higher-index", file="pkg/engine.py",
             old="(birth[a], -a) >= (birth[b], -b)",
             new="(birth[a], a) >= (birth[b], b)",
             invisible=True),   # an invariance: the measurement must not depend on it
]
$ mutgate run tests/mutations.py
mutation             verdict      fired  expected
keep-larger          OK               3  TestElderRule
skip-absolute-floor  DECORATION       0  TestAbsoluteFloor
                       did not fire: TestAbsoluteFloor
tie-to-higher-index  OK               0  nothing

2 OK, 1 DECORATION

Why

A green test suite guards only the defects that shaped it. The usual way to find out whether a test is load-bearing is to break the code on purpose and see whether the test notices — and in practice that is done by hand, in a scratch copy, with sed, once, and then the result is written into a design note as prose. mutgate makes that a checked artefact:

  • the mutation is named and exact. old must occur exactly count times in the file, or the verdict is NOT_APPLIED. A mutation that silently does not land looks identical to a working guard; this is the trap that motivated the tool.
  • the contract says which tests must fire. A named test that does not fire is a DECORATION — a guard that would not go red.
  • a test outside the contract firing is a finding, not noise. OVERREACH means the mutation reaches further than its author believed. When a convention (a tie-breaking rule, a sign, an ordering) is changed and tests that should only relabel things fail on values, the convention exists in two places that have drifted apart.
  • an invariance is a mutation that must fire nothing. invisible=True pins that the suite passes unchanged under the alternative convention; if it does not, the verdict is VISIBLE. ⚠ An OK on an invariance is a negative result, and it is only as strong as the evidence that the mutated line ran: a dead function, a site the named TESTS never reach, or a suite that imports an installed copy instead of the sandbox all read OK honestly. Pair every invariance with a firing mutation at the same site in the same declaration — the known-positive control — so the file itself proves the site is exercised by these tests. An unpaired OK on an invariance is unevidenced.
  • the working tree is never touched. Each mutation is applied and restored in a temporary copy (git ls-files, so uncommitted work is included and the venv is not), and the copy is put first on PYTHONPATH so an editable install elsewhere cannot shadow it.
  • a red baseline aborts. Mutations mean nothing on a suite that already fails.
  • a run whose failures cannot be read is an error, never a verdict. If the project's pytest configuration suppresses the short summary (--no-summary), pytest's exit code says "failed" while nothing parses; that is reported as ERROR, not silently as OK. And a run cut short is never a verdict either: mutgate owns --maxfail=0 after every user argument (a project's -x would truncate the fired set to one test), and a conftest that forces maxfail anyway trips pytest's own "stopping after N failures" line, which reads as ERROR.

The sandbox holds the project's files but not its .git; a suite that shells out to git (a setuptools_scm-style version check, say) goes red at baseline and says so.

What it is not

It is not automatic mutation testing. Tools like mutmut and cosmic-ray generate operator mutations at random and report a kill rate; that answers "how thorough is this suite?". mutgate answers a narrower question that random mutation cannot: does this specific guard fire under the specific defect it was written for, and only that? The two are complementary.

Declaration file

A Python file (conventionally tests/mutations.py) defining:

name required meaning
MUTATIONS yes a sequence of Mutation
TESTS no pytest targets; default: the declaration file's own directory
PATHS no PYTHONPATH entries relative to the root; default ("src", ".")
ROOT no project root, relative to the declaration file; default: the nearest ancestor with pyproject.toml or .git
PYTHON no interpreter to run pytest with: a bare name is looked up on PATH, a relative path is relative to the root; default: the one running mutgate

Mutation(name, file, old, new, fires=(), may_fire=(), invisible=False, count=1, note=""). Entries in fires and may_fire are substrings of pytest node ids, so "TestElderRule", "test_engine.py::TestElderRule" and "test_tie_goes_to_lower_index" all work.

CLI

mutgate run tests/mutations.py [--tests T ...] [--only NAME ...] [--python PY]
                               [--pytest-arg ARG] [--markdown] [--keep] [-x] [-v]
mutgate list tests/mutations.py

--markdown prints a table meant to be pasted into a design note's build record. Exit code 0 when every contract holds, 1 when a verdict is not OK, 2 when the baseline is red or the file cannot be loaded.

In CI

- run: pip install mutgate
- run: mutgate run tests/mutations.py

Each mutation runs the named tests once, so the cost is the suite's cost times the number of mutations; point TESTS at the module the mutations concern.

Install

pip install mutgate

No runtime dependencies. Python 3.10+. MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mutgate-0.1.1.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

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

mutgate-0.1.1-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

Details for the file mutgate-0.1.1.tar.gz.

File metadata

  • Download URL: mutgate-0.1.1.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mutgate-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c2b97dbf490d233aab4e16af4d3b842777e4302642a5553bd68ff54d454dad9e
MD5 729a2d0dfb8e55d0588ead61438d7464
BLAKE2b-256 cb528f523cfde5c66d0235ea51f57572829b65f965f9f1c31af1b797dabaebc0

See more details on using hashes here.

Provenance

The following attestation bundles were made for mutgate-0.1.1.tar.gz:

Publisher: publish-pypi.yml on JimGalasyn/mutgate

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

File details

Details for the file mutgate-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: mutgate-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 15.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mutgate-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8be329870730973cc2f3951f85cdea894f4223abfc4741cacedbb5144004e201
MD5 bc6a5e791306755927cec087f2493216
BLAKE2b-256 cd3db5ad776cb9a187febb3ba5ea4d3b599102ebd9f38e1e230cbc03a64c3d87

See more details on using hashes here.

Provenance

The following attestation bundles were made for mutgate-0.1.1-py3-none-any.whl:

Publisher: publish-pypi.yml on JimGalasyn/mutgate

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

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page