Merge pull request #6478 from blueyed/merge-master-into-features
Merge master into features
This commit is contained in:
commit
a4f5b8a4d6
|
@ -24,3 +24,5 @@ exclude_lines =
|
||||||
\#\s*pragma: no cover
|
\#\s*pragma: no cover
|
||||||
^\s*raise NotImplementedError\b
|
^\s*raise NotImplementedError\b
|
||||||
^\s*return NotImplemented\b
|
^\s*return NotImplemented\b
|
||||||
|
|
||||||
|
^\s*if TYPE_CHECKING:
|
||||||
|
|
|
@ -11,6 +11,9 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- features
|
- features
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
@ -156,3 +159,35 @@ jobs:
|
||||||
flags: ${{ runner.os }}
|
flags: ${{ runner.os }}
|
||||||
fail_ci_if_error: false
|
fail_ci_if_error: false
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
needs: [build]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: "3.7"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade wheel setuptools tox
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
python setup.py sdist bdist_wheel
|
||||||
|
- name: Publish package to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@master
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.pypi_token }}
|
||||||
|
- name: Publish GitHub release notes
|
||||||
|
env:
|
||||||
|
GH_RELEASE_NOTES_TOKEN: ${{ secrets.release_notes }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get install pandoc
|
||||||
|
tox -e publish-gh-release-notes
|
||||||
|
|
|
@ -37,7 +37,7 @@ repos:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py3-plus]
|
args: [--py3-plus]
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.761
|
rev: v0.761 # NOTE: keep this in sync with setup.py.
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/)
|
files: ^(src/|testing/)
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -55,6 +55,7 @@ Charles Cloud
|
||||||
Charles Machalow
|
Charles Machalow
|
||||||
Charnjit SiNGH (CCSJ)
|
Charnjit SiNGH (CCSJ)
|
||||||
Chris Lamb
|
Chris Lamb
|
||||||
|
Chris NeJame
|
||||||
Christian Boelsen
|
Christian Boelsen
|
||||||
Christian Fetzer
|
Christian Fetzer
|
||||||
Christian Neumüller
|
Christian Neumüller
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Captured output during teardown is shown with ``-rP``.
|
|
@ -0,0 +1,3 @@
|
||||||
|
:class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and
|
||||||
|
parameterized fixtures that execute before them in the fixture stack so they are torn
|
||||||
|
down at the right times, and in the right order.
|
|
@ -19,8 +19,9 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from _pytest import __version__ as version
|
from _pytest import __version__ as version
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
import sphinx.application
|
import sphinx.application
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,19 +68,21 @@ def main(argv):
|
||||||
if len(argv) > 1:
|
if len(argv) > 1:
|
||||||
tag_name = argv[1]
|
tag_name = argv[1]
|
||||||
else:
|
else:
|
||||||
tag_name = os.environ.get("TRAVIS_TAG")
|
tag_name = os.environ.get("GITHUB_REF")
|
||||||
if not tag_name:
|
if not tag_name:
|
||||||
print("tag_name not given and $TRAVIS_TAG not set", file=sys.stderr)
|
print("tag_name not given and $GITHUB_REF not set", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
if tag_name.startswith("refs/tags/"):
|
||||||
|
tag_name = tag_name[len("refs/tags/") :]
|
||||||
|
|
||||||
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
|
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
|
||||||
if not token:
|
if not token:
|
||||||
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
|
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
slug = os.environ.get("TRAVIS_REPO_SLUG")
|
slug = os.environ.get("GITHUB_REPOSITORY")
|
||||||
if not slug:
|
if not slug:
|
||||||
print("TRAVIS_REPO_SLUG not set", file=sys.stderr)
|
print("GITHUB_REPOSITORY not set", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
rst_body = parse_changelog(tag_name)
|
rst_body = parse_changelog(tag_name)
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -29,7 +29,10 @@ def main():
|
||||||
"nose",
|
"nose",
|
||||||
"requests",
|
"requests",
|
||||||
"xmlschema",
|
"xmlschema",
|
||||||
]
|
],
|
||||||
|
"checkqa-mypy": [
|
||||||
|
"mypy==v0.761", # keep this in sync with .pre-commit-config.yaml.
|
||||||
|
],
|
||||||
},
|
},
|
||||||
install_requires=INSTALL_REQUIRES,
|
install_requires=INSTALL_REQUIRES,
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,8 +32,9 @@ import _pytest
|
||||||
from _pytest._io.saferepr import safeformat
|
from _pytest._io.saferepr import safeformat
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import overload
|
from _pytest.compat import overload
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
from weakref import ReferenceType # noqa: F401
|
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 fail
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
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)
|
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 ExceptionInfo
|
||||||
from _pytest._code import filter_traceback
|
from _pytest._code import filter_traceback
|
||||||
from _pytest.compat import importlib_metadata
|
from _pytest.compat import importlib_metadata
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
from _pytest.warning_types import PytestConfigWarning
|
from _pytest.warning_types import PytestConfigWarning
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from .argparsing import Argument
|
from .argparsing import Argument
|
||||||
|
|
|
@ -15,9 +15,10 @@ from typing import Union
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ from typing import Optional
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from .exceptions import UsageError
|
from .exceptions import UsageError
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
|
||||||
if False:
|
if TYPE_CHECKING:
|
||||||
from . import Config # noqa: F401
|
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 ReprFileLocation
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
from _pytest.python_api import approx
|
from _pytest.python_api import approx
|
||||||
from _pytest.warning_types import PytestWarning
|
from _pytest.warning_types import PytestWarning
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
import doctest
|
import doctest
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,13 @@ from _pytest.compat import getlocation
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
|
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
|
||||||
from _pytest.deprecated import FUNCARGNAMES
|
from _pytest.deprecated import FUNCARGNAMES
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
from _pytest.outcomes import TEST_OUTCOME
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
@ -880,9 +881,7 @@ class FixtureDef:
|
||||||
self._finalizers = []
|
self._finalizers = []
|
||||||
|
|
||||||
def execute(self, request):
|
def execute(self, request):
|
||||||
# get required arguments and register our own finish()
|
for argname in self._dependee_fixture_argnames(request):
|
||||||
# with their finalization
|
|
||||||
for argname in self.argnames:
|
|
||||||
fixturedef = request._get_active_fixturedef(argname)
|
fixturedef = request._get_active_fixturedef(argname)
|
||||||
if argname != "request":
|
if argname != "request":
|
||||||
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
|
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
|
||||||
|
@ -905,6 +904,61 @@ class FixtureDef:
|
||||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
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):
|
def cache_key(self, request):
|
||||||
return request.param_index if not hasattr(request, "param") else request.param
|
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._code.code import ReprExceptionInfo
|
||||||
from _pytest.compat import cached_property
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import getfslineno
|
from _pytest.compat import getfslineno
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.deprecated import NODE_USE_FROM_PARENT
|
from _pytest.deprecated import NODE_USE_FROM_PARENT
|
||||||
from _pytest.fixtures import FixtureDef
|
from _pytest.fixtures import FixtureDef
|
||||||
|
@ -27,7 +28,7 @@ from _pytest.mark.structures import MarkDecorator
|
||||||
from _pytest.mark.structures import NodeKeywords
|
from _pytest.mark.structures import NodeKeywords
|
||||||
from _pytest.outcomes import Failed
|
from _pytest.outcomes import Failed
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
# Imported here due to circular import.
|
# Imported here due to circular import.
|
||||||
from _pytest.main import Session # noqa: F401
|
from _pytest.main import Session # noqa: F401
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ def _splitnode(nodeid):
|
||||||
[]
|
[]
|
||||||
['testing', 'code']
|
['testing', 'code']
|
||||||
['testing', 'code', 'test_excinfo.py']
|
['testing', 'code', 'test_excinfo.py']
|
||||||
['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
|
['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo']
|
||||||
"""
|
"""
|
||||||
if nodeid == "":
|
if nodeid == "":
|
||||||
# If there is no root node at all, return an empty list so the caller's logic can remain sane
|
# 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
|
from packaging.version import Version
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
TYPE_CHECKING = False # avoid circular import through compat
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ from _pytest._code import Source
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.capture import MultiCapture
|
from _pytest.capture import MultiCapture
|
||||||
from _pytest.capture import SysCapture
|
from _pytest.capture import SysCapture
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.main import ExitCode
|
from _pytest.main import ExitCode
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
|
@ -35,7 +36,7 @@ from _pytest.monkeypatch import MonkeyPatch
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
from _pytest.reports import TestReport
|
from _pytest.reports import TestReport
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,7 +190,7 @@ class ParsedCall:
|
||||||
del d["_name"]
|
del d["_name"]
|
||||||
return "<ParsedCall {!r}(**{!r})>".format(self._name, d)
|
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.
|
# The class has undetermined attributes, this tells mypy about it.
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
@ -23,9 +23,10 @@ from more_itertools.more import always_iterable
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest.compat import overload
|
from _pytest.compat import overload
|
||||||
from _pytest.compat import STRING_TYPES
|
from _pytest.compat import STRING_TYPES
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type # noqa: F401 (used in type string)
|
from typing import Type # noqa: F401 (used in type string)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,11 @@ from typing import Tuple
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from _pytest.compat import overload
|
from _pytest.compat import overload
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.fixtures import yield_fixture
|
from _pytest.fixtures import yield_fixture
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,13 @@ from .reports import CollectErrorRepr
|
||||||
from .reports import CollectReport
|
from .reports import CollectReport
|
||||||
from .reports import TestReport
|
from .reports import TestReport
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.nodes import Node
|
from _pytest.nodes import Node
|
||||||
from _pytest.outcomes import Exit
|
from _pytest.outcomes import Exit
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
from _pytest.outcomes import TEST_OUTCOME
|
||||||
|
|
||||||
if False: # TYPE_CHECKING
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -834,8 +834,20 @@ class TerminalReporter:
|
||||||
msg = self._getfailureheadline(rep)
|
msg = self._getfailureheadline(rep)
|
||||||
self.write_sep("_", msg, green=True, bold=True)
|
self.write_sep("_", msg, green=True, bold=True)
|
||||||
self._outrep_summary(rep)
|
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
|
showcapture = self.config.option.showcapture
|
||||||
if showcapture == "no":
|
if showcapture == "no":
|
||||||
return
|
return
|
||||||
|
@ -859,17 +871,11 @@ class TerminalReporter:
|
||||||
line = self._getcrashline(rep)
|
line = self._getcrashline(rep)
|
||||||
self.write_line(line)
|
self.write_line(line)
|
||||||
else:
|
else:
|
||||||
teardown_sections = {}
|
|
||||||
for report in self.getreports(""):
|
|
||||||
if report.when == "teardown":
|
|
||||||
teardown_sections.setdefault(report.nodeid, []).append(report)
|
|
||||||
|
|
||||||
for rep in reports:
|
for rep in reports:
|
||||||
msg = self._getfailureheadline(rep)
|
msg = self._getfailureheadline(rep)
|
||||||
self.write_sep("_", msg, red=True, bold=True)
|
self.write_sep("_", msg, red=True, bold=True)
|
||||||
self._outrep_summary(rep)
|
self._outrep_summary(rep)
|
||||||
for report in teardown_sections.get(rep.nodeid, []):
|
self._handle_teardown_sections(rep.nodeid)
|
||||||
self.print_teardown_sections(report)
|
|
||||||
|
|
||||||
def summary_errors(self):
|
def summary_errors(self):
|
||||||
if self.config.option.tbstyle != "no":
|
if self.config.option.tbstyle != "no":
|
||||||
|
|
|
@ -4,8 +4,9 @@ from typing import TypeVar
|
||||||
|
|
||||||
import attr
|
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)
|
from typing import Type # noqa: F401 (used in type string)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1716,6 +1716,138 @@ class TestAutouseDiscovery:
|
||||||
reprec.assertoutcome(passed=3)
|
reprec.assertoutcome(passed=3)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMultiLevelAutouseAndParameterization:
|
||||||
|
def test_setup_and_teardown_order(self, testdir):
|
||||||
|
"""Tests that parameterized fixtures effect subsequent fixtures. (#6436)
|
||||||
|
|
||||||
|
If a fixture uses a parameterized fixture, or, for any other reason, is executed
|
||||||
|
after the parameterized fixture in the fixture stack, then it should be affected
|
||||||
|
by the parameterization, and as a result, should be torn down before the
|
||||||
|
parameterized fixture, every time the parameterized fixture is torn down. This
|
||||||
|
should be the case even if autouse is involved and/or the linear order of
|
||||||
|
fixture execution isn't deterministic. In other words, before any fixture can be
|
||||||
|
torn down, every fixture that was executed after it must also be torn down.
|
||||||
|
"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
test_auto="""
|
||||||
|
import pytest
|
||||||
|
def f(param):
|
||||||
|
return param
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def s_fix(request):
|
||||||
|
yield
|
||||||
|
@pytest.fixture(scope="package", params=["p1", "p2"], ids=f, autouse=True)
|
||||||
|
def p_fix(request):
|
||||||
|
yield
|
||||||
|
@pytest.fixture(scope="module", params=["m1", "m2"], ids=f, autouse=True)
|
||||||
|
def m_fix(request):
|
||||||
|
yield
|
||||||
|
@pytest.fixture(scope="class", autouse=True)
|
||||||
|
def another_c_fix(m_fix):
|
||||||
|
yield
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def c_fix():
|
||||||
|
yield
|
||||||
|
@pytest.fixture(scope="function", params=["f1", "f2"], ids=f, autouse=True)
|
||||||
|
def f_fix(request):
|
||||||
|
yield
|
||||||
|
class TestFixtures:
|
||||||
|
def test_a(self, c_fix):
|
||||||
|
pass
|
||||||
|
def test_b(self, c_fix):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--setup-plan")
|
||||||
|
test_fixtures_used = (
|
||||||
|
"(fixtures used: another_c_fix, c_fix, f_fix, m_fix, p_fix, request, s_fix)"
|
||||||
|
)
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
"""
|
||||||
|
SETUP S s_fix
|
||||||
|
SETUP P p_fix[p1]
|
||||||
|
SETUP M m_fix[m1]
|
||||||
|
SETUP C another_c_fix (fixtures used: m_fix)
|
||||||
|
SETUP C c_fix
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_a[p1-m1-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_a[p1-m1-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_b[p1-m1-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_b[p1-m1-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
TEARDOWN C c_fix
|
||||||
|
TEARDOWN C another_c_fix
|
||||||
|
TEARDOWN M m_fix[m1]
|
||||||
|
SETUP M m_fix[m2]
|
||||||
|
SETUP C another_c_fix (fixtures used: m_fix)
|
||||||
|
SETUP C c_fix
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_a[p1-m2-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_a[p1-m2-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_b[p1-m2-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_b[p1-m2-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
TEARDOWN C c_fix
|
||||||
|
TEARDOWN C another_c_fix
|
||||||
|
TEARDOWN M m_fix[m2]
|
||||||
|
TEARDOWN P p_fix[p1]
|
||||||
|
SETUP P p_fix[p2]
|
||||||
|
SETUP M m_fix[m1]
|
||||||
|
SETUP C another_c_fix (fixtures used: m_fix)
|
||||||
|
SETUP C c_fix
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_a[p2-m1-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_a[p2-m1-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_b[p2-m1-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_b[p2-m1-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
TEARDOWN C c_fix
|
||||||
|
TEARDOWN C another_c_fix
|
||||||
|
TEARDOWN M m_fix[m1]
|
||||||
|
SETUP M m_fix[m2]
|
||||||
|
SETUP C another_c_fix (fixtures used: m_fix)
|
||||||
|
SETUP C c_fix
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_a[p2-m2-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_a[p2-m2-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
SETUP F f_fix[f1]
|
||||||
|
test_auto.py::TestFixtures::test_b[p2-m2-f1] {0}
|
||||||
|
TEARDOWN F f_fix[f1]
|
||||||
|
SETUP F f_fix[f2]
|
||||||
|
test_auto.py::TestFixtures::test_b[p2-m2-f2] {0}
|
||||||
|
TEARDOWN F f_fix[f2]
|
||||||
|
TEARDOWN C c_fix
|
||||||
|
TEARDOWN C another_c_fix
|
||||||
|
TEARDOWN M m_fix[m2]
|
||||||
|
TEARDOWN P p_fix[p2]
|
||||||
|
TEARDOWN S s_fix
|
||||||
|
""".format(
|
||||||
|
test_fixtures_used
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestAutouseManagement:
|
class TestAutouseManagement:
|
||||||
def test_autouse_conftest_mid_directory(self, testdir):
|
def test_autouse_conftest_mid_directory(self, testdir):
|
||||||
pkgdir = testdir.mkpydir("xyz123")
|
pkgdir = testdir.mkpydir("xyz123")
|
||||||
|
|
|
@ -146,12 +146,16 @@ def test_is_generator_async_gen_syntax(testdir):
|
||||||
|
|
||||||
|
|
||||||
class ErrorsHelper:
|
class ErrorsHelper:
|
||||||
|
@property
|
||||||
|
def raise_baseexception(self):
|
||||||
|
raise BaseException("base exception should be raised")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def raise_exception(self):
|
def raise_exception(self):
|
||||||
raise Exception("exception should be catched")
|
raise Exception("exception should be catched")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def raise_fail(self):
|
def raise_fail_outcome(self):
|
||||||
pytest.fail("fail should be catched")
|
pytest.fail("fail should be catched")
|
||||||
|
|
||||||
|
|
||||||
|
@ -160,13 +164,15 @@ def test_helper_failures():
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
helper.raise_exception
|
helper.raise_exception
|
||||||
with pytest.raises(OutcomeException):
|
with pytest.raises(OutcomeException):
|
||||||
helper.raise_fail
|
helper.raise_fail_outcome
|
||||||
|
|
||||||
|
|
||||||
def test_safe_getattr():
|
def test_safe_getattr():
|
||||||
helper = ErrorsHelper()
|
helper = ErrorsHelper()
|
||||||
assert safe_getattr(helper, "raise_exception", "default") == "default"
|
assert safe_getattr(helper, "raise_exception", "default") == "default"
|
||||||
assert safe_getattr(helper, "raise_fail", "default") == "default"
|
assert safe_getattr(helper, "raise_fail_outcome", "default") == "default"
|
||||||
|
with pytest.raises(BaseException):
|
||||||
|
assert safe_getattr(helper, "raise_baseexception", "default")
|
||||||
|
|
||||||
|
|
||||||
def test_safe_isclass():
|
def test_safe_isclass():
|
||||||
|
|
|
@ -821,8 +821,15 @@ def test_pass_reporting_on_fail(testdir):
|
||||||
def test_pass_output_reporting(testdir):
|
def test_pass_output_reporting(testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
def setup_module():
|
||||||
|
print("setup_module")
|
||||||
|
|
||||||
|
def teardown_module():
|
||||||
|
print("teardown_module")
|
||||||
|
|
||||||
def test_pass_has_output():
|
def test_pass_has_output():
|
||||||
print("Four score and seven years ago...")
|
print("Four score and seven years ago...")
|
||||||
|
|
||||||
def test_pass_no_output():
|
def test_pass_no_output():
|
||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
|
@ -837,8 +844,12 @@ def test_pass_output_reporting(testdir):
|
||||||
[
|
[
|
||||||
"*= PASSES =*",
|
"*= PASSES =*",
|
||||||
"*_ test_pass_has_output _*",
|
"*_ test_pass_has_output _*",
|
||||||
|
"*- Captured stdout setup -*",
|
||||||
|
"setup_module",
|
||||||
"*- Captured stdout call -*",
|
"*- Captured stdout call -*",
|
||||||
"Four score and seven years ago...",
|
"Four score and seven years ago...",
|
||||||
|
"*- Captured stdout teardown -*",
|
||||||
|
"teardown_module",
|
||||||
"*= short test summary info =*",
|
"*= short test summary info =*",
|
||||||
"PASSED test_pass_output_reporting.py::test_pass_has_output",
|
"PASSED test_pass_output_reporting.py::test_pass_has_output",
|
||||||
"PASSED test_pass_output_reporting.py::test_pass_no_output",
|
"PASSED test_pass_output_reporting.py::test_pass_no_output",
|
||||||
|
|
6
tox.ini
6
tox.ini
|
@ -55,6 +55,10 @@ basepython = python3
|
||||||
deps = pre-commit>=1.11.0
|
deps = pre-commit>=1.11.0
|
||||||
commands = pre-commit run --all-files --show-diff-on-failure {posargs:}
|
commands = pre-commit run --all-files --show-diff-on-failure {posargs:}
|
||||||
|
|
||||||
|
[testenv:mypy]
|
||||||
|
extras = checkqa-mypy, testing
|
||||||
|
commands = mypy {posargs:src testing}
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
|
@ -131,7 +135,7 @@ commands = python scripts/release.py {posargs}
|
||||||
description = create GitHub release after deployment
|
description = create GitHub release after deployment
|
||||||
basepython = python3
|
basepython = python3
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
passenv = GH_RELEASE_NOTES_TOKEN TRAVIS_TAG TRAVIS_REPO_SLUG
|
passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY
|
||||||
deps =
|
deps =
|
||||||
github3.py
|
github3.py
|
||||||
pypandoc
|
pypandoc
|
||||||
|
|
Loading…
Reference in New Issue