Skip to content

BugFix #14329 - #14332

Closed
Wintreist wants to merge 2 commits into
pytest-dev:mainfrom
Wintreist:wintreist-bugfix-closest-marker
Closed

BugFix #14329#14332
Wintreist wants to merge 2 commits into
pytest-dev:mainfrom
Wintreist:wintreist-bugfix-closest-marker

Conversation

@Wintreist

Copy link
Copy Markdown

Closes #14329

Fixed the issue of getting the really closest label to the test. I abolished the priority of base classes over labels, but supported getting the usefixtures mark in reverse order (i.e. from the base class to the heir), since this is a unique case when you really need to call fixtures sequentially from the base class.

I did not add myself to the Authors, as I do not plan to be a pytest developer yet, I fixed the problem, as it interferes with my main project. Thanks

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Mar 28, 2026
Wintreist and others added 2 commits April 7, 2026 18:11
@bluetech
bluetech force-pushed the wintreist-bugfix-closest-marker branch from 4374480 to 5d467d3 Compare April 7, 2026 16:16

@bluetech bluetech left a comment

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.

Thanks, the changes LGTM. I pushed a few tweaks.

I'll let others a few days to review, then merge.

@RonnyPfannschmidt RonnyPfannschmidt left a comment

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.

Under strict pedantic this is a breaking change
Im a bit torn for the classification

@bluetech

bluetech commented Apr 7, 2026

Copy link
Copy Markdown
Member

Under strict pedantic every bug fix is a breaking change 😀

I think this case is sensible for a minor release. I don't actually expect the MRO thing to come up that much...

@bluetech

bluetech commented Apr 7, 2026

Copy link
Copy Markdown
Member

Maybe the usefixtures change could be a bit more disruptive, but it will be annoying to separate the two...

@RonnyPfannschmidt

Copy link
Copy Markdown
Member

The order of items changes slightly

That likes to break things

@bluetech

bluetech commented Apr 7, 2026

Copy link
Copy Markdown
Member

The order of items changes slightly

What items are you referring to here? The mark list items, or the Items (tests) order changing due to usefixture changes?

That likes to break things

Would you prefer to defer this PR to pytest 10?

RonnyPfannschmidt added a commit to RonnyPfannschmidt/pytest that referenced this pull request Jul 14, 2026
…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>
RonnyPfannschmidt added a commit to RonnyPfannschmidt/pytest that referenced this pull request Jul 21, 2026
…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>
RonnyPfannschmidt added a commit to RonnyPfannschmidt/pytest that referenced this pull request Aug 9, 2026
…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>
RonnyPfannschmidt added a commit to RonnyPfannschmidt/pytest that referenced this pull request Aug 9, 2026
The regression test for pytest-dev#14329 is Wintreist's, taken verbatim from
pytest-dev#14332; carry the attribution and add them to AUTHORS.

Also spell out the parametrize consequence in the changelog: class level
`parametrize` markers are consumed through `iter_markers`, so inherited
ones now compose IDs closest-first (`[1-x]` -> `[x-1]`). That invalidates
pinned ID selections and the `--last-failed` cache, which is worth more
warning than the marker reorder itself.

Co-authored-by: Wintreist <49996562+Wintreist@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RonnyPfannschmidt added a commit to RonnyPfannschmidt/pytest that referenced this pull request Aug 9, 2026
`_lookup_derived` scanned `own_markers` front-to-back and returned the
first name match, but the eager `keywords.update((mark.name, mark) ...)`
it replaces let *later* markers win. A Class stores MRO-inherited
markers base class first, so `keywords["foo"]` silently changed from the
subclass' marker to the base class' one. Scan in reverse instead.

Note this direction is tied to `own_markers` order: it has to flip if
pytest-dev#14332 lands rather than pytest-dev#14630.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@RonnyPfannschmidt

Copy link
Copy Markdown
Member

Thanks for the patience here, and sorry it took this long to get to a decision.

I'm going to land #14630 instead and close this one — but your test goes in with it verbatim, with a Co-authored-by trailer, and you're in AUTHORS. The diagnosis and the regression test were the hard part; the disagreement is only about which layer the fix belongs in.

The concrete reason: reversing get_unpacked_marks also reverses Class.own_markers, and PyobjMixin.obj does

self.keywords.update((mark.name, mark) for mark in self.own_markers)

which is last-wins. With base-first own_markers that puts the closest marker into keywords; reversing flips it to the base class one:

@pytest.mark.foo("base")
class TestBase: pass

@pytest.mark.foo("child")
class TestChild(TestBase):
    def test_it(self, request):
        request.node.parent.keywords["foo"].args[0]  # "child" on main, "base" with this PR

Nothing in the suite catches it — test_unpacked_marks_added_to_keywords uses distinct mark names per level. It is one reversed() away from being fixed, but it is the kind of fallout that makes me want the fix in the iteration layer rather than in the stored order: own_markers is public and plugins read it.

#14630 leaves own_markers alone and overrides iteration on Class instead. Same user-visible fix for #14329, same test.

For the record, one of my claims about this PR was wrong, and I've corrected it on mine: I said reversing breaks parametrize ID ordering in a way an iteration-layer fix avoids. It doesn't. _genfunctions consumes definition.iter_markers("parametrize"), so both approaches reorder inherited class level parametrize IDs identically (TestChild::test_it[1-x] becomes TestChild::test_it[x-1]). That is now called out in the changelog on #14630, since it invalidates pinned ID selections and the --last-failed cache.

@bluetech you approved this one — say the word if you'd rather go this way instead, reopening is cheap.

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.

get_closest_marker is working unexpectedly

3 participants