diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 53f33d3e1..5bdee3096 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -16,6 +16,7 @@ from typing import Generic from typing import Iterable from typing import Iterator from typing import List +from typing import MutableMapping from typing import Optional from typing import overload from typing import Sequence @@ -525,9 +526,10 @@ class FixtureRequest: return self._pyfuncitem.fspath # type: ignore @property - def keywords(self): + def keywords(self) -> MutableMapping[str, Any]: """Keywords/markers dictionary for the underlying node.""" - return self.node.keywords + node: nodes.Node = self.node + return node.keywords @property def session(self) -> "Session": @@ -607,14 +609,11 @@ class FixtureRequest: def _get_fixturestack(self) -> List["FixtureDef[Any]"]: current = self values: List[FixtureDef[Any]] = [] - while 1: - fixturedef = getattr(current, "_fixturedef", None) - if fixturedef is None: - values.reverse() - return values - values.append(fixturedef) - assert isinstance(current, SubRequest) + while isinstance(current, SubRequest): + values.append(current._fixturedef) # type: ignore[has-type] current = current._parent_request + values.reverse() + return values def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: """Create a SubRequest based on "self" and call the execute method diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index bb8f99772..de91af85a 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -2,11 +2,12 @@ from _pytest import python from _pytest import unittest from _pytest.config import hookimpl +from _pytest.fixtures import getfixturemarker from _pytest.nodes import Item @hookimpl(trylast=True) -def pytest_runtest_setup(item): +def pytest_runtest_setup(item) -> None: if is_potential_nosetest(item): if not call_optional(item.obj, "setup"): # Call module level setup if there is no object level one. @@ -15,7 +16,7 @@ def pytest_runtest_setup(item): item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item) -def teardown_nose(item): +def teardown_nose(item) -> None: if is_potential_nosetest(item): if not call_optional(item.obj, "teardown"): call_optional(item.parent.obj, "teardown") @@ -29,11 +30,16 @@ def is_potential_nosetest(item: Item) -> bool: ) -def call_optional(obj, name): +def call_optional(obj: object, name: str) -> bool: method = getattr(obj, name, None) - isfixture = hasattr(method, "_pytestfixturefunction") - if method is not None and not isfixture and callable(method): - # If there's any problems allow the exception to raise rather than - # silently ignoring them. - method() - return True + if method is None: + return False + is_fixture = getfixturemarker(method) is not None + if is_fixture: + return False + if not callable(method): + return False + # If there are any problems allow the exception to raise rather than + # silently ignoring it. + method() + return True diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 50ea60c2d..29ebd176b 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -922,10 +922,6 @@ class CallSpec2: cs._idlist = list(self._idlist) return cs - def _checkargnotcontained(self, arg: str) -> None: - if arg in self.params or arg in self.funcargs: - raise ValueError(f"duplicate {arg!r}") - def getparam(self, name: str) -> object: try: return self.params[name] @@ -947,7 +943,8 @@ class CallSpec2: param_index: int, ) -> None: for arg, val in zip(argnames, valset): - self._checkargnotcontained(arg) + if arg in self.params or arg in self.funcargs: + raise ValueError(f"duplicate {arg!r}") valtype_for_arg = valtypes[arg] if valtype_for_arg == "params": self.params[arg] = val diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index bcd40fb36..d2d7115b2 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -65,6 +65,7 @@ class BaseReport: ] sections: List[Tuple[str, str]] nodeid: str + outcome: "Literal['passed', 'failed', 'skipped']" def __init__(self, **kw: Any) -> None: self.__dict__.update(kw) @@ -141,9 +142,17 @@ class BaseReport: content for (prefix, content) in self.get_sections("Captured stderr") ) - passed = property(lambda x: x.outcome == "passed") - failed = property(lambda x: x.outcome == "failed") - skipped = property(lambda x: x.outcome == "skipped") + @property + def passed(self) -> bool: + return self.outcome == "passed" + + @property + def failed(self) -> bool: + return self.outcome == "failed" + + @property + def skipped(self) -> bool: + return self.outcome == "skipped" @property def fspath(self) -> str: @@ -348,8 +357,10 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: "Literal['passed', 'skipped', 'failed']", - longrepr, + outcome: "Literal['passed', 'failed', 'skipped']", + longrepr: Union[ + None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr + ], result: Optional[List[Union[Item, Collector]]], sections: Iterable[Tuple[str, str]] = (), **extra, diff --git a/testing/python/integration.py b/testing/python/integration.py index 5dce6bdca..8576fcee3 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -3,6 +3,7 @@ from typing import Any import pytest from _pytest import runner from _pytest._code import getfslineno +from _pytest.fixtures import getfixturemarker from _pytest.pytester import Pytester @@ -334,7 +335,8 @@ class TestReRunTests: def test_pytestconfig_is_session_scoped() -> None: from _pytest.fixtures import pytestconfig - marker = pytestconfig._pytestfixturefunction # type: ignore + marker = getfixturemarker(pytestconfig) + assert marker is not None assert marker.scope == "session" diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 6d0a23fe0..e536f7098 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2230,19 +2230,19 @@ def test_skip_reasons_folding() -> None: ev1 = cast(CollectReport, X()) ev1.when = "execute" - ev1.skipped = True + ev1.skipped = True # type: ignore[misc] ev1.longrepr = longrepr ev2 = cast(CollectReport, X()) ev2.when = "execute" ev2.longrepr = longrepr - ev2.skipped = True + ev2.skipped = True # type: ignore[misc] # ev3 might be a collection report ev3 = cast(CollectReport, X()) ev3.when = "collect" ev3.longrepr = longrepr - ev3.skipped = True + ev3.skipped = True # type: ignore[misc] values = _folded_skips(Path.cwd(), [ev1, ev2, ev3]) assert len(values) == 1