Metadata-Version: 2.4
Name: interlocks-mutmut
Version: 3.5.1
Summary: interlocks-published fork of mutmut for PyPI-only installs (no git required at install time).
Keywords: mutmut,mutant,mutation,test,testing,interlocks
Author: Anders Hovmöller
Author-email: Anders Hovmöller <boxed@killingar.net>
License-Expression: BSD-3-Clause
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: click>=8.0.0
Requires-Dist: coverage>=7.3.0
Requires-Dist: libcst>=1.8.5
Requires-Dist: pytest>=6.2.5
Requires-Dist: setproctitle>=1.1.0
Requires-Dist: textual>=1.0.0
Requires-Dist: toml>=0.10.2 ; python_full_version < '3.11'
Maintainer: interlocks maintainers
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/boxed/mutmut
Project-URL: Upstream, https://github.com/boxed/mutmut
Project-URL: interlocks, https://github.com/0xjgv/interlocks
Project-URL: Documentation, https://mutmut.readthedocs.io/en/latest/
Project-URL: Repository, https://github.com/boxed/mutmut
Project-URL: Issues, https://github.com/boxed/mutmut/issues
Project-URL: Changelog, https://github.com/boxed/mutmut/blob/main/HISTORY.rst
Description-Content-Type: text/x-rst

mutmut - python mutation tester
===============================

.. image:: https://github.com/boxed/mutmut/actions/workflows/tests.yml/badge.svg
    :target: https://github.com/boxed/mutmut/actions/workflows/tests.yml

.. image:: https://readthedocs.org/projects/mutmut/badge/?version=latest
    :target: https://mutmut.readthedocs.io/en/latest/?badge=latest
    :alt: Documentation Status


Mutmut is a mutation testing system for Python, with a strong focus on ease
of use. If you don't know what mutation testing is try starting with
`this article <https://kodare.net/2016/12/01/mutmut-a-python-mutation-testing-system.html>`_.

Some highlight features:

- Found mutants can be applied on disk with a simple command making it very
  easy to work with the results
- Remembers work that has been done, so you can work incrementally
- Knows which tests to execute, speeding up mutation testing
- Interactive terminal based UI
- Parallel and fast execution

.. image:: browse_screenshot.png


If you want to mutate code outside of functions, you can try using mutmut 2,
which has a different execution model than mutmut 3+.


Requirements
------------

Mutmut must be run on a system with `fork` support. This means that if you want
to run on windows, you must run inside WSL.



Install and run
---------------

You can get started with a simple:

.. code-block:: console

    pip install mutmut
    mutmut run

This will by run pytest on tests in the "tests" or "test" folder and
it will try to figure out where the code to mutate is.



You can stop the mutation run at any time and mutmut will restart where you
left off.

To work with the results, use `mutmut browse` where you can see the mutants,
retest them when you've updated your tests.

You can also write a mutant to disk from the `browse` interface, or via
`mutmut apply <mutant>`. You should **REALLY** have the file you mutate under
source code control and committed before you apply a mutant!


If during the installation you get an error for the `libcst` dependency mentioning the lack of a rust compiler on your system, it is because your architecture does not have a prebuilt binary for `libcst` and it requires both `rustc` and `cargo` from the [rust toolchain](https://www.rust-lang.org/tools/install) to be built. This is known for at least the `x86_64-darwin` architecture.


Wildcards for testing mutants
-----------------------------

Unix filename pattern matching style on mutants is supported. Example:

.. code-block:: console

    mutmut run "my_module*"
    mutmut run "my_module.my_function*"

In the `browse` TUI you can press `f` to retest a function, and `m` to retest
an entire module.


Configuration
-------------

In `setup.cfg` in the root of your project you can configure mutmut if you need to:

.. code-block:: ini

    [mutmut]
    paths_to_mutate=src/
    pytest_add_cli_args_test_selection=tests/

If you use `pyproject.toml`, you must specify the paths as array in a `tool.mutmut` section:

.. code-block:: toml

    [tool.mutmut]
    paths_to_mutate = [ "src/" ]
    pytest_add_cli_args_test_selection= [ "tests/" ]

See below for more options for configuring mutmut.


"also copy" files
~~~~~~~~~~~~~~~~~

To run the full test suite some files are often needed above the tests and the
source. You can configure to copy extra files that you need by adding
directories and files to `also_copy` in your `setup.cfg`:

.. code-block:: ini

    also_copy=
        iommi/snapshots/
        conftest.py


Limit stack depth
~~~~~~~~~~~~~~~~~

In big code bases some functions are called incidentally by huge swaths of the
codebase, but you really don't want tests that hit those executions to count
for mutation testing purposes. Incidentally tested functions lead to slow
mutation testing as hundreds of tests can be checked for things that should
have clean and fast unit tests, and it leads to bad test suites as any
introduced bug in those base functions will lead to many tests that fail which
are hard to understand how they relate to the function with the change.

You can configure mutmut to only count a test as being relevant for a function
if the stack depth from the test to the function is below some limit. In your
`setup.cfg` add:

.. code-block:: ini

    max_stack_depth=8

A lower value will increase mutation speed and lead to more localized tests,
but will also lead to more surviving mutants that would otherwise have been
caught.


Exclude files from mutation
~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can exclude files from mutation in `setup.cfg`:

.. code-block::

    do_not_mutate=
        *__tests.py


Enable coverage.py filtering of lines to mutate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, mutmut will mutate only functions that are called. But, if you would like a finer grained (line-level)
check for coverage, mutmut can use coverage.py to do that.

If you only want to mutate lines that are called (according to coverage.py), you can set
`mutate_only_covered_lines` to `true` in your configuration. The default value is `false`.


.. code-block::

    mutate_only_covered_lines=true


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

When your project is type checked using `mypy` or `pyrefly`, 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.

Using this filter can improve performance and reduce noise, however it can also hide a few relevant mutations:

1. `x: str = None` may not be valid, but if your tests do not detect such a change it indicates that
    the value of `x` is not properly tested (even if your type checker would catch this particular modification)
2. In some edge cases with class properties (usually in the `__init__` method), the way `mypy` and `pyrefly` infer types does not work well
    with the way mutmut mutates code. Some valid mutations like changing `self.x = 123` to `self.x = None` can
    be filtered out, even though the may be valid.

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', 'your_source_dir', '--output', 'json', '--disable-error-code', 'unused-ignore']

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)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, mutmut "swallows" all the test output etc. so that you get a nice clean output.

If you want to see all the detail to aid with debugging, you can set `debug` to `true` in your configuration.
Note that not all displayed errors are necessarily bad. In particular test runs of the mutated code will lead
to failing tests.

.. code-block::

    debug=true


Disable setproctitle (macOS)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Mutmut uses ``setproctitle`` to show the current mutant name in the process
list, which is helpful for monitoring long runs. However, ``setproctitle``
uses CoreFoundation APIs on macOS that are not fork-safe, causing segfaults
in child processes.

By default, mutmut automatically disables ``setproctitle`` on macOS and
enables it on other platforms. If you need to override this (e.g. to enable it on
macOS at your own risk, or to disable it on other platforms), set ``use_setproctitle``:

.. code-block:: toml

    # pyproject.toml
    [tool.mutmut]
    use_setproctitle = false


Whitelisting
~~~~~~~~~~~~

You can mark lines like this:

.. code-block:: python

    some_code_here()  # pragma: no mutate

to stop mutation on those lines. Some cases we've found where you need to
whitelist lines are:

- The version string on your library. You really shouldn't have a test for this :P
- Optimizing break instead of continue. The code runs fine when mutating break
  to continue, but it's slower.


Skipping Code Blocks
~~~~~~~~~~~~~~~~~~~~

You can skip an entire indentation block from mutation using
``# pragma: no mutate block``. This works on any compound statement --
functions, classes, ``if``/``elif``/``else``, loops, context managers, etc.

Both syntax styles are supported:

- ``# pragma: no mutate block``
- ``# pragma: no mutate: block``

**Skipping an entire function or class** -- place the pragma inline on the
definition line. The entire node (including all children) is skipped and no
trampoline is generated:

.. code-block:: python

    def complex_algorithm():  # pragma: no mutate block
        return some_complex_calculation()

    class MySettings:  # pragma: no mutate block
        DEBUG = True
        MAX_RETRIES = 3

**Skipping only the body of a function** -- place the pragma on its own line
inside the function. The function definition (including default arguments) is
still mutable, but the body is suppressed:

.. code-block:: python

    def foo(val=1):
        # pragma: no mutate block
        x = 1
        y = complex_calculation()
        z = x + y

    def bar():
        # this function is still mutated normally
        return 42

**Skipping an ``if`` branch without affecting ``elif``/``else``** -- place
the pragma inline on the ``if``. Only the ``if`` condition and its indented
body are suppressed; sibling branches (``elif``, ``else``) remain mutable
because they exit the original indentation scope:

.. code-block:: python

    if error_condition:  # pragma: no mutate block
        log_error()
        send_alert()
    elif other_condition:
        # still mutated -- this branch is outside the block scope
        handle_other()
    else:
        # still mutated
        handle_success()

The same principle applies to ``for``/``else``, ``while``/``else``,
``try``/``except``/``finally``, and ``match``/``case``.

This is useful for:

- Functions or classes that should be excluded from mutation entirely
- Error-handling branches that are hard to unit test in isolation
- Logging or telemetry blocks that don't affect program correctness
- Generated or boilerplate code within an otherwise mutable function


Skipping Code Regions
~~~~~~~~~~~~~~~~~~~~~

For suppressing mutations across a range of lines regardless of indentation,
use ``# pragma: no mutate start`` and ``# pragma: no mutate end``:

.. code-block:: python

    a = mutate_this()

    # pragma: no mutate start
    b = skip_this()
    c = skip_this_too()
    # pragma: no mutate end

    d = mutate_this_too()

Every line between the markers (inclusive) is suppressed. This works inside
functions, classes, or at module level, and ignores indentation entirely.

An unmatched ``# pragma: no mutate end`` without a preceding ``start`` raises
a ``PragmaParseError`` at parse time.  An unclosed ``# pragma: no mutate start``
(no matching ``end`` before end-of-file) raises a ``PragmaParseError``.
Both errors include the filename and line number.

**Nesting restriction:** opening a new ``block`` or ``start`` context while
another context is already active is not allowed and raises a
``PragmaParseError``.  The error message includes both the offending line and
the line where the existing context was opened.  Close the current context
first (dedent for ``block``, or ``# pragma: no mutate end`` for ``start``)
before opening a new one.


Modifying pytest arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~

You can add and override pytest arguments:

.. code-block:: python

    # for CLI args that select or deselect tests, use `pytest_add_cli_args_test_selection`
    pytest_add_cli_args_test_selection = ["-m", "not fail", "-k", "test_include"]

    # for other CLI args, use `pytest_add_cli_args`
    pytest_add_cli_args = ["-p", "no:some_plugin"] # disable a plugin
    pytest_add_cli_args = ["-o", "xfail_strict=False"] # overrides xfail_strict from your normal config

    # if you want to ignore the normal pytest configuration
    # you can specify a diferent pytest ini file to be used
    pytest_add_cli_args = ["-c", "mutmut_pytest.ini"]
    also_copy = ["mutmut_pytest.ini"]


Unstable configs
~~~~~~~~~~~~~~~~

Following configurations exist, but may be changed in any minor version.
If you use them, expect that a new version could change or break this feature.


.. code-block:: toml

    # Configure how long mutmut waits before killing a slow mutation
    # Currently calculated as (duration_of_original_tests + timeout_constant) * timeout_multiplier seconds
    timeout_constant = 1.0
    timeout_multiplier = 15.0


Example mutations
-----------------

- Integer literals are changed by adding 1. So 0 becomes 1, 5 becomes 6, etc.
- `<` is changed to `<=`
- break is changed to continue and vice versa

In general the idea is that the mutations should be as subtle as possible.
See `node_mutation.py` for the full list and `test_mutation.py` for tests describing them.


Workflow
--------

This section describes how to work with mutmut to enhance your test suite.

1. Run mutmut with `mutmut run`. A full run is preferred but if you're just
   getting started you can exit in the middle and start working with what you
   have found so far.
2. Show the mutants with `mutmut browse`
3. Find a mutant you want to work on and write a test to try to kill it.
4. Press `r` to rerun the mutant and see if you successfully managed to kill it.

Mutmut keeps the data of what it has done and the mutants in the `mutants/`
directory.If  you want to make sure you run a full mutmut run you can delete
this directory to start from scratch.

Contributing to Mutmut
----------------------

If you wish to contribute to Mutmut, please see our `contributing guide <CONTRIBUTING.rst>`_.
