Generated trampoline names look like public members to tests that inspect classes (more visible since 3.8.0 mutates dataclasses)

オープン
#586 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@kellyhair がすでに取り組んでいます。

2026年9月26日 から。

  • #587 @kellyhair による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
停滞
技術スタック
python
領域
testing-qa

調査の方向性

Start by reviewing mutmut.utils.format_utils.is_mutated_method_name and the generated names from mutmut.mutation.file_mutation, then inspect the related trampoline handling and README guidance. Compare the proposed filtering approach with the alternative of prefixing generated names, including mutant IDs and classmethod lookup. Done means the chosen behavior covers generated class and module members without breaking existing mutation workflows.

索引モデルが issue の本文から書いたものです。

説明

Mutated modules and classes gain functions and dicts whose names don't start with an underscore, such as x_span__mutmut_orig, xǁDateRangeǁ__post_init____mutmut_1 and mutants_x_span__mutmut. Any test that walks vars(), dir() or inspect.getmembers() and treats names without a leading _ as public API sees them as undocumented public members.

This isn't new, but 3.8.0 started mutating @dataclass methods (#480, #539), so it now reaches classes it didn't before. In our project, a test that checks every public member of the exported classes has a docstring started failing in the stats run after upgrading to 3.8.0. The stats pass stopped with exit code 2, so no mutants were tested at all.

Reproduction (mutmut 3.8.0)

import inspect, types
from mutmut.mutation.file_mutation import mutate_file_contents

src = '''
from dataclasses import dataclass

@dataclass
class DateRange:
    """A closed date interval."""
    start: int
    end: int

    def __post_init__(self):
        if self.end < self.start:
            raise ValueError("end before start")

def span(r):
    """Length of the range."""
    return r.end - r.start + 1
'''
mod = types.ModuleType("demo")
exec(compile(mutate_file_contents("demo.py", src).code, "demo.py", "exec"), mod.__dict__)
public = lambda ns: sorted(n for n in ns if not n.startswith("_"))
print(public(vars(mod.DateRange)))
print(public(vars(mod)))

Output:

['xǁDateRangeǁ__post_init____mutmut_1', 'xǁDateRangeǁ__post_init____mutmut_2', 'xǁDateRangeǁ__post_init____mutmut_3', 'xǁDateRangeǁ__post_init____mutmut_4', 'xǁDateRangeǁ__post_init____mutmut_orig']
['DateRange', 'MutantDict', 'dataclass', 'mutants_x_span__mutmut', 'mutants_xǁDateRangeǁ__post_init____mutmut', 'span', 'x_span__mutmut_1', 'x_span__mutmut_2', 'x_span__mutmut_3', 'x_span__mutmut_orig']

The copies of __post_init__ have no docstring. The original is a dunder, so docstring checks usually skip it, but the copies' names aren't dunders.

Options

  1. Prefix generated names with _. This is the real fix, but the mangled names are also the mutant IDs in mutmut show, mutmut apply, the stats and meta files, and is_mutated_method_name. Classmethod dispatch also looks the copies up by name with getattr. It would change every mutant ID, so it's your call whether that's worth it.
  2. Give users a supported way to filter the names. Our workaround copies the logic of mutmut.utils.format_utils.is_mutated_method_name, which isn't public API and doesn't cover the mutants_... dicts. A public predicate plus a README note fixes this without changing any names.

I've opened a PR for option 2. It's small and doesn't break anything, and it doesn't rule out option 1 later.

MutantDict also shows up as a module attribute. It comes from the generated from mutmut.mutation.trampoline import ... MutantDict import. The PR doesn't filter it, because MutantDict is a name user code could also define. Importing it under an alias like _MutantDict would fix that, if you want it.

AI disclosure: Claude Code (Anthropic) did the investigation and wrote this issue and the reproduction, and posted it on my account at my request.

主要言語
Python
スター
1.5k
フォーク
190
平均マージ
1日 11時間
マージ済み PR(30日)
8

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

boxed/mutmut のほかの issue

boxed/mutmut の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。