Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Orenef11 committed Oct 12, 2021
1 parent 6b9f5de commit 1da5f82
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 10 deletions.
1 change: 1 addition & 0 deletions doc/en/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pytest 6.2.5 (2021-08-29)
Bug Fixes
---------

- `#9175 <https://github.com/pytest-dev/pytest/issues/9175>`_: Fix duplicate parametrizes in the same module.
- `#7792 <https://github.com/pytest-dev/pytest/issues/7792>`_: Fix missing marks when inheritance from multiple classes.


Expand Down
19 changes: 13 additions & 6 deletions src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from .._code import getfslineno
from ..compat import ascii_escaped
from ..compat import cached_property
from ..compat import final
from ..compat import NOTSET
from ..compat import NotSetType
Expand Down Expand Up @@ -259,6 +260,11 @@ def combined_with(self, other: "Mark") -> "Mark":
_ispytest=True,
)

@cached_property
def unique_name(self):
# For "parametrize" mark, the name value is "parametrize" and not the name that the user was wrote
return self.args[0] if self.name == "parametrize" else self.name


# A generic parameter designating an object to which a Mark may
# be applied -- a test function (callable) or class.
Expand Down Expand Up @@ -370,17 +376,18 @@ def get_unpacked_marks(obj: object) -> Iterable[Mark]:

def get_mro_marks(cls: type):
if cls is object or cls is None or hasattr(cls, "mro_markers"):
return getattr(cls, "mro_markers", [])
return getattr(cls, "mro_markers", {})

# markers = list(mark for mark in get_unpacked_marks(cls) if mark.name != "parametrize")
markers = list(mark for mark in get_unpacked_marks(cls))
mro_markers = {mark.unique_name: mark for mark in get_unpacked_marks(cls)}
for parent_obj in cls.__mro__[1:]:
if parent_obj is not object:
markers.extend(get_mro_marks(parent_obj))
for unique_name, mark in get_mro_marks(parent_obj).items():
if unique_name not in mro_markers:
mro_markers[unique_name] = mark

# To not extract the marks for each item's classes, I store the variable in "cls" as variable cached.
setattr(cls, "mro_markers", list({str(mark): mark for mark in markers}.values()))
return markers
setattr(cls, "mro_markers", mro_markers)
return mro_markers


def normalize_mark_list(
Expand Down
6 changes: 3 additions & 3 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,9 @@ def iter_markers_with_node(
for node in reversed(self.listchain()):
for mark in node.own_markers:
if name is None or getattr(mark, "name", None) == name:
mark_to_str = str(mark)
if mark_to_str not in duplicate_marks:
duplicate_marks[mark_to_str] = mark
unique_name = mark.unique_name
if unique_name not in duplicate_marks:
duplicate_marks[unique_name] = mark
yield node, mark

@overload
Expand Down
7 changes: 6 additions & 1 deletion src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -1620,7 +1620,12 @@ def __init__(
self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_unpacked_marks(self.obj))
if self.cls:
self.own_markers.extend(get_mro_marks(self.cls))
self.own_markers = list(
dict(
get_mro_marks(self.cls),
**{mark.unique_name: mark for mark in self.own_markers},
).values()
)
if callspec:
self.callspec = callspec
# this is total hostile and a mess
Expand Down
32 changes: 32 additions & 0 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,7 @@ def test_foo():


def test_markers_from_multiple_inheritances(pytester: Pytester) -> None:
# https://github.com/pytest-dev/pytest/issues/7792
pytester.makepyfile(
"""
import pytest
Expand All @@ -1150,3 +1151,34 @@ def test_multiple_inheritances(self, request):
)
result = pytester.inline_run()
result.assertoutcome(passed=1)


def test_duplicate_fixtures_in_same_class(pytester: Pytester) -> None:
# https://github.com/pytest-dev/pytest/issues/9175
pytester.makepyfile(
"""
from typing import Any
import pytest
@pytest.fixture
def some_fixture(request) -> Any:
return request.param
# I apply to all test methods
@pytest.mark.parametrize("some_fixture", [{"b": "b"}], indirect=True)
class TestMultiParameterization:
# I apply to just this one test method
@pytest.mark.parametrize("some_fixture", [{"a": "a"}], indirect=True)
def test_local_some_fixture(self, some_fixture: Any) -> None:
assert "a" in some_fixture
def test_global_some_fixture(self, some_fixture: Any) -> None:
assert "b" in some_fixture
"""
)
result = pytester.inline_run()
result.assertoutcome(passed=2)

0 comments on commit 1da5f82

Please sign in to comment.