Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

String mutations: the two case flips are equivalent wherever the consumer is case-insensitive, and no option turns them off

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

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

@nabi-noor がすでに取り組んでいます。

2026年9月10日 から。

  • #579 @nabi-noor による — オープン

評価

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

調査の方向性

operator_string エントリポイントと、既存の --disable-mutation-types および do_not_mutate_patterns の設定動作から始めます。example.py、test_example.py、pyproject.toml を使って、残っている小文字化および大文字化ミュータントを再現し、その後、大文字と小文字の反転を無効にしても XX ミュータントやインデックス付けミュータントを削除しないことを示すリグレッションテストのカバレッジを追加します。

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

説明

enhancement good first issue

Disclaimer: AI-drafted, verified by me before filing. I answer follow-ups.

operator_string emits three mutants for every string literal: the literal wrapped in XX, lowercased, and uppercased. Where the literal's consumer is case-insensitive -- SQL, codec names, HTTP header names -- the case flips are equivalent mutants by construction.

Expected: a mutation I cannot kill can be switched off, as --disable-mutation-types did in 2.x (#175, added by #225).
Actual: both case flips survive every run, and the only lever in 3.x, do_not_mutate_patterns, matches whole lines, so it drops the XX mutant on that line too.

They were 25% and 11% of all survivors on two of my codebases: re-read on every run, and nothing to be done about them.

Reproduce -- three files, mutmut 3.7.0, pytest 9.1.1, CPython 3.12.12
# example.py
import sqlite3


def note_count(db_path: str) -> int:
    con = sqlite3.connect(db_path)
    return con.execute("SELECT COUNT(*) FROM notes").fetchone()[0]
# test_example.py
import sqlite3

from example import note_count


def test_note_count(tmp_path):
    db = tmp_path / "n.db"
    con = sqlite3.connect(db)
    con.execute("CREATE TABLE notes (id int)")
    con.execute("INSERT INTO notes VALUES (1)")
    con.commit()
    con.close()
    assert note_count(str(db)) == 1
# pyproject.toml
[project]
name = "repro"
version = "0"
requires-python = ">=3.11"

[tool.mutmut]
source_paths = ["example.py"]
$ mutmut run
7/7  🎉 5 🫥 0  ⏰ 0  🤔 0  🙁 2  🔇 0  🧙 0

$ mutmut results
    example.x_note_count__mutmut_5: survived
    example.x_note_count__mutmut_6: survived

$ mutmut show example.x_note_count__mutmut_5
# example.x_note_count__mutmut_5: survived
--- example.py
+++ example.py
@@ -1,3 +1,3 @@
 def note_count(db_path: str) -> int:
     con = sqlite3.connect(db_path)
-    return con.execute("SELECT COUNT(*) FROM notes").fetchone()[0]
+    return con.execute("select count(*) from notes").fetchone()[0]

$ mutmut show example.x_note_count__mutmut_6      # the same line, uppercased
+    return con.execute("SELECT COUNT(*) FROM NOTES").fetchone()[0]

SQLite parses either casing and resolves table names case-insensitively, so both mutants are equivalent to the original. The only test that would kill them asserts the query string's exact casing, which tests the literal rather than the behaviour.

What the line pattern costs -- 7 mutants become 2
[tool.mutmut]
source_paths = ["example.py"]
do_not_mutate_patterns = ["SELECT"]
$ mutmut run
2/2  🎉 2 🫥 0  ⏰ 0  🤔 0  🙁 0  🔇 0  🧙 0

Five of the seven mutants are gone. Two of them, mutant 4 ("XXSELECT COUNT(*) FROM notesXX") and mutant 7 (.fetchone()[1]), are killed by the test as it stands, so the pattern removes working signal in order to remove two mutants that carry none.

主要言語
Python
スター
1.5k
フォーク
182
平均マージ
2日 14時間
マージ済み PR(30日)
3

環境構築

はじめの一歩

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

boxed/mutmut のほかの issue

boxed/mutmut の issue をすべて見る

似ている issue

Python の issue をもっと見る

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

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