Merge pull request #7671 from bluetech/ignored-names
RFC: python: skip work pytest_pycollect_makeitem work on certain names
This commit is contained in:
commit
2fcf763d7e
|
@ -0,0 +1,6 @@
|
||||||
|
When collecting tests, pytest finds test classes and functions by examining the
|
||||||
|
attributes of python objects (modules, classes and instances). To speed up this
|
||||||
|
process, pytest now ignores builtin attributes (like ``__class__``,
|
||||||
|
``__delattr__`` and ``__new__``) without consulting the ``python_classes`` and
|
||||||
|
``python_functions`` configuration options and without passing them to plugins
|
||||||
|
using the ``pytest_pycollect_makeitem`` hook.
|
|
@ -5,6 +5,7 @@ import inspect
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import types
|
||||||
import typing
|
import typing
|
||||||
import warnings
|
import warnings
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
@ -343,6 +344,26 @@ class PyobjMixin:
|
||||||
return fspath, lineno, modpath
|
return fspath, lineno, modpath
|
||||||
|
|
||||||
|
|
||||||
|
# As an optimization, these builtin attribute names are pre-ignored when
|
||||||
|
# iterating over an object during collection -- the pytest_pycollect_makeitem
|
||||||
|
# hook is not called for them.
|
||||||
|
# fmt: off
|
||||||
|
class _EmptyClass: pass # noqa: E701
|
||||||
|
IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305
|
||||||
|
frozenset(),
|
||||||
|
# Module.
|
||||||
|
dir(types.ModuleType("empty_module")),
|
||||||
|
# Some extra module attributes the above doesn't catch.
|
||||||
|
{"__builtins__", "__file__", "__cached__"},
|
||||||
|
# Class.
|
||||||
|
dir(_EmptyClass),
|
||||||
|
# Instance.
|
||||||
|
dir(_EmptyClass()),
|
||||||
|
)
|
||||||
|
del _EmptyClass
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
class PyCollector(PyobjMixin, nodes.Collector):
|
class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
def funcnamefilter(self, name: str) -> bool:
|
def funcnamefilter(self, name: str) -> bool:
|
||||||
return self._matches_prefix_or_glob_option("python_functions", name)
|
return self._matches_prefix_or_glob_option("python_functions", name)
|
||||||
|
@ -404,6 +425,8 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
# Note: seems like the dict can change during iteration -
|
# Note: seems like the dict can change during iteration -
|
||||||
# be careful not to remove the list() without consideration.
|
# be careful not to remove the list() without consideration.
|
||||||
for name, obj in list(dic.items()):
|
for name, obj in list(dic.items()):
|
||||||
|
if name in IGNORED_ATTRIBUTES:
|
||||||
|
continue
|
||||||
if name in seen:
|
if name in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(name)
|
seen.add(name)
|
||||||
|
|
|
@ -885,6 +885,34 @@ class TestConftestCustomization:
|
||||||
result = testdir.runpytest_subprocess()
|
result = testdir.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||||
|
|
||||||
|
def test_early_ignored_attributes(self, testdir: Testdir) -> None:
|
||||||
|
"""Builtin attributes should be ignored early on, even if
|
||||||
|
configuration would otherwise allow them.
|
||||||
|
|
||||||
|
This tests a performance optimization, not correctness, really,
|
||||||
|
although it tests PytestCollectionWarning is not raised, while
|
||||||
|
it would have been raised otherwise.
|
||||||
|
"""
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
python_classes=*
|
||||||
|
python_functions=*
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
class TestEmpty:
|
||||||
|
pass
|
||||||
|
test_empty = TestEmpty()
|
||||||
|
def test_real():
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
items, rec = testdir.inline_genitems()
|
||||||
|
assert rec.ret == 0
|
||||||
|
assert len(items) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_setup_only_available_in_subdir(testdir):
|
def test_setup_only_available_in_subdir(testdir):
|
||||||
sub1 = testdir.mkpydir("sub1")
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
|
Loading…
Reference in New Issue