fix: iter_markers/get_closest_marker returns correct closest MRO marker (#14329)#14630
Open
RonnyPfannschmidt wants to merge 3 commits into
Open
fix: iter_markers/get_closest_marker returns correct closest MRO marker (#14329)#14630RonnyPfannschmidt wants to merge 3 commits into
RonnyPfannschmidt wants to merge 3 commits into
Conversation
nicoddemus
reviewed
Jun 20, 2026
| """The public constructor.""" | ||
| return super().from_parent(name=name, parent=parent, **kw) | ||
|
|
||
| def _iter_own_markers_closest_first(self) -> Iterator[Mark]: |
Member
There was a problem hiding this comment.
Suggested change
| def _iter_own_markers_closest_first(self) -> Iterator[Mark]: | |
| @override | |
| def _iter_own_markers_closest_first(self) -> Iterator[Mark]: |
| mro_mark_ids: set[int] = set() | ||
| for cls in self.obj.__mro__: | ||
| cls_marks = cls.__dict__.get("pytestmark", []) | ||
| if not isinstance(cls_marks, list): |
Member
There was a problem hiding this comment.
Do we require a list, or any sequence would do?
Suggested change
| if not isinstance(cls_marks, list): | |
| if not isinstance(cls_marks, Sequence): |
Comment on lines
+771
to
+774
| # Yield any dynamically added markers (via add_marker) not from MRO. | ||
| for mark in self.own_markers: | ||
| if id(mark) not in mro_mark_ids: | ||
| yield mark |
Member
There was a problem hiding this comment.
Seems we should add a test specifically for this behavior
…O marker Fix get_closest_marker and iter_markers to return markers in correct closest-first order when class inheritance (MRO) is involved (pytest-dev#14329). Previously, own_markers on Class nodes stored MRO-inherited markers in base-first (farthest) order, and iter_markers yielded them in that same order. This caused get_closest_marker to return a base class marker instead of the overriding child class marker. The fix introduces _iter_own_markers_closest_first() on Node, overridden by Class to walk the MRO in natural closest-first order while preserving decorator stacking order within each class. This avoids changing own_markers (keeping its base-first construction order) and avoids breaking parametrize naming order. Also reverses usefixtures marker iteration to maintain farthest-first setup ordering (module -> base class -> child class -> function). Alternative structural approach to PR pytest-dev#14332 that preserves own_markers order for backward compatibility. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4.6 <claude@anthropic.com>
- Add @OverRide decorator to Class._iter_own_markers_closest_first (with version-gated import for Python <3.12) - Broaden isinstance check from list to Sequence for cls_marks - Add test for dynamically added markers on Class collectors Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4.6 <claude@anthropic.com>
a1f29c3 to
3c7d2d5
Compare
There was a problem hiding this comment.
Pull request overview
Fixes marker resolution for inherited test classes so iter_markers/get_closest_marker return the closest (most-derived) class’ marks first, while attempting to preserve backward-compatible own_markers ordering and existing fixture-setup behavior.
Changes:
- Add
Node._iter_own_markers_closest_first()and switch marker iteration to use it for closest-first semantics. - Override marker iteration for
python.Classto traverse class MRO in closest-first order while preserving per-class decorator stacking. - Adjust
usefixturesprocessing to preserve farthest-first fixture requesting, and add regression tests + changelog entry.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| testing/test_mark.py | Adds regression tests for closest-first MRO marker resolution and updates a mock-based test for the new iteration hook. |
| src/_pytest/python.py | Implements Class._iter_own_markers_closest_first() and introduces an override decorator compatibility shim. |
| src/_pytest/nodes.py | Routes marker iteration through _iter_own_markers_closest_first() and updates docstrings to state closest-first behavior. |
| src/_pytest/fixtures.py | Reverses usefixtures marker iteration to keep farthest-first fixture request ordering after marker iteration changes. |
| changelog/14329.bugfix.rst | Documents the bugfix for closest marker resolution with class inheritance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1844
to
+1848
| # Reverse order (farthest to closest) is more natural for usefixtures, | ||
| # e.g. want a module-level usefixture to be requested before a class one, | ||
| # a parent class' before a child's, etc. | ||
| for marker_node, mark in reversed( | ||
| list(node.iter_markers_with_node(name="usefixtures")) |
Comment on lines
+83
to
+89
| if sys.version_info >= (3, 12): | ||
| from typing import override | ||
| else: | ||
|
|
||
| def override(func): | ||
| return func | ||
|
|
Comment on lines
+781
to
+784
| # Yield any dynamically added markers (via add_marker) not from MRO. | ||
| for mark in self.own_markers: | ||
| if id(mark) not in mro_mark_ids: | ||
| yield mark |
Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4.6 <claude@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
c18c2f5 to
9808ef4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alternative approach to #14332 for fixing #14329 (
get_closest_markerreturning the wrong marker with class inheritance).Node._iter_own_markers_closest_first(), overridden byClassto walk MRO in natural closest-first order while preserving decorator stacking order within each classown_markersorder — it stays in base-first (construction) order for backward compatibilityusefixturesmarker iteration to maintain farthest-first fixture setup orderingKey difference from #14332
PR #14332 reverses the MRO traversal in
get_unpacked_marks, which changesown_markersorder on Class nodes. This approach keepsown_markersunchanged and instead fixes the iteration layer (iter_markers/get_closest_marker), which is the actual consumer that needs closest-first semantics.This avoids:
own_markersorder (observable by plugins)keywordsdict valuesTest plan
test_mark_closest_mroverifying correct closest-first MRO marker resolutiontest_mark_mro/get_unpacked_markstest passes unchanged (own_markers order preserved)testing/test_mark.py,testing/test_nodes.py,testing/python/Made with Cursor