Mutating a typing.Protocol stub adds generated members to the Protocol and breaks type_check_command
I maintainer di solito rispondono entro 2 giorni
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 68/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- python
- Ambito
- testing-qa
Direzione di ricerca
Reproduce the failure with mini/proto.py, mini/impl.py, tests/test_mini.py, and setup.cfg, then inspect Protocol mutation and filter_mutants_with_type_checker handling. Compare the generated members and type-checker errors with the unmutated control. Done means mutmut run completes the reproduction without an unowned type error and preserves the expected mutant filtering behavior.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Version: mutmut 3.8.0, mypy 2.4.0, Python 3.12, Linux.
What happens
With type_check_command set to mypy, mutmut run stops with:
Exception: Could not find mutant for type error .../mutants/pkg/store.py:85752 (Argument 1 to "project_current_state" has incompatible type "PgVectorStore"; expected "StateStore").
The unmutated project type checks clean.
Cause, as far as I can tell
The Protocol has a method stub with a default argument:
class StateStore(Protocol):
def iter_chunks(self, batch_size: int = 1000) -> Iterable[Chunk]: ...
mutmut mutates that default (1000 to 1001) and rewrites the class with the trampoline decorator plus xǁStateStoreǁiter_chunks__mutmut_orig and xǁStateStoreǁiter_chunks__mutmut_1. Those two methods are now members of the Protocol. Any concrete class that satisfied it structurally no longer does, and mypy says so in its hint:
"PgVectorStore" is missing following "StateStore" protocol members:
xǁStateStoreǁiter_chunks__mutmut_1, xǁStateStoreǁiter_chunks__mutmut_orig
Every call that passes the concrete class where the Protocol is expected then becomes a type error. One of them sat in the original method body behind the trampoline decorator, which no mutant owns, so filter_mutants_with_type_checker raised. The other 139 sat inside mutant functions, so without the crash those mutants would have been discarded as caught by the type checker, although none of them caused the error.
Minimal reproduction
mini/__init__.py is empty.
mini/proto.py:
from typing import Protocol
class Source(Protocol):
def read(self, size: int = 10) -> str: ...
def consume(source: Source) -> str:
return source.read(10)
mini/impl.py:
from mini.proto import consume
class File:
def read(self, size: int = 10) -> str:
return "x" * size
def text(self) -> str:
return consume(self)
tests/test_mini.py:
from mini.impl import File
def test_text() -> None:
assert File().text() == "x" * 10
setup.cfg:
[mutmut]
source_paths=
mini/
also_copy=
tests/
type_check_command=
mypy
--output
json
mini/proto.py
mini/impl.py
mypy mini/proto.py mini/impl.py reports no issues. mutmut run then fails:
Exception: Could not find mutant for type error .../mutants/mini/impl.py:26 (Argument 1 to "consume" has incompatible type "File"; expected "Source").
Line 26 of the mutated impl.py is the original body of File.text, under @_mutmut_mutated(mutants_xǁFileǁtext__mutmut). The mutated Protocol reads:
class Source(Protocol):
@_mutmut_mutated(mutants_xǁSourceǁread__mutmut)
def read(self, size: int = 10) -> str: ...
def xǁSourceǁread__mutmut_orig(self, size: int = 10) -> str: ...
def xǁSourceǁread__mutmut_1(self, size: int = 11) -> str: ...
Control: the same project with the stub's default removed (def read(self, size: int) -> str: ...) leaves Source unmutated, and mutmut run completes all 7 mutants (3 killed, 1 survived, 3 caught by the type checker).
Related
This is the same exception as #541, with a different trigger. #522 covered errors in files without mutants and is fixed; here the file does have mutants. It is also a concrete case of the concern raised in #541, that type errors may not stay inside the mutated function: the rewritten Protocol puts an error at every call site that passes a conforming class. In my project, 139 of the 140 resulting errors sat inside unrelated mutant functions, so those mutants would have been discarded as caught by the type checker, and a plain continue on the one unowned error would not have prevented that.
Suggestions
- Do not mutate methods of a class that inherits from
typing.Protocol. A stub's default is never executed, so such a mutant cannot be killed, and the rewrite changes the Protocol's set of members. This removes the cause rather than the symptom. - For the strict or flexible option discussed in #541: an error whose message or hint names a generated identifier (
xǁ...__mutmut_...) is caused by the rewrite rather than by any mutant, so it could be ignored for attribution in either mode.
I worked around it with a wrapper around mypy that drops errors naming generated identifiers and errors outside every mutant, and I am happy to share it.
- Lingua principale
- Python
- Stelle
- 1.5k
- Fork
- 181
- Merge medio
- 2g 7h
- PR unite (30g)
- 1
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di boxed/mutmut
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 2 giorni
-
`run` with a `name__mutmut_*` pattern runs the whole test suite in the clean-test checkForse già presa @Ishaan400 l’ha presa 8 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
boxed/mutmut#592 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
Invalid unary plus generated in match statementForse già presa @GhostCoder6969 l’ha presa 17 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 2 giorni
-
What do the emoji's mean?Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
boxed/mutmut#560 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
boxed/mutmut#529 · 7 commenti ·
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di boxed/mutmut
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
TomerAberbach/profiler-md#733 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
its-me-prash/vwgroup-connect-ha#1858 ·
I maintainer di solito rispondono entro 1 giorno
-
area: client bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
Deleting all alarms or timers shows deleted ones until the next update when there are none leftApertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
leikoilja/ha-google-home#1107 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
Graphify-Labs/graphify#4524 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno