python: remove the `Instance` collector node
This commit is contained in:
		
							parent
							
								
									17b38259fd
								
							
						
					
					
						commit
						062d91ab47
					
				|  | @ -0,0 +1,3 @@ | ||||||
|  | The ``pytest.Instance`` collector type has been removed. | ||||||
|  | Importing ``pytest.Instance`` or ``_pytest.python.Instance`` returns a dummy type and emits a deprecation warning. | ||||||
|  | See :ref:`instance-collector-deprecation` for details. | ||||||
|  | @ -18,6 +18,25 @@ Deprecated Features | ||||||
| Below is a complete list of all pytest features which are considered deprecated. Using those features will issue | Below is a complete list of all pytest features which are considered deprecated. Using those features will issue | ||||||
| :class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`. | :class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`. | ||||||
| 
 | 
 | ||||||
|  | .. _instance-collector-deprecation: | ||||||
|  | 
 | ||||||
|  | The ``pytest.Instance`` collector | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  | 
 | ||||||
|  | .. versionremoved:: 7.0 | ||||||
|  | 
 | ||||||
|  | The ``pytest.Instance`` collector type has been removed. | ||||||
|  | 
 | ||||||
|  | Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`. | ||||||
|  | Now :class:`~pytest.Class` collects the test methods directly. | ||||||
|  | 
 | ||||||
|  | Most plugins which reference ``Instance`` do so in order to ignore or skip it, | ||||||
|  | using a check such as ``if isinstance(node, Instance): return``. | ||||||
|  | Such plugins should simply remove consideration of ``Instance`` on pytest>=7. | ||||||
|  | However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``, | ||||||
|  | and importing it emits a deprecation warning. This will be removed in pytest 8. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| .. _node-ctor-fspath-deprecation: | .. _node-ctor-fspath-deprecation: | ||||||
| 
 | 
 | ||||||
| ``fspath`` argument for Node constructors replaced with ``pathlib.Path`` | ``fspath`` argument for Node constructors replaced with ``pathlib.Path`` | ||||||
|  |  | ||||||
|  | @ -119,6 +119,11 @@ KEYWORD_MSG_ARG = UnformattedWarning( | ||||||
|     "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", |     "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | INSTANCE_COLLECTOR = PytestDeprecationWarning( | ||||||
|  |     "The pytest.Instance collector type is deprecated and is no longer used. " | ||||||
|  |     "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| # You want to make some `__init__` or function "private". | # You want to make some `__init__` or function "private". | ||||||
| # | # | ||||||
| #   def my_private_function(some, args): | #   def my_private_function(some, args): | ||||||
|  |  | ||||||
|  | @ -450,10 +450,6 @@ def pytest_unconfigure(config: Config) -> None: | ||||||
| def mangle_test_address(address: str) -> List[str]: | def mangle_test_address(address: str) -> List[str]: | ||||||
|     path, possible_open_bracket, params = address.partition("[") |     path, possible_open_bracket, params = address.partition("[") | ||||||
|     names = path.split("::") |     names = path.split("::") | ||||||
|     try: |  | ||||||
|         names.remove("()") |  | ||||||
|     except ValueError: |  | ||||||
|         pass |  | ||||||
|     # Convert file path to dotted path. |     # Convert file path to dotted path. | ||||||
|     names[0] = names[0].replace(nodes.SEP, ".") |     names[0] = names[0].replace(nodes.SEP, ".") | ||||||
|     names[0] = re.sub(r"\.py$", "", names[0]) |     names[0] = re.sub(r"\.py$", "", names[0]) | ||||||
|  |  | ||||||
|  | @ -781,9 +781,6 @@ class Session(nodes.FSCollector): | ||||||
|                                     submatchnodes.append(r) |                                     submatchnodes.append(r) | ||||||
|                             if submatchnodes: |                             if submatchnodes: | ||||||
|                                 work.append((submatchnodes, matchnames[1:])) |                                 work.append((submatchnodes, matchnames[1:])) | ||||||
|                             # XXX Accept IDs that don't have "()" for class instances. |  | ||||||
|                             elif len(rep.result) == 1 and rep.result[0].name == "()": |  | ||||||
|                                 work.append((rep.result, matchnames)) |  | ||||||
|                         else: |                         else: | ||||||
|                             # Report collection failures here to avoid failing to run some test |                             # Report collection failures here to avoid failing to run some test | ||||||
|                             # specified in the command line because the module could not be |                             # specified in the command line because the module could not be | ||||||
|  |  | ||||||
|  | @ -158,7 +158,7 @@ class KeywordMatcher: | ||||||
|         import pytest |         import pytest | ||||||
| 
 | 
 | ||||||
|         for node in item.listchain(): |         for node in item.listchain(): | ||||||
|             if not isinstance(node, (pytest.Instance, pytest.Session)): |             if not isinstance(node, pytest.Session): | ||||||
|                 mapped_names.add(node.name) |                 mapped_names.add(node.name) | ||||||
| 
 | 
 | ||||||
|         # Add the names added as extra keywords to current or parent items. |         # Add the names added as extra keywords to current or parent items. | ||||||
|  |  | ||||||
|  | @ -225,9 +225,7 @@ class Node(metaclass=NodeMeta): | ||||||
|         else: |         else: | ||||||
|             if not self.parent: |             if not self.parent: | ||||||
|                 raise TypeError("nodeid or parent must be provided") |                 raise TypeError("nodeid or parent must be provided") | ||||||
|             self._nodeid = self.parent.nodeid |             self._nodeid = self.parent.nodeid + "::" + self.name | ||||||
|             if self.name != "()": |  | ||||||
|                 self._nodeid += "::" + self.name |  | ||||||
| 
 | 
 | ||||||
|         #: A place where plugins can store information on the node for their |         #: A place where plugins can store information on the node for their | ||||||
|         #: own use. |         #: own use. | ||||||
|  |  | ||||||
|  | @ -58,6 +58,7 @@ from _pytest.config import hookimpl | ||||||
| from _pytest.config.argparsing import Parser | from _pytest.config.argparsing import Parser | ||||||
| from _pytest.deprecated import check_ispytest | from _pytest.deprecated import check_ispytest | ||||||
| from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH | from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH | ||||||
|  | from _pytest.deprecated import INSTANCE_COLLECTOR | ||||||
| from _pytest.fixtures import FuncFixtureInfo | from _pytest.fixtures import FuncFixtureInfo | ||||||
| from _pytest.main import Session | from _pytest.main import Session | ||||||
| from _pytest.mark import MARK_GEN | from _pytest.mark import MARK_GEN | ||||||
|  | @ -275,12 +276,6 @@ class PyobjMixin(nodes.Node): | ||||||
|         node = self.getparent(Class) |         node = self.getparent(Class) | ||||||
|         return node.obj if node is not None else None |         return node.obj if node is not None else None | ||||||
| 
 | 
 | ||||||
|     @property |  | ||||||
|     def instance(self): |  | ||||||
|         """Python instance object this node was collected from (can be None).""" |  | ||||||
|         node = self.getparent(Instance) |  | ||||||
|         return node.obj if node is not None else None |  | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def obj(self): |     def obj(self): | ||||||
|         """Underlying Python object.""" |         """Underlying Python object.""" | ||||||
|  | @ -288,7 +283,7 @@ class PyobjMixin(nodes.Node): | ||||||
|         if obj is None: |         if obj is None: | ||||||
|             self._obj = obj = self._getobj() |             self._obj = obj = self._getobj() | ||||||
|             # XXX evil hack |             # XXX evil hack | ||||||
|             # used to avoid Instance collector 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)) | ||||||
|         return obj |         return obj | ||||||
|  | @ -310,8 +305,6 @@ class PyobjMixin(nodes.Node): | ||||||
|         chain.reverse() |         chain.reverse() | ||||||
|         parts = [] |         parts = [] | ||||||
|         for node in chain: |         for node in chain: | ||||||
|             if isinstance(node, Instance): |  | ||||||
|                 continue |  | ||||||
|             name = node.name |             name = node.name | ||||||
|             if isinstance(node, Module): |             if isinstance(node, Module): | ||||||
|                 name = os.path.splitext(name)[0] |                 name = os.path.splitext(name)[0] | ||||||
|  | @ -410,7 +403,8 @@ class PyCollector(PyobjMixin, nodes.Collector): | ||||||
| 
 | 
 | ||||||
|         # Avoid random getattrs and peek in the __dict__ instead. |         # Avoid random getattrs and peek in the __dict__ instead. | ||||||
|         dicts = [getattr(self.obj, "__dict__", {})] |         dicts = [getattr(self.obj, "__dict__", {})] | ||||||
|         for basecls in self.obj.__class__.__mro__: |         if isinstance(self.obj, type): | ||||||
|  |             for basecls in self.obj.__mro__: | ||||||
|                 dicts.append(basecls.__dict__) |                 dicts.append(basecls.__dict__) | ||||||
| 
 | 
 | ||||||
|         # In each class, nodes should be definition ordered. Since Python 3.6, |         # In each class, nodes should be definition ordered. Since Python 3.6, | ||||||
|  | @ -491,7 +485,6 @@ class PyCollector(PyobjMixin, nodes.Collector): | ||||||
|                     self, |                     self, | ||||||
|                     name=subname, |                     name=subname, | ||||||
|                     callspec=callspec, |                     callspec=callspec, | ||||||
|                     callobj=funcobj, |  | ||||||
|                     fixtureinfo=fixtureinfo, |                     fixtureinfo=fixtureinfo, | ||||||
|                     keywords={callspec.id: True}, |                     keywords={callspec.id: True}, | ||||||
|                     originalname=name, |                     originalname=name, | ||||||
|  | @ -776,6 +769,9 @@ class Class(PyCollector): | ||||||
|         """The public constructor.""" |         """The public constructor.""" | ||||||
|         return super().from_parent(name=name, parent=parent, **kw) |         return super().from_parent(name=name, parent=parent, **kw) | ||||||
| 
 | 
 | ||||||
|  |     def newinstance(self): | ||||||
|  |         return self.obj() | ||||||
|  | 
 | ||||||
|     def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: |     def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: | ||||||
|         if not safe_getattr(self.obj, "__test__", True): |         if not safe_getattr(self.obj, "__test__", True): | ||||||
|             return [] |             return [] | ||||||
|  | @ -803,7 +799,9 @@ class Class(PyCollector): | ||||||
|         self._inject_setup_class_fixture() |         self._inject_setup_class_fixture() | ||||||
|         self._inject_setup_method_fixture() |         self._inject_setup_method_fixture() | ||||||
| 
 | 
 | ||||||
|         return [Instance.from_parent(self, name="()")] |         self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) | ||||||
|  | 
 | ||||||
|  |         return super().collect() | ||||||
| 
 | 
 | ||||||
|     def _inject_setup_class_fixture(self) -> None: |     def _inject_setup_class_fixture(self) -> None: | ||||||
|         """Inject a hidden autouse, class scoped fixture into the collected class object |         """Inject a hidden autouse, class scoped fixture into the collected class object | ||||||
|  | @ -874,25 +872,22 @@ class Class(PyCollector): | ||||||
|         self.obj.__pytest_setup_method = xunit_setup_method_fixture |         self.obj.__pytest_setup_method = xunit_setup_method_fixture | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Instance(PyCollector): | class InstanceDummy: | ||||||
|     _ALLOW_MARKERS = False  # hack, destroy later |     """Instance used to be a node type between Class and Function. It has been | ||||||
|     # Instances share the object with their parents in a way |     removed in pytest 7.0. Some plugins exist which reference `pytest.Instance` | ||||||
|     # that duplicates markers instances if not taken out |     only to ignore it; this dummy class keeps them working. This will be removed | ||||||
|     # can be removed at node structure reorganization time. |     in pytest 8.""" | ||||||
| 
 | 
 | ||||||
|     def _getobj(self): |     pass | ||||||
|         # TODO: Improve the type of `parent` such that assert/ignore aren't needed. |  | ||||||
|         assert self.parent is not None |  | ||||||
|         obj = self.parent.obj  # type: ignore[attr-defined] |  | ||||||
|         return obj() |  | ||||||
| 
 | 
 | ||||||
|     def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: |  | ||||||
|         self.session._fixturemanager.parsefactories(self) |  | ||||||
|         return super().collect() |  | ||||||
| 
 | 
 | ||||||
|     def newinstance(self): | # Note: module __getattr__ only works on Python>=3.7. Unfortunately | ||||||
|         self.obj = self._getobj() | # we can't provide this deprecation warning on Python 3.6. | ||||||
|         return self.obj | def __getattr__(name: str) -> object: | ||||||
|  |     if name == "Instance": | ||||||
|  |         warnings.warn(INSTANCE_COLLECTOR, 2) | ||||||
|  |         return InstanceDummy | ||||||
|  |     raise AttributeError(f"module {__name__} has no attribute {name}") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def hasinit(obj: object) -> bool: | def hasinit(obj: object) -> bool: | ||||||
|  | @ -1686,9 +1681,23 @@ class Function(PyobjMixin, nodes.Item): | ||||||
|         """Underlying python 'function' object.""" |         """Underlying python 'function' object.""" | ||||||
|         return getimfunc(self.obj) |         return getimfunc(self.obj) | ||||||
| 
 | 
 | ||||||
|  |     @property | ||||||
|  |     def instance(self): | ||||||
|  |         """Python instance object the function is bound to. | ||||||
|  | 
 | ||||||
|  |         Returns None if not a test method, e.g. for a standalone test function | ||||||
|  |         or a staticmethod. | ||||||
|  |         """ | ||||||
|  |         return getattr(self.obj, "__self__", None) | ||||||
|  | 
 | ||||||
|     def _getobj(self): |     def _getobj(self): | ||||||
|         assert self.parent is not None |         assert self.parent is not None | ||||||
|         return getattr(self.parent.obj, self.originalname)  # type: ignore[attr-defined] |         if isinstance(self.parent, Class): | ||||||
|  |             # Each Function gets a fresh class instance. | ||||||
|  |             parent_obj = self.parent.newinstance() | ||||||
|  |         else: | ||||||
|  |             parent_obj = self.parent.obj  # type: ignore[attr-defined] | ||||||
|  |         return getattr(parent_obj, self.originalname) | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def _pyfuncitem(self): |     def _pyfuncitem(self): | ||||||
|  | @ -1700,9 +1709,6 @@ class Function(PyobjMixin, nodes.Item): | ||||||
|         self.ihook.pytest_pyfunc_call(pyfuncitem=self) |         self.ihook.pytest_pyfunc_call(pyfuncitem=self) | ||||||
| 
 | 
 | ||||||
|     def setup(self) -> None: |     def setup(self) -> None: | ||||||
|         if isinstance(self.parent, Instance): |  | ||||||
|             self.parent.newinstance() |  | ||||||
|             self.obj = self._getobj() |  | ||||||
|         self._request._fillfixtures() |         self._request._fillfixtures() | ||||||
| 
 | 
 | ||||||
|     def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: |     def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: | ||||||
|  |  | ||||||
|  | @ -756,9 +756,6 @@ class TerminalReporter: | ||||||
|                     rep.toterminal(self._tw) |                     rep.toterminal(self._tw) | ||||||
| 
 | 
 | ||||||
|     def _printcollecteditems(self, items: Sequence[Item]) -> None: |     def _printcollecteditems(self, items: Sequence[Item]) -> None: | ||||||
|         # To print out items and their parent collectors |  | ||||||
|         # we take care to leave out Instances aka () |  | ||||||
|         # because later versions are going to get rid of them anyway. |  | ||||||
|         if self.config.option.verbose < 0: |         if self.config.option.verbose < 0: | ||||||
|             if self.config.option.verbose < -1: |             if self.config.option.verbose < -1: | ||||||
|                 counts = Counter(item.nodeid.split("::", 1)[0] for item in items) |                 counts = Counter(item.nodeid.split("::", 1)[0] for item in items) | ||||||
|  | @ -778,8 +775,6 @@ class TerminalReporter: | ||||||
|                 stack.pop() |                 stack.pop() | ||||||
|             for col in needed_collectors[len(stack) :]: |             for col in needed_collectors[len(stack) :]: | ||||||
|                 stack.append(col) |                 stack.append(col) | ||||||
|                 if col.name == "()":  # Skip Instances. |  | ||||||
|                     continue |  | ||||||
|                 indent = (len(stack) - 1) * "  " |                 indent = (len(stack) - 1) * "  " | ||||||
|                 self._tw.line(f"{indent}{col}") |                 self._tw.line(f"{indent}{col}") | ||||||
|                 if self.config.option.verbose >= 1: |                 if self.config.option.verbose >= 1: | ||||||
|  |  | ||||||
|  | @ -48,7 +48,6 @@ from _pytest.pytester import RecordedHookCall | ||||||
| from _pytest.pytester import RunResult | from _pytest.pytester import RunResult | ||||||
| from _pytest.python import Class | from _pytest.python import Class | ||||||
| from _pytest.python import Function | from _pytest.python import Function | ||||||
| from _pytest.python import Instance |  | ||||||
| from _pytest.python import Metafunc | from _pytest.python import Metafunc | ||||||
| from _pytest.python import Module | from _pytest.python import Module | ||||||
| from _pytest.python import Package | from _pytest.python import Package | ||||||
|  | @ -77,6 +76,7 @@ from _pytest.warning_types import PytestWarning | ||||||
| 
 | 
 | ||||||
| set_trace = __pytestPDB.set_trace | set_trace = __pytestPDB.set_trace | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| __all__ = [ | __all__ = [ | ||||||
|     "__version__", |     "__version__", | ||||||
|     "_fillfuncargs", |     "_fillfuncargs", | ||||||
|  | @ -106,7 +106,6 @@ __all__ = [ | ||||||
|     "HookRecorder", |     "HookRecorder", | ||||||
|     "hookspec", |     "hookspec", | ||||||
|     "importorskip", |     "importorskip", | ||||||
|     "Instance", |  | ||||||
|     "Item", |     "Item", | ||||||
|     "LineMatcher", |     "LineMatcher", | ||||||
|     "LogCaptureFixture", |     "LogCaptureFixture", | ||||||
|  | @ -153,3 +152,12 @@ __all__ = [ | ||||||
|     "xfail", |     "xfail", | ||||||
|     "yield_fixture", |     "yield_fixture", | ||||||
| ] | ] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def __getattr__(name: str) -> object: | ||||||
|  |     if name == "Instance": | ||||||
|  |         # The import emits a deprecation warning. | ||||||
|  |         from _pytest.python import Instance | ||||||
|  | 
 | ||||||
|  |         return Instance | ||||||
|  |     raise AttributeError(f"module {__name__} has no attribute {name}") | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ COLLECT_FAKEMODULE_ATTRIBUTES = [ | ||||||
|     "Collector", |     "Collector", | ||||||
|     "Module", |     "Module", | ||||||
|     "Function", |     "Function", | ||||||
|     "Instance", |  | ||||||
|     "Session", |     "Session", | ||||||
|     "Item", |     "Item", | ||||||
|     "Class", |     "Class", | ||||||
|  |  | ||||||
|  | @ -286,3 +286,21 @@ def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: | ||||||
|             parent=mod.parent, |             parent=mod.parent, | ||||||
|             fspath=legacy_path("bla"), |             fspath=legacy_path("bla"), | ||||||
|         ) |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.skipif( | ||||||
|  |     sys.version_info < (3, 7), | ||||||
|  |     reason="This deprecation can only be emitted on python>=3.7", | ||||||
|  | ) | ||||||
|  | def test_importing_instance_is_deprecated(pytester: Pytester) -> None: | ||||||
|  |     with pytest.warns( | ||||||
|  |         pytest.PytestDeprecationWarning, | ||||||
|  |         match=re.escape("The pytest.Instance collector type is deprecated"), | ||||||
|  |     ): | ||||||
|  |         pytest.Instance | ||||||
|  | 
 | ||||||
|  |     with pytest.warns( | ||||||
|  |         pytest.PytestDeprecationWarning, | ||||||
|  |         match=re.escape("The pytest.Instance collector type is deprecated"), | ||||||
|  |     ): | ||||||
|  |         from _pytest.python import Instance  # noqa: F401 | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ from _pytest.monkeypatch import MonkeyPatch | ||||||
| from _pytest.nodes import Collector | from _pytest.nodes import Collector | ||||||
| from _pytest.pytester import Pytester | from _pytest.pytester import Pytester | ||||||
| from _pytest.python import Class | from _pytest.python import Class | ||||||
| from _pytest.python import Instance | from _pytest.python import Function | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestModule: | class TestModule: | ||||||
|  | @ -585,7 +585,7 @@ class TestFunction: | ||||||
|                     pass |                     pass | ||||||
|         """ |         """ | ||||||
|         ) |         ) | ||||||
|         colitems = modcol.collect()[0].collect()[0].collect() |         colitems = modcol.collect()[0].collect() | ||||||
|         assert colitems[0].name == "test1[a-c]" |         assert colitems[0].name == "test1[a-c]" | ||||||
|         assert colitems[1].name == "test1[b-c]" |         assert colitems[1].name == "test1[b-c]" | ||||||
|         assert colitems[2].name == "test2[a-c]" |         assert colitems[2].name == "test2[a-c]" | ||||||
|  | @ -1183,19 +1183,26 @@ class TestReportInfo: | ||||||
|         modcol = pytester.getmodulecol( |         modcol = pytester.getmodulecol( | ||||||
|             """ |             """ | ||||||
|             # lineno 0 |             # lineno 0 | ||||||
|             class TestClass(object): |             class TestClass: | ||||||
|                 def __getattr__(self, name): |                 def __getattr__(self, name): | ||||||
|                     return "this is not an int" |                     return "this is not an int" | ||||||
| 
 | 
 | ||||||
|  |                 def __class_getattr__(cls, name): | ||||||
|  |                     return "this is not an int" | ||||||
|  | 
 | ||||||
|                 def intest_foo(self): |                 def intest_foo(self): | ||||||
|                     pass |                     pass | ||||||
|  | 
 | ||||||
|  |                 def test_bar(self): | ||||||
|  |                     pass | ||||||
|         """ |         """ | ||||||
|         ) |         ) | ||||||
|         classcol = pytester.collect_by_name(modcol, "TestClass") |         classcol = pytester.collect_by_name(modcol, "TestClass") | ||||||
|         assert isinstance(classcol, Class) |         assert isinstance(classcol, Class) | ||||||
|         instance = list(classcol.collect())[0] |         path, lineno, msg = classcol.reportinfo() | ||||||
|         assert isinstance(instance, Instance) |         func = list(classcol.collect())[0] | ||||||
|         path, lineno, msg = instance.reportinfo() |         assert isinstance(func, Function) | ||||||
|  |         path, lineno, msg = func.reportinfo() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_customized_python_discovery(pytester: Pytester) -> None: | def test_customized_python_discovery(pytester: Pytester) -> None: | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ from _pytest import runner | ||||||
| from _pytest._code import getfslineno | from _pytest._code import getfslineno | ||||||
| from _pytest.fixtures import getfixturemarker | from _pytest.fixtures import getfixturemarker | ||||||
| from _pytest.pytester import Pytester | from _pytest.pytester import Pytester | ||||||
|  | from _pytest.python import Function | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestOEJSKITSpecials: | class TestOEJSKITSpecials: | ||||||
|  | @ -475,3 +476,28 @@ class TestParameterize: | ||||||
|         ) |         ) | ||||||
|         res = pytester.runpytest("--collect-only") |         res = pytester.runpytest("--collect-only") | ||||||
|         res.stdout.fnmatch_lines(["*spam-2*", "*ham-2*"]) |         res.stdout.fnmatch_lines(["*spam-2*", "*ham-2*"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_function_instance(pytester: Pytester) -> None: | ||||||
|  |     items = pytester.getitems( | ||||||
|  |         """ | ||||||
|  |         def test_func(): pass | ||||||
|  |         class TestIt: | ||||||
|  |             def test_method(self): pass | ||||||
|  |             @classmethod | ||||||
|  |             def test_class(cls): pass | ||||||
|  |             @staticmethod | ||||||
|  |             def test_static(): pass | ||||||
|  |         """ | ||||||
|  |     ) | ||||||
|  |     assert len(items) == 3 | ||||||
|  |     assert isinstance(items[0], Function) | ||||||
|  |     assert items[0].name == "test_func" | ||||||
|  |     assert items[0].instance is None | ||||||
|  |     assert isinstance(items[1], Function) | ||||||
|  |     assert items[1].name == "test_method" | ||||||
|  |     assert items[1].instance is not None | ||||||
|  |     assert items[1].instance.__class__.__name__ == "TestIt" | ||||||
|  |     assert isinstance(items[2], Function) | ||||||
|  |     assert items[2].name == "test_static" | ||||||
|  |     assert items[2].instance is None | ||||||
|  |  | ||||||
|  | @ -74,9 +74,7 @@ class TestCollector: | ||||||
|         ) |         ) | ||||||
|         cls = pytester.collect_by_name(modcol, "TestClass") |         cls = pytester.collect_by_name(modcol, "TestClass") | ||||||
|         assert isinstance(cls, pytest.Class) |         assert isinstance(cls, pytest.Class) | ||||||
|         instance = pytester.collect_by_name(cls, "()") |         fn = pytester.collect_by_name(cls, "test_foo") | ||||||
|         assert isinstance(instance, pytest.Instance) |  | ||||||
|         fn = pytester.collect_by_name(instance, "test_foo") |  | ||||||
|         assert isinstance(fn, pytest.Function) |         assert isinstance(fn, pytest.Function) | ||||||
| 
 | 
 | ||||||
|         module_parent = fn.getparent(pytest.Module) |         module_parent = fn.getparent(pytest.Module) | ||||||
|  |  | ||||||
|  | @ -936,7 +936,7 @@ class TestPython: | ||||||
| def test_mangle_test_address() -> None: | def test_mangle_test_address() -> None: | ||||||
|     from _pytest.junitxml import mangle_test_address |     from _pytest.junitxml import mangle_test_address | ||||||
| 
 | 
 | ||||||
|     address = "::".join(["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"]) |     address = "::".join(["a/my.py.thing.py", "Class", "method", "[a-1-::]"]) | ||||||
|     newnames = mangle_test_address(address) |     newnames = mangle_test_address(address) | ||||||
|     assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] |     assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -272,8 +272,10 @@ def test_nose_setup_ordering(pytester: Pytester) -> None: | ||||||
|         class TestClass(object): |         class TestClass(object): | ||||||
|             def setup(self): |             def setup(self): | ||||||
|                 assert visited |                 assert visited | ||||||
|  |                 self.visited_cls = True | ||||||
|             def test_first(self): |             def test_first(self): | ||||||
|                 pass |                 assert visited | ||||||
|  |                 assert self.visited_cls | ||||||
|         """ |         """ | ||||||
|     ) |     ) | ||||||
|     result = pytester.runpytest() |     result = pytester.runpytest() | ||||||
|  |  | ||||||
|  | @ -227,7 +227,7 @@ class TestNewSession(SessionTests): | ||||||
|         started = reprec.getcalls("pytest_collectstart") |         started = reprec.getcalls("pytest_collectstart") | ||||||
|         finished = reprec.getreports("pytest_collectreport") |         finished = reprec.getreports("pytest_collectreport") | ||||||
|         assert len(started) == len(finished) |         assert len(started) == len(finished) | ||||||
|         assert len(started) == 8 |         assert len(started) == 6 | ||||||
|         colfail = [x for x in finished if x.failed] |         colfail = [x for x in finished if x.failed] | ||||||
|         assert len(colfail) == 1 |         assert len(colfail) == 1 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue