-
Notifications
You must be signed in to change notification settings - Fork 145
feat: Support Enum, @staticmethod, @classmethod mutations #476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8e394fd
49b4a1e
b6c5fa3
a8cf8dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,8 +53,33 @@ 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. It will continue where it left off, and re-test functions that were | ||
| modified since last run. | ||
| left off. | ||
|
|
||
| Incremental Testing | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Mutmut is designed for incremental workflows. It remembers which mutants have | ||
| been tested and their results, so subsequent runs skip already-tested mutants. | ||
|
|
||
| **Function-level change detection:** Mutmut computes a hash of each function's | ||
| source code. When you modify a function, mutmut detects the change and | ||
| automatically re-tests all mutants in that function. Unchanged functions keep | ||
| their previous results. | ||
|
|
||
| **Limitation:** Change detection only tracks direct function changes, not | ||
| transitive dependencies. If function A calls function B, and you modify B, | ||
| mutants in A are not automatically re-tested. For significant changes to | ||
| shared utilities, use ``mutmut run "module*"`` to re-test affected modules, | ||
| or delete the ``mutants/`` directory for a full re-run. | ||
|
|
||
| This means you can: | ||
|
|
||
| - Run ``mutmut run``, stop partway through, and continue later | ||
| - Modify your source code and re-run - only changed functions are re-tested | ||
| - Update your tests and use ``mutmut browse`` to selectively re-test mutants | ||
|
|
||
| The mutation data is stored in the ``mutants/`` directory. Delete this | ||
| directory to start completely fresh. | ||
|
|
||
| To work with the results, use `mutmut browse` where you can see the mutants, | ||
| retest them when you've updated your tests. | ||
|
|
@@ -209,6 +234,25 @@ to failing tests. | |
| 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 | ||
| ~~~~~~~~~~~~ | ||
|
|
||
|
|
@@ -226,6 +270,92 @@ whitelist lines are: | |
| to continue, but it's slower. | ||
|
|
||
|
|
||
| Enum Classes and Metaclass Compatibility | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels more like something for HISTORY.rst than the README. The README is already pretty long (we should split it up into multiple files at some point imo) and mentioning everything that mutmut supports seems unnecessary (we also don't mention that we support methods, classes, subclasses, etc.).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good call, probably a case of over-eager documentation here. |
||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Mutmut 3.x fully supports mutating enum classes. Methods inside enum classes | ||
| (``Enum``, ``IntEnum``, ``Flag``, ``IntFlag``, ``StrEnum``) are automatically | ||
| mutated using an external injection pattern that avoids conflicts with the | ||
| enum metaclass. | ||
|
|
||
| This means enums with methods like: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from enum import Enum | ||
|
|
||
| class Color(Enum): | ||
| RED = 1 | ||
| GREEN = 2 | ||
|
|
||
| def describe(self): | ||
| return self.name.lower() | ||
|
|
||
| @staticmethod | ||
| def count(): | ||
| return 3 | ||
|
|
||
| ...will have their methods mutated just like regular class methods. | ||
|
|
||
| **Disabling Enum Mutation** | ||
|
|
||
| If you prefer to skip enum mutation entirely, you can disable it in your config: | ||
|
|
||
| .. code-block:: toml | ||
|
|
||
| # pyproject.toml | ||
| [tool.mutmut] | ||
| mutate_enums = false | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an option you need? I don't feel like disabling enum mutations is a use case we need, and would probably be better part of #47
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was me trying to adhere (probably a bit too much) to leaving at least the option of having the base behaviour be relatively unaffected. A codebase which uses mutmut and a large number of dataclasses with basic data manipulation functions, which are of generally low mutant value, may see a very large increase in the number of mutants for little gain for example, so I wanted to give them an easy out of what would become the new default which I in general I tried to leave to current behaviour. I just figured core language features like Enums merit a more "opt out" type approach, though I can see the case to remove this entirely and leave it as a defacto truth that mutmut will try and support more language features over time. |
||
|
|
||
| Or skip a specific enum class using the pragma: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| class Color(Enum): # pragma: no mutate class | ||
| RED = 1 | ||
| GREEN = 2 | ||
|
|
||
| def describe(self): | ||
| return f"Color is {self.name}" | ||
|
|
||
| This tells mutmut to completely skip the class—no mutations will be created | ||
| for any methods. | ||
|
|
||
| Both syntax styles are supported: | ||
|
|
||
| - ``# pragma: no mutate class`` | ||
| - ``# pragma: no mutate: class`` | ||
|
|
||
| **Note:** The regular ``# pragma: no mutate`` on a class line only prevents | ||
| mutations on that specific line. It does NOT prevent mutations inside methods. | ||
| Use ``# pragma: no mutate class`` to skip the entire class (kept for backward | ||
| compatibility with <v3.5.0). | ||
|
|
||
|
|
||
| Skipping Entire Functions | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Similarly, you can skip an entire function from mutation using | ||
| ``# pragma: no mutate function``: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| def complex_algorithm(): # pragma: no mutate function | ||
| # This function won't be mutated at all | ||
| return some_complex_calculation() | ||
|
|
||
| Both syntax styles are supported: | ||
|
|
||
| - ``# pragma: no mutate function`` | ||
| - ``# pragma: no mutate: function`` | ||
|
|
||
| This is useful for functions that: | ||
|
|
||
| - Have complex side effects that make mutation testing impractical | ||
| - Are performance-critical and you want to avoid trampoline overhead | ||
| - Are known to cause issues with the mutation testing framework | ||
|
|
||
|
|
||
| Modifying pytest arguments | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,21 @@ | ||
| FROM python:3.10.19-slim-trixie AS base | ||
| ARG PYTHON_VERSION=3.10 | ||
|
|
||
| FROM python:${PYTHON_VERSION}-slim-trixie AS base | ||
|
|
||
| WORKDIR /mutmut | ||
|
|
||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ | ||
|
|
||
| ENV UV_PROJECT_ENVIRONMENT=/opt/venv | ||
| ARG PYTHON_VERSION | ||
| ENV UV_PROJECT_ENVIRONMENT=/opt/venv \ | ||
| UV_PYTHON_PREFERENCE=only-system \ | ||
| UV_PYTHON=${PYTHON_VERSION} | ||
|
|
||
| COPY . . | ||
| COPY pyproject.toml uv.lock ./ | ||
|
|
||
| RUN uv sync --group dev | ||
| RUN uv sync --group dev --no-install-project | ||
|
|
||
| COPY . . | ||
|
|
||
| ENTRYPOINT ["uv", "run", "pytest"] | ||
| CMD ["--verbose"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet with this PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol getting ahead of myself, rebasing got a little messy as you can see thanks for catching it 😅