Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-mutmut for openSUSE:Factory checked in at 2026-10-29 22:22:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-mutmut (Old) and /work/SRC/openSUSE:Factory/.python-mutmut.new.1255 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-mutmut" Thu Oct 29 22:22:31 2026 rev:13 rq:1393707 version:3.8.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-mutmut/python-mutmut.changes 2026-09-14 16:22:37.429176351 +0200 +++ /work/SRC/openSUSE:Factory/.python-mutmut.new.1255/python-mutmut.changes 2026-10-29 22:22:35.552364358 +0100 @@ -1,0 +2,11 @@ +Thu Oct 29 10:14:45 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to 3.8.1: + * Fix crash when mypy reports no errors, and stats + collection crash with pytest 9.2 + * Fix enums with custom __init__ failing to import, + and float literals too large for a float + * No equivalent mutants inside string escape sequences; + honour "pragma: no mutate" in multi-line statements + +------------------------------------------------------------------- Old: ---- 3.8.0.tar.gz New: ---- 3.8.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-mutmut.spec ++++++ --- /var/tmp/diff_new_pack.kSXFnW/_old 2026-10-29 22:22:36.281394989 +0100 +++ /var/tmp/diff_new_pack.kSXFnW/_new 2026-10-29 22:22:36.283395073 +0100 @@ -23,7 +23,7 @@ %endif %{?sle15_python_module_pythons} Name: python-mutmut -Version: 3.8.0 +Version: 3.8.1 Release: 0 Summary: Python mutation testing License: BSD-3-Clause @@ -104,7 +104,7 @@ %pre %python_libalternatives_reset_alternative mutmut -%files %{python_files} +%files %python_files %doc README.rst %license LICENSE %python_alternative %{_bindir}/mutmut ++++++ 3.8.0.tar.gz -> 3.8.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/HISTORY.rst new/mutmut-3.8.1/HISTORY.rst --- old/mutmut-3.8.0/HISTORY.rst 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/HISTORY.rst 2026-10-28 08:46:47.000000000 +0100 @@ -1,6 +1,21 @@ Changelog --------- +3.8.1 +~~~~~ + +* Fix stats collection crashing with pytest 9.2 (`#615`, `#616`) + +* Fix ``# pragma: no mutate`` being ignored inside multi-line statements (`#614`) + +* Don't produce equivalent case mutants inside string escape sequences like `` `` and ``\N{...}`` (`#609`, `#611`) + +* Fix enums with a custom ``__init__`` failing to import under mutmut (`#600`, `#603`) + +* Fix crash when mutating float literals too large for a float, like ``1e999`` (`#604`, `#605`) + +* Fix crash when mypy reports no errors (`#598`, `#599`) + 3.8.0 ~~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/README.rst new/mutmut-3.8.1/README.rst --- old/mutmut-3.8.0/README.rst 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/README.rst 2026-10-28 08:46:47.000000000 +0100 @@ -373,7 +373,7 @@ You can use following comments to disable mutation: - `# pragma: no mutate`: disable mutation on this single line -- `# pragma: no mutate block`: disable mutation in the whole intendation block +- `# pragma: no mutate block`: disable mutation in the whole indentation block - `# pragma: no mutate start/end`: disable mutation between the start and end comments Skipping single lines diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/pyproject.toml new/mutmut-3.8.1/pyproject.toml --- old/mutmut-3.8.0/pyproject.toml 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/pyproject.toml 2026-10-28 08:46:47.000000000 +0100 @@ -1,6 +1,6 @@ [project] name = "mutmut" -version = "3.8.0" +version = "3.8.1" description = "mutation testing for Python 3" keywords = ["mutmut", "mutant", "mutation", "test", "testing"] authors = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/src/mutmut/mutation/file_mutation.py new/mutmut-3.8.1/src/mutmut/mutation/file_mutation.py --- old/mutmut-3.8.0/src/mutmut/mutation/file_mutation.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/src/mutmut/mutation/file_mutation.py 2026-10-28 08:46:47.000000000 +0100 @@ -35,6 +35,8 @@ from mutmut.utils.format_utils import is_mutated_method_name NEVER_MUTATE_FUNCTION_NAMES = {"__getattribute__", "__setattr__", "__new__"} +NEVER_MUTATE_ENUM_FUNCTION_NAMES = {"__init__", "_generate_next_value_"} +ENUM_BASE_NAMES = {"Enum", "IntEnum", "StrEnum", "Flag", "IntFlag"} NEVER_MUTATE_FUNCTION_CALLS = {"len", "isinstance"} @@ -217,6 +219,7 @@ ) self.ignored_classes: set[str] = set() self.ignored_functions: set[str] = set() + self._enum_creation_hooks: set[cst.FunctionDef] = set() def on_visit(self, node: cst.CSTNode) -> bool: if self._skip_node_and_children(node): @@ -300,6 +303,15 @@ if isinstance(node, cst.BaseExpression): return True + if isinstance(node, cst.ClassDef) and _is_enum_class(node): + self._enum_creation_hooks.update( + child + for child in node.body.body + if isinstance(child, cst.FunctionDef) and child.name.value in NEVER_MUTATE_ENUM_FUNCTION_NAMES + ) + if node in self._enum_creation_hooks: + return True + if ( isinstance(node, cst.Call) and isinstance(node.func, cst.Name) @@ -344,6 +356,11 @@ return False +def _is_enum_class(node: cst.ClassDef) -> bool: + bases = (base.value.attr if isinstance(base.value, cst.Attribute) else base.value for base in node.bases) + return any(isinstance(base, cst.Name) and base.value in ENUM_BASE_NAMES for base in bases) + + def _walk(node: cst.CSTNode) -> Iterator[cst.CSTNode]: """Yield `node` and every node below it.""" stack = [node] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/src/mutmut/mutation/mutators.py new/mutmut-3.8.1/src/mutmut/mutation/mutators.py --- old/mutmut-3.8.0/src/mutmut/mutation/mutators.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/src/mutmut/mutation/mutators.py 2026-10-28 08:46:47.000000000 +0100 @@ -1,5 +1,7 @@ """This module contains the mutations for indidvidual nodes, e.g. replacing a != b with a == b.""" +import cmath +import math import re from collections.abc import Callable from collections.abc import Iterable @@ -17,15 +19,33 @@ ] ] -# pattern to match (nearly) all chars in a string that are not part of an escape sequence -NON_ESCAPE_SEQUENCE = re.compile(r"((?<!\\)[^\\]+)") +# Pattern matching either: (1) escape sequences with case-sensitive parts (preserved as group None), +# or (2) runs of non-backslash characters (captured as group 1 for case transformation). +# For non-raw strings, preserve \xHH, \uHHHH, \UHHHHHHHH, \N{NAME} to avoid equivalent mutants. +NON_ESCAPE_SEQUENCE = re.compile( + r"\\x[0-9a-fA-F]{2}" # \xHH + r"|\\u[0-9a-fA-F]{4}" # \uHHHH + r"|\\U[0-9a-fA-F]{8}" # \UHHHHHHHH + r"|\\N\{[^}]+\}" # \N{NAME} + r"|(\\.)" # other escapes like \\, \n, \t (group 1, but treat as escape) + r"|((?<!\\)[^\\]+)" # OR non-backslash run (group 2, to be transformed) +) def operator_number(node: cst.BaseNumber) -> Iterable[cst.BaseNumber]: + # A float literal too large for a float, like 1e999 (a common way to spell + # infinity), evaluates to inf. inf + 1 is still inf, which has no literal + # form, so there is no number mutant to make. if isinstance(node, cst.Integer | cst.Float): - yield node.with_changes(value=repr(node.evaluated_value + 1)) + value = node.evaluated_value + 1 + if isinstance(value, float) and math.isinf(value): + return + yield node.with_changes(value=repr(value)) elif isinstance(node, cst.Imaginary): - yield node.with_changes(value=repr(node.evaluated_value + 1j)) + complex_value = node.evaluated_value + 1j + if cmath.isinf(complex_value): + return + yield node.with_changes(value=repr(complex_value)) else: print("Unexpected number type", node) @@ -42,13 +62,31 @@ # that mutation is meaningless for return + # For raw strings (r"..." or R"..."), escape sequences are literal, so case changes are NOT equivalent + is_raw = "r" in prefix.lower() + supported_str_mutations: list[Callable[[str], str]] = [ lambda x: "XX" + x + "XX", - # do not modify escape sequences, as this could break python syntax - lambda x: NON_ESCAPE_SEQUENCE.sub(lambda match: match.group(1).lower(), x), - lambda x: NON_ESCAPE_SEQUENCE.sub(lambda match: match.group(1).upper(), x), ] + if is_raw: + # Raw strings: use original regex (all non-backslash chars) + old_pattern = re.compile(r"((?<!\\)[^\\]+)") + supported_str_mutations.extend( + [ + lambda x: old_pattern.sub(lambda m: m.group(1).lower(), x), + lambda x: old_pattern.sub(lambda m: m.group(1).upper(), x), + ] + ) + else: + # Regular strings: preserve escape sequences with case-sensitive hex/names + supported_str_mutations.extend( + [ + lambda x: NON_ESCAPE_SEQUENCE.sub(lambda m: m.group(2).lower() if m.group(2) else m.group(0), x), + lambda x: NON_ESCAPE_SEQUENCE.sub(lambda m: m.group(2).upper() if m.group(2) else m.group(0), x), + ] + ) + for mut_func in supported_str_mutations: new_value = f"{prefix}{value[0]}{mut_func(value[1:-1])}{value[-1]}" if new_value == value: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/src/mutmut/mutation/pragma_handling.py new/mutmut-3.8.1/src/mutmut/mutation/pragma_handling.py --- old/mutmut-3.8.0/src/mutmut/mutation/pragma_handling.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/src/mutmut/mutation/pragma_handling.py 2026-10-28 08:46:47.000000000 +0100 @@ -217,6 +217,15 @@ self.no_mutate_lines.add(line) return True + def visit_ParenthesizedWhitespace(self, node: cst.ParenthesizedWhitespace) -> bool | None: + # A comment ending a line inside brackets (a call, a literal, a parenthesized + # condition) belongs to this whitespace node, not to the statement's trailing + # whitespace, so visit_SimpleStatementLine never sees it. + if _parse_pragma_token(node.first_line.comment) == "bare": + line = self.get_metadata(PositionProvider, node).start.line + self.no_mutate_lines.add(line) + return True + def _visit_compound_header(self, node: cst.CSTNode) -> None: body = getattr(node, "body", None) if isinstance(body, cst.IndentedBlock): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/src/mutmut/runners/harness.py new/mutmut-3.8.1/src/mutmut/runners/harness.py --- old/mutmut-3.8.0/src/mutmut/runners/harness.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/src/mutmut/runners/harness.py 2026-10-28 08:46:47.000000000 +0100 @@ -154,7 +154,7 @@ def pytest_runtest_teardown(self, item: Any, nextitem: Any) -> None: unused(nextitem) for function in state()._stats: - state().tests_by_mangled_function_name[function].add(strip_prefix(item._nodeid, prefix="mutants/")) + state().tests_by_mangled_function_name[function].add(strip_prefix(item.nodeid, prefix="mutants/")) state()._stats.clear() # noinspection PyMethodMayBeStatic diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/src/mutmut/type_checking.py new/mutmut-3.8.1/src/mutmut/type_checking.py --- old/mutmut-3.8.0/src/mutmut/type_checking.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/src/mutmut/type_checking.py 2026-10-28 08:46:47.000000000 +0100 @@ -21,7 +21,8 @@ try: if "mypy" in type_check_command: - report = [json.loads(line) for line in completed_process.stdout.splitlines()] + # mypy prints a lone newline (not an empty string) when it found no errors + report = [json.loads(line) for line in completed_process.stdout.splitlines() if line.strip()] else: report = json.loads(completed_process.stdout) except json.JSONDecodeError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/tests/e2e/test_e2e_my_lib.py new/mutmut-3.8.1/tests/e2e/test_e2e_my_lib.py --- old/mutmut-3.8.0/tests/e2e/test_e2e_my_lib.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/tests/e2e/test_e2e_my_lib.py 2026-10-28 08:46:47.000000000 +0100 @@ -85,7 +85,6 @@ "my_lib.x_escape_sequences__mutmut_2": 0, "my_lib.x_escape_sequences__mutmut_3": 1, "my_lib.x_escape_sequences__mutmut_4": 0, - "my_lib.x_escape_sequences__mutmut_5": 0, "my_lib.x_create_a_segfault_when_mutated__mutmut_1": -11, "my_lib.x_create_a_segfault_when_mutated__mutmut_2": 0, "my_lib.x_create_a_segfault_when_mutated__mutmut_3": 0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/tests/mutation/test_mutation.py new/mutmut-3.8.1/tests/mutation/test_mutation.py --- old/mutmut-3.8.0/tests/mutation/test_mutation.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/tests/mutation/test_mutation.py 2026-10-28 08:46:47.000000000 +0100 @@ -234,11 +234,19 @@ r'a: str = "Fo\N{ghost} \U11223344"', [ r'a: str = "XXFo\N{ghost} \U11223344XX"', - r'a: str = "FO\N{GHOST} \U11223344"', + r'a: str = "FO\N{ghost} \U11223344"', r'a: str = "fo\N{ghost} \U11223344"', "a: str = None", ], ), + # Escape sequences with hex digits should not produce equivalent case-flip mutants + (r'"\u00a0"', [r'"XX\u00a0XX"']), + (r'"\U0001f600"', [r'"XX\U0001f600XX"']), + (r'"\x7f"', [r'"XX\x7fXX"']), + (r'"\N{GREEK CAPITAL LETTER DELTA}"', [r'"XX\N{GREEK CAPITAL LETTER DELTA}XX"']), + (r'"Hello\u00a0World"', [r'"XXHello\u00a0WorldXX"', r'"hello\u00a0world"', r'"HELLO\u00a0WORLD"']), + # Raw strings: escapes are literal, so case changes are NOT equivalent + (r'r"\u00a0"', [r'r"XX\u00a0XX"', r'r"\u00A0"']), ("lambda: 0", ["lambda: 1", "lambda: None"]), ("1 in (1, 2)", ["2 in (1, 2)", "1 not in (1, 2)", "1 in (2, 2)", "1 in (1, 3)"]), ("1+1", ["2+1", "1 - 1", "1+2"]), @@ -299,6 +307,11 @@ ("1.0j", "2j"), ("0o1", "2"), ("1.0e10", "10000000001.0"), + # too large for a float, so they evaluate to inf, which has no literal form + ("1e999", []), + ("1e999j", []), + ("a = 1e999", "a = None"), + ("1.5e400 + 2", ["1.5e400 - 2", "1.5e400 + 3"]), ("a = {x for x in y}", "a = None"), ("x+=1", ["x = 1", "x -= 1", "x+=2"]), ("x-=1", ["x = 1", "x += 1", "x-=2"]), @@ -603,6 +616,37 @@ assert "return val + 1" in mutated_code +def test_pragma_no_mutate_inside_multiline_call(): + """A pragma on one line of a multi-line call suppresses that line only; + the other arguments are still mutated.""" + source = """ +def foo(a, b): + return max( + a + 1, # pragma: no mutate + b - 1, + ) +""".strip() + mutated_code = mutated_module(source) + assert "a - 1" not in mutated_code + assert "a + 2" not in mutated_code + assert "b + 1" in mutated_code + assert "b - 2" in mutated_code + + +def test_pragma_no_mutate_inside_multiline_dict(): + source = """ +def foo(a): + return { + "x": a * 2, # pragma: no mutate + "y": a * 3, + } +""".strip() + mutated_code = mutated_module(source) + assert "a / 2" not in mutated_code + assert "a * 3, # pragma" not in mutated_code + assert "a / 3" in mutated_code + + def test_pragma_no_mutate_on_match_case_guard(): """A pragma on a `case ... if guard:` line suppresses mutation of the guard expression, e.g. `n > 0` becoming `n >= 0`.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/tests/mutation/test_mutation_runtime.py new/mutmut-3.8.1/tests/mutation/test_mutation_runtime.py --- old/mutmut-3.8.0/tests/mutation/test_mutation_runtime.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/tests/mutation/test_mutation_runtime.py 2026-10-28 08:46:47.000000000 +0100 @@ -38,6 +38,39 @@ monkeypatch.setenv("MUTANT_UNDER_TEST", mutant_name) +def test_enum_creation_hooks_runtime_execution(monkeypatch): + """Test that an enum whose __init__ and _generate_next_value_ run during class creation can be executed.""" + source = """ +from enum import Enum, auto + +class Color(Enum): + def _generate_next_value_(name, start, count, last_values): + return count + 10 + + RED = auto() + GREEN = auto() + + def __init__(self, value): + self.label = f"c{value}" + + def describe(self): + return self.name.lower() +""".strip() + + mutated_file = mutate_file_contents("test.py", source) + mutated_code, mutant_names = mutated_file.code, mutated_file.mutant_names + assert len(mutant_names) > 0, "Should have at least one mutant" + + monkeypatch.setenv("MUTANT_UNDER_TEST", "none") + namespace = {"__name__": "test_module"} + exec(mutated_code, namespace) + Color = namespace["Color"] + + assert Color.GREEN.value == 11 + assert Color.GREEN.label == "c11" + assert Color.GREEN.describe() == "green" + + def test_type_annotation_runtime_execution(monkeypatch): """Test that forward and quoted annotations can be executed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/tests/mutation/test_pragma_handling.py new/mutmut-3.8.1/tests/mutation/test_pragma_handling.py --- old/mutmut-3.8.0/tests/mutation/test_pragma_handling.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/tests/mutation/test_pragma_handling.py 2026-10-28 08:46:47.000000000 +0100 @@ -113,6 +113,56 @@ assert ignored_code.ignore_node_lines == set() +class TestMultiLineStatementPragma: + """A pragma at the end of a line inside brackets suppresses that line.""" + + def test_call_argument(self): + source = """ +def foo(a, b): + return max( + a + 1, # pragma: no mutate + b - 1, + ) +""" + ignored_code = _parse_ignored_code("test.py", source) + assert ignored_code.no_mutate_lines == {4} + assert ignored_code.ignore_node_lines == set() + + def test_dict_entry(self): + source = """ +def foo(a): + return { + "x": a * 2, # pragma: no mutate + "y": a * 3, + } +""" + ignored_code = _parse_ignored_code("test.py", source) + assert ignored_code.no_mutate_lines == {4} + + def test_parenthesized_condition(self): + source = """ +def foo(a, b): + if ( + a > 1 # pragma: no mutate + and b > 1 + ): + return 1 +""" + ignored_code = _parse_ignored_code("test.py", source) + assert ignored_code.no_mutate_lines == {4} + + def test_other_comment_inside_brackets_ignored(self): + source = """ +def foo(a, b): + return max( + a + 1, # just a comment + b - 1, + ) +""" + ignored_code = _parse_ignored_code("test.py", source) + assert ignored_code.no_mutate_lines == set() + + class TestMatchCasePragma: """Tests for pragmas on `match` and `case` headers.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/tests/test_type_checking.py new/mutmut-3.8.1/tests/test_type_checking.py --- old/mutmut-3.8.0/tests/test_type_checking.py 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/tests/test_type_checking.py 2026-10-28 08:46:47.000000000 +0100 @@ -1,3 +1,5 @@ +import json +import subprocess from pathlib import Path import pytest @@ -7,6 +9,54 @@ from mutmut.type_checking import TypeCheckingError from mutmut.type_checking import parse_mypy_report from mutmut.type_checking import parse_pyrefly_report +from mutmut.type_checking import run_type_checker + +MYPY_COMMAND = ["mypy", "src", "--output", "json"] + + +def _fake_completed_process(monkeypatch, stdout: str, returncode: int = 0, stderr: str = "") -> None: + def fake_run(command, **kwargs): + return subprocess.CompletedProcess(command, returncode, stdout=stdout, stderr=stderr) + + monkeypatch.setattr("mutmut.type_checking.subprocess.run", fake_run) + + +def test_run_type_checker_mypy_without_errors(monkeypatch): + # mypy prints a lone newline (and exits with 0) when it found no type errors at all + _fake_completed_process(monkeypatch, stdout="\n", returncode=0) + + assert run_type_checker(MYPY_COMMAND) == [] + + +def test_run_type_checker_mypy_with_errors(monkeypatch): + error = { + "file": "src/mod.py", + "line": 2, + "column": 11, + "message": 'Incompatible return value type (got "int", expected "str")', + "hint": None, + "code": "return-value", + "severity": "error", + } + # mypy prints one JSON object per line and exits with 1 when it found type errors + _fake_completed_process(monkeypatch, stdout=f"{json.dumps(error)}\n{json.dumps(error)}\n", returncode=1) + + errors = run_type_checker(MYPY_COMMAND) + + assert [(e.file_path.name, e.line_number) for e in errors] == [("mod.py", 2), ("mod.py", 2)] + + +def test_run_type_checker_mypy_still_raises_on_non_json_output(monkeypatch): + # a fatal mypy failure (exit code 2) must stay loud and not be mistaken for "no errors" + _fake_completed_process( + monkeypatch, + stdout="Found 1 error in 1 file (errors prevented further checking)\n", + returncode=2, + stderr="mypy: error: Cannot read file 'src': No such file or directory", + ) + + with pytest.raises(Exception, match="type check command did not return JSON"): + run_type_checker(MYPY_COMMAND) def test_mypy_parsing(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mutmut-3.8.0/uv.lock new/mutmut-3.8.1/uv.lock --- old/mutmut-3.8.0/uv.lock 2026-09-12 18:53:22.000000000 +0200 +++ new/mutmut-3.8.1/uv.lock 2026-10-28 08:46:47.000000000 +0100 @@ -486,7 +486,7 @@ [[package]] name = "mutmut" -version = "3.8.0" +version = "3.8.1" source = { editable = "." } dependencies = [ { name = "click" },
