Mark editable-installed plugins for assertion rewriting - #15040
GangEunzzang wants to merge 4 commits into
Conversation
A PEP 660 editable install does not record the package's Python files in the distribution metadata, so _mark_plugins_for_rewrite() found no module to mark and assertions inside the plugin were no longer rewritten. Fall back to the top-level package of the pytest11 entry points when a distribution is installed in editable mode and its recorded files yield nothing. Editable mode is read from direct_url.json. Fixes pytest-dev#11783
Use typing.cast for the recording hook passed to _mark_plugins_for_rewrite(), matching the pattern used elsewhere in the test suite.
| names = { | ||
| top_level | ||
| for ep in entry_points | ||
| if (top_level := ep.value.partition(":")[0].strip().split(".")[0]) |
There was a problem hiding this comment.
potential edge-case - this might mark all of a set of namespace packages intead of just the one with the plugin
i'like to see this validated, but i recon its pretty tricky to create the conditions
There was a problem hiding this comment.
@RonnyPfannschmidt Thanks for the review — you were right.
I set the conditions up with two distributions sharing a PEP 420 namespace: nsplug installed editable with a myns.plug.plugin entry point, and an unrelated nsother installed normally. myns did get marked, so an assertion inside myns/other/helper.py came out rewritten:
# main
E AssertionError
# this branch, before the fix
E AssertionError: assert {'a': 1} == {'a': 2}
The fallback now walks the entry point module and takes the outermost package that isn't a namespace package, so it stops at myns.plug and leaves myns.other alone. It resolves through PathFinder rather than importlib.util.find_spec, since that one imports the parent package, which would defeat the point of marking it.
Added a unit test covering both shapes.
Marking the top-level name of an entry point rewrites every distribution sharing that name when it is a namespace package. Walk the entry point module instead and take the outermost package that is not a namespace package. PathFinder is used rather than importlib.util.find_spec because the latter imports the parent package, which has to stay unimported until it is marked.
PathFinder.find_spec returns None for names it cannot resolve rather than raising, and Distribution.read_text already suppresses the errors raised by a missing metadata file, so neither guard could be reached.
Problem
_mark_plugins_for_rewrite()decides which modules to mark for assertion rewriting by walking the Python files recorded in each plugin distribution's metadata. A PEP 660 editable install doesn't record them —dist.filesonly holds the__editable__*.pthshim and thedist-infoentries — so nothing matches and the plugin never gets marked.The effect is that assertions inside a plugin stop being rewritten as soon as it is installed with
pip install -e ., which is the usual way to work on one. With a plugin that asserts in its own helpers:Fix
When a distribution is installed in editable mode and its recorded files yield no rewritable module, fall back to the top-level package of its
pytest11entry points. Editable mode is read fromdirect_url.json(PEP 610), so regular installs keep taking the existing file-based path untouched.The
direct_url.jsongate matters. Without it the fallback also fires for distributions that simply record no files, which a number of tests in the suite construct, and 13 of them change behaviour.Tests
The new test covers three distributions: an editable one, a regular install from a local directory, and one installed from an index with no
direct_url.json. Only the editable case fails onmain.Fixes #11783