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
if self._ALLOW_MARKERS:
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
@obj.setter
@ -1634,7 +1637,7 @@ class Function(PyobjMixin, nodes.Item):
config: Optional[Config] = None,
callspec: Optional[CallSpec2] = None,
callobj=NOTSET,
keywords=None,
keywords: Optional[Mapping[str, Any]] = None,
session: Optional[Session] = None,
fixtureinfo: Optional[FuncFixtureInfo] = None,
originalname: Optional[str] = None,
@ -1655,31 +1658,20 @@ class Function(PyobjMixin, nodes.Item):
# Note: when FunctionDefinition is introduced, we should change ``originalname``
# to a readonly property that returns FunctionDefinition.name.
self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_unpacked_marks(self.obj))
if callspec:
self.callspec = callspec
# this is total hostile and a mess
# 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)
self.own_markers.extend(callspec.marks)
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
self.keywords.update(
{
mark.name: True
for mark in self.iter_markers()
if mark.name not in self.keywords
}
)
# Note: the order of the updates is important here; indicates what
# takes priority (ctor argument over function attributes over markers).
# Take own_markers only; NodeKeywords handles parent traversal on its own.
self.keywords.update((mark.name, mark) for mark in self.own_markers)
self.keywords.update(self.obj.__dict__)
if keywords:
self.keywords.update(keywords)
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(

View File

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

View File

@ -881,6 +881,36 @@ class TestNodeKeywords:
assert item.keywords["kw"] == "method"
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(
test_01_failure="""