Merge pull request #9171 from bluetech/optimize-keywords-init

Optimizations/fixes around Function `keywords`
This commit is contained in:
Ran Benita 2022-01-21 15:19:53 +02:00 committed by GitHub
commit 888026f7a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 21 deletions

View File

@ -303,6 +303,9 @@ class PyobjMixin(nodes.Node):
# used to avoid Function marker duplication # used to avoid Function marker duplication
if self._ALLOW_MARKERS: if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj)) self.own_markers.extend(get_unpacked_marks(self.obj))
# This assumes that `obj` is called before there is a chance
# to add custom keys to `self.keywords`, so no fear of overriding.
self.keywords.update((mark.name, mark) for mark in self.own_markers)
return obj return obj
@obj.setter @obj.setter
@ -1634,7 +1637,7 @@ class Function(PyobjMixin, nodes.Item):
config: Optional[Config] = None, config: Optional[Config] = None,
callspec: Optional[CallSpec2] = None, callspec: Optional[CallSpec2] = None,
callobj=NOTSET, callobj=NOTSET,
keywords=None, keywords: Optional[Mapping[str, Any]] = None,
session: Optional[Session] = None, session: Optional[Session] = None,
fixtureinfo: Optional[FuncFixtureInfo] = None, fixtureinfo: Optional[FuncFixtureInfo] = None,
originalname: Optional[str] = None, originalname: Optional[str] = None,
@ -1655,31 +1658,20 @@ class Function(PyobjMixin, nodes.Item):
# Note: when FunctionDefinition is introduced, we should change ``originalname`` # Note: when FunctionDefinition is introduced, we should change ``originalname``
# to a readonly property that returns FunctionDefinition.name. # to a readonly property that returns FunctionDefinition.name.
self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_unpacked_marks(self.obj)) self.own_markers.extend(get_unpacked_marks(self.obj))
if callspec: if callspec:
self.callspec = callspec self.callspec = callspec
# this is total hostile and a mess self.own_markers.extend(callspec.marks)
# keywords are broken by design by now
# this will be redeemed later
for mark in callspec.marks:
# feel free to cry, this was broken for years before
# and keywords can't fix it per design
self.keywords[mark.name] = mark
self.own_markers.extend(normalize_mark_list(callspec.marks))
if keywords:
self.keywords.update(keywords)
# todo: this is a hell of a hack # todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569 # https://github.com/pytest-dev/pytest/issues/4569
# Note: the order of the updates is important here; indicates what
self.keywords.update( # takes priority (ctor argument over function attributes over markers).
{ # Take own_markers only; NodeKeywords handles parent traversal on its own.
mark.name: True self.keywords.update((mark.name, mark) for mark in self.own_markers)
for mark in self.iter_markers() self.keywords.update(self.obj.__dict__)
if mark.name not in self.keywords if keywords:
} self.keywords.update(keywords)
)
if fixtureinfo is None: if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo( fixtureinfo = self.session._fixturemanager.getfixtureinfo(

View File

@ -7,6 +7,7 @@ from typing import Dict
from typing import Iterable from typing import Iterable
from typing import Iterator from typing import Iterator
from typing import List from typing import List
from typing import Mapping
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
from typing import Type from typing import Type
@ -254,7 +255,7 @@ class TestReport(BaseReport):
self, self,
nodeid: str, nodeid: str,
location: Tuple[str, Optional[int], str], location: Tuple[str, Optional[int], str],
keywords, keywords: Mapping[str, Any],
outcome: "Literal['passed', 'failed', 'skipped']", outcome: "Literal['passed', 'failed', 'skipped']",
longrepr: Union[ longrepr: Union[
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr

View File

@ -881,6 +881,36 @@ class TestNodeKeywords:
assert item.keywords["kw"] == "method" assert item.keywords["kw"] == "method"
assert len(item.keywords) == len(set(item.keywords)) assert len(item.keywords) == len(set(item.keywords))
def test_unpacked_marks_added_to_keywords(self, pytester: Pytester) -> None:
item = pytester.getitem(
"""
import pytest
pytestmark = pytest.mark.foo
class TestClass:
pytestmark = pytest.mark.bar
def test_method(self): pass
test_method.pytestmark = pytest.mark.baz
""",
"test_method",
)
assert isinstance(item, pytest.Function)
cls = item.getparent(pytest.Class)
assert cls is not None
mod = item.getparent(pytest.Module)
assert mod is not None
assert item.keywords["foo"] == pytest.mark.foo.mark
assert item.keywords["bar"] == pytest.mark.bar.mark
assert item.keywords["baz"] == pytest.mark.baz.mark
assert cls.keywords["foo"] == pytest.mark.foo.mark
assert cls.keywords["bar"] == pytest.mark.bar.mark
assert "baz" not in cls.keywords
assert mod.keywords["foo"] == pytest.mark.foo.mark
assert "bar" not in mod.keywords
assert "baz" not in mod.keywords
COLLECTION_ERROR_PY_FILES = dict( COLLECTION_ERROR_PY_FILES = dict(
test_01_failure=""" test_01_failure="""