Skip to content

Commit 3c7d2d5

Browse files
RonnyPfannschmidtCursor AIclaude
committed
fix: address review comments on MRO marker iteration
- 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>
1 parent c4c5fa9 commit 3c7d2d5

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

src/_pytest/python.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import os
2222
from pathlib import Path
2323
import re
24+
import sys
2425
import textwrap
2526
import types
2627
from typing import Any
@@ -79,6 +80,14 @@
7980
from _pytest.warning_types import PytestReturnNotNoneWarning
8081

8182

83+
if sys.version_info >= (3, 12):
84+
from typing import override
85+
else:
86+
87+
def override(func):
88+
return func
89+
90+
8291
if TYPE_CHECKING:
8392
from typing_extensions import Self
8493

@@ -751,6 +760,7 @@ def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[o
751760
"""The public constructor."""
752761
return super().from_parent(name=name, parent=parent, **kw)
753762

763+
@override
754764
def _iter_own_markers_closest_first(self) -> Iterator[Mark]:
755765
"""own_markers stores MRO markers in base-first order
756766
(construction order). For closest-first iteration, reverse at the
@@ -763,7 +773,7 @@ def _iter_own_markers_closest_first(self) -> Iterator[Mark]:
763773
mro_mark_ids: set[int] = set()
764774
for cls in self.obj.__mro__:
765775
cls_marks = cls.__dict__.get("pytestmark", [])
766-
if not isinstance(cls_marks, list):
776+
if not isinstance(cls_marks, Sequence):
767777
cls_marks = [cls_marks]
768778
for mark in normalize_mark_list(cls_marks):
769779
mro_mark_ids.add(id(mark))

testing/test_mark.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,40 @@ def test_function_and_class(self, request):
690690
result = pytester.runpytest()
691691
result.assert_outcomes(passed=4)
692692

693+
def test_mark_closest_mro_with_dynamic_class_marker(
694+
self, pytester: Pytester
695+
) -> None:
696+
"""Dynamic markers added to a Class collector via add_marker appear
697+
alongside MRO markers in iter_markers (#14329)."""
698+
pytester.makeconftest(
699+
"""
700+
import pytest
701+
702+
def pytest_collectstart(collector):
703+
if getattr(collector, "name", None) == "TestChild":
704+
collector.add_marker(pytest.mark.foo(99))
705+
"""
706+
)
707+
pytester.makepyfile(
708+
"""
709+
import pytest
710+
711+
@pytest.mark.foo(0)
712+
class TestBase:
713+
pass
714+
715+
@pytest.mark.foo(1)
716+
class TestChild(TestBase):
717+
def test_it(self, request):
718+
names = [m.args[0] for m in request.node.iter_markers("foo")]
719+
# MRO markers (child=1, base=0) plus dynamic marker (99)
720+
assert 99 in names
721+
assert names.index(1) < names.index(0)
722+
"""
723+
)
724+
result = pytester.runpytest()
725+
result.assert_outcomes(passed=1)
726+
693727
def test_mark_with_wrong_marker(self, pytester: Pytester) -> None:
694728
reprec = pytester.inline_runsource(
695729
"""

0 commit comments

Comments
 (0)