Skip to content

fix: iter_markers/get_closest_marker returns correct closest MRO marker (#14329)#14630

Open
RonnyPfannschmidt wants to merge 3 commits into
pytest-dev:mainfrom
RonnyPfannschmidt:fix/iter-markers-mro-closest-first
Open

fix: iter_markers/get_closest_marker returns correct closest MRO marker (#14329)#14630
RonnyPfannschmidt wants to merge 3 commits into
pytest-dev:mainfrom
RonnyPfannschmidt:fix/iter-markers-mro-closest-first

Conversation

@RonnyPfannschmidt

@RonnyPfannschmidt RonnyPfannschmidt commented Jun 20, 2026

Copy link
Copy Markdown
Member

Alternative approach to #14332 for fixing #14329 (get_closest_marker returning the wrong marker with class inheritance).

  • Introduces Node._iter_own_markers_closest_first(), overridden by Class to walk MRO in natural closest-first order while preserving decorator stacking order within each class
  • Does not change own_markers order — it stays in base-first (construction) order for backward compatibility
  • Reverses usefixtures marker iteration to maintain farthest-first fixture setup ordering

Key difference from #14332

PR #14332 reverses the MRO traversal in get_unpacked_marks, which changes own_markers order on Class nodes. This approach keeps own_markers unchanged and instead fixes the iteration layer (iter_markers / get_closest_marker), which is the actual consumer that needs closest-first semantics.

This avoids:

  • Changing own_markers order (observable by plugins)
  • Breaking parametrize naming order (which naive reversal causes)
  • Changing keywords dict values

Test plan

  • New test test_mark_closest_mro verifying correct closest-first MRO marker resolution
  • Existing test_mark_mro / get_unpacked_marks test passes unchanged (own_markers order preserved)
  • Existing parametrize ordering tests pass (decorator stacking order preserved within each class)
  • 647 tests passing in testing/test_mark.py, testing/test_nodes.py, testing/python/
  • Full CI

Made with Cursor

Comment thread src/_pytest/python.py
"""The public constructor."""
return super().from_parent(name=name, parent=parent, **kw)

def _iter_own_markers_closest_first(self) -> Iterator[Mark]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _iter_own_markers_closest_first(self) -> Iterator[Mark]:
@override
def _iter_own_markers_closest_first(self) -> Iterator[Mark]:

Comment thread src/_pytest/python.py Outdated
mro_mark_ids: set[int] = set()
for cls in self.obj.__mro__:
cls_marks = cls.__dict__.get("pytestmark", [])
if not isinstance(cls_marks, list):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 thread src/_pytest/python.py
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems we should add a test specifically for this behavior

RonnyPfannschmidt and others added 2 commits July 14, 2026 10:30
…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>
@RonnyPfannschmidt RonnyPfannschmidt force-pushed the fix/iter-markers-mro-closest-first branch from a1f29c3 to 3c7d2d5 Compare July 14, 2026 08:40
@RonnyPfannschmidt RonnyPfannschmidt marked this pull request as ready for review July 14, 2026 10:25
Copilot AI review requested due to automatic review settings July 14, 2026 10:25
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Jul 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.Class to traverse class MRO in closest-first order while preserving per-class decorator stacking.
  • Adjust usefixtures processing 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 thread src/_pytest/fixtures.py
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 thread src/_pytest/python.py
Comment on lines +83 to +89
if sys.version_info >= (3, 12):
from typing import override
else:

def override(func):
return func

Comment thread src/_pytest/python.py
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>
@RonnyPfannschmidt RonnyPfannschmidt force-pushed the fix/iter-markers-mro-closest-first branch from c18c2f5 to 9808ef4 Compare July 14, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants