diff --git a/.coveragerc b/.coveragerc index ed1fb9759..fc2321e87 100644 --- a/.coveragerc +++ b/.coveragerc @@ -24,3 +24,5 @@ exclude_lines = \#\s*pragma: no cover ^\s*raise NotImplementedError\b ^\s*return NotImplemented\b + + ^\s*if TYPE_CHECKING: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d6e5ddd1d..b99e109d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,6 +11,9 @@ on: branches: - master - features + tags: + - "*" + pull_request: branches: - master @@ -156,3 +159,35 @@ jobs: flags: ${{ runner.os }} fail_ci_if_error: false 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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99d728ffa..d750b297f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: - id: pyupgrade args: [--py3-plus] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.761 + rev: v0.761 # NOTE: keep this in sync with setup.py. hooks: - id: mypy files: ^(src/|testing/) diff --git a/AUTHORS b/AUTHORS index 389eb41d8..fb20de20d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -55,6 +55,7 @@ Charles Cloud Charles Machalow Charnjit SiNGH (CCSJ) Chris Lamb +Chris NeJame Christian Boelsen Christian Fetzer Christian Neumüller diff --git a/changelog/2780.bugfix.rst b/changelog/2780.bugfix.rst new file mode 100644 index 000000000..d1d7e9914 --- /dev/null +++ b/changelog/2780.bugfix.rst @@ -0,0 +1 @@ +Captured output during teardown is shown with ``-rP``. diff --git a/changelog/6436.bugfix.rst b/changelog/6436.bugfix.rst new file mode 100644 index 000000000..9afa252d5 --- /dev/null +++ b/changelog/6436.bugfix.rst @@ -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. diff --git a/doc/en/conf.py b/doc/en/conf.py index 3fb6002bc..bd2fd9871 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -19,8 +19,9 @@ import os import sys from _pytest import __version__ as version +from _pytest.compat import TYPE_CHECKING -if False: # TYPE_CHECKING +if TYPE_CHECKING: import sphinx.application diff --git a/scripts/publish-gh-release-notes.py b/scripts/publish-gh-release-notes.py index 34ccaa63d..f8d8b3986 100644 --- a/scripts/publish-gh-release-notes.py +++ b/scripts/publish-gh-release-notes.py @@ -68,19 +68,21 @@ def main(argv): if len(argv) > 1: tag_name = argv[1] else: - tag_name = os.environ.get("TRAVIS_TAG") + tag_name = os.environ.get("GITHUB_REF") 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 + if tag_name.startswith("refs/tags/"): + tag_name = tag_name[len("refs/tags/") :] token = os.environ.get("GH_RELEASE_NOTES_TOKEN") if not token: print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr) return 1 - slug = os.environ.get("TRAVIS_REPO_SLUG") + slug = os.environ.get("GITHUB_REPOSITORY") if not slug: - print("TRAVIS_REPO_SLUG not set", file=sys.stderr) + print("GITHUB_REPOSITORY not set", file=sys.stderr) return 1 rst_body = parse_changelog(tag_name) diff --git a/setup.py b/setup.py index d7f3d7dbf..892b55aed 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,10 @@ def main(): "nose", "requests", "xmlschema", - ] + ], + "checkqa-mypy": [ + "mypy==v0.761", # keep this in sync with .pre-commit-config.yaml. + ], }, install_requires=INSTALL_REQUIRES, ) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index d1a8ec2f1..f0e503f4f 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -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 diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 9eec0aeda..b6576c8a6 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -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) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index fb965b261..d6a704b1d 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -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 diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index d0870ed56..fa9d8f5dc 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -15,9 +15,10 @@ from typing import Union import py +from _pytest.compat import TYPE_CHECKING from _pytest.config.exceptions import UsageError -if False: # TYPE_CHECKING +if TYPE_CHECKING: from typing import NoReturn from typing_extensions import Literal # noqa: F401 diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 728246dfc..d9b5f7543 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -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 diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 3475ac9c2..30eaba035 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -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 diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 44802e000..9482dc8f4 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -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 diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 3eaafa91d..3d75a6a09 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -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 diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 947136625..0cd1072ef 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -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 diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index d2313f280..9c60dc1aa 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -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 "".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() diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 9f206ce9b..5e5eddc5b 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -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) diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 5cf32c894..956a90783 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -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 diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 50e4d4307..417783b47 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -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 # diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index a8ab5c130..395a9ff8a 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -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": diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 22cb17dba..2e03c578c 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -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) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 52fd32cc4..3e3b41812 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1716,6 +1716,138 @@ class TestAutouseDiscovery: 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: def test_autouse_conftest_mid_directory(self, testdir): pkgdir = testdir.mkpydir("xyz123") diff --git a/testing/test_compat.py b/testing/test_compat.py index 04d818b4e..45468b5f8 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -146,12 +146,16 @@ def test_is_generator_async_gen_syntax(testdir): class ErrorsHelper: + @property + def raise_baseexception(self): + raise BaseException("base exception should be raised") + @property def raise_exception(self): raise Exception("exception should be catched") @property - def raise_fail(self): + def raise_fail_outcome(self): pytest.fail("fail should be catched") @@ -160,13 +164,15 @@ def test_helper_failures(): with pytest.raises(Exception): helper.raise_exception with pytest.raises(OutcomeException): - helper.raise_fail + helper.raise_fail_outcome def test_safe_getattr(): helper = ErrorsHelper() 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(): diff --git a/testing/test_terminal.py b/testing/test_terminal.py index c109d2c78..98d7d96bc 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -821,8 +821,15 @@ def test_pass_reporting_on_fail(testdir): def test_pass_output_reporting(testdir): testdir.makepyfile( """ + def setup_module(): + print("setup_module") + + def teardown_module(): + print("teardown_module") + def test_pass_has_output(): print("Four score and seven years ago...") + def test_pass_no_output(): pass """ @@ -837,8 +844,12 @@ def test_pass_output_reporting(testdir): [ "*= PASSES =*", "*_ test_pass_has_output _*", + "*- Captured stdout setup -*", + "setup_module", "*- Captured stdout call -*", "Four score and seven years ago...", + "*- Captured stdout teardown -*", + "teardown_module", "*= short test summary info =*", "PASSED test_pass_output_reporting.py::test_pass_has_output", "PASSED test_pass_output_reporting.py::test_pass_no_output", diff --git a/tox.ini b/tox.ini index c26b7cf82..cf50945fb 100644 --- a/tox.ini +++ b/tox.ini @@ -55,6 +55,10 @@ basepython = python3 deps = pre-commit>=1.11.0 commands = pre-commit run --all-files --show-diff-on-failure {posargs:} +[testenv:mypy] +extras = checkqa-mypy, testing +commands = mypy {posargs:src testing} + [testenv:docs] basepython = python3 usedevelop = True @@ -131,7 +135,7 @@ commands = python scripts/release.py {posargs} description = create GitHub release after deployment basepython = python3 usedevelop = True -passenv = GH_RELEASE_NOTES_TOKEN TRAVIS_TAG TRAVIS_REPO_SLUG +passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY deps = github3.py pypandoc