Skip to content
Merged
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
36 changes: 36 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
default_language_version:
python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-docstring-first
exclude: ^e2e_projects/
- id: check-json
- id: check-merge-conflict
exclude: \.rst$
- id: check-yaml
- id: debug-statements
exclude: tests/data/test_generation/invalid_syntax\.py
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.1
hooks:
- id: mypy
args: [--config-file=mypy.ini]
exclude: (tests/|docs/)
additional_dependencies:
- click>=8.0.0
- coverage>=7.3.0
- libcst>=1.8.5
- pytest>=6.2.5
- setproctitle>=1.1.0
- textual>=1.0.0
- types-toml>=0.10.2
17 changes: 17 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ We use `inline-snapshot` for E2E and integration tests, to prevent unexpected ch

If pytest terminates before reporting the test failures, it likely hit a case where mutmut calls `os._exit(...)`. Try looking at these calls first for troubleshooting.

Running the tests in a container
--------------------------------

Tests are run in CI using a Linux container, if you are not running natively on Linux, you can run the tests in a container using the following script:

.. code-block:: console

./scripts/run_tests.sh

Running your local version of Mutmut against a test codebase
------------------------------------------------------------

Expand All @@ -46,6 +55,14 @@ Codebases using Poetry
# Install dependencies in your Poetry environment
pip install -r <path_to_mutmut_codebase>/requirements.txt


Linting and Formatting
^^^^^^^^^^^^^^^^^^^^^^

This project primarily uses `ruff` for linting and formatting through `pre-commit`. You can run the linting and formatting locally with `uv run pre-commit run --all-files`.

Additionally (and recommended), you can run `pre-commit install` to install the pre-commit hooks to run automatically when running `git commit`.

Documentation about mutmut's architecture
-----------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion cached-results-plan.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Mutmut cached results plan:

Unanswered question:
Unanswered question:
When do we update the cache? It must be safe so that you can quit mutmut at any time and the cache won't be broken.
14 changes: 14 additions & 0 deletions docker/Dockerfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.10.19-slim-trixie AS base

WORKDIR /mutmut

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

ENV UV_PROJECT_ENVIRONMENT=/opt/venv

COPY . .
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI, this will cause any change to the source code to re-execute the RUN uv sync --group dev command and download the packages. There are patterns to make this more efficient (iirc only copy pyproject.toml + uv.lock first, then sync, then copy the rest / use a volume mount).

So if you want better performance, you could change that. I don't mind it being like this though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call, I'll add this to the next pr


RUN uv sync --group dev

ENTRYPOINT ["uv", "run", "pytest"]
CMD ["--verbose"]
50 changes: 25 additions & 25 deletions docs/_themes/flask/static/flasky.css_t
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

{% set page_width = '940px' %}
{% set sidebar_width = '220px' %}

@import url("basic.css");

/* -- page layout ----------------------------------------------------------- */

body {
font-family: 'Georgia', serif;
font-size: 17px;
Expand Down Expand Up @@ -43,7 +43,7 @@ div.sphinxsidebar {
hr {
border: 1px solid #B1B4B6;
}

div.body {
background-color: #ffffff;
color: #3E4349;
Expand All @@ -54,7 +54,7 @@ img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}

div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
Expand All @@ -70,7 +70,7 @@ div.footer a {
div.related {
display: none;
}

div.sphinxsidebar a {
color: #444;
text-decoration: none;
Expand All @@ -80,7 +80,7 @@ div.sphinxsidebar a {
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}

div.sphinxsidebar {
font-size: 14px;
line-height: 1.5;
Expand All @@ -95,7 +95,7 @@ div.sphinxsidebarwrapper p.logo {
margin: 0;
text-align: center;
}

div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: 'Garamond', 'Georgia', serif;
Expand All @@ -109,7 +109,7 @@ div.sphinxsidebar h4 {
div.sphinxsidebar h4 {
font-size: 20px;
}

div.sphinxsidebar h3 a {
color: #444;
}
Expand All @@ -120,7 +120,7 @@ div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}

div.sphinxsidebar p {
color: #555;
margin: 10px 0;
Expand All @@ -131,25 +131,25 @@ div.sphinxsidebar ul {
padding: 0;
color: #000;
}

div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: 'Georgia', serif;
font-size: 1em;
}

/* -- body styles ----------------------------------------------------------- */

a {
color: #004B6B;
text-decoration: underline;
}

a:hover {
color: #6D4100;
text-decoration: underline;
}

div.body h1,
div.body h2,
div.body h3,
Expand All @@ -169,24 +169,24 @@ div.indexwrapper h1 {
height: {{ theme_index_logo_height }};
}
{% endif %}

div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }

a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}

a.headerlink:hover {
color: #444;
}

div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
Expand Down Expand Up @@ -233,20 +233,20 @@ div.note {
background-color: #eee;
border: 1px solid #ccc;
}

div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}

div.topic {
background-color: #eee;
}

p.admonition-title {
display: inline;
}

p.admonition-title:after {
content: ":";
}
Expand Down Expand Up @@ -340,7 +340,7 @@ ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}

pre {
background: #eee;
padding: 7px 30px;
Expand All @@ -357,7 +357,7 @@ dl dl pre {
margin-left: -90px;
padding-left: 90px;
}

tt {
background-color: #ecf0f3;
color: #222;
Expand Down
4 changes: 2 additions & 2 deletions docs/_themes/flask/theme.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle

[options]
index_logo =
index_logo =
index_logo_height = 120px
touch_icon =
touch_icon =
14 changes: 12 additions & 2 deletions docs/_themes/flask_theme_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,18 @@
"""
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
from pygments.token import Comment
from pygments.token import Error
from pygments.token import Generic
from pygments.token import Keyword
from pygments.token import Literal
from pygments.token import Name
from pygments.token import Number
from pygments.token import Operator
from pygments.token import Other
from pygments.token import Punctuation
from pygments.token import String
from pygments.token import Whitespace


class FlaskyStyle(Style):
Expand Down
2 changes: 1 addition & 1 deletion e2e_projects/config/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This project uses most/all of the mutmut configuration in pyproject.toml.
This project uses most/all of the mutmut configuration in pyproject.toml.
2 changes: 1 addition & 1 deletion e2e_projects/config/config_pkg/ignore_me.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
def this_function_shall_NOT_be_mutated():
return 1 + 2
return 1 + 2
2 changes: 1 addition & 1 deletion e2e_projects/config/data/data.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"comment": "this should be copied to the mutants folder"
}
}
4 changes: 2 additions & 2 deletions e2e_projects/config/tests/main/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_include_data_exists():
data = json.load(f)
assert data['comment'] == 'this should be copied to the mutants folder'

# ignored, because it does not match -k 'test_include'
# ignored, because it does not match -k 'test_include'
def test_should_be_ignored():
assert 'This test should be ignored' == 1234

Expand All @@ -38,4 +38,4 @@ def test_include_xfail_that_does_not_fail():
# ignored, because of -m 'not fail'
@pytest.mark.fail
def test_include_that_should_be_ignored():
assert 'This test should be ignored' == 1234
assert 'This test should be ignored' == 1234
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,3 @@ def mutate_only_covered_lines_multiline(simple_branch: bool) -> str:
]
return f"Hello from mutate_only_covered_lines!" \
f" (false) {x} {y}"

Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
def this_function_shall_NOT_be_mutated():
return 1 + 2
return 1 + 2
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ def test_mutate_only_covered_lines_multiline():
assert mutate_only_covered_lines_multiline(True) == "Hello from mutate_only_covered_lines! (true) FooBar [0, 4, 8, 12, 16]"

def call_ignored_function():
assert this_function_shall_NOT_be_mutated() == 3
assert this_function_shall_NOT_be_mutated() == 3
2 changes: 1 addition & 1 deletion e2e_projects/my_lib/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This project will be E2E tested. It mainly serves as a "canary" that alerts you when code changes affect which mutants survive.
This project will be E2E tested. It mainly serves as a "canary" that alerts you when code changes affect which mutants survive.
2 changes: 1 addition & 1 deletion e2e_projects/my_lib/src/my_lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@ def func_with_star(a, /, b, *, c, **kwargs):

def func_with_arbitrary_args_clone(*args, **kwargs): pass # pragma: no mutate
def func_with_arbitrary_args(*args, **kwargs):
return len(args) + len(kwargs)
return len(args) + len(kwargs)
1 change: 0 additions & 1 deletion e2e_projects/my_lib/tests/test_my_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,3 @@ def test_signature_functions_are_callable():

def test_signature_is_coroutine():
assert asyncio.iscoroutinefunction(async_consumer)

2 changes: 1 addition & 1 deletion e2e_projects/py3_14_features/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This project will be E2E tested. It mainly serves as a "canary" that alerts you when code changes affect which mutants survive.
This project will be E2E tested. It mainly serves as a "canary" that alerts you when code changes affect which mutants survive.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def get_len(data: Collection):
def get_len_clone(data: Collection): pass # pragma: no mutate


# verify that mutmut can handle annotations that area
# verify that mutmut can handle annotations that area
def get_foo_len(data: Foo) -> int:
return len(data.foo) + 0

Expand Down
1 change: 0 additions & 1 deletion e2e_projects/py3_14_features/tests/test_py3_14_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ def test_annotations():
def test_signature():
# mutmut currently only achieves a stringified version, because we cannot eagerly evalute the signature
assert inspect.signature(get_len, annotation_format=inspect.Format.STRING) == inspect.signature(get_len_clone, annotation_format=inspect.Format.STRING)

2 changes: 1 addition & 1 deletion e2e_projects/type_checking/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This project uses type checking to detect invalid mutants.
This project uses type checking to detect invalid mutants.
2 changes: 1 addition & 1 deletion e2e_projects/type_checking/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ project-includes = [
]

[tool.pyright]
typeCheckingMode = "strict"
typeCheckingMode = "strict"
2 changes: 1 addition & 1 deletion e2e_projects/type_checking/tests/test_type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ def test_a_hello_wrapper():
assert isinstance(a_hello_wrapper(), str)

def test_mutate_me():
assert mutate_me() == "charlie"
assert mutate_me() == "charlie"
Loading