From 4c2a2795ef9f23e5880dbf4d9ea7d63a6f797e8c Mon Sep 17 00:00:00 2001 From: Ishaan Kumar Date: Fri, 9 Oct 2026 01:28:37 +0000 Subject: [PATCH] Fix wildcard mutant patterns selecting no tests for the clean run A pattern such as pkg.mod.x_f__mutmut_* never matched the function-name keys in tests_by_mangled_function_name, so the clean-test step got an empty selection and ran the whole test suite. Match the pattern's function part instead. Fixes #592 --- src/mutmut/__main__.py | 7 ++++++- tests/mutation/test_mutation.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/mutmut/__main__.py b/src/mutmut/__main__.py index 0254aef6..45d7a956 100644 --- a/src/mutmut/__main__.py +++ b/src/mutmut/__main__.py @@ -1090,8 +1090,13 @@ def tests_for_mutant_names(mutant_names: tuple[str, ...] | list[str]) -> set[str tests = set() for mutant_name in mutant_names: if "*" in mutant_name: + # Keys are function names (``pkg.mod.x_f``), so a pattern over the mutant + # suffix (``pkg.mod.x_f__mutmut_*``) is matched by its function part. This may + # select a few extra tests, which is safe for the clean run; selecting none is not, + # because an empty selection runs the whole suite. + function_pattern = mutant_name.partition("__mutmut_")[0] for name, tests_of_this_name in state().tests_by_mangled_function_name.items(): - if fnmatch.fnmatch(name, mutant_name): + if fnmatch.fnmatch(name, function_pattern): tests |= set(tests_of_this_name) else: tests |= set(state().tests_by_mangled_function_name[mangled_name_from_mutant_name(mutant_name)]) diff --git a/tests/mutation/test_mutation.py b/tests/mutation/test_mutation.py index 0ff0d198..e5b082ce 100644 --- a/tests/mutation/test_mutation.py +++ b/tests/mutation/test_mutation.py @@ -2055,3 +2055,26 @@ def test_rejects_a_mutant_the_meta_file_does_not_know(self, tmp_path): ) assert unknown not in data.exit_code_by_key + + +@pytest.mark.parametrize( + "pattern, expected", + [ + # A pattern over the mutant suffix must select the tests of that function only. + ("pkg.mod.x_f__mutmut_*", {"test_f"}), + ("pkg.mod.x_f__mutmut_1*", {"test_f"}), + # Patterns over function names keep working. + ("pkg.mod.*", {"test_f", "test_g"}), + ("pkg.mod.x_*", {"test_f", "test_g"}), + ], +) +def test_tests_for_mutant_names_wildcard(pattern, expected): + """Regression test for #592: a ``__mutmut_*`` pattern selected no tests, so the clean run fell back to the whole suite.""" + reset_state() + state().tests_by_mangled_function_name["pkg.mod.x_f"] = {"test_f"} + state().tests_by_mangled_function_name["pkg.mod.x_g"] = {"test_g"} + state().tests_by_mangled_function_name["other.x_h"] = {"test_h"} + + # Called via the module: a bare ``tests_for_*`` import would be collected by pytest as a test. + assert mutmut.__main__.tests_for_mutant_names([pattern]) == expected + reset_state()