Merge pull request #6478 from blueyed/merge-master-into-features

Merge master into features
This commit is contained in:
Daniel Hahler 2020-01-16 21:14:30 +01:00 committed by GitHub
commit a4f5b8a4d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 318 additions and 38 deletions

View File

@ -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:

View File

@ -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

View File

@ -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/)

View File

@ -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

View File

@ -0,0 +1 @@
Captured output during teardown is shown with ``-rP``.

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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,
) )

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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
# #

View File

@ -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":

View File

@ -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)

View File

@ -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")

View File

@ -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():

View File

@ -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",

View File

@ -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