From 54ad048be7182018e70479bd3d9b88bcb6376c00 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 May 2020 14:40:17 +0300 Subject: [PATCH] Enable check_untyped_defs mypy option for testing/ too --- setup.cfg | 4 +- src/_pytest/_code/code.py | 3 +- src/_pytest/logging.py | 12 +- src/_pytest/pytester.py | 2 +- src/_pytest/python_api.py | 1 + testing/code/test_excinfo.py | 87 +++-- testing/deprecated_test.py | 6 +- .../dataclasses/test_compare_dataclasses.py | 4 +- ...ompare_dataclasses_field_comparison_off.py | 4 +- .../test_compare_dataclasses_verbose.py | 4 +- .../test_compare_two_different_dataclasses.py | 8 +- testing/example_scripts/issue_519.py | 4 +- .../unittest/test_unittest_asyncio.py | 3 +- .../unittest/test_unittest_asynctest.py | 3 +- testing/io/test_saferepr.py | 12 +- testing/logging/test_formatter.py | 15 +- testing/logging/test_reporting.py | 13 +- testing/python/approx.py | 3 +- testing/python/collect.py | 30 +- testing/python/integration.py | 29 +- testing/python/raises.py | 32 +- testing/test_assertion.py | 90 +++-- testing/test_assertrewrite.py | 365 ++++++++++-------- testing/test_capture.py | 6 +- testing/test_collection.py | 3 +- testing/test_config.py | 40 +- testing/test_debugging.py | 8 +- testing/test_doctest.py | 8 +- testing/test_junitxml.py | 37 +- testing/test_mark.py | 17 +- testing/test_monkeypatch.py | 40 +- testing/test_nodes.py | 23 +- testing/test_pastebin.py | 7 +- testing/test_pluginmanager.py | 15 +- testing/test_reports.py | 16 +- testing/test_runner_xunit.py | 8 +- testing/test_skipping.py | 8 +- testing/test_terminal.py | 25 +- testing/test_tmpdir.py | 30 +- testing/test_unittest.py | 9 +- testing/test_warnings.py | 7 +- 41 files changed, 598 insertions(+), 443 deletions(-) diff --git a/setup.cfg b/setup.cfg index a42ae68ae..5dc778d99 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,6 +91,7 @@ formats = sdist.tgz,bdist_wheel [mypy] mypy_path = src +check_untyped_defs = True ignore_missing_imports = True no_implicit_optional = True show_error_codes = True @@ -98,6 +99,3 @@ strict_equality = True warn_redundant_casts = True warn_return_any = True warn_unused_configs = True - -[mypy-_pytest.*] -check_untyped_defs = True diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 7b17d7612..09b2c1af5 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -15,6 +15,7 @@ from typing import Dict from typing import Generic from typing import Iterable from typing import List +from typing import Mapping from typing import Optional from typing import Pattern from typing import Sequence @@ -728,7 +729,7 @@ class FormattedExcinfo: failindent = indentstr return lines - def repr_locals(self, locals: Dict[str, object]) -> Optional["ReprLocals"]: + def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index ce3a18f03..c1f13b701 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -96,7 +96,7 @@ class PercentStyleMultiline(logging.PercentStyle): formats the message as if each line were logged separately. """ - def __init__(self, fmt: str, auto_indent: Union[int, str, bool]) -> None: + def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: super().__init__(fmt) self._auto_indent = self._get_auto_indent(auto_indent) @@ -109,7 +109,7 @@ class PercentStyleMultiline(logging.PercentStyle): return tmp @staticmethod - def _get_auto_indent(auto_indent_option: Union[int, str, bool]) -> int: + def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: """Determines the current auto indentation setting Specify auto indent behavior (on/off/fixed) by passing in @@ -139,7 +139,9 @@ class PercentStyleMultiline(logging.PercentStyle): >0 (explicitly set indentation position). """ - if type(auto_indent_option) is int: + if auto_indent_option is None: + return 0 + elif type(auto_indent_option) is int: return int(auto_indent_option) elif type(auto_indent_option) is str: try: @@ -732,7 +734,9 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): stream = None # type: TerminalReporter # type: ignore def __init__( - self, terminal_reporter: TerminalReporter, capture_manager: CaptureManager + self, + terminal_reporter: TerminalReporter, + capture_manager: Optional[CaptureManager], ) -> None: """ :param _pytest.terminal.TerminalReporter terminal_reporter: diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 754ecc10f..8df5992d6 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -401,7 +401,7 @@ def _sys_snapshot(): @pytest.fixture -def _config_for_test(): +def _config_for_test() -> Generator[Config, None, None]: from _pytest.config import get_config config = get_config() diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index abace3196..c185a0676 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -712,6 +712,7 @@ def raises( # noqa: F811 fail(message) +# This doesn't work with mypy for now. Use fail.Exception instead. raises.Exception = fail.Exception # type: ignore diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 08c0619e3..0ff00bcaa 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -4,6 +4,7 @@ import os import queue import sys import textwrap +from typing import Tuple from typing import Union import py @@ -14,6 +15,7 @@ from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo from _pytest._io import TerminalWriter +from _pytest.compat import TYPE_CHECKING from _pytest.pytester import LineMatcher try: @@ -23,6 +25,9 @@ except ImportError: else: invalidate_import_caches = getattr(importlib, "invalidate_caches", None) +if TYPE_CHECKING: + from _pytest._code.code import _TracebackStyle + @pytest.fixture def limited_recursion_depth(): @@ -40,10 +45,11 @@ def test_excinfo_simple() -> None: assert info.type == ValueError -def test_excinfo_from_exc_info_simple(): +def test_excinfo_from_exc_info_simple() -> None: try: raise ValueError except ValueError as e: + assert e.__traceback__ is not None info = _pytest._code.ExceptionInfo.from_exc_info((type(e), e, e.__traceback__)) assert info.type == ValueError @@ -317,25 +323,25 @@ def test_excinfo_exconly(): assert msg.endswith("world") -def test_excinfo_repr_str(): - excinfo = pytest.raises(ValueError, h) - assert repr(excinfo) == "" - assert str(excinfo) == "" +def test_excinfo_repr_str() -> None: + excinfo1 = pytest.raises(ValueError, h) + assert repr(excinfo1) == "" + assert str(excinfo1) == "" class CustomException(Exception): def __repr__(self): return "custom_repr" - def raises(): + def raises() -> None: raise CustomException() - excinfo = pytest.raises(CustomException, raises) - assert repr(excinfo) == "" - assert str(excinfo) == "" + excinfo2 = pytest.raises(CustomException, raises) + assert repr(excinfo2) == "" + assert str(excinfo2) == "" -def test_excinfo_for_later(): - e = ExceptionInfo.for_later() +def test_excinfo_for_later() -> None: + e = ExceptionInfo[BaseException].for_later() assert "for raises" in repr(e) assert "for raises" in str(e) @@ -463,7 +469,7 @@ class TestFormattedExcinfo: assert lines[0] == "| def f(x):" assert lines[1] == " pass" - def test_repr_source_excinfo(self): + def test_repr_source_excinfo(self) -> None: """ check if indentation is right """ pr = FormattedExcinfo() excinfo = self.excinfo_from_exec( @@ -475,6 +481,7 @@ class TestFormattedExcinfo: ) pr = FormattedExcinfo() source = pr._getentrysource(excinfo.traceback[-1]) + assert source is not None lines = pr.get_source(source, 1, excinfo) assert lines == [" def f():", "> assert 0", "E AssertionError"] @@ -522,17 +529,18 @@ raise ValueError() assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" - def test_repr_local(self): + def test_repr_local(self) -> None: p = FormattedExcinfo(showlocals=True) loc = {"y": 5, "z": 7, "x": 3, "@x": 2, "__builtins__": {}} reprlocals = p.repr_locals(loc) + assert reprlocals is not None assert reprlocals.lines assert reprlocals.lines[0] == "__builtins__ = " assert reprlocals.lines[1] == "x = 3" assert reprlocals.lines[2] == "y = 5" assert reprlocals.lines[3] == "z = 7" - def test_repr_local_with_error(self): + def test_repr_local_with_error(self) -> None: class ObjWithErrorInRepr: def __repr__(self): raise NotImplementedError @@ -540,11 +548,12 @@ raise ValueError() p = FormattedExcinfo(showlocals=True, truncate_locals=False) loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} reprlocals = p.repr_locals(loc) + assert reprlocals is not None assert reprlocals.lines assert reprlocals.lines[0] == "__builtins__ = " assert "[NotImplementedError() raised in repr()]" in reprlocals.lines[1] - def test_repr_local_with_exception_in_class_property(self): + def test_repr_local_with_exception_in_class_property(self) -> None: class ExceptionWithBrokenClass(Exception): # Type ignored because it's bypassed intentionally. @property # type: ignore @@ -558,23 +567,26 @@ raise ValueError() p = FormattedExcinfo(showlocals=True, truncate_locals=False) loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} reprlocals = p.repr_locals(loc) + assert reprlocals is not None assert reprlocals.lines assert reprlocals.lines[0] == "__builtins__ = " assert "[ExceptionWithBrokenClass() raised in repr()]" in reprlocals.lines[1] - def test_repr_local_truncated(self): + def test_repr_local_truncated(self) -> None: loc = {"l": [i for i in range(10)]} p = FormattedExcinfo(showlocals=True) truncated_reprlocals = p.repr_locals(loc) + assert truncated_reprlocals is not None assert truncated_reprlocals.lines assert truncated_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, ...]" q = FormattedExcinfo(showlocals=True, truncate_locals=False) full_reprlocals = q.repr_locals(loc) + assert full_reprlocals is not None assert full_reprlocals.lines assert full_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" - def test_repr_tracebackentry_lines(self, importasmod): + def test_repr_tracebackentry_lines(self, importasmod) -> None: mod = importasmod( """ def func1(): @@ -602,11 +614,12 @@ raise ValueError() assert not lines[4:] loc = repr_entry.reprfileloc + assert loc is not None assert loc.path == mod.__file__ assert loc.lineno == 3 # assert loc.message == "ValueError: hello" - def test_repr_tracebackentry_lines2(self, importasmod, tw_mock): + def test_repr_tracebackentry_lines2(self, importasmod, tw_mock) -> None: mod = importasmod( """ def func1(m, x, y, z): @@ -618,6 +631,7 @@ raise ValueError() entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) + assert reprfuncargs is not None assert reprfuncargs.args[0] == ("m", repr("m" * 90)) assert reprfuncargs.args[1] == ("x", "5") assert reprfuncargs.args[2] == ("y", "13") @@ -625,13 +639,14 @@ raise ValueError() p = FormattedExcinfo(funcargs=True) repr_entry = p.repr_traceback_entry(entry) + assert repr_entry.reprfuncargs is not None assert repr_entry.reprfuncargs.args == reprfuncargs.args repr_entry.toterminal(tw_mock) assert tw_mock.lines[0] == "m = " + repr("m" * 90) assert tw_mock.lines[1] == "x = 5, y = 13" assert tw_mock.lines[2] == "z = " + repr("z" * 120) - def test_repr_tracebackentry_lines_var_kw_args(self, importasmod, tw_mock): + def test_repr_tracebackentry_lines_var_kw_args(self, importasmod, tw_mock) -> None: mod = importasmod( """ def func1(x, *y, **z): @@ -643,17 +658,19 @@ raise ValueError() entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) + assert reprfuncargs is not None assert reprfuncargs.args[0] == ("x", repr("a")) assert reprfuncargs.args[1] == ("y", repr(("b",))) assert reprfuncargs.args[2] == ("z", repr({"c": "d"})) p = FormattedExcinfo(funcargs=True) repr_entry = p.repr_traceback_entry(entry) + assert repr_entry.reprfuncargs assert repr_entry.reprfuncargs.args == reprfuncargs.args repr_entry.toterminal(tw_mock) assert tw_mock.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}" - def test_repr_tracebackentry_short(self, importasmod): + def test_repr_tracebackentry_short(self, importasmod) -> None: mod = importasmod( """ def func1(): @@ -668,6 +685,7 @@ raise ValueError() lines = reprtb.lines basename = py.path.local(mod.__file__).basename assert lines[0] == " func1()" + assert reprtb.reprfileloc is not None assert basename in str(reprtb.reprfileloc.path) assert reprtb.reprfileloc.lineno == 5 @@ -677,6 +695,7 @@ raise ValueError() lines = reprtb.lines assert lines[0] == ' raise ValueError("hello")' assert lines[1] == "E ValueError: hello" + assert reprtb.reprfileloc is not None assert basename in str(reprtb.reprfileloc.path) assert reprtb.reprfileloc.lineno == 3 @@ -716,7 +735,7 @@ raise ValueError() reprtb = p.repr_traceback(excinfo) assert len(reprtb.reprentries) == 3 - def test_traceback_short_no_source(self, importasmod, monkeypatch): + def test_traceback_short_no_source(self, importasmod, monkeypatch) -> None: mod = importasmod( """ def func1(): @@ -729,7 +748,7 @@ raise ValueError() from _pytest._code.code import Code monkeypatch.setattr(Code, "path", "bogus") - excinfo.traceback[0].frame.code.path = "bogus" + excinfo.traceback[0].frame.code.path = "bogus" # type: ignore[misc] # noqa: F821 p = FormattedExcinfo(style="short") reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) lines = reprtb.lines @@ -742,7 +761,7 @@ raise ValueError() assert last_lines[0] == ' raise ValueError("hello")' assert last_lines[1] == "E ValueError: hello" - def test_repr_traceback_and_excinfo(self, importasmod): + def test_repr_traceback_and_excinfo(self, importasmod) -> None: mod = importasmod( """ def f(x): @@ -753,7 +772,8 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) - for style in ("long", "short"): + styles = ("long", "short") # type: Tuple[_TracebackStyle, ...] + for style in styles: p = FormattedExcinfo(style=style) reprtb = p.repr_traceback(excinfo) assert len(reprtb.reprentries) == 2 @@ -765,10 +785,11 @@ raise ValueError() assert repr.chain[0][0] assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) + assert repr.reprcrash is not None assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.message == "ValueError: 0" - def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch): + def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch) -> None: mod = importasmod( """ def f(x): @@ -787,7 +808,9 @@ raise ValueError() def raiseos(): nonlocal raised - if sys._getframe().f_back.f_code.co_name == "checked_call": + upframe = sys._getframe().f_back + assert upframe is not None + if upframe.f_code.co_name == "checked_call": # Only raise with expected calls, but not via e.g. inspect for # py38-windows. raised += 1 @@ -831,7 +854,7 @@ raise ValueError() assert tw_mock.lines[-1] == "content" assert tw_mock.lines[-2] == ("-", "title") - def test_repr_excinfo_reprcrash(self, importasmod): + def test_repr_excinfo_reprcrash(self, importasmod) -> None: mod = importasmod( """ def entry(): @@ -840,6 +863,7 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) repr = excinfo.getrepr() + assert repr.reprcrash is not None assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.lineno == 3 assert repr.reprcrash.message == "ValueError" @@ -864,7 +888,7 @@ raise ValueError() assert reprtb.extraline == "!!! Recursion detected (same locals & position)" assert str(reprtb) - def test_reprexcinfo_getrepr(self, importasmod): + def test_reprexcinfo_getrepr(self, importasmod) -> None: mod = importasmod( """ def f(x): @@ -875,14 +899,15 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) - for style in ("short", "long", "no"): + styles = ("short", "long", "no") # type: Tuple[_TracebackStyle, ...] + for style in styles: for showlocals in (True, False): repr = excinfo.getrepr(style=style, showlocals=showlocals) assert repr.reprtraceback.style == style assert isinstance(repr, ExceptionChainRepr) - for repr in repr.chain: - assert repr[0].style == style + for r in repr.chain: + assert r[0].style == style def test_reprexcinfo_unicode(self): from _pytest._code.code import TerminalRepr diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 93264f3fc..b5ad94861 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -103,7 +103,7 @@ def test_warn_about_imminent_junit_family_default_change(testdir, junit_family): result.stdout.fnmatch_lines([warning_msg]) -def test_node_direct_ctor_warning(): +def test_node_direct_ctor_warning() -> None: class MockConfig: pass @@ -112,8 +112,8 @@ def test_node_direct_ctor_warning(): DeprecationWarning, match="Direct construction of .* has been deprecated, please use .*.from_parent.*", ) as w: - nodes.Node(name="test", config=ms, session=ms, nodeid="None") - assert w[0].lineno == inspect.currentframe().f_lineno - 1 + nodes.Node(name="test", config=ms, session=ms, nodeid="None") # type: ignore + assert w[0].lineno == inspect.currentframe().f_lineno - 1 # type: ignore assert w[0].filename == __file__ diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_dataclasses.py index 82a685c63..d96c90a91 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses.py @@ -2,11 +2,11 @@ from dataclasses import dataclass from dataclasses import field -def test_dataclasses(): +def test_dataclasses() -> None: @dataclass class SimpleDataObject: field_a: int = field() - field_b: int = field() + field_b: str = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py index fa89e4a20..7479c66c1 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py @@ -2,11 +2,11 @@ from dataclasses import dataclass from dataclasses import field -def test_dataclasses_with_attribute_comparison_off(): +def test_dataclasses_with_attribute_comparison_off() -> None: @dataclass class SimpleDataObject: field_a: int = field() - field_b: int = field(compare=False) + field_b: str = field(compare=False) left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py index 06634565b..4737ef904 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py @@ -2,11 +2,11 @@ from dataclasses import dataclass from dataclasses import field -def test_dataclasses_verbose(): +def test_dataclasses_verbose() -> None: @dataclass class SimpleDataObject: field_a: int = field() - field_b: int = field() + field_b: str = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py index 4c638e1fc..22e981e33 100644 --- a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -2,18 +2,18 @@ from dataclasses import dataclass from dataclasses import field -def test_comparing_two_different_data_classes(): +def test_comparing_two_different_data_classes() -> None: @dataclass class SimpleDataObjectOne: field_a: int = field() - field_b: int = field() + field_b: str = field() @dataclass class SimpleDataObjectTwo: field_a: int = field() - field_b: int = field() + field_b: str = field() left = SimpleDataObjectOne(1, "b") right = SimpleDataObjectTwo(1, "c") - assert left != right + assert left != right # type: ignore[comparison-overlap] # noqa: F821 diff --git a/testing/example_scripts/issue_519.py b/testing/example_scripts/issue_519.py index 7199df820..52d5d3f55 100644 --- a/testing/example_scripts/issue_519.py +++ b/testing/example_scripts/issue_519.py @@ -1,4 +1,6 @@ import pprint +from typing import List +from typing import Tuple import pytest @@ -13,7 +15,7 @@ def pytest_generate_tests(metafunc): @pytest.fixture(scope="session") def checked_order(): - order = [] + order = [] # type: List[Tuple[str, str, str]] yield order pprint.pprint(order) diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py index 76eebf74a..21b9d2cd9 100644 --- a/testing/example_scripts/unittest/test_unittest_asyncio.py +++ b/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -1,7 +1,8 @@ +from typing import List from unittest import IsolatedAsyncioTestCase # type: ignore -teardowns = [] +teardowns = [] # type: List[None] class AsyncArguments(IsolatedAsyncioTestCase): diff --git a/testing/example_scripts/unittest/test_unittest_asynctest.py b/testing/example_scripts/unittest/test_unittest_asynctest.py index bddbe250a..47b5f3f6d 100644 --- a/testing/example_scripts/unittest/test_unittest_asynctest.py +++ b/testing/example_scripts/unittest/test_unittest_asynctest.py @@ -1,10 +1,11 @@ """Issue #7110""" import asyncio +from typing import List import asynctest -teardowns = [] +teardowns = [] # type: List[None] class Test(asynctest.TestCase): diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index f4ced8fac..6912a113f 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -25,7 +25,7 @@ def test_maxsize_error_on_instance(): assert s[0] == "(" and s[-1] == ")" -def test_exceptions(): +def test_exceptions() -> None: class BrokenRepr: def __init__(self, ex): self.ex = ex @@ -34,8 +34,8 @@ def test_exceptions(): raise self.ex class BrokenReprException(Exception): - __str__ = None - __repr__ = None + __str__ = None # type: ignore[assignment] # noqa: F821 + __repr__ = None # type: ignore[assignment] # noqa: F821 assert "Exception" in saferepr(BrokenRepr(Exception("broken"))) s = saferepr(BrokenReprException("really broken")) @@ -44,7 +44,7 @@ def test_exceptions(): none = None try: - none() + none() # type: ignore[misc] # noqa: F821 except BaseException as exc: exp_exc = repr(exc) obj = BrokenRepr(BrokenReprException("omg even worse")) @@ -136,10 +136,10 @@ def test_big_repr(): assert len(saferepr(range(1000))) <= len("[" + SafeRepr(0).maxlist * "1000" + "]") -def test_repr_on_newstyle(): +def test_repr_on_newstyle() -> None: class Function: def __repr__(self): - return "<%s>" % (self.name) + return "<%s>" % (self.name) # type: ignore[attr-defined] # noqa: F821 assert saferepr(Function()) diff --git a/testing/logging/test_formatter.py b/testing/logging/test_formatter.py index 85e949d7a..a90384a95 100644 --- a/testing/logging/test_formatter.py +++ b/testing/logging/test_formatter.py @@ -1,10 +1,11 @@ import logging +from typing import Any from _pytest._io import TerminalWriter from _pytest.logging import ColoredLevelFormatter -def test_coloredlogformatter(): +def test_coloredlogformatter() -> None: logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" record = logging.LogRecord( @@ -14,7 +15,7 @@ def test_coloredlogformatter(): lineno=10, msg="Test Message", args=(), - exc_info=False, + exc_info=None, ) class ColorConfig: @@ -35,7 +36,7 @@ def test_coloredlogformatter(): assert output == ("dummypath 10 INFO Test Message") -def test_multiline_message(): +def test_multiline_message() -> None: from _pytest.logging import PercentStyleMultiline logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" @@ -47,8 +48,8 @@ def test_multiline_message(): lineno=10, msg="Test Message line1\nline2", args=(), - exc_info=False, - ) + exc_info=None, + ) # type: Any # this is called by logging.Formatter.format record.message = record.getMessage() @@ -124,7 +125,7 @@ def test_multiline_message(): ) -def test_colored_short_level(): +def test_colored_short_level() -> None: logfmt = "%(levelname).1s %(message)s" record = logging.LogRecord( @@ -134,7 +135,7 @@ def test_colored_short_level(): lineno=10, msg="Test Message", args=(), - exc_info=False, + exc_info=None, ) class ColorConfig: diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 709df2b57..bbdf28b38 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1,9 +1,12 @@ import io import os import re +from typing import cast import pytest +from _pytest.capture import CaptureManager from _pytest.pytester import Testdir +from _pytest.terminal import TerminalReporter def test_nothing_logged(testdir): @@ -808,7 +811,7 @@ def test_log_file_unicode(testdir): @pytest.mark.parametrize("has_capture_manager", [True, False]) -def test_live_logging_suspends_capture(has_capture_manager, request): +def test_live_logging_suspends_capture(has_capture_manager: bool, request) -> None: """Test that capture manager is suspended when we emitting messages for live logging. This tests the implementation calls instead of behavior because it is difficult/impossible to do it using @@ -835,8 +838,10 @@ def test_live_logging_suspends_capture(has_capture_manager, request): def section(self, *args, **kwargs): pass - out_file = DummyTerminal() - capture_manager = MockCaptureManager() if has_capture_manager else None + out_file = cast(TerminalReporter, DummyTerminal()) + capture_manager = ( + cast(CaptureManager, MockCaptureManager()) if has_capture_manager else None + ) handler = _LiveLoggingStreamHandler(out_file, capture_manager) handler.set_when("call") @@ -849,7 +854,7 @@ def test_live_logging_suspends_capture(has_capture_manager, request): assert MockCaptureManager.calls == ["enter disabled", "exit disabled"] else: assert MockCaptureManager.calls == [] - assert out_file.getvalue() == "\nsome message\n" + assert cast(io.StringIO, out_file).getvalue() == "\nsome message\n" def test_collection_live_logging(testdir): diff --git a/testing/python/approx.py b/testing/python/approx.py index 76d995773..8581475e1 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -428,10 +428,11 @@ class TestApprox: assert a12 != approx(a21) assert a21 != approx(a12) - def test_doctests(self, mocked_doctest_runner): + def test_doctests(self, mocked_doctest_runner) -> None: import doctest parser = doctest.DocTestParser() + assert approx.__doc__ is not None test = parser.get_doctest( approx.__doc__, {"approx": approx}, approx.__name__, None, None ) diff --git a/testing/python/collect.py b/testing/python/collect.py index cbc798ad8..7824ceff1 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,6 +1,8 @@ import os import sys import textwrap +from typing import Any +from typing import Dict import _pytest._code import pytest @@ -698,7 +700,7 @@ class TestFunction: class TestSorting: - def test_check_equality(self, testdir): + def test_check_equality(self, testdir) -> None: modcol = testdir.getmodulecol( """ def test_pass(): pass @@ -720,10 +722,10 @@ class TestSorting: assert fn1 != fn3 for fn in fn1, fn2, fn3: - assert fn != 3 + assert fn != 3 # type: ignore[comparison-overlap] # noqa: F821 assert fn != modcol - assert fn != [1, 2, 3] - assert [1, 2, 3] != fn + assert fn != [1, 2, 3] # type: ignore[comparison-overlap] # noqa: F821 + assert [1, 2, 3] != fn # type: ignore[comparison-overlap] # noqa: F821 assert modcol != fn def test_allow_sane_sorting_for_decorators(self, testdir): @@ -1006,7 +1008,7 @@ class TestTracebackCutting: assert "INTERNALERROR>" not in out result.stdout.fnmatch_lines(["*ValueError: fail me*", "* 1 error in *"]) - def test_filter_traceback_generated_code(self): + def test_filter_traceback_generated_code(self) -> None: """test that filter_traceback() works with the fact that _pytest._code.code.Code.path attribute might return an str object. In this case, one of the entries on the traceback was produced by @@ -1017,17 +1019,18 @@ class TestTracebackCutting: from _pytest.python import filter_traceback try: - ns = {} + ns = {} # type: Dict[str, Any] exec("def foo(): raise ValueError", ns) ns["foo"]() except ValueError: _, _, tb = sys.exc_info() - tb = _pytest._code.Traceback(tb) - assert isinstance(tb[-1].path, str) - assert not filter_traceback(tb[-1]) + assert tb is not None + traceback = _pytest._code.Traceback(tb) + assert isinstance(traceback[-1].path, str) + assert not filter_traceback(traceback[-1]) - def test_filter_traceback_path_no_longer_valid(self, testdir): + def test_filter_traceback_path_no_longer_valid(self, testdir) -> None: """test that filter_traceback() works with the fact that _pytest._code.code.Code.path attribute might return an str object. In this case, one of the files in the traceback no longer exists. @@ -1049,10 +1052,11 @@ class TestTracebackCutting: except ValueError: _, _, tb = sys.exc_info() + assert tb is not None testdir.tmpdir.join("filter_traceback_entry_as_str.py").remove() - tb = _pytest._code.Traceback(tb) - assert isinstance(tb[-1].path, str) - assert filter_traceback(tb[-1]) + traceback = _pytest._code.Traceback(tb) + assert isinstance(traceback[-1].path, str) + assert filter_traceback(traceback[-1]) class TestReportInfo: diff --git a/testing/python/integration.py b/testing/python/integration.py index 3409b6446..537057484 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -1,10 +1,14 @@ +from typing import Any + import pytest from _pytest import python from _pytest import runner class TestOEJSKITSpecials: - def test_funcarg_non_pycollectobj(self, testdir, recwarn): # rough jstests usage + def test_funcarg_non_pycollectobj( + self, testdir, recwarn + ) -> None: # rough jstests usage testdir.makeconftest( """ import pytest @@ -28,13 +32,14 @@ class TestOEJSKITSpecials: ) # this hook finds funcarg factories rep = runner.collect_one_node(collector=modcol) - clscol = rep.result[0] + # TODO: Don't treat as Any. + clscol = rep.result[0] # type: Any clscol.obj = lambda arg1: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert clscol.funcargs["arg1"] == 42 - def test_autouse_fixture(self, testdir, recwarn): # rough jstests usage + def test_autouse_fixture(self, testdir, recwarn) -> None: # rough jstests usage testdir.makeconftest( """ import pytest @@ -61,20 +66,21 @@ class TestOEJSKITSpecials: ) # this hook finds funcarg factories rep = runner.collect_one_node(modcol) - clscol = rep.result[0] + # TODO: Don't treat as Any. + clscol = rep.result[0] # type: Any clscol.obj = lambda: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert not clscol.funcargs -def test_wrapped_getfslineno(): +def test_wrapped_getfslineno() -> None: def func(): pass def wrap(f): - func.__wrapped__ = f - func.patchings = ["qwe"] + func.__wrapped__ = f # type: ignore + func.patchings = ["qwe"] # type: ignore return func @wrap @@ -87,14 +93,14 @@ def test_wrapped_getfslineno(): class TestMockDecoration: - def test_wrapped_getfuncargnames(self): + def test_wrapped_getfuncargnames(self) -> None: from _pytest.compat import getfuncargnames def wrap(f): def func(): pass - func.__wrapped__ = f + func.__wrapped__ = f # type: ignore return func @wrap @@ -322,10 +328,11 @@ class TestReRunTests: ) -def test_pytestconfig_is_session_scoped(): +def test_pytestconfig_is_session_scoped() -> None: from _pytest.fixtures import pytestconfig - assert pytestconfig._pytestfixturefunction.scope == "session" + marker = pytestconfig._pytestfixturefunction # type: ignore + assert marker.scope == "session" class TestNoselikeTestAttribute: diff --git a/testing/python/raises.py b/testing/python/raises.py index 6c607464d..e55eb6f54 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -6,9 +6,9 @@ from _pytest.outcomes import Failed class TestRaises: - def test_check_callable(self): + def test_check_callable(self) -> None: with pytest.raises(TypeError, match=r".* must be callable"): - pytest.raises(RuntimeError, "int('qwe')") + pytest.raises(RuntimeError, "int('qwe')") # type: ignore[call-overload] # noqa: F821 def test_raises(self): excinfo = pytest.raises(ValueError, int, "qwe") @@ -18,19 +18,19 @@ class TestRaises: excinfo = pytest.raises(ValueError, int, "hello") assert "invalid literal" in str(excinfo.value) - def test_raises_callable_no_exception(self): + def test_raises_callable_no_exception(self) -> None: class A: def __call__(self): pass try: pytest.raises(ValueError, A()) - except pytest.raises.Exception: + except pytest.fail.Exception: pass - def test_raises_falsey_type_error(self): + def test_raises_falsey_type_error(self) -> None: with pytest.raises(TypeError): - with pytest.raises(AssertionError, match=0): + with pytest.raises(AssertionError, match=0): # type: ignore[call-overload] # noqa: F821 raise AssertionError("ohai") def test_raises_repr_inflight(self): @@ -126,23 +126,23 @@ class TestRaises: result = testdir.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) - def test_noclass(self): + def test_noclass(self) -> None: with pytest.raises(TypeError): - pytest.raises("wrong", lambda: None) + pytest.raises("wrong", lambda: None) # type: ignore[call-overload] # noqa: F821 - def test_invalid_arguments_to_raises(self): + def test_invalid_arguments_to_raises(self) -> None: with pytest.raises(TypeError, match="unknown"): - with pytest.raises(TypeError, unknown="bogus"): + with pytest.raises(TypeError, unknown="bogus"): # type: ignore[call-overload] # noqa: F821 raise ValueError() def test_tuple(self): with pytest.raises((KeyError, ValueError)): raise KeyError("oops") - def test_no_raise_message(self): + def test_no_raise_message(self) -> None: try: pytest.raises(ValueError, int, "0") - except pytest.raises.Exception as e: + except pytest.fail.Exception as e: assert e.msg == "DID NOT RAISE {}".format(repr(ValueError)) else: assert False, "Expected pytest.raises.Exception" @@ -150,7 +150,7 @@ class TestRaises: try: with pytest.raises(ValueError): pass - except pytest.raises.Exception as e: + except pytest.fail.Exception as e: assert e.msg == "DID NOT RAISE {}".format(repr(ValueError)) else: assert False, "Expected pytest.raises.Exception" @@ -252,7 +252,7 @@ class TestRaises: ): pytest.raises(ClassLooksIterableException, lambda: None) - def test_raises_with_raising_dunder_class(self): + def test_raises_with_raising_dunder_class(self) -> None: """Test current behavior with regard to exceptions via __class__ (#4284).""" class CrappyClass(Exception): @@ -262,12 +262,12 @@ class TestRaises: assert False, "via __class__" with pytest.raises(AssertionError) as excinfo: - with pytest.raises(CrappyClass()): + with pytest.raises(CrappyClass()): # type: ignore[call-overload] # noqa: F821 pass assert "via __class__" in excinfo.value.args[0] def test_raises_context_manager_with_kwargs(self): with pytest.raises(TypeError) as excinfo: - with pytest.raises(Exception, foo="bar"): + with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] # noqa: F821 pass assert "Unexpected keyword arguments" in str(excinfo.value) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 042aa7055..f28876edc 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -279,9 +279,9 @@ class TestImportHookInstallation: ] ) - def test_register_assert_rewrite_checks_types(self): + def test_register_assert_rewrite_checks_types(self) -> None: with pytest.raises(TypeError): - pytest.register_assert_rewrite(["pytest_tests_internal_non_existing"]) + pytest.register_assert_rewrite(["pytest_tests_internal_non_existing"]) # type: ignore pytest.register_assert_rewrite( "pytest_tests_internal_non_existing", "pytest_tests_internal_non_existing2" ) @@ -326,8 +326,10 @@ class TestAssert_reprcompare: def test_different_types(self): assert callequal([0, 1], "foo") is None - def test_summary(self): - summary = callequal([0, 1], [0, 2])[0] + def test_summary(self) -> None: + lines = callequal([0, 1], [0, 2]) + assert lines is not None + summary = lines[0] assert len(summary) < 65 def test_text_diff(self): @@ -337,21 +339,24 @@ class TestAssert_reprcompare: "+ spam", ] - def test_text_skipping(self): + def test_text_skipping(self) -> None: lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs") + assert lines is not None assert "Skipping" in lines[1] for line in lines: assert "a" * 50 not in line - def test_text_skipping_verbose(self): + def test_text_skipping_verbose(self) -> None: lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs", verbose=1) + assert lines is not None assert "- " + "a" * 50 + "eggs" in lines assert "+ " + "a" * 50 + "spam" in lines - def test_multiline_text_diff(self): + def test_multiline_text_diff(self) -> None: left = "foo\nspam\nbar" right = "foo\neggs\nbar" diff = callequal(left, right) + assert diff is not None assert "- eggs" in diff assert "+ spam" in diff @@ -376,8 +381,9 @@ class TestAssert_reprcompare: "+ b'spam'", ] - def test_list(self): + def test_list(self) -> None: expl = callequal([0, 1], [0, 2]) + assert expl is not None assert len(expl) > 1 @pytest.mark.parametrize( @@ -421,21 +427,25 @@ class TestAssert_reprcompare: ), ], ) - def test_iterable_full_diff(self, left, right, expected): + def test_iterable_full_diff(self, left, right, expected) -> None: """Test the full diff assertion failure explanation. When verbose is False, then just a -v notice to get the diff is rendered, when verbose is True, then ndiff of the pprint is returned. """ expl = callequal(left, right, verbose=0) + assert expl is not None assert expl[-1] == "Use -v to get the full diff" - expl = "\n".join(callequal(left, right, verbose=1)) - assert expl.endswith(textwrap.dedent(expected).strip()) + verbose_expl = callequal(left, right, verbose=1) + assert verbose_expl is not None + assert "\n".join(verbose_expl).endswith(textwrap.dedent(expected).strip()) - def test_list_different_lengths(self): + def test_list_different_lengths(self) -> None: expl = callequal([0, 1], [0, 1, 2]) + assert expl is not None assert len(expl) > 1 expl = callequal([0, 1, 2], [0, 1]) + assert expl is not None assert len(expl) > 1 def test_list_wrap_for_multiple_lines(self): @@ -545,27 +555,31 @@ class TestAssert_reprcompare: " }", ] - def test_dict(self): + def test_dict(self) -> None: expl = callequal({"a": 0}, {"a": 1}) + assert expl is not None assert len(expl) > 1 - def test_dict_omitting(self): + def test_dict_omitting(self) -> None: lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}) + assert lines is not None assert lines[1].startswith("Omitting 1 identical item") assert "Common items" not in lines for line in lines[1:]: assert "b" not in line - def test_dict_omitting_with_verbosity_1(self): + def test_dict_omitting_with_verbosity_1(self) -> None: """ Ensure differing items are visible for verbosity=1 (#1512) """ lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=1) + assert lines is not None assert lines[1].startswith("Omitting 1 identical item") assert lines[2].startswith("Differing items") assert lines[3] == "{'a': 0} != {'a': 1}" assert "Common items" not in lines - def test_dict_omitting_with_verbosity_2(self): + def test_dict_omitting_with_verbosity_2(self) -> None: lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=2) + assert lines is not None assert lines[1].startswith("Common items:") assert "Omitting" not in lines[1] assert lines[2] == "{'b': 1}" @@ -614,15 +628,17 @@ class TestAssert_reprcompare: "+ (1, 2, 3)", ] - def test_set(self): + def test_set(self) -> None: expl = callequal({0, 1}, {0, 2}) + assert expl is not None assert len(expl) > 1 - def test_frozenzet(self): + def test_frozenzet(self) -> None: expl = callequal(frozenset([0, 1]), {0, 2}) + assert expl is not None assert len(expl) > 1 - def test_Sequence(self): + def test_Sequence(self) -> None: # Test comparing with a Sequence subclass. class TestSequence(collections.abc.MutableSequence): def __init__(self, iterable): @@ -644,15 +660,18 @@ class TestAssert_reprcompare: pass expl = callequal(TestSequence([0, 1]), list([0, 2])) + assert expl is not None assert len(expl) > 1 - def test_list_tuples(self): + def test_list_tuples(self) -> None: expl = callequal([], [(1, 2)]) + assert expl is not None assert len(expl) > 1 expl = callequal([(1, 2)], []) + assert expl is not None assert len(expl) > 1 - def test_repr_verbose(self): + def test_repr_verbose(self) -> None: class Nums: def __init__(self, nums): self.nums = nums @@ -669,21 +688,25 @@ class TestAssert_reprcompare: assert callequal(nums_x, nums_y) is None expl = callequal(nums_x, nums_y, verbose=1) + assert expl is not None assert "+" + repr(nums_x) in expl assert "-" + repr(nums_y) in expl expl = callequal(nums_x, nums_y, verbose=2) + assert expl is not None assert "+" + repr(nums_x) in expl assert "-" + repr(nums_y) in expl - def test_list_bad_repr(self): + def test_list_bad_repr(self) -> None: class A: def __repr__(self): raise ValueError(42) expl = callequal([], [A()]) + assert expl is not None assert "ValueError" in "".join(expl) expl = callequal({}, {"1": A()}, verbose=2) + assert expl is not None assert expl[0].startswith("{} == <[ValueError") assert "raised in repr" in expl[0] assert expl[1:] == [ @@ -707,9 +730,10 @@ class TestAssert_reprcompare: expl = callequal(A(), "") assert not expl - def test_repr_no_exc(self): - expl = " ".join(callequal("foo", "bar")) - assert "raised in repr()" not in expl + def test_repr_no_exc(self) -> None: + expl = callequal("foo", "bar") + assert expl is not None + assert "raised in repr()" not in " ".join(expl) def test_unicode(self): assert callequal("£€", "£") == [ @@ -734,11 +758,12 @@ class TestAssert_reprcompare: def test_format_nonascii_explanation(self): assert util.format_explanation("λ") - def test_mojibake(self): + def test_mojibake(self) -> None: # issue 429 left = b"e" right = b"\xc3\xa9" expl = callequal(left, right) + assert expl is not None for line in expl: assert isinstance(line, str) msg = "\n".join(expl) @@ -791,7 +816,7 @@ class TestAssert_reprcompare_dataclass: class TestAssert_reprcompare_attrsclass: - def test_attrs(self): + def test_attrs(self) -> None: @attr.s class SimpleDataObject: field_a = attr.ib() @@ -801,12 +826,13 @@ class TestAssert_reprcompare_attrsclass: right = SimpleDataObject(1, "c") lines = callequal(left, right) + assert lines is not None assert lines[1].startswith("Omitting 1 identical item") assert "Matching attributes" not in lines for line in lines[1:]: assert "field_a" not in line - def test_attrs_verbose(self): + def test_attrs_verbose(self) -> None: @attr.s class SimpleDataObject: field_a = attr.ib() @@ -816,6 +842,7 @@ class TestAssert_reprcompare_attrsclass: right = SimpleDataObject(1, "c") lines = callequal(left, right, verbose=2) + assert lines is not None assert lines[1].startswith("Matching attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" @@ -824,12 +851,13 @@ class TestAssert_reprcompare_attrsclass: @attr.s class SimpleDataObject: field_a = attr.ib() - field_b = attr.ib(**{ATTRS_EQ_FIELD: False}) + field_b = attr.ib(**{ATTRS_EQ_FIELD: False}) # type: ignore left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) + assert lines is not None assert lines[1].startswith("Matching attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" @@ -946,8 +974,8 @@ class TestTruncateExplanation: # to calculate that results have the expected length. LINES_IN_TRUNCATION_MSG = 2 - def test_doesnt_truncate_when_input_is_empty_list(self): - expl = [] + def test_doesnt_truncate_when_input_is_empty_list(self) -> None: + expl = [] # type: List[str] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) assert result == expl diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 212c631ef..3813993be 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -9,6 +9,13 @@ import sys import textwrap import zipfile from functools import partial +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional +from typing import Set + +import py import _pytest._code import pytest @@ -25,24 +32,26 @@ from _pytest.pathlib import Path from _pytest.pytester import Testdir -def rewrite(src): +def rewrite(src: str) -> ast.Module: tree = ast.parse(src) rewrite_asserts(tree, src.encode()) return tree -def getmsg(f, extra_ns=None, must_pass=False): +def getmsg( + f, extra_ns: Optional[Mapping[str, object]] = None, *, must_pass: bool = False +) -> Optional[str]: """Rewrite the assertions in f, run it, and get the failure message.""" src = "\n".join(_pytest._code.Code(f).source().lines) mod = rewrite(src) code = compile(mod, "", "exec") - ns = {} + ns = {} # type: Dict[str, object] if extra_ns is not None: ns.update(extra_ns) exec(code, ns) func = ns[f.__name__] try: - func() + func() # type: ignore[operator] # noqa: F821 except AssertionError: if must_pass: pytest.fail("shouldn't have raised") @@ -53,6 +62,7 @@ def getmsg(f, extra_ns=None, must_pass=False): else: if not must_pass: pytest.fail("function didn't raise at all") + return None class TestAssertionRewrite: @@ -98,10 +108,11 @@ class TestAssertionRewrite: assert imp.col_offset == 0 assert isinstance(m.body[3], ast.Expr) - def test_dont_rewrite(self): + def test_dont_rewrite(self) -> None: s = """'PYTEST_DONT_REWRITE'\nassert 14""" m = rewrite(s) assert len(m.body) == 2 + assert isinstance(m.body[1], ast.Assert) assert m.body[1].msg is None def test_dont_rewrite_plugin(self, testdir): @@ -145,28 +156,28 @@ class TestAssertionRewrite: monkeypatch.syspath_prepend(xdir) testdir.runpytest().assert_outcomes(passed=1) - def test_name(self, request): - def f(): + def test_name(self, request) -> None: + def f1() -> None: assert False - assert getmsg(f) == "assert False" + assert getmsg(f1) == "assert False" - def f(): + def f2() -> None: f = False assert f - assert getmsg(f) == "assert False" + assert getmsg(f2) == "assert False" - def f(): - assert a_global # noqa + def f3() -> None: + assert a_global # type: ignore[name-defined] # noqa - assert getmsg(f, {"a_global": False}) == "assert False" + assert getmsg(f3, {"a_global": False}) == "assert False" - def f(): - assert sys == 42 + def f4() -> None: + assert sys == 42 # type: ignore[comparison-overlap] # noqa: F821 verbose = request.config.getoption("verbose") - msg = getmsg(f, {"sys": sys}) + msg = getmsg(f4, {"sys": sys}) if verbose > 0: assert msg == ( "assert == 42\n" @@ -176,64 +187,74 @@ class TestAssertionRewrite: else: assert msg == "assert sys == 42" - def f(): - assert cls == 42 # noqa: F821 + def f5() -> None: + assert cls == 42 # type: ignore[name-defined] # noqa: F821 class X: pass - msg = getmsg(f, {"cls": X}).splitlines() + msg = getmsg(f5, {"cls": X}) + assert msg is not None + lines = msg.splitlines() if verbose > 1: - assert msg == ["assert {!r} == 42".format(X), " +{!r}".format(X), " -42"] + assert lines == [ + "assert {!r} == 42".format(X), + " +{!r}".format(X), + " -42", + ] elif verbose > 0: - assert msg == [ + assert lines == [ "assert .X'> == 42", " +{!r}".format(X), " -42", ] else: - assert msg == ["assert cls == 42"] + assert lines == ["assert cls == 42"] - def test_assertrepr_compare_same_width(self, request): + def test_assertrepr_compare_same_width(self, request) -> None: """Should use same width/truncation with same initial width.""" - def f(): + def f() -> None: assert "1234567890" * 5 + "A" == "1234567890" * 5 + "B" - msg = getmsg(f).splitlines()[0] + msg = getmsg(f) + assert msg is not None + line = msg.splitlines()[0] if request.config.getoption("verbose") > 1: - assert msg == ( + assert line == ( "assert '12345678901234567890123456789012345678901234567890A' " "== '12345678901234567890123456789012345678901234567890B'" ) else: - assert msg == ( + assert line == ( "assert '123456789012...901234567890A' " "== '123456789012...901234567890B'" ) - def test_dont_rewrite_if_hasattr_fails(self, request): + def test_dont_rewrite_if_hasattr_fails(self, request) -> None: class Y: """ A class whos getattr fails, but not with `AttributeError` """ def __getattr__(self, attribute_name): raise KeyError() - def __repr__(self): + def __repr__(self) -> str: return "Y" - def __init__(self): + def __init__(self) -> None: self.foo = 3 - def f(): - assert cls().foo == 2 # noqa + def f() -> None: + assert cls().foo == 2 # type: ignore[name-defined] # noqa: F821 # XXX: looks like the "where" should also be there in verbose mode?! - message = getmsg(f, {"cls": Y}).splitlines() + msg = getmsg(f, {"cls": Y}) + assert msg is not None + lines = msg.splitlines() if request.config.getoption("verbose") > 0: - assert message == ["assert 3 == 2", " +3", " -2"] + assert lines == ["assert 3 == 2", " +3", " -2"] else: - assert message == [ + assert lines == [ "assert 3 == 2", " + where 3 = Y.foo", " + where Y = cls()", @@ -314,145 +335,145 @@ class TestAssertionRewrite: assert result.ret == 1 result.stdout.fnmatch_lines(["*AssertionError: b'ohai!'", "*assert False"]) - def test_boolop(self): - def f(): + def test_boolop(self) -> None: + def f1() -> None: f = g = False assert f and g - assert getmsg(f) == "assert (False)" + assert getmsg(f1) == "assert (False)" - def f(): + def f2() -> None: f = True g = False assert f and g - assert getmsg(f) == "assert (True and False)" + assert getmsg(f2) == "assert (True and False)" - def f(): + def f3() -> None: f = False g = True assert f and g - assert getmsg(f) == "assert (False)" + assert getmsg(f3) == "assert (False)" - def f(): + def f4() -> None: f = g = False assert f or g - assert getmsg(f) == "assert (False or False)" + assert getmsg(f4) == "assert (False or False)" - def f(): + def f5() -> None: f = g = False assert not f and not g - getmsg(f, must_pass=True) + getmsg(f5, must_pass=True) - def x(): + def x() -> bool: return False - def f(): + def f6() -> None: assert x() and x() assert ( - getmsg(f, {"x": x}) + getmsg(f6, {"x": x}) == """assert (False) + where False = x()""" ) - def f(): + def f7() -> None: assert False or x() assert ( - getmsg(f, {"x": x}) + getmsg(f7, {"x": x}) == """assert (False or False) + where False = x()""" ) - def f(): + def f8() -> None: assert 1 in {} and 2 in {} - assert getmsg(f) == "assert (1 in {})" + assert getmsg(f8) == "assert (1 in {})" - def f(): + def f9() -> None: x = 1 y = 2 assert x in {1: None} and y in {} - assert getmsg(f) == "assert (1 in {1: None} and 2 in {})" + assert getmsg(f9) == "assert (1 in {1: None} and 2 in {})" - def f(): + def f10() -> None: f = True g = False assert f or g - getmsg(f, must_pass=True) + getmsg(f10, must_pass=True) - def f(): + def f11() -> None: f = g = h = lambda: True assert f() and g() and h() - getmsg(f, must_pass=True) + getmsg(f11, must_pass=True) - def test_short_circuit_evaluation(self): - def f(): - assert True or explode # noqa + def test_short_circuit_evaluation(self) -> None: + def f1() -> None: + assert True or explode # type: ignore[name-defined] # noqa: F821 - getmsg(f, must_pass=True) + getmsg(f1, must_pass=True) - def f(): + def f2() -> None: x = 1 assert x == 1 or x == 2 - getmsg(f, must_pass=True) + getmsg(f2, must_pass=True) - def test_unary_op(self): - def f(): + def test_unary_op(self) -> None: + def f1() -> None: x = True assert not x - assert getmsg(f) == "assert not True" + assert getmsg(f1) == "assert not True" - def f(): + def f2() -> None: x = 0 assert ~x + 1 - assert getmsg(f) == "assert (~0 + 1)" + assert getmsg(f2) == "assert (~0 + 1)" - def f(): + def f3() -> None: x = 3 assert -x + x - assert getmsg(f) == "assert (-3 + 3)" + assert getmsg(f3) == "assert (-3 + 3)" - def f(): + def f4() -> None: x = 0 assert +x + x - assert getmsg(f) == "assert (+0 + 0)" + assert getmsg(f4) == "assert (+0 + 0)" - def test_binary_op(self): - def f(): + def test_binary_op(self) -> None: + def f1() -> None: x = 1 y = -1 assert x + y - assert getmsg(f) == "assert (1 + -1)" + assert getmsg(f1) == "assert (1 + -1)" - def f(): + def f2() -> None: assert not 5 % 4 - assert getmsg(f) == "assert not (5 % 4)" + assert getmsg(f2) == "assert not (5 % 4)" - def test_boolop_percent(self): - def f(): + def test_boolop_percent(self) -> None: + def f1() -> None: assert 3 % 2 and False - assert getmsg(f) == "assert ((3 % 2) and False)" + assert getmsg(f1) == "assert ((3 % 2) and False)" - def f(): + def f2() -> None: assert False or 4 % 2 - assert getmsg(f) == "assert (False or (4 % 2))" + assert getmsg(f2) == "assert (False or (4 % 2))" def test_at_operator_issue1290(self, testdir): testdir.makepyfile( @@ -480,133 +501,133 @@ class TestAssertionRewrite: ) testdir.runpytest().assert_outcomes(passed=1) - def test_call(self): - def g(a=42, *args, **kwargs): + def test_call(self) -> None: + def g(a=42, *args, **kwargs) -> bool: return False ns = {"g": g} - def f(): + def f1() -> None: assert g() assert ( - getmsg(f, ns) + getmsg(f1, ns) == """assert False + where False = g()""" ) - def f(): + def f2() -> None: assert g(1) assert ( - getmsg(f, ns) + getmsg(f2, ns) == """assert False + where False = g(1)""" ) - def f(): + def f3() -> None: assert g(1, 2) assert ( - getmsg(f, ns) + getmsg(f3, ns) == """assert False + where False = g(1, 2)""" ) - def f(): + def f4() -> None: assert g(1, g=42) assert ( - getmsg(f, ns) + getmsg(f4, ns) == """assert False + where False = g(1, g=42)""" ) - def f(): + def f5() -> None: assert g(1, 3, g=23) assert ( - getmsg(f, ns) + getmsg(f5, ns) == """assert False + where False = g(1, 3, g=23)""" ) - def f(): + def f6() -> None: seq = [1, 2, 3] assert g(*seq) assert ( - getmsg(f, ns) + getmsg(f6, ns) == """assert False + where False = g(*[1, 2, 3])""" ) - def f(): + def f7() -> None: x = "a" assert g(**{x: 2}) assert ( - getmsg(f, ns) + getmsg(f7, ns) == """assert False + where False = g(**{'a': 2})""" ) - def test_attribute(self): + def test_attribute(self) -> None: class X: g = 3 ns = {"x": X} - def f(): - assert not x.g # noqa + def f1() -> None: + assert not x.g # type: ignore[name-defined] # noqa: F821 assert ( - getmsg(f, ns) + getmsg(f1, ns) == """assert not 3 + where 3 = x.g""" ) - def f(): - x.a = False # noqa - assert x.a # noqa + def f2() -> None: + x.a = False # type: ignore[name-defined] # noqa: F821 + assert x.a # type: ignore[name-defined] # noqa: F821 assert ( - getmsg(f, ns) + getmsg(f2, ns) == """assert False + where False = x.a""" ) - def test_comparisons(self): - def f(): + def test_comparisons(self) -> None: + def f1() -> None: a, b = range(2) assert b < a - assert getmsg(f) == """assert 1 < 0""" + assert getmsg(f1) == """assert 1 < 0""" - def f(): + def f2() -> None: a, b, c = range(3) assert a > b > c - assert getmsg(f) == """assert 0 > 1""" + assert getmsg(f2) == """assert 0 > 1""" - def f(): + def f3() -> None: a, b, c = range(3) assert a < b > c - assert getmsg(f) == """assert 1 > 2""" + assert getmsg(f3) == """assert 1 > 2""" - def f(): + def f4() -> None: a, b, c = range(3) assert a < b <= c - getmsg(f, must_pass=True) + getmsg(f4, must_pass=True) - def f(): + def f5() -> None: a, b, c = range(3) assert a < b assert b < c - getmsg(f, must_pass=True) + getmsg(f5, must_pass=True) def test_len(self, request): def f(): @@ -619,29 +640,29 @@ class TestAssertionRewrite: else: assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])" - def test_custom_reprcompare(self, monkeypatch): - def my_reprcompare(op, left, right): + def test_custom_reprcompare(self, monkeypatch) -> None: + def my_reprcompare1(op, left, right) -> str: return "42" - monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + monkeypatch.setattr(util, "_reprcompare", my_reprcompare1) - def f(): + def f1() -> None: assert 42 < 3 - assert getmsg(f) == "assert 42" + assert getmsg(f1) == "assert 42" - def my_reprcompare(op, left, right): + def my_reprcompare2(op, left, right) -> str: return "{} {} {}".format(left, op, right) - monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + monkeypatch.setattr(util, "_reprcompare", my_reprcompare2) - def f(): + def f2() -> None: assert 1 < 3 < 5 <= 4 < 7 - assert getmsg(f) == "assert 5 <= 4" + assert getmsg(f2) == "assert 5 <= 4" - def test_assert_raising__bool__in_comparison(self): - def f(): + def test_assert_raising__bool__in_comparison(self) -> None: + def f() -> None: class A: def __bool__(self): raise ValueError(42) @@ -652,21 +673,25 @@ class TestAssertionRewrite: def __repr__(self): return "" - def myany(x): + def myany(x) -> bool: return False assert myany(A() < 0) - assert " < 0" in getmsg(f) + msg = getmsg(f) + assert msg is not None + assert " < 0" in msg - def test_formatchar(self): - def f(): - assert "%test" == "test" + def test_formatchar(self) -> None: + def f() -> None: + assert "%test" == "test" # type: ignore[comparison-overlap] # noqa: F821 - assert getmsg(f).startswith("assert '%test' == 'test'") + msg = getmsg(f) + assert msg is not None + assert msg.startswith("assert '%test' == 'test'") - def test_custom_repr(self, request): - def f(): + def test_custom_repr(self, request) -> None: + def f() -> None: class Foo: a = 1 @@ -676,14 +701,16 @@ class TestAssertionRewrite: f = Foo() assert 0 == f.a - lines = util._format_lines([getmsg(f)]) + msg = getmsg(f) + assert msg is not None + lines = util._format_lines([msg]) if request.config.getoption("verbose") > 0: assert lines == ["assert 0 == 1\n +0\n -1"] else: assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"] - def test_custom_repr_non_ascii(self): - def f(): + def test_custom_repr_non_ascii(self) -> None: + def f() -> None: class A: name = "ä" @@ -694,6 +721,7 @@ class TestAssertionRewrite: assert not a.name msg = getmsg(f) + assert msg is not None assert "UnicodeDecodeError" not in msg assert "UnicodeEncodeError" not in msg @@ -895,6 +923,7 @@ def test_rewritten(): hook, "_warn_already_imported", lambda code, msg: warnings.append(msg) ) spec = hook.find_spec("test_remember_rewritten_modules") + assert spec is not None module = importlib.util.module_from_spec(spec) hook.exec_module(module) hook.mark_rewrite("test_remember_rewritten_modules") @@ -1007,7 +1036,7 @@ class TestAssertionRewriteHookDetails: result = testdir.runpytest_subprocess() result.assert_outcomes(passed=1) - def test_read_pyc(self, tmpdir): + def test_read_pyc(self, tmp_path: Path) -> None: """ Ensure that the `_read_pyc` can properly deal with corrupted pyc files. In those circumstances it should just give up instead of generating @@ -1016,18 +1045,18 @@ class TestAssertionRewriteHookDetails: import py_compile from _pytest.assertion.rewrite import _read_pyc - source = tmpdir.join("source.py") - pyc = source + "c" + source = tmp_path / "source.py" + pyc = Path(str(source) + "c") - source.write("def test(): pass") + source.write_text("def test(): pass") py_compile.compile(str(source), str(pyc)) - contents = pyc.read(mode="rb") + contents = pyc.read_bytes() strip_bytes = 20 # header is around 8 bytes, strip a little more assert len(contents) > strip_bytes - pyc.write(contents[:strip_bytes], mode="wb") + pyc.write_bytes(contents[:strip_bytes]) - assert _read_pyc(str(source), str(pyc)) is None # no error + assert _read_pyc(source, pyc) is None # no error def test_reload_is_same_and_reloads(self, testdir: Testdir) -> None: """Reloading a (collected) module after change picks up the change.""" @@ -1178,17 +1207,17 @@ def test_source_mtime_long_long(testdir, offset): assert result.ret == 0 -def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch): +def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch) -> None: """Fix infinite recursion when writing pyc files: if an import happens to be triggered when writing the pyc file, this would cause another call to the hook, which would trigger another pyc writing, which could trigger another import, and so on. (#3506)""" - from _pytest.assertion import rewrite + from _pytest.assertion import rewrite as rewritemod testdir.syspathinsert() testdir.makepyfile(test_foo="def test_foo(): pass") testdir.makepyfile(test_bar="def test_bar(): pass") - original_write_pyc = rewrite._write_pyc + original_write_pyc = rewritemod._write_pyc write_pyc_called = [] @@ -1199,7 +1228,7 @@ def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch): assert hook.find_spec("test_bar") is None return original_write_pyc(*args, **kwargs) - monkeypatch.setattr(rewrite, "_write_pyc", spy_write_pyc) + monkeypatch.setattr(rewritemod, "_write_pyc", spy_write_pyc) monkeypatch.setattr(sys, "dont_write_bytecode", False) hook = AssertionRewritingHook(pytestconfig) @@ -1212,14 +1241,14 @@ def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch): class TestEarlyRewriteBailout: @pytest.fixture - def hook(self, pytestconfig, monkeypatch, testdir): + def hook(self, pytestconfig, monkeypatch, testdir) -> AssertionRewritingHook: """Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track if PathFinder.find_spec has been called. """ import importlib.machinery - self.find_spec_calls = [] - self.initial_paths = set() + self.find_spec_calls = [] # type: List[str] + self.initial_paths = set() # type: Set[py.path.local] class StubSession: _initialpaths = self.initial_paths @@ -1229,17 +1258,17 @@ class TestEarlyRewriteBailout: def spy_find_spec(name, path): self.find_spec_calls.append(name) - return importlib.machinery.PathFinder.find_spec(name, path) + return importlib.machinery.PathFinder.find_spec(name, path) # type: ignore hook = AssertionRewritingHook(pytestconfig) # use default patterns, otherwise we inherit pytest's testing config hook.fnpats[:] = ["test_*.py", "*_test.py"] monkeypatch.setattr(hook, "_find_spec", spy_find_spec) - hook.set_session(StubSession()) + hook.set_session(StubSession()) # type: ignore[arg-type] # noqa: F821 testdir.syspathinsert() return hook - def test_basic(self, testdir, hook): + def test_basic(self, testdir, hook: AssertionRewritingHook) -> None: """ Ensure we avoid calling PathFinder.find_spec when we know for sure a certain module will not be rewritten to optimize assertion rewriting (#3918). @@ -1272,7 +1301,9 @@ class TestEarlyRewriteBailout: assert hook.find_spec("foobar") is not None assert self.find_spec_calls == ["conftest", "test_foo", "foobar"] - def test_pattern_contains_subdirectories(self, testdir, hook): + def test_pattern_contains_subdirectories( + self, testdir, hook: AssertionRewritingHook + ) -> None: """If one of the python_files patterns contain subdirectories ("tests/**.py") we can't bailout early because we need to match with the full path, which can only be found by calling PathFinder.find_spec """ @@ -1515,17 +1546,17 @@ def test_get_assertion_exprs(src, expected): assert _get_assertion_exprs(src) == expected -def test_try_makedirs(monkeypatch, tmp_path): +def test_try_makedirs(monkeypatch, tmp_path: Path) -> None: from _pytest.assertion.rewrite import try_makedirs p = tmp_path / "foo" # create - assert try_makedirs(str(p)) + assert try_makedirs(p) assert p.is_dir() # already exist - assert try_makedirs(str(p)) + assert try_makedirs(p) # monkeypatch to simulate all error situations def fake_mkdir(p, exist_ok=False, *, exc): @@ -1533,25 +1564,25 @@ def test_try_makedirs(monkeypatch, tmp_path): raise exc monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=FileNotFoundError())) - assert not try_makedirs(str(p)) + assert not try_makedirs(p) monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=NotADirectoryError())) - assert not try_makedirs(str(p)) + assert not try_makedirs(p) monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=PermissionError())) - assert not try_makedirs(str(p)) + assert not try_makedirs(p) err = OSError() err.errno = errno.EROFS monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) - assert not try_makedirs(str(p)) + assert not try_makedirs(p) # unhandled OSError should raise err = OSError() err.errno = errno.ECHILD monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) with pytest.raises(OSError) as exc_info: - try_makedirs(str(p)) + try_makedirs(p) assert exc_info.value.errno == errno.ECHILD diff --git a/testing/test_capture.py b/testing/test_capture.py index 1301a0e69..9e5036a66 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -6,7 +6,9 @@ import sys import textwrap from io import UnsupportedOperation from typing import BinaryIO +from typing import cast from typing import Generator +from typing import TextIO import pytest from _pytest import capture @@ -1351,7 +1353,7 @@ def test_error_attribute_issue555(testdir): not sys.platform.startswith("win") and sys.version_info[:2] >= (3, 6), reason="only py3.6+ on windows", ) -def test_py36_windowsconsoleio_workaround_non_standard_streams(): +def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None: """ Ensure _py36_windowsconsoleio_workaround function works with objects that do not implement the full ``io``-based stream protocol, for example execnet channels (#2666). @@ -1362,7 +1364,7 @@ def test_py36_windowsconsoleio_workaround_non_standard_streams(): def write(self, s): pass - stream = DummyStream() + stream = cast(TextIO, DummyStream()) _py36_windowsconsoleio_workaround(stream) diff --git a/testing/test_collection.py b/testing/test_collection.py index dfbfe9ba8..8e5d5aacc 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -634,13 +634,14 @@ class TestSession: class Test_getinitialnodes: - def test_global_file(self, testdir, tmpdir): + def test_global_file(self, testdir, tmpdir) -> None: x = tmpdir.ensure("x.py") with tmpdir.as_cwd(): config = testdir.parseconfigure(x) col = testdir.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == "x.py" + assert col.parent is not None assert col.parent.parent is None for col in col.listchain(): assert col.config is config diff --git a/testing/test_config.py b/testing/test_config.py index c102202ed..867012e93 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2,6 +2,9 @@ import os import re import sys import textwrap +from typing import Dict +from typing import List +from typing import Sequence import py.path @@ -264,9 +267,9 @@ class TestConfigCmdlineParsing: class TestConfigAPI: - def test_config_trace(self, testdir): + def test_config_trace(self, testdir) -> None: config = testdir.parseconfig() - values = [] + values = [] # type: List[str] config.trace.root.setwriter(values.append) config.trace("hello") assert len(values) == 1 @@ -519,9 +522,9 @@ class TestConfigFromdictargs: assert config.option.capture == "no" assert config.args == args - def test_invocation_params_args(self, _sys_snapshot): + def test_invocation_params_args(self, _sys_snapshot) -> None: """Show that fromdictargs can handle args in their "orig" format""" - option_dict = {} + option_dict = {} # type: Dict[str, object] args = ["-vvvv", "-s", "a", "b"] config = Config.fromdictargs(option_dict, args) @@ -566,8 +569,8 @@ class TestConfigFromdictargs: assert config.inicfg.get("should_not_be_set") is None -def test_options_on_small_file_do_not_blow_up(testdir): - def runfiletest(opts): +def test_options_on_small_file_do_not_blow_up(testdir) -> None: + def runfiletest(opts: Sequence[str]) -> None: reprec = testdir.inline_run(*opts) passed, skipped, failed = reprec.countoutcomes() assert failed == 2 @@ -580,19 +583,16 @@ def test_options_on_small_file_do_not_blow_up(testdir): """ ) - for opts in ( - [], - ["-l"], - ["-s"], - ["--tb=no"], - ["--tb=short"], - ["--tb=long"], - ["--fulltrace"], - ["--traceconfig"], - ["-v"], - ["-v", "-v"], - ): - runfiletest(opts + [path]) + runfiletest([path]) + runfiletest(["-l", path]) + runfiletest(["-s", path]) + runfiletest(["--tb=no", path]) + runfiletest(["--tb=short", path]) + runfiletest(["--tb=long", path]) + runfiletest(["--fulltrace", path]) + runfiletest(["--traceconfig", path]) + runfiletest(["-v", path]) + runfiletest(["-v", "-v", path]) def test_preparse_ordering_with_setuptools(testdir, monkeypatch): @@ -1360,7 +1360,7 @@ def test_invocation_args(testdir): # args cannot be None with pytest.raises(TypeError): - Config.InvocationParams(args=None, plugins=None, dir=Path()) + Config.InvocationParams(args=None, plugins=None, dir=Path()) # type: ignore[arg-type] # noqa: F821 @pytest.mark.parametrize( diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 00af4a088..948b621f7 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -50,7 +50,7 @@ def custom_pdb_calls(): def interaction(self, *args): called.append("interaction") - _pytest._CustomPdb = _CustomPdb + _pytest._CustomPdb = _CustomPdb # type: ignore return called @@ -73,9 +73,9 @@ def custom_debugger_hook(): print("**CustomDebugger**") called.append("set_trace") - _pytest._CustomDebugger = _CustomDebugger + _pytest._CustomDebugger = _CustomDebugger # type: ignore yield called - del _pytest._CustomDebugger + del _pytest._CustomDebugger # type: ignore class TestPDB: @@ -895,7 +895,7 @@ class TestDebuggingBreakpoints: if sys.version_info >= (3, 7): assert SUPPORTS_BREAKPOINT_BUILTIN is True if sys.version_info.major == 3 and sys.version_info.minor == 5: - assert SUPPORTS_BREAKPOINT_BUILTIN is False + assert SUPPORTS_BREAKPOINT_BUILTIN is False # type: ignore[comparison-overlap] @pytest.mark.skipif( not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" diff --git a/testing/test_doctest.py b/testing/test_doctest.py index c3ba60deb..2b98b5267 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,5 +1,7 @@ import inspect import textwrap +from typing import Callable +from typing import Optional import pytest from _pytest.compat import MODULE_NOT_FOUND_ERROR @@ -1477,7 +1479,9 @@ class Broken: @pytest.mark.parametrize( # pragma: no branch (lambdas are not called) "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) -def test_warning_on_unwrap_of_broken_object(stop): +def test_warning_on_unwrap_of_broken_object( + stop: Optional[Callable[[object], object]] +) -> None: bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" with _patch_unwrap_mock_aware(): @@ -1486,7 +1490,7 @@ def test_warning_on_unwrap_of_broken_object(stop): pytest.PytestWarning, match="^Got KeyError.* when unwrapping" ): with pytest.raises(KeyError): - inspect.unwrap(bad_instance, stop=stop) + inspect.unwrap(bad_instance, stop=stop) # type: ignore[arg-type] # noqa: F821 assert inspect.unwrap.__module__ == "inspect" diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 83e61e1d9..d7771cc97 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1,16 +1,22 @@ import os import platform from datetime import datetime +from typing import cast +from typing import List +from typing import Tuple from xml.dom import minidom import py import xmlschema import pytest +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config from _pytest.junitxml import bin_xml_escape from _pytest.junitxml import LogXML from _pytest.pathlib import Path from _pytest.reports import BaseReport +from _pytest.reports import TestReport from _pytest.store import Store @@ -860,10 +866,13 @@ def test_mangle_test_address(): assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] -def test_dont_configure_on_slaves(tmpdir): - gotten = [] +def test_dont_configure_on_slaves(tmpdir) -> None: + gotten = [] # type: List[object] class FakeConfig: + if TYPE_CHECKING: + slaveinput = None + def __init__(self): self.pluginmanager = self self.option = self @@ -877,7 +886,7 @@ def test_dont_configure_on_slaves(tmpdir): xmlpath = str(tmpdir.join("junix.xml")) register = gotten.append - fake_config = FakeConfig() + fake_config = cast(Config, FakeConfig()) from _pytest import junitxml junitxml.pytest_configure(fake_config) @@ -1089,18 +1098,18 @@ def test_double_colon_split_method_issue469(testdir, run_and_parse): node.assert_attr(name="test_func[double::colon]") -def test_unicode_issue368(testdir): +def test_unicode_issue368(testdir) -> None: path = testdir.tmpdir.join("test.xml") log = LogXML(str(path), None) ustr = "ВНИ!" class Report(BaseReport): longrepr = ustr - sections = [] + sections = [] # type: List[Tuple[str, str]] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" - test_report = Report() + test_report = cast(TestReport, Report()) # hopefully this is not too brittle ... log.pytest_sessionstart() @@ -1113,7 +1122,7 @@ def test_unicode_issue368(testdir): node_reporter.append_skipped(test_report) test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣" node_reporter.append_skipped(test_report) - test_report.wasxfail = ustr + test_report.wasxfail = ustr # type: ignore[attr-defined] # noqa: F821 node_reporter.append_skipped(test_report) log.pytest_sessionfinish() @@ -1363,17 +1372,17 @@ def test_fancy_items_regression(testdir, run_and_parse): @parametrize_families -def test_global_properties(testdir, xunit_family): +def test_global_properties(testdir, xunit_family) -> None: path = testdir.tmpdir.join("test_global_properties.xml") log = LogXML(str(path), None, family=xunit_family) class Report(BaseReport): - sections = [] + sections = [] # type: List[Tuple[str, str]] nodeid = "test_node_id" log.pytest_sessionstart() - log.add_global_property("foo", 1) - log.add_global_property("bar", 2) + log.add_global_property("foo", "1") + log.add_global_property("bar", "2") log.pytest_sessionfinish() dom = minidom.parse(str(path)) @@ -1397,19 +1406,19 @@ def test_global_properties(testdir, xunit_family): assert actual == expected -def test_url_property(testdir): +def test_url_property(testdir) -> None: test_url = "http://www.github.com/pytest-dev" path = testdir.tmpdir.join("test_url_property.xml") log = LogXML(str(path), None) class Report(BaseReport): longrepr = "FooBarBaz" - sections = [] + sections = [] # type: List[Tuple[str, str]] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" url = test_url - test_report = Report() + test_report = cast(TestReport, Report()) log.pytest_sessionstart() node_reporter = log._opentestcase(test_report) diff --git a/testing/test_mark.py b/testing/test_mark.py index c14f770da..cdd4df9dd 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -13,14 +13,14 @@ from _pytest.nodes import Node class TestMark: @pytest.mark.parametrize("attr", ["mark", "param"]) @pytest.mark.parametrize("modulename", ["py.test", "pytest"]) - def test_pytest_exists_in_namespace_all(self, attr, modulename): + def test_pytest_exists_in_namespace_all(self, attr: str, modulename: str) -> None: module = sys.modules[modulename] - assert attr in module.__all__ + assert attr in module.__all__ # type: ignore - def test_pytest_mark_notcallable(self): + def test_pytest_mark_notcallable(self) -> None: mark = Mark() with pytest.raises(TypeError): - mark() + mark() # type: ignore[operator] # noqa: F821 def test_mark_with_param(self): def some_function(abc): @@ -30,10 +30,11 @@ class TestMark: pass assert pytest.mark.foo(some_function) is some_function - assert pytest.mark.foo.with_args(some_function) is not some_function + marked_with_args = pytest.mark.foo.with_args(some_function) + assert marked_with_args is not some_function # type: ignore[comparison-overlap] # noqa: F821 assert pytest.mark.foo(SomeClass) is SomeClass - assert pytest.mark.foo.with_args(SomeClass) is not SomeClass + assert pytest.mark.foo.with_args(SomeClass) is not SomeClass # type: ignore[comparison-overlap] # noqa: F821 def test_pytest_mark_name_starts_with_underscore(self): mark = Mark() @@ -1044,9 +1045,9 @@ def test_markers_from_parametrize(testdir): result.assert_outcomes(passed=4) -def test_pytest_param_id_requires_string(): +def test_pytest_param_id_requires_string() -> None: with pytest.raises(TypeError) as excinfo: - pytest.param(id=True) + pytest.param(id=True) # type: ignore[arg-type] # noqa: F821 (msg,) = excinfo.value.args assert msg == "Expected id to be a string, got : True" diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 8c2fceb3f..1a3afbea9 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -2,6 +2,8 @@ import os import re import sys import textwrap +from typing import Dict +from typing import Generator import pytest from _pytest.compat import TYPE_CHECKING @@ -12,7 +14,7 @@ if TYPE_CHECKING: @pytest.fixture -def mp(): +def mp() -> Generator[MonkeyPatch, None, None]: cwd = os.getcwd() sys_path = list(sys.path) yield MonkeyPatch() @@ -20,14 +22,14 @@ def mp(): os.chdir(cwd) -def test_setattr(): +def test_setattr() -> None: class A: x = 1 monkeypatch = MonkeyPatch() pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2) monkeypatch.setattr(A, "y", 2, raising=False) - assert A.y == 2 + assert A.y == 2 # type: ignore monkeypatch.undo() assert not hasattr(A, "y") @@ -49,17 +51,17 @@ class TestSetattrWithImportPath: monkeypatch.setattr("os.path.abspath", lambda x: "hello2") assert os.path.abspath("123") == "hello2" - def test_string_expression_class(self, monkeypatch): + def test_string_expression_class(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("_pytest.config.Config", 42) import _pytest - assert _pytest.config.Config == 42 + assert _pytest.config.Config == 42 # type: ignore - def test_unicode_string(self, monkeypatch): + def test_unicode_string(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("_pytest.config.Config", 42) import _pytest - assert _pytest.config.Config == 42 + assert _pytest.config.Config == 42 # type: ignore monkeypatch.delattr("_pytest.config.Config") def test_wrong_target(self, monkeypatch): @@ -73,10 +75,10 @@ class TestSetattrWithImportPath: AttributeError, lambda: monkeypatch.setattr("os.path.qweqwe", None) ) - def test_unknown_attr_non_raising(self, monkeypatch): + def test_unknown_attr_non_raising(self, monkeypatch: MonkeyPatch) -> None: # https://github.com/pytest-dev/pytest/issues/746 monkeypatch.setattr("os.path.qweqwe", 42, raising=False) - assert os.path.qweqwe == 42 + assert os.path.qweqwe == 42 # type: ignore def test_delattr(self, monkeypatch): monkeypatch.delattr("os.path.abspath") @@ -123,8 +125,8 @@ def test_setitem(): assert d["x"] == 5 -def test_setitem_deleted_meanwhile(): - d = {} +def test_setitem_deleted_meanwhile() -> None: + d = {} # type: Dict[str, object] monkeypatch = MonkeyPatch() monkeypatch.setitem(d, "x", 2) del d["x"] @@ -148,8 +150,8 @@ def test_setenv_deleted_meanwhile(before): assert key not in os.environ -def test_delitem(): - d = {"x": 1} +def test_delitem() -> None: + d = {"x": 1} # type: Dict[str, object] monkeypatch = MonkeyPatch() monkeypatch.delitem(d, "x") assert "x" not in d @@ -241,7 +243,7 @@ def test_monkeypatch_plugin(testdir): assert tuple(res) == (1, 0, 0), res -def test_syspath_prepend(mp): +def test_syspath_prepend(mp: MonkeyPatch): old = list(sys.path) mp.syspath_prepend("world") mp.syspath_prepend("hello") @@ -253,7 +255,7 @@ def test_syspath_prepend(mp): assert sys.path == old -def test_syspath_prepend_double_undo(mp): +def test_syspath_prepend_double_undo(mp: MonkeyPatch): old_syspath = sys.path[:] try: mp.syspath_prepend("hello world") @@ -265,24 +267,24 @@ def test_syspath_prepend_double_undo(mp): sys.path[:] = old_syspath -def test_chdir_with_path_local(mp, tmpdir): +def test_chdir_with_path_local(mp: MonkeyPatch, tmpdir): mp.chdir(tmpdir) assert os.getcwd() == tmpdir.strpath -def test_chdir_with_str(mp, tmpdir): +def test_chdir_with_str(mp: MonkeyPatch, tmpdir): mp.chdir(tmpdir.strpath) assert os.getcwd() == tmpdir.strpath -def test_chdir_undo(mp, tmpdir): +def test_chdir_undo(mp: MonkeyPatch, tmpdir): cwd = os.getcwd() mp.chdir(tmpdir) mp.undo() assert os.getcwd() == cwd -def test_chdir_double_undo(mp, tmpdir): +def test_chdir_double_undo(mp: MonkeyPatch, tmpdir): mp.chdir(tmpdir.strpath) mp.undo() tmpdir.chdir() diff --git a/testing/test_nodes.py b/testing/test_nodes.py index 5bd31b342..e5d8ffd71 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -2,6 +2,7 @@ import py import pytest from _pytest import nodes +from _pytest.pytester import Testdir @pytest.mark.parametrize( @@ -17,19 +18,19 @@ from _pytest import nodes ("foo/bar", "foo/bar::TestBop", True), ), ) -def test_ischildnode(baseid, nodeid, expected): +def test_ischildnode(baseid: str, nodeid: str, expected: bool) -> None: result = nodes.ischildnode(baseid, nodeid) assert result is expected -def test_node_from_parent_disallowed_arguments(): +def test_node_from_parent_disallowed_arguments() -> None: with pytest.raises(TypeError, match="session is"): - nodes.Node.from_parent(None, session=None) + nodes.Node.from_parent(None, session=None) # type: ignore[arg-type] # noqa: F821 with pytest.raises(TypeError, match="config is"): - nodes.Node.from_parent(None, config=None) + nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] # noqa: F821 -def test_std_warn_not_pytestwarning(testdir): +def test_std_warn_not_pytestwarning(testdir: Testdir) -> None: items = testdir.getitems( """ def test(): @@ -40,24 +41,24 @@ def test_std_warn_not_pytestwarning(testdir): items[0].warn(UserWarning("some warning")) -def test__check_initialpaths_for_relpath(): +def test__check_initialpaths_for_relpath() -> None: """Ensure that it handles dirs, and does not always use dirname.""" cwd = py.path.local() - class FakeSession: + class FakeSession1: _initialpaths = [cwd] - assert nodes._check_initialpaths_for_relpath(FakeSession, cwd) == "" + assert nodes._check_initialpaths_for_relpath(FakeSession1, cwd) == "" sub = cwd.join("file") - class FakeSession: + class FakeSession2: _initialpaths = [cwd] - assert nodes._check_initialpaths_for_relpath(FakeSession, sub) == "file" + assert nodes._check_initialpaths_for_relpath(FakeSession2, sub) == "file" outside = py.path.local("/outside") - assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None + assert nodes._check_initialpaths_for_relpath(FakeSession2, outside) is None def test_failure_with_changed_cwd(testdir): diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 86a42f9e8..0701641f8 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -1,10 +1,13 @@ +from typing import List +from typing import Union + import pytest class TestPasteCapture: @pytest.fixture - def pastebinlist(self, monkeypatch, request): - pastebinlist = [] + def pastebinlist(self, monkeypatch, request) -> List[Union[str, bytes]]: + pastebinlist = [] # type: List[Union[str, bytes]] plugin = request.config.pluginmanager.getplugin("pastebin") monkeypatch.setattr(plugin, "create_new_paste", pastebinlist.append) return pastebinlist diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 336f468a8..713687578 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,6 +1,7 @@ import os import sys import types +from typing import List import pytest from _pytest.config import ExitCode @@ -10,7 +11,7 @@ from _pytest.main import Session @pytest.fixture -def pytestpm(): +def pytestpm() -> PytestPluginManager: return PytestPluginManager() @@ -86,7 +87,7 @@ class TestPytestPluginInteractions: config.pluginmanager.register(A()) assert len(values) == 2 - def test_hook_tracing(self, _config_for_test): + def test_hook_tracing(self, _config_for_test) -> None: pytestpm = _config_for_test.pluginmanager # fully initialized with plugins saveindent = [] @@ -99,7 +100,7 @@ class TestPytestPluginInteractions: saveindent.append(pytestpm.trace.root.indent) raise ValueError() - values = [] + values = [] # type: List[str] pytestpm.trace.root.setwriter(values.append) undo = pytestpm.enable_tracing() try: @@ -215,20 +216,20 @@ class TestPytestPluginManager: assert pm.get_plugin("pytest_xyz") == mod assert pm.is_registered(mod) - def test_consider_module(self, testdir, pytestpm): + def test_consider_module(self, testdir, pytestpm: PytestPluginManager) -> None: testdir.syspathinsert() testdir.makepyfile(pytest_p1="#") testdir.makepyfile(pytest_p2="#") mod = types.ModuleType("temp") - mod.pytest_plugins = ["pytest_p1", "pytest_p2"] + mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"] pytestpm.consider_module(mod) assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1" assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2" - def test_consider_module_import_module(self, testdir, _config_for_test): + def test_consider_module_import_module(self, testdir, _config_for_test) -> None: pytestpm = _config_for_test.pluginmanager mod = types.ModuleType("x") - mod.pytest_plugins = "pytest_a" + mod.__dict__["pytest_plugins"] = "pytest_a" aplugin = testdir.makepyfile(pytest_a="#") reprec = testdir.make_hook_recorder(pytestpm) testdir.syspathinsert(aplugin.dirpath()) diff --git a/testing/test_reports.py b/testing/test_reports.py index 81778e27d..08ac014a4 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -32,7 +32,7 @@ class TestReportSerialization: assert test_b_call.outcome == "passed" assert test_b_call._to_json()["longrepr"] is None - def test_xdist_report_longrepr_reprcrash_130(self, testdir): + def test_xdist_report_longrepr_reprcrash_130(self, testdir) -> None: """Regarding issue pytest-xdist#130 This test came originally from test_remote.py in xdist (ca03269). @@ -50,6 +50,7 @@ class TestReportSerialization: rep.longrepr.sections.append(added_section) d = rep._to_json() a = TestReport._from_json(d) + assert a.longrepr is not None # Check assembled == rep assert a.__dict__.keys() == rep.__dict__.keys() for key in rep.__dict__.keys(): @@ -67,7 +68,7 @@ class TestReportSerialization: # Missing section attribute PR171 assert added_section in a.longrepr.sections - def test_reprentries_serialization_170(self, testdir): + def test_reprentries_serialization_170(self, testdir) -> None: """Regarding issue pytest-xdist#170 This test came originally from test_remote.py in xdist (ca03269). @@ -87,6 +88,7 @@ class TestReportSerialization: rep = reports[1] d = rep._to_json() a = TestReport._from_json(d) + assert a.longrepr is not None rep_entries = rep.longrepr.reprtraceback.reprentries a_entries = a.longrepr.reprtraceback.reprentries @@ -102,7 +104,7 @@ class TestReportSerialization: assert rep_entries[i].reprlocals.lines == a_entries[i].reprlocals.lines assert rep_entries[i].style == a_entries[i].style - def test_reprentries_serialization_196(self, testdir): + def test_reprentries_serialization_196(self, testdir) -> None: """Regarding issue pytest-xdist#196 This test came originally from test_remote.py in xdist (ca03269). @@ -122,6 +124,7 @@ class TestReportSerialization: rep = reports[1] d = rep._to_json() a = TestReport._from_json(d) + assert a.longrepr is not None rep_entries = rep.longrepr.reprtraceback.reprentries a_entries = a.longrepr.reprtraceback.reprentries @@ -157,6 +160,7 @@ class TestReportSerialization: assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped if newrep.skipped and not hasattr(newrep, "wasxfail"): + assert newrep.longrepr is not None assert len(newrep.longrepr) == 3 assert newrep.outcome == rep.outcome assert newrep.when == rep.when @@ -316,7 +320,7 @@ class TestReportSerialization: # elsewhere and we do check the contents of the longrepr object after loading it. loaded_report.longrepr.toterminal(tw_mock) - def test_chained_exceptions_no_reprcrash(self, testdir, tw_mock): + def test_chained_exceptions_no_reprcrash(self, testdir, tw_mock) -> None: """Regression test for tracebacks without a reprcrash (#5971) This happens notably on exceptions raised by multiprocess.pool: the exception transfer @@ -367,7 +371,7 @@ class TestReportSerialization: reports = reprec.getreports("pytest_runtest_logreport") - def check_longrepr(longrepr): + def check_longrepr(longrepr) -> None: assert isinstance(longrepr, ExceptionChainRepr) assert len(longrepr.chain) == 2 entry1, entry2 = longrepr.chain @@ -378,6 +382,7 @@ class TestReportSerialization: assert "ValueError: value error" in str(tb2) assert fileloc1 is None + assert fileloc2 is not None assert fileloc2.message == "ValueError: value error" # 3 reports: setup/call/teardown: get the call report @@ -394,6 +399,7 @@ class TestReportSerialization: check_longrepr(loaded_report.longrepr) # for same reasons as previous test, ensure we don't blow up here + assert loaded_report.longrepr is not None loaded_report.longrepr.toterminal(tw_mock) def test_report_prevent_ConftestImportFailure_hiding_exception(self, testdir): diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index 0ff508d2c..1b5d97371 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -2,6 +2,8 @@ test correct setup/teardowns at module, class, and instance level """ +from typing import List + import pytest @@ -242,12 +244,12 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir): @pytest.mark.parametrize("arg", ["", "arg"]) def test_setup_teardown_function_level_with_optional_argument( - testdir, monkeypatch, arg -): + testdir, monkeypatch, arg: str, +) -> None: """parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" import sys - trace_setups_teardowns = [] + trace_setups_teardowns = [] # type: List[str] monkeypatch.setattr( sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False ) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index f48e78364..a6f1a9c09 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -98,7 +98,7 @@ class TestEvaluator: expl = ev.getexplanation() assert expl == "condition: not hasattr(os, 'murks')" - def test_marked_skip_with_not_string(self, testdir): + def test_marked_skip_with_not_string(self, testdir) -> None: item = testdir.getitem( """ import pytest @@ -109,6 +109,7 @@ class TestEvaluator: ) ev = MarkEvaluator(item, "skipif") exc = pytest.raises(pytest.fail.Exception, ev.istrue) + assert exc.value.msg is not None assert ( """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg @@ -869,7 +870,7 @@ def test_reportchars_all_error(testdir): result.stdout.fnmatch_lines(["ERROR*test_foo*"]) -def test_errors_in_xfail_skip_expressions(testdir): +def test_errors_in_xfail_skip_expressions(testdir) -> None: testdir.makepyfile( """ import pytest @@ -886,7 +887,8 @@ def test_errors_in_xfail_skip_expressions(testdir): ) result = testdir.runpytest() markline = " ^" - if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (6,): + pypy_version_info = getattr(sys, "pypy_version_info", None) + if pypy_version_info is not None and pypy_version_info < (6,): markline = markline[5:] elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): markline = markline[4:] diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 17fd29238..7d7c82ad6 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -6,6 +6,7 @@ import os import sys import textwrap from io import StringIO +from typing import cast from typing import Dict from typing import List from typing import Tuple @@ -17,9 +18,11 @@ import _pytest.config import _pytest.terminal import pytest from _pytest._io.wcwidth import wcswidth +from _pytest.config import Config from _pytest.config import ExitCode from _pytest.pytester import Testdir from _pytest.reports import BaseReport +from _pytest.reports import CollectReport from _pytest.terminal import _folded_skips from _pytest.terminal import _get_line_with_reprcrash_message from _pytest.terminal import _plugin_nameversions @@ -1043,17 +1046,17 @@ def test_color_yes_collection_on_non_atty(testdir, verbose): assert "collected 10 items" in result.stdout.str() -def test_getreportopt(): +def test_getreportopt() -> None: from _pytest.terminal import _REPORTCHARS_DEFAULT - class Config: + class FakeConfig: class Option: reportchars = _REPORTCHARS_DEFAULT disable_warnings = False option = Option() - config = Config() + config = cast(Config, FakeConfig()) assert _REPORTCHARS_DEFAULT == "fE" @@ -1994,7 +1997,7 @@ class TestProgressWithTeardown: output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"]) -def test_skip_reasons_folding(): +def test_skip_reasons_folding() -> None: path = "xyz" lineno = 3 message = "justso" @@ -2003,28 +2006,28 @@ def test_skip_reasons_folding(): class X: pass - ev1 = X() + ev1 = cast(CollectReport, X()) ev1.when = "execute" ev1.skipped = True ev1.longrepr = longrepr - ev2 = X() + ev2 = cast(CollectReport, X()) ev2.when = "execute" ev2.longrepr = longrepr ev2.skipped = True # ev3 might be a collection report - ev3 = X() + ev3 = cast(CollectReport, X()) ev3.when = "collect" ev3.longrepr = longrepr ev3.skipped = True values = _folded_skips(py.path.local(), [ev1, ev2, ev3]) assert len(values) == 1 - num, fspath, lineno, reason = values[0] + num, fspath, lineno_, reason = values[0] assert num == 3 assert fspath == path - assert lineno == lineno + assert lineno_ == lineno assert reason == message @@ -2052,8 +2055,8 @@ def test_line_with_reprcrash(monkeypatch): def check(msg, width, expected): __tracebackhide__ = True if msg: - rep.longrepr.reprcrash.message = msg - actual = _get_line_with_reprcrash_message(config, rep(), width) + rep.longrepr.reprcrash.message = msg # type: ignore + actual = _get_line_with_reprcrash_message(config, rep(), width) # type: ignore assert actual == expected if actual != "{} {}".format(mocked_verbose_word, mocked_pos): diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 3316751fb..26a34c656 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,6 +1,8 @@ import os import stat import sys +from typing import Callable +from typing import List import attr @@ -263,10 +265,10 @@ class TestNumberedDir: lockfile.unlink() - def test_lock_register_cleanup_removal(self, tmp_path): + def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: lock = create_cleanup_lock(tmp_path) - registry = [] + registry = [] # type: List[Callable[..., None]] register_cleanup_lock_removal(lock, register=registry.append) (cleanup_func,) = registry @@ -285,7 +287,7 @@ class TestNumberedDir: assert not lock.exists() - def _do_cleanup(self, tmp_path): + def _do_cleanup(self, tmp_path: Path) -> None: self.test_make(tmp_path) cleanup_numbered_dir( root=tmp_path, @@ -367,7 +369,7 @@ class TestRmRf: assert not adir.is_dir() - def test_on_rm_rf_error(self, tmp_path): + def test_on_rm_rf_error(self, tmp_path: Path) -> None: adir = tmp_path / "dir" adir.mkdir() @@ -377,32 +379,32 @@ class TestRmRf: # unknown exception with pytest.warns(pytest.PytestWarning): - exc_info = (None, RuntimeError(), None) - on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) + exc_info1 = (None, RuntimeError(), None) + on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path) assert fn.is_file() # we ignore FileNotFoundError - exc_info = (None, FileNotFoundError(), None) - assert not on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path) + exc_info2 = (None, FileNotFoundError(), None) + assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path) # unknown function with pytest.warns( pytest.PytestWarning, match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ", ): - exc_info = (None, PermissionError(), None) - on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path) + exc_info3 = (None, PermissionError(), None) + on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path) assert fn.is_file() # ignored function with pytest.warns(None) as warninfo: - exc_info = (None, PermissionError(), None) - on_rm_rf_error(os.open, str(fn), exc_info, start_path=tmp_path) + exc_info4 = (None, PermissionError(), None) + on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) assert fn.is_file() assert not [x.message for x in warninfo] - exc_info = (None, PermissionError(), None) - on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) + exc_info5 = (None, PermissionError(), None) + on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path) assert not fn.is_file() diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 74a36c41b..6ddc6186b 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,4 +1,5 @@ import gc +from typing import List import pytest from _pytest.config import ExitCode @@ -1158,13 +1159,13 @@ def test_trace(testdir, monkeypatch): assert result.ret == 0 -def test_pdb_teardown_called(testdir, monkeypatch): +def test_pdb_teardown_called(testdir, monkeypatch) -> None: """Ensure tearDown() is always called when --pdb is given in the command-line. We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling tearDown() eventually to avoid memory leaks when using --pdb. """ - teardowns = [] + teardowns = [] # type: List[str] monkeypatch.setattr( pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False ) @@ -1194,11 +1195,11 @@ def test_pdb_teardown_called(testdir, monkeypatch): @pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) -def test_pdb_teardown_skipped(testdir, monkeypatch, mark): +def test_pdb_teardown_skipped(testdir, monkeypatch, mark: str) -> None: """ With --pdb, setUp and tearDown should not be called for skipped tests. """ - tracked = [] + tracked = [] # type: List[str] monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) testdir.makepyfile( diff --git a/testing/test_warnings.py b/testing/test_warnings.py index ea7ab397d..e21ccf42a 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,5 +1,8 @@ import os import warnings +from typing import List +from typing import Optional +from typing import Tuple import pytest from _pytest.fixtures import FixtureRequest @@ -661,7 +664,9 @@ class TestStackLevel: @pytest.fixture def capwarn(self, testdir): class CapturedWarnings: - captured = [] + captured = ( + [] + ) # type: List[Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]]] @classmethod def pytest_warning_recorded(cls, warning_message, when, nodeid, location):