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
2 changes: 1 addition & 1 deletion src/mutmut/mutation/file_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[])
Expand Down
2 changes: 1 addition & 1 deletion src/mutmut/mutation/trampoline_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 35 additions & 19 deletions tests/mutation/test_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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():
Expand All @@ -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():
Expand All @@ -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():
Expand All @@ -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():
Expand All @@ -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():
Expand All @@ -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, "<test>", "exec")


def test_mutate_only_covered_lines_none():
Expand Down
Loading