|
46 | 46 | from _pytest.compat import getlocation
|
47 | 47 | from _pytest.compat import is_generator
|
48 | 48 | from _pytest.compat import NOTSET
|
| 49 | +from _pytest.compat import NotSetType |
49 | 50 | from _pytest.compat import overload
|
50 | 51 | from _pytest.compat import safe_getattr
|
51 | 52 | from _pytest.config import _PluggyPlugin
|
@@ -112,16 +113,18 @@ def pytest_sessionstart(session: "Session") -> None:
|
112 | 113 | session._fixturemanager = FixtureManager(session)
|
113 | 114 |
|
114 | 115 |
|
115 |
| -def get_scope_package(node, fixturedef: "FixtureDef[object]"): |
116 |
| - import pytest |
| 116 | +def get_scope_package( |
| 117 | + node: nodes.Item, |
| 118 | + fixturedef: "FixtureDef[object]", |
| 119 | +) -> Optional[Union[nodes.Item, nodes.Collector]]: |
| 120 | + from _pytest.python import Package |
117 | 121 |
|
118 |
| - cls = pytest.Package |
119 |
| - current = node |
| 122 | + current: Optional[Union[nodes.Item, nodes.Collector]] = node |
120 | 123 | fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
|
121 | 124 | while current and (
|
122 |
| - type(current) is not cls or fixture_package_name != current.nodeid |
| 125 | + not isinstance(current, Package) or fixture_package_name != current.nodeid |
123 | 126 | ):
|
124 |
| - current = current.parent |
| 127 | + current = current.parent # type: ignore[assignment] |
125 | 128 | if current is None:
|
126 | 129 | return node.session
|
127 | 130 | return current
|
@@ -434,7 +437,23 @@ def fixturenames(self) -> List[str]:
|
434 | 437 | @property
|
435 | 438 | def node(self):
|
436 | 439 | """Underlying collection node (depends on current request scope)."""
|
437 |
| - return self._getscopeitem(self._scope) |
| 440 | + scope = self._scope |
| 441 | + if scope is Scope.Function: |
| 442 | + # This might also be a non-function Item despite its attribute name. |
| 443 | + node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem |
| 444 | + elif scope is Scope.Package: |
| 445 | + # FIXME: _fixturedef is not defined on FixtureRequest (this class), |
| 446 | + # but on FixtureRequest (a subclass). |
| 447 | + node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] |
| 448 | + else: |
| 449 | + node = get_scope_node(self._pyfuncitem, scope) |
| 450 | + if node is None and scope is Scope.Class: |
| 451 | + # Fallback to function item itself. |
| 452 | + node = self._pyfuncitem |
| 453 | + assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format( |
| 454 | + scope, self._pyfuncitem |
| 455 | + ) |
| 456 | + return node |
438 | 457 |
|
439 | 458 | def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
|
440 | 459 | fixturedefs = self._arg2fixturedefs.get(argname, None)
|
@@ -518,11 +537,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
518 | 537 | """Add finalizer/teardown function to be called without arguments after
|
519 | 538 | the last test within the requesting test context finished execution."""
|
520 | 539 | # XXX usually this method is shadowed by fixturedef specific ones.
|
521 |
| - self._addfinalizer(finalizer, scope=self.scope) |
522 |
| - |
523 |
| - def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None: |
524 |
| - node = self._getscopeitem(scope) |
525 |
| - node.addfinalizer(finalizer) |
| 540 | + self.node.addfinalizer(finalizer) |
526 | 541 |
|
527 | 542 | def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
528 | 543 | """Apply a marker to a single test function invocation.
|
@@ -717,28 +732,6 @@ def _factorytraceback(self) -> List[str]:
|
717 | 732 | lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
|
718 | 733 | return lines
|
719 | 734 |
|
720 |
| - def _getscopeitem( |
721 |
| - self, scope: Union[Scope, "_ScopeName"] |
722 |
| - ) -> Union[nodes.Item, nodes.Collector]: |
723 |
| - if isinstance(scope, str): |
724 |
| - scope = Scope(scope) |
725 |
| - if scope is Scope.Function: |
726 |
| - # This might also be a non-function Item despite its attribute name. |
727 |
| - node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem |
728 |
| - elif scope is Scope.Package: |
729 |
| - # FIXME: _fixturedef is not defined on FixtureRequest (this class), |
730 |
| - # but on FixtureRequest (a subclass). |
731 |
| - node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] |
732 |
| - else: |
733 |
| - node = get_scope_node(self._pyfuncitem, scope) |
734 |
| - if node is None and scope is Scope.Class: |
735 |
| - # Fallback to function item itself. |
736 |
| - node = self._pyfuncitem |
737 |
| - assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format( |
738 |
| - scope, self._pyfuncitem |
739 |
| - ) |
740 |
| - return node |
741 |
| - |
742 | 735 | def __repr__(self) -> str:
|
743 | 736 | return "<FixtureRequest for %r>" % (self.node)
|
744 | 737 |
|
@@ -1593,13 +1586,52 @@ def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None:
|
1593 | 1586 | # Separate parametrized setups.
|
1594 | 1587 | items[:] = reorder_items(items)
|
1595 | 1588 |
|
| 1589 | + @overload |
1596 | 1590 | def parsefactories(
|
1597 |
| - self, node_or_obj, nodeid=NOTSET, unittest: bool = False |
| 1591 | + self, |
| 1592 | + node_or_obj: nodes.Node, |
| 1593 | + *, |
| 1594 | + unittest: bool = ..., |
1598 | 1595 | ) -> None:
|
| 1596 | + raise NotImplementedError() |
| 1597 | + |
| 1598 | + @overload |
| 1599 | + def parsefactories( # noqa: F811 |
| 1600 | + self, |
| 1601 | + node_or_obj: object, |
| 1602 | + nodeid: Optional[str], |
| 1603 | + *, |
| 1604 | + unittest: bool = ..., |
| 1605 | + ) -> None: |
| 1606 | + raise NotImplementedError() |
| 1607 | + |
| 1608 | + def parsefactories( # noqa: F811 |
| 1609 | + self, |
| 1610 | + node_or_obj: Union[nodes.Node, object], |
| 1611 | + nodeid: Union[str, NotSetType, None] = NOTSET, |
| 1612 | + *, |
| 1613 | + unittest: bool = False, |
| 1614 | + ) -> None: |
| 1615 | + """Collect fixtures from a collection node or object. |
| 1616 | +
|
| 1617 | + Found fixtures are parsed into `FixtureDef`s and saved. |
| 1618 | +
|
| 1619 | + If `node_or_object` is a collection node (with an underlying Python |
| 1620 | + object), the node's object is traversed and the node's nodeid is used to |
| 1621 | + determine the fixtures' visibilty. `nodeid` must not be specified in |
| 1622 | + this case. |
| 1623 | +
|
| 1624 | + If `node_or_object` is an object (e.g. a plugin), the object is |
| 1625 | + traversed and the given `nodeid` is used to determine the fixtures' |
| 1626 | + visibility. `nodeid` must be specified in this case; None and "" mean |
| 1627 | + total visibility. |
| 1628 | + """ |
1599 | 1629 | if nodeid is not NOTSET:
|
1600 | 1630 | holderobj = node_or_obj
|
1601 | 1631 | else:
|
1602 |
| - holderobj = node_or_obj.obj |
| 1632 | + assert isinstance(node_or_obj, nodes.Node) |
| 1633 | + holderobj = cast(object, node_or_obj.obj) # type: ignore[attr-defined] |
| 1634 | + assert isinstance(node_or_obj.nodeid, str) |
1603 | 1635 | nodeid = node_or_obj.nodeid
|
1604 | 1636 | if holderobj in self._holderobjseen:
|
1605 | 1637 | return
|
|
0 commit comments