diff --git a/src/mutmut/mutation/file_mutation.py b/src/mutmut/mutation/file_mutation.py index 55f642b5..ec9b8fdc 100644 --- a/src/mutmut/mutation/file_mutation.py +++ b/src/mutmut/mutation/file_mutation.py @@ -361,7 +361,7 @@ def _external_method_injection( external_nodes: list[MODULE_STATEMENT] = [] mutant_names: list[str] = [] method_name = method.name.value - prefix = f"_{class_name}_{method_name}" + prefix = f"_mutmut_{class_name}_{method_name}" mangled_name = mangle_function_name(name=method_name, class_name=class_name) + "__mutmut" orig_func = method.with_changes(name=cst.Name(f"{prefix}_orig"), decorators=[]) diff --git a/src/mutmut/mutation/trampoline_templates.py b/src/mutmut/mutation/trampoline_templates.py index f2b9c124..f8cb4b9c 100644 --- a/src/mutmut/mutation/trampoline_templates.py +++ b/src/mutmut/mutation/trampoline_templates.py @@ -67,7 +67,7 @@ def build_enum_trampoline( :param method_type: 'instance', 'static', or 'classmethod' :return: (trampoline code, mutants dict and orign name fix code) """ - prefix = f"_{class_name}_{method_name}" + prefix = f"_mutmut_{class_name}_{method_name}" mangled_name = mangle_function_name(name=method_name, class_name=class_name) # Build mutants dict diff --git a/tests/mutation/test_mutation.py b/tests/mutation/test_mutation.py index af335ab9..9374e0f0 100644 --- a/tests/mutation/test_mutation.py +++ b/tests/mutation/test_mutation.py @@ -661,15 +661,15 @@ def describe(self): mutated_code = mutated_module(source) # Should NOT have mutant attributes injected INTO the class body (breaks enums) # The mutant dict should be OUTSIDE the class (before the class definition) - assert "_Color_describe_mutants" in mutated_code + assert "_mutmut_Color_describe_mutants" in mutated_code # External trampoline function should exist - assert "_Color_describe_trampoline" in mutated_code + assert "_mutmut_Color_describe_trampoline" in mutated_code # The method inside the class should be a simple assignment - assert "describe = _Color_describe_trampoline" in mutated_code + assert "describe = _mutmut_Color_describe_trampoline" in mutated_code # Ensure no ClassVar inside the class (which would break enum) # Split to get just the class body class_start = mutated_code.find("class Color(Enum):") - assert class_start > mutated_code.find("_Color_describe_mutants") # mutants dict is BEFORE class + assert class_start > mutated_code.find("_mutmut_Color_describe_mutants") # mutants dict is BEFORE class def test_enum_mutation_with_staticmethod(): @@ -686,9 +686,9 @@ def helper(): """.strip() mutated_code = mutated_module(source) # Should have external trampoline - assert "_Color_helper_trampoline" in mutated_code + assert "_mutmut_Color_helper_trampoline" in mutated_code # Assignment should use staticmethod wrapper - assert "helper = staticmethod(_Color_helper_trampoline)" in mutated_code + assert "helper = staticmethod(_mutmut_Color_helper_trampoline)" in mutated_code def test_enum_mutation_with_classmethod(): @@ -705,9 +705,9 @@ def from_string(cls): """.strip() mutated_code = mutated_module(source) # Should have external trampoline - assert "_Color_from_string_trampoline" in mutated_code + assert "_mutmut_Color_from_string_trampoline" in mutated_code # Assignment should use classmethod wrapper - assert "from_string = classmethod(_Color_from_string_trampoline)" in mutated_code + assert "from_string = classmethod(_mutmut_Color_from_string_trampoline)" in mutated_code def test_enum_mutation_preserves_enum_members(): @@ -729,7 +729,7 @@ def is_active(self): assert "ACTIVE = 'active'" in mutated_code assert "DONE = 'done'" in mutated_code # But method should be mutated externally - assert "_Status_is_active_trampoline" in mutated_code + assert "_mutmut_Status_is_active_trampoline" in mutated_code def test_regular_class_staticmethod_mutation(): @@ -742,10 +742,10 @@ def add(a, b): """.strip() mutated_code = mutated_module(source) # Should use external injection pattern - assert "_Calculator_add_trampoline" in mutated_code - assert "_Calculator_add_orig" in mutated_code + assert "_mutmut_Calculator_add_trampoline" in mutated_code + assert "_mutmut_Calculator_add_orig" in mutated_code # Assignment should use staticmethod wrapper - assert "add = staticmethod(_Calculator_add_trampoline)" in mutated_code + assert "add = staticmethod(_mutmut_Calculator_add_trampoline)" in mutated_code def test_regular_class_classmethod_mutation(): @@ -758,10 +758,10 @@ def create(cls, value): """.strip() mutated_code = mutated_module(source) # Should use external injection pattern - assert "_Factory_create_trampoline" in mutated_code - assert "_Factory_create_orig" in mutated_code + assert "_mutmut_Factory_create_trampoline" in mutated_code + assert "_mutmut_Factory_create_orig" in mutated_code # Assignment should use classmethod wrapper - assert "create = classmethod(_Factory_create_trampoline)" in mutated_code + assert "create = classmethod(_mutmut_Factory_create_trampoline)" in mutated_code def test_regular_class_mixed_methods(): @@ -783,10 +783,26 @@ def class_method(cls): # Instance method uses internal trampoline (inside class) assert "xǁMyClassǁinstance_method__mutmut_orig" in mutated_code # Static and class methods use external injection - assert "_MyClass_static_method_trampoline" in mutated_code - assert "_MyClass_class_method_trampoline" in mutated_code - assert "static_method = staticmethod(_MyClass_static_method_trampoline)" in mutated_code - assert "class_method = classmethod(_MyClass_class_method_trampoline)" in mutated_code + assert "_mutmut_MyClass_static_method_trampoline" in mutated_code + assert "_mutmut_MyClass_class_method_trampoline" in mutated_code + assert "static_method = staticmethod(_mutmut_MyClass_static_method_trampoline)" in mutated_code + assert "class_method = classmethod(_mutmut_MyClass_class_method_trampoline)" in mutated_code + + +def test_leading_underscore_class_no_name_mangling(): + """Test that classes with leading underscore don't trigger Python name mangling.""" + source = """ +class _InternalValidator: + @classmethod + def validate(cls, value): + return value + 1 +""".strip() + mutated_code = mutated_module(source) + # Prefix should be _mutmut__InternalValidator_validate (no name mangling) + assert "_mutmut__InternalValidator_validate_trampoline" in mutated_code + assert "validate = classmethod(_mutmut__InternalValidator_validate_trampoline)" in mutated_code + # The generated code must be importable (no NameError from mangling) + compile(mutated_code, "", "exec") def test_mutate_only_covered_lines_none():