Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,26 @@ If you only want to mutate lines that are called (according to coverage.py), you
mutate_only_covered_lines=true


Filter generated mutants with type checker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When your project is type checked, you can also use it to filter out invalid mutants.
For instance, mutmut mutates `x: str = 'foo'` to `x: str = None` which can easily caught by type checkers.

To enable this filtering, configure the `type_check_command` to output json results as follows:

.. code-block::

# for pyrefly
type_check_command = ['pyrefly', 'check', '--output-format=json']
# for mypy
type_check_command = ['mypy', 'traces_parser', '--output', 'json']

Currently, only `pyrefly` and `mypy` are supported.
With `pyright` and `ty`, mutating a class method `Foo.bar()` can break the types of all methods of `Foo`,
and therefore mutmut cannot match the type error with the mutant that caused the type error.


Enable debug output (increase verbosity)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
Empty file.
1 change: 1 addition & 0 deletions e2e_projects/type_checking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This project uses type checking to detect invalid mutants.
32 changes: 32 additions & 0 deletions e2e_projects/type_checking/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[project]
name = "type-checking"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = []
requires-python = ">=3.10"
dependencies = []

[build-system]
requires = ["uv_build>=0.9.18,<0.10.0"]
build-backend = "uv_build"

[dependency-groups]
dev = [
"pyrefly>=0.52.0",
"pyright>=1.1.408",
"pytest>=8.2.0",
]

[tool.mutmut]
debug = true
type_check_command = ["pyrefly", "check", "--output-format=json"]

[tool.pyrefly]
project-includes = [
"**/*.py*",
"**/*.ipynb",
]

[tool.pyright]
typeCheckingMode = "strict"
23 changes: 23 additions & 0 deletions e2e_projects/type_checking/src/type_checking/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def hello() -> str:
greeting: str = "Hello from type-checking!"
return greeting

def a_hello_wrapper() -> str:
# verify that hello() keeps the return type str
# (if not, this will type error and not be mutated)
return hello() + "2"

class Person:
def set_name(self, name: str):
self.name = name

def get_name(self):
# return type should be inferred as "str"
return self.name

def mutate_me():
p = Person()
p.set_name('charlie')
# Verify that p.get_name keeps the return type str
name: str = p.get_name()
return name
10 changes: 10 additions & 0 deletions e2e_projects/type_checking/tests/test_type_checking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from type_checking import *

def test_hello():
assert hello() == "Hello from type-checking!"

def test_a_hello_wrapper():
assert isinstance(a_hello_wrapper(), str)

def test_mutate_me():
assert mutate_me() == "charlie"
200 changes: 200 additions & 0 deletions e2e_projects/type_checking/uv.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ source-include = ["HISTORY.rst"]
[dependency-groups]
dev = [
"inline-snapshot>=0.32.0",
"pyrefly>=0.53.0",
"pytest-asyncio>=1.0.0",
"ruff>=0.15.1",
]
Expand Down
Loading