Merge master into features
Conflicts: .github/workflows/main.yml
This commit is contained in:
@@ -32,8 +32,9 @@ import _pytest
|
||||
from _pytest._io.saferepr import safeformat
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
from typing_extensions import Literal
|
||||
from weakref import ReferenceType # noqa: F401
|
||||
|
||||
@@ -28,7 +28,13 @@ from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if sys.version_info < (3, 5, 2):
|
||||
TYPE_CHECKING = False # type: bool
|
||||
else:
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type # noqa: F401 (used in type string)
|
||||
|
||||
|
||||
|
||||
@@ -37,12 +37,13 @@ from .findpaths import exists
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest.compat import importlib_metadata
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.pathlib import Path
|
||||
from _pytest.warning_types import PytestConfigWarning
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
from .argparsing import Argument
|
||||
|
||||
@@ -5,9 +5,10 @@ from typing import Optional
|
||||
import py
|
||||
|
||||
from .exceptions import UsageError
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
if False:
|
||||
if TYPE_CHECKING:
|
||||
from . import Config # noqa: F401
|
||||
|
||||
|
||||
|
||||
@@ -19,12 +19,13 @@ from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprFileLocation
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.python_api import approx
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
import doctest
|
||||
from typing import Type
|
||||
|
||||
|
||||
@@ -27,12 +27,13 @@ from _pytest.compat import getlocation
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
|
||||
from _pytest.deprecated import FUNCARGNAMES
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
from _pytest import nodes
|
||||
@@ -880,9 +881,7 @@ class FixtureDef:
|
||||
self._finalizers = []
|
||||
|
||||
def execute(self, request):
|
||||
# get required arguments and register our own finish()
|
||||
# with their finalization
|
||||
for argname in self.argnames:
|
||||
for argname in self._dependee_fixture_argnames(request):
|
||||
fixturedef = request._get_active_fixturedef(argname)
|
||||
if argname != "request":
|
||||
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
|
||||
@@ -905,6 +904,61 @@ class FixtureDef:
|
||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||
|
||||
def _dependee_fixture_argnames(self, request):
|
||||
"""A list of argnames for fixtures that this fixture depends on.
|
||||
|
||||
Given a request, this looks at the currently known list of fixture argnames, and
|
||||
attempts to determine what slice of the list contains fixtures that it can know
|
||||
should execute before it. This information is necessary so that this fixture can
|
||||
know what fixtures to register its finalizer with to make sure that if they
|
||||
would be torn down, they would tear down this fixture before themselves. It's
|
||||
crucial for fixtures to be torn down in the inverse order that they were set up
|
||||
in so that they don't try to clean up something that another fixture is still
|
||||
depending on.
|
||||
|
||||
When autouse fixtures are involved, it can be tricky to figure out when fixtures
|
||||
should be torn down. To solve this, this method leverages the ``fixturenames``
|
||||
list provided by the ``request`` object, as this list is at least somewhat
|
||||
sorted (in terms of the order fixtures are set up in) by the time this method is
|
||||
reached. It's sorted enough that the starting point of fixtures that depend on
|
||||
this one can be found using the ``self._parent_request`` stack.
|
||||
|
||||
If a request in the ``self._parent_request`` stack has a ``:class:FixtureDef``
|
||||
associated with it, then that fixture is dependent on this one, so any fixture
|
||||
names that appear in the list of fixture argnames that come after it can also be
|
||||
ruled out. The argnames of all fixtures associated with a request in the
|
||||
``self._parent_request`` stack are found, and the lowest index argname is
|
||||
considered the earliest point in the list of fixture argnames where everything
|
||||
from that point onward can be considered to execute after this fixture.
|
||||
Everything before this point can be considered fixtures that this fixture
|
||||
depends on, and so this fixture should register its finalizer with all of them
|
||||
to ensure that if any of them are to be torn down, they will tear this fixture
|
||||
down first.
|
||||
|
||||
This is the first part of the list of fixture argnames that is returned. The last
|
||||
part of the list is everything in ``self.argnames`` as those are explicit
|
||||
dependees of this fixture, so this fixture should definitely register its
|
||||
finalizer with them.
|
||||
"""
|
||||
all_fix_names = request.fixturenames
|
||||
try:
|
||||
current_fix_index = all_fix_names.index(self.argname)
|
||||
except ValueError:
|
||||
current_fix_index = len(request.fixturenames)
|
||||
parent_fixture_indexes = set()
|
||||
|
||||
parent_request = request._parent_request
|
||||
while hasattr(parent_request, "_parent_request"):
|
||||
if hasattr(parent_request, "_fixturedef"):
|
||||
parent_fix_name = parent_request._fixturedef.argname
|
||||
if parent_fix_name in all_fix_names:
|
||||
parent_fixture_indexes.add(all_fix_names.index(parent_fix_name))
|
||||
parent_request = parent_request._parent_request
|
||||
|
||||
stack_slice_index = min([current_fix_index, *parent_fixture_indexes])
|
||||
active_fixture_argnames = all_fix_names[:stack_slice_index]
|
||||
return {*active_fixture_argnames, *self.argnames}
|
||||
|
||||
def cache_key(self, request):
|
||||
return request.param_index if not hasattr(request, "param") else request.param
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprExceptionInfo
|
||||
from _pytest.compat import cached_property
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import Config
|
||||
from _pytest.deprecated import NODE_USE_FROM_PARENT
|
||||
from _pytest.fixtures import FixtureDef
|
||||
@@ -27,7 +28,7 @@ from _pytest.mark.structures import MarkDecorator
|
||||
from _pytest.mark.structures import NodeKeywords
|
||||
from _pytest.outcomes import Failed
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
# Imported here due to circular import.
|
||||
from _pytest.main import Session # noqa: F401
|
||||
|
||||
@@ -50,7 +51,7 @@ def _splitnode(nodeid):
|
||||
[]
|
||||
['testing', 'code']
|
||||
['testing', 'code', 'test_excinfo.py']
|
||||
['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
|
||||
['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo']
|
||||
"""
|
||||
if nodeid == "":
|
||||
# If there is no root node at all, return an empty list so the caller's logic can remain sane
|
||||
|
||||
@@ -8,7 +8,9 @@ from typing import Optional
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
TYPE_CHECKING = False # avoid circular import through compat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from _pytest._code import Source
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.capture import MultiCapture
|
||||
from _pytest.capture import SysCapture
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.main import ExitCode
|
||||
from _pytest.main import Session
|
||||
@@ -35,7 +36,7 @@ from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.pathlib import Path
|
||||
from _pytest.reports import TestReport
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
|
||||
@@ -189,7 +190,7 @@ class ParsedCall:
|
||||
del d["_name"]
|
||||
return "<ParsedCall {!r}(**{!r})>".format(self._name, d)
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
# The class has undetermined attributes, this tells mypy about it.
|
||||
def __getattr__(self, key):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -23,9 +23,10 @@ from more_itertools.more import always_iterable
|
||||
import _pytest._code
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type # noqa: F401 (used in type string)
|
||||
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@ from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.fixtures import yield_fixture
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
|
||||
|
||||
@@ -15,12 +15,13 @@ from .reports import CollectErrorRepr
|
||||
from .reports import CollectReport
|
||||
from .reports import TestReport
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.nodes import Node
|
||||
from _pytest.outcomes import Exit
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
#
|
||||
|
||||
@@ -834,8 +834,20 @@ class TerminalReporter:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg, green=True, bold=True)
|
||||
self._outrep_summary(rep)
|
||||
self._handle_teardown_sections(rep.nodeid)
|
||||
|
||||
def print_teardown_sections(self, rep):
|
||||
def _get_teardown_reports(self, nodeid: str) -> List[TestReport]:
|
||||
return [
|
||||
report
|
||||
for report in self.getreports("")
|
||||
if report.when == "teardown" and report.nodeid == nodeid
|
||||
]
|
||||
|
||||
def _handle_teardown_sections(self, nodeid: str) -> None:
|
||||
for report in self._get_teardown_reports(nodeid):
|
||||
self.print_teardown_sections(report)
|
||||
|
||||
def print_teardown_sections(self, rep: TestReport) -> None:
|
||||
showcapture = self.config.option.showcapture
|
||||
if showcapture == "no":
|
||||
return
|
||||
@@ -859,17 +871,11 @@ class TerminalReporter:
|
||||
line = self._getcrashline(rep)
|
||||
self.write_line(line)
|
||||
else:
|
||||
teardown_sections = {}
|
||||
for report in self.getreports(""):
|
||||
if report.when == "teardown":
|
||||
teardown_sections.setdefault(report.nodeid, []).append(report)
|
||||
|
||||
for rep in reports:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg, red=True, bold=True)
|
||||
self._outrep_summary(rep)
|
||||
for report in teardown_sections.get(rep.nodeid, []):
|
||||
self.print_teardown_sections(report)
|
||||
self._handle_teardown_sections(rep.nodeid)
|
||||
|
||||
def summary_errors(self):
|
||||
if self.config.option.tbstyle != "no":
|
||||
|
||||
@@ -4,8 +4,9 @@ from typing import TypeVar
|
||||
|
||||
import attr
|
||||
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type # noqa: F401 (used in type string)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user