fix: support non-editable installs (site-packages import precedence)#487
fix: support non-editable installs (site-packages import precedence)#487pythoniste wants to merge 2 commits intoboxed:mainfrom
Conversation
When the package-under-test is installed in site-packages as a non-editable wheel rather than via `pip install -e .`, mutmut's trampoline-injected code in the mutants/ directory was never loaded. Python imported the original (non-mutated) modules from site-packages because they were already cached in sys.modules. The stats phase found zero test coverage and all mutants were marked "not checked". Add _patch_imported_packages() to setup_source_paths(): after adding mutants/ to sys.path, patch __path__ on every already-imported package so submodule lookups prefer the mutants tree. Flush leaf modules whose mutated .py exists on disk so they get re-imported with trampoline code. Add _copy_parent_init_files() to copy_src_dir(): when paths_to_mutate targets individual files, parent __init__.py files were missing from the mutants tree, preventing Python from recognising it as a package. Tested on a Pydantic-heavy project (8,996 mutants): stats phase now finds 477 covered functions and achieves a 70% kill rate, where previously it found zero coverage.
|
By the way, the branch name is misleading. At first, I wanted to fix Pydantic and then, I fixed something else. |
Otto-AA
left a comment
There was a problem hiding this comment.
Hi, thanks for the PR.
I don't have a lot of time for reviewing currently, so I probably won't get to test and read through this PR in the next weeks.
I don't fully understand the editable vs non-editable problem yet. Why does the editable install work fine, but the non-editable version gets imported as non-mutated version and needs to get flushed out of the modules? We do sys.path.insert(0, abs_path) to preferably import mutated code, is the non-editable version already installed at this point?
And would installing the project as editable be possible for you? I would tend to detect if the project is installed as non-editable and raise an Exception asking the user to install as editable instead.
| Without them Python's import system cannot recognise ``pkg`` as a package | ||
| and falls back to the copy installed in site-packages, skipping the | ||
| trampoline-injected mutant code entirely. | ||
| """ |
There was a problem hiding this comment.
paths_to_mutate should not cover individual files (#414 )
| """Remove cached test modules from sys.modules so they get re-imported. | ||
|
|
||
| Called in the forked child process when testing a mutant that targets | ||
| import-time code (e.g. __init_subclass__). Without this, the child |
There was a problem hiding this comment.
I'm a bit confused, mutmut does not support import-time code mutations. Is this a feature PR to add this?
e.g. in following file:
MY_CONST = 1234
def foo():
return 1234Only foo will be mutated, MY_CONST won't be mutated.
When the package-under-test is installed in site-packages as a
non-editable wheel rather than via
pip install -e ., mutmut'strampoline-injected code in the mutants/ directory was never loaded.
Python imported the original (non-mutated) modules from site-packages
because they were already cached in sys.modules. The stats phase found
zero test coverage and all mutants were marked "not checked".
Add _patch_imported_packages() to setup_source_paths(): after adding
mutants/ to sys.path, patch path on every already-imported package
so submodule lookups prefer the mutants tree. Flush leaf modules whose
mutated .py exists on disk so they get re-imported with trampoline code.
Add _copy_parent_init_files() to copy_src_dir(): when paths_to_mutate
targets individual files, parent init.py files were missing from
the mutants tree, preventing Python from recognising it as a package.
Tested on a Pydantic-heavy project (8,996 mutants): stats phase now
finds 477 covered functions and achieves a 70% kill rate, where
previously it found zero coverage.