Merge pull request #11914 - Activate flake8-bugbear and flake8-pyi

This commit is contained in:
Pierre Sassoulas 2024-02-05 09:37:37 +01:00 committed by GitHub
commit 0d1f4c63fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 64 additions and 52 deletions

View File

@ -128,15 +128,24 @@ ignore = "W009"
src = ["src"] src = ["src"]
line-length = 88 line-length = 88
select = [ select = [
"B", # bugbear
"D", # pydocstyle "D", # pydocstyle
"E", # pycodestyle "E", # pycodestyle
"F", # pyflakes "F", # pyflakes
"I", # isort "I", # isort
"PYI", # flake8-pyi
"UP", # pyupgrade "UP", # pyupgrade
"RUF", # ruff "RUF", # ruff
"W", # pycodestyle "W", # pycodestyle
] ]
ignore = [ ignore = [
# bugbear ignore
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
"B007", # Loop control variable `i` not used within loop body
"B009", # Do not call `getattr` with a constant attribute value
"B010", # [*] Do not call `setattr` with a constant attribute value.
"B011", # Do not `assert False` (`python -O` removes these calls)
"B028", # No explicit `stacklevel` keyword argument found
# pycodestyle ignore # pycodestyle ignore
# pytest can do weird low-level things, and we usually know # pytest can do weird low-level things, and we usually know
# what we're doing when we use type(..) is ... # what we're doing when we use type(..) is ...
@ -180,4 +189,6 @@ known-local-folder = ["pytest", "_pytest"]
lines-after-imports = 2 lines-after-imports = 2
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"src/_pytest/_py/**/*.py" = ["B", "PYI"]
"src/_pytest/_version.py" = ["I001"] "src/_pytest/_version.py" = ["I001"]
"testing/python/approx.py" = ["B015"]

View File

@ -79,7 +79,7 @@ def prepare_release_pr(
) )
except InvalidFeatureRelease as e: except InvalidFeatureRelease as e:
print(f"{Fore.RED}{e}") print(f"{Fore.RED}{e}")
raise SystemExit(1) raise SystemExit(1) from None
print(f"Version: {Fore.CYAN}{version}") print(f"Version: {Fore.CYAN}{version}")

View File

@ -208,7 +208,7 @@ def main() -> None:
f.write(f"This list contains {len(plugins)} plugins.\n\n") f.write(f"This list contains {len(plugins)} plugins.\n\n")
f.write(".. only:: not latex\n\n") f.write(".. only:: not latex\n\n")
wcwidth # reference library that must exist for tabulate to work _ = wcwidth # reference library that must exist for tabulate to work
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst") plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
f.write(indent(plugin_table, " ")) f.write(indent(plugin_table, " "))
f.write("\n\n") f.write("\n\n")

View File

@ -232,17 +232,17 @@ class TerminalWriter:
# which may lead to the previous color being propagated to the # which may lead to the previous color being propagated to the
# start of the expression, so reset first. # start of the expression, so reset first.
return "\x1b[0m" + highlighted return "\x1b[0m" + highlighted
except pygments.util.ClassNotFound: except pygments.util.ClassNotFound as e:
raise UsageError( raise UsageError(
"PYTEST_THEME environment variable had an invalid value: '{}'. " "PYTEST_THEME environment variable had an invalid value: '{}'. "
"Only valid pygment styles are allowed.".format( "Only valid pygment styles are allowed.".format(
os.getenv("PYTEST_THEME") os.getenv("PYTEST_THEME")
) )
) ) from e
except pygments.util.OptionError: except pygments.util.OptionError as e:
raise UsageError( raise UsageError(
"PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. "
"The only allowed values are 'dark' and 'light'.".format( "The only allowed values are 'dark' and 'light'.".format(
os.getenv("PYTEST_THEME_MODE") os.getenv("PYTEST_THEME_MODE")
) )
) ) from e

View File

@ -598,7 +598,8 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING:
else: else:
class CaptureResult( class CaptureResult(
collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr] collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024
Generic[AnyStr],
): ):
"""The result of :method:`caplog.readouterr() <pytest.CaptureFixture.readouterr>`.""" """The result of :method:`caplog.readouterr() <pytest.CaptureFixture.readouterr>`."""

View File

@ -15,11 +15,6 @@ from typing import Any
from typing import Callable from typing import Callable
from typing import Final from typing import Final
from typing import NoReturn from typing import NoReturn
from typing import TypeVar
_T = TypeVar("_T")
_S = TypeVar("_S")
# fmt: off # fmt: off

View File

@ -1848,13 +1848,13 @@ def parse_warning_filter(
try: try:
action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
except warnings._OptionError as e: except warnings._OptionError as e:
raise UsageError(error_template.format(error=str(e))) raise UsageError(error_template.format(error=str(e))) from None
try: try:
category: Type[Warning] = _resolve_warning_category(category_) category: Type[Warning] = _resolve_warning_category(category_)
except Exception: except Exception:
exc_info = ExceptionInfo.from_current() exc_info = ExceptionInfo.from_current()
exception_text = exc_info.getrepr(style="native") exception_text = exc_info.getrepr(style="native")
raise UsageError(error_template.format(error=exception_text)) raise UsageError(error_template.format(error=exception_text)) from None
if message and escape: if message and escape:
message = re.escape(message) message = re.escape(message)
if module and escape: if module and escape:
@ -1867,7 +1867,7 @@ def parse_warning_filter(
except ValueError as e: except ValueError as e:
raise UsageError( raise UsageError(
error_template.format(error=f"invalid lineno {lineno_!r}: {e}") error_template.format(error=f"invalid lineno {lineno_!r}: {e}")
) ) from None
else: else:
lineno = 0 lineno = 0
return action, message, category, module, lineno return action, message, category, module, lineno

View File

@ -209,8 +209,8 @@ class TestCaseFunction(Function):
) )
# Invoke the attributes to trigger storing the traceback # Invoke the attributes to trigger storing the traceback
# trial causes some issue there. # trial causes some issue there.
excinfo.value _ = excinfo.value
excinfo.traceback _ = excinfo.traceback
except TypeError: except TypeError:
try: try:
try: try:
@ -361,14 +361,21 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
# Twisted trial support. # Twisted trial support.
classImplements_has_run = False
@hookimpl(wrapper=True) @hookimpl(wrapper=True)
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
ut: Any = sys.modules["twisted.python.failure"] ut: Any = sys.modules["twisted.python.failure"]
global classImplements_has_run
Failure__init__ = ut.Failure.__init__ Failure__init__ = ut.Failure.__init__
check_testcase_implements_trial_reporter() if not classImplements_has_run:
from twisted.trial.itrial import IReporter
from zope.interface import classImplements
classImplements(TestCaseFunction, IReporter)
classImplements_has_run = True
def excstore( def excstore(
self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
@ -396,16 +403,6 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
return res return res
def check_testcase_implements_trial_reporter(done: List[int] = []) -> None:
if done:
return
from twisted.trial.itrial import IReporter
from zope.interface import classImplements
classImplements(TestCaseFunction, IReporter)
done.append(1)
def _is_skipped(obj) -> bool: def _is_skipped(obj) -> bool:
"""Return True if the given object has been marked with @unittest.skip.""" """Return True if the given object has been marked with @unittest.skip."""
return bool(getattr(obj, "__unittest_skip__", False)) return bool(getattr(obj, "__unittest_skip__", False))

View File

@ -1241,9 +1241,9 @@ class TestWINLocalPath:
def test_owner_group_not_implemented(self, path1): def test_owner_group_not_implemented(self, path1):
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
path1.stat().owner _ = path1.stat().owner
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
path1.stat().group _ = path1.stat().group
def test_chmod_simple_int(self, path1): def test_chmod_simple_int(self, path1):
mode = path1.stat().mode mode = path1.stat().mode

View File

@ -387,7 +387,7 @@ def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
excinfo = pytest.raises(ValueError, template.render, h=h) excinfo = pytest.raises(ValueError, template.render, h=h)
for item in excinfo.traceback: for item in excinfo.traceback:
print(item) # XXX: for some reason jinja.Template.render is printed in full print(item) # XXX: for some reason jinja.Template.render is printed in full
item.source # shouldn't fail _ = item.source # shouldn't fail
if isinstance(item.path, Path) and item.path.name == "test.txt": if isinstance(item.path, Path) and item.path.name == "test.txt":
assert str(item.source) == "{{ h()}}:" assert str(item.source) == "{{ h()}}:"
@ -418,7 +418,7 @@ def test_codepath_Queue_example() -> None:
def test_match_succeeds(): def test_match_succeeds():
with pytest.raises(ZeroDivisionError) as excinfo: with pytest.raises(ZeroDivisionError) as excinfo:
0 // 0 _ = 0 // 0
excinfo.match(r".*zero.*") excinfo.match(r".*zero.*")
@ -584,7 +584,7 @@ class TestFormattedExcinfo:
try: try:
def f(): def f():
1 / 0 _ = 1 / 0
f() f()
@ -601,7 +601,7 @@ class TestFormattedExcinfo:
print(line) print(line)
assert lines == [ assert lines == [
" def f():", " def f():",
"> 1 / 0", "> _ = 1 / 0",
"E ZeroDivisionError: division by zero", "E ZeroDivisionError: division by zero",
] ]
@ -638,7 +638,7 @@ raise ValueError()
pr = FormattedExcinfo() pr = FormattedExcinfo()
try: try:
1 / 0 _ = 1 / 0
except ZeroDivisionError: except ZeroDivisionError:
excinfo = ExceptionInfo.from_current() excinfo = ExceptionInfo.from_current()
@ -1582,7 +1582,7 @@ def test_no_recursion_index_on_recursion_error():
return getattr(self, "_" + attr) return getattr(self, "_" + attr)
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
RecursionDepthError().trigger _ = RecursionDepthError().trigger
assert "maximum recursion" in str(excinfo.getrepr()) assert "maximum recursion" in str(excinfo.getrepr())

View File

@ -280,7 +280,7 @@ class TestRaises:
def test_raises_context_manager_with_kwargs(self): def test_raises_context_manager_with_kwargs(self):
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] with pytest.raises(OSError, foo="bar"): # type: ignore[call-overload]
pass pass
assert "Unexpected keyword arguments" in str(excinfo.value) assert "Unexpected keyword arguments" in str(excinfo.value)

View File

@ -1,10 +1,10 @@
# mypy: allow-untyped-defs # mypy: allow-untyped-defs
import collections
import sys import sys
import textwrap import textwrap
from typing import Any from typing import Any
from typing import List from typing import List
from typing import MutableSequence from typing import MutableSequence
from typing import NamedTuple
from typing import Optional from typing import Optional
import attr import attr
@ -1179,7 +1179,9 @@ class TestAssert_reprcompare_attrsclass:
class TestAssert_reprcompare_namedtuple: class TestAssert_reprcompare_namedtuple:
def test_namedtuple(self) -> None: def test_namedtuple(self) -> None:
NT = collections.namedtuple("NT", ["a", "b"]) class NT(NamedTuple):
a: Any
b: Any
left = NT(1, "b") left = NT(1, "b")
right = NT(1, "c") right = NT(1, "c")
@ -1200,8 +1202,13 @@ class TestAssert_reprcompare_namedtuple:
] ]
def test_comparing_two_different_namedtuple(self) -> None: def test_comparing_two_different_namedtuple(self) -> None:
NT1 = collections.namedtuple("NT1", ["a", "b"]) class NT1(NamedTuple):
NT2 = collections.namedtuple("NT2", ["a", "b"]) a: Any
b: Any
class NT2(NamedTuple):
a: Any
b: Any
left = NT1(1, "b") left = NT1(1, "b")
right = NT2(2, "b") right = NT2(2, "b")

View File

@ -169,17 +169,17 @@ class ErrorsHelper:
def test_helper_failures() -> None: def test_helper_failures() -> None:
helper = ErrorsHelper() helper = ErrorsHelper()
with pytest.raises(Exception): with pytest.raises(Exception): # noqa: B017
helper.raise_exception _ = helper.raise_exception
with pytest.raises(OutcomeException): with pytest.raises(OutcomeException):
helper.raise_fail_outcome _ = helper.raise_fail_outcome
def test_safe_getattr() -> None: def test_safe_getattr() -> None:
helper = ErrorsHelper() helper = ErrorsHelper()
assert safe_getattr(helper, "raise_exception", "default") == "default" assert safe_getattr(helper, "raise_exception", "default") == "default"
assert safe_getattr(helper, "raise_fail_outcome", "default") == "default" assert safe_getattr(helper, "raise_fail_outcome", "default") == "default"
with pytest.raises(BaseException): with pytest.raises(BaseException): # noqa: B017
assert safe_getattr(helper, "raise_baseexception", "default") assert safe_getattr(helper, "raise_baseexception", "default")

View File

@ -108,7 +108,7 @@ class TestFixtureRequestSessionScoped:
AttributeError, AttributeError,
match="path not available in session-scoped context", match="path not available in session-scoped context",
): ):
session_request.fspath _ = session_request.fspath
@pytest.mark.parametrize("config_type", ["ini", "pyproject"]) @pytest.mark.parametrize("config_type", ["ini", "pyproject"])

View File

@ -42,7 +42,7 @@ class TestMark:
def test_pytest_mark_name_starts_with_underscore(self) -> None: def test_pytest_mark_name_starts_with_underscore(self) -> None:
mark = MarkGenerator(_ispytest=True) mark = MarkGenerator(_ispytest=True)
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
mark._some_name _ = mark._some_name
def test_marked_class_run_twice(pytester: Pytester) -> None: def test_marked_class_run_twice(pytester: Pytester) -> None:

View File

@ -228,7 +228,7 @@ class TestDeprecatedCall:
for warning in other_warnings: for warning in other_warnings:
def f(): def f():
warnings.warn(warning("hi")) warnings.warn(warning("hi")) # noqa: B023
with pytest.warns(warning): with pytest.warns(warning):
with pytest.raises(pytest.fail.Exception): with pytest.raises(pytest.fail.Exception):

View File

@ -1,6 +1,5 @@
# mypy: allow-untyped-defs # mypy: allow-untyped-defs
"""Terminal reporting of the full testing process.""" """Terminal reporting of the full testing process."""
import collections
from io import StringIO from io import StringIO
import os import os
from pathlib import Path from pathlib import Path
@ -10,6 +9,7 @@ from types import SimpleNamespace
from typing import cast from typing import cast
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import NamedTuple
from typing import Tuple from typing import Tuple
import pluggy import pluggy
@ -34,7 +34,9 @@ from _pytest.terminal import TerminalReporter
import pytest import pytest
DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"]) class DistInfo(NamedTuple):
project_name: str
version: int
TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"}) TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"})

View File

@ -18,8 +18,7 @@ WARNINGS_SUMMARY_HEADER = "warnings summary"
def pyfile_with_warnings(pytester: Pytester, request: FixtureRequest) -> str: def pyfile_with_warnings(pytester: Pytester, request: FixtureRequest) -> str:
"""Create a test file which calls a function in a module which generates warnings.""" """Create a test file which calls a function in a module which generates warnings."""
pytester.syspathinsert() pytester.syspathinsert()
test_name = request.function.__name__ module_name = request.function.__name__[len("test_") :] + "_module"
module_name = test_name.lstrip("test_") + "_module"
test_file = pytester.makepyfile( test_file = pytester.makepyfile(
f""" f"""
import {module_name} import {module_name}