From 2b5952ca5aaff42ec8d546e525ceb9c70e09dabd Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 15:26:12 +0200
Subject: [PATCH 1/3] testing: avoid legacy forms of `warns`, `raises`,
`deprecated_call`
Use the nicer `with` forms instead.
---
testing/_py/test_local.py | 15 ++--
testing/code/test_code.py | 6 +-
testing/code/test_excinfo.py | 126 +++++++++++++++++++++++-----------
testing/code/test_source.py | 9 ++-
testing/python/collect.py | 12 ++--
testing/python/fixtures.py | 8 +--
testing/python/raises.py | 21 +++---
testing/test_cacheprovider.py | 6 +-
testing/test_capture.py | 59 ++++++++++------
testing/test_config.py | 15 ++--
testing/test_debugging.py | 5 +-
testing/test_legacypath.py | 3 +-
testing/test_mark.py | 4 +-
testing/test_monkeypatch.py | 12 ++--
testing/test_parseopt.py | 3 +-
testing/test_pluginmanager.py | 23 ++++---
testing/test_pytester.py | 6 +-
testing/test_recwarn.py | 51 +++++++-------
testing/test_runner.py | 6 +-
testing/test_session.py | 3 +-
testing/test_skipping.py | 3 +-
21 files changed, 252 insertions(+), 144 deletions(-)
diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py
index c32e71f6542..be6f4197b26 100644
--- a/testing/_py/test_local.py
+++ b/testing/_py/test_local.py
@@ -620,7 +620,8 @@ def test_chdir_gone(self, path1):
p = path1.ensure("dir_to_be_removed", dir=1)
p.chdir()
p.remove()
- pytest.raises(error.ENOENT, local)
+ with pytest.raises(error.ENOENT):
+ local()
assert path1.chdir() is None
assert os.getcwd() == str(path1)
@@ -989,8 +990,10 @@ def test_locked_make_numbered_dir(self, tmpdir):
assert numdir.new(ext=str(j)).check()
def test_error_preservation(self, path1):
- pytest.raises(EnvironmentError, path1.join("qwoeqiwe").mtime)
- pytest.raises(EnvironmentError, path1.join("qwoeqiwe").read)
+ with pytest.raises(EnvironmentError):
+ path1.join("qwoeqiwe").mtime()
+ with pytest.raises(EnvironmentError):
+ path1.join("qwoeqiwe").read()
# def test_parentdirmatch(self):
# local.parentdirmatch('std', startmodule=__name__)
@@ -1090,7 +1093,8 @@ def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir):
pseudopath = tmpdir.ensure(name + "123.py")
mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod)
- excinfo = pytest.raises(pseudopath.ImportMismatchError, p.pyimport)
+ with pytest.raises(pseudopath.ImportMismatchError) as excinfo:
+ p.pyimport()
modname, modfile, orig = excinfo.value.args
assert modname == name
assert modfile == pseudopath
@@ -1388,7 +1392,8 @@ def test_stat_helpers(self, tmpdir, monkeypatch):
def test_stat_non_raising(self, tmpdir):
path1 = tmpdir.join("file")
- pytest.raises(error.ENOENT, path1.stat)
+ with pytest.raises(error.ENOENT):
+ path1.stat()
res = path1.stat(raising=False)
assert res is None
diff --git a/testing/code/test_code.py b/testing/code/test_code.py
index 63f6031e396..e10139dee50 100644
--- a/testing/code/test_code.py
+++ b/testing/code/test_code.py
@@ -85,10 +85,8 @@ def test_code_from_func() -> None:
def test_unicode_handling() -> None:
value = "ąć".encode()
- def f() -> None:
- raise ValueError(value)
-
- excinfo = pytest.raises(ValueError, f)
+ with pytest.raises(Exception) as excinfo:
+ raise Exception(value)
str(excinfo)
diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py
index 9998ad1b141..d578148b554 100644
--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -222,17 +222,18 @@ def h():
g()
#
- excinfo = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo:
+ h()
traceback = excinfo.traceback
ntraceback = traceback.filter(excinfo)
print(f"old: {traceback!r}")
print(f"new: {ntraceback!r}")
if matching:
- assert len(ntraceback) == len(traceback) - 2
- else:
# -1 because of the __tracebackhide__ in pytest.raises
assert len(ntraceback) == len(traceback) - 1
+ else:
+ assert len(ntraceback) == len(traceback)
def test_traceback_recursion_index(self):
def f(n):
@@ -240,7 +241,8 @@ def f(n):
n += 1
f(n)
- excinfo = pytest.raises(RecursionError, f, 8)
+ with pytest.raises(RecursionError) as excinfo:
+ f(8)
traceback = excinfo.traceback
recindex = traceback.recursionindex()
assert recindex == 3
@@ -251,7 +253,8 @@ def f(n):
raise RuntimeError("hello")
f(n - 1)
- excinfo = pytest.raises(RuntimeError, f, 25)
+ with pytest.raises(RuntimeError) as excinfo:
+ f(25)
monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
repr = excinfo.getrepr()
assert "RuntimeError: hello" in str(repr.reprcrash)
@@ -273,8 +276,8 @@ def f(n: int) -> None:
except BaseException:
reraise_me()
- excinfo = pytest.raises(RuntimeError, f, 8)
- assert excinfo is not None
+ with pytest.raises(RuntimeError) as excinfo:
+ f(8)
traceback = excinfo.traceback
recindex = traceback.recursionindex()
assert recindex is None
@@ -294,7 +297,8 @@ def fail():
fail = log(log(fail))
- excinfo = pytest.raises(ValueError, fail)
+ with pytest.raises(ValueError) as excinfo:
+ fail()
assert excinfo.traceback.recursionindex() is None
def test_getreprcrash(self):
@@ -312,7 +316,8 @@ def g():
def f():
g()
- excinfo = pytest.raises(ValueError, f)
+ with pytest.raises(ValueError) as excinfo:
+ f()
reprcrash = excinfo._getreprcrash()
assert reprcrash is not None
co = _pytest._code.Code.from_function(h)
@@ -320,6 +325,8 @@ def f():
assert reprcrash.lineno == co.firstlineno + 1 + 1
def test_getreprcrash_empty(self):
+ __tracebackhide__ = True
+
def g():
__tracebackhide__ = True
raise ValueError
@@ -328,12 +335,14 @@ def f():
__tracebackhide__ = True
g()
- excinfo = pytest.raises(ValueError, f)
+ with pytest.raises(ValueError) as excinfo:
+ f()
assert excinfo._getreprcrash() is None
def test_excinfo_exconly():
- excinfo = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo:
+ h()
assert excinfo.exconly().startswith("ValueError")
with pytest.raises(ValueError) as excinfo:
raise ValueError("hello\nworld")
@@ -343,7 +352,8 @@ def test_excinfo_exconly():
def test_excinfo_repr_str() -> None:
- excinfo1 = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo1:
+ h()
assert repr(excinfo1) == ""
assert str(excinfo1) == ""
@@ -354,7 +364,8 @@ def __repr__(self):
def raises() -> None:
raise CustomException()
- excinfo2 = pytest.raises(CustomException, raises)
+ with pytest.raises(CustomException) as excinfo2:
+ raises()
assert repr(excinfo2) == ""
assert str(excinfo2) == ""
@@ -366,7 +377,8 @@ def test_excinfo_for_later() -> None:
def test_excinfo_errisinstance():
- excinfo = pytest.raises(ValueError, h)
+ with pytest.raises(ValueError) as excinfo:
+ h()
assert excinfo.errisinstance(ValueError)
@@ -390,7 +402,8 @@ def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
loader = jinja2.FileSystemLoader(str(tmp_path))
env = jinja2.Environment(loader=loader)
template = env.get_template("test.txt")
- excinfo = pytest.raises(ValueError, template.render, h=h)
+ with pytest.raises(ValueError) as excinfo:
+ template.render(h=h)
for item in excinfo.traceback:
print(item) # XXX: for some reason jinja.Template.render is printed in full
_ = item.source # shouldn't fail
@@ -754,7 +767,8 @@ def func1(m):
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1, "m" * 500)
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1("m" * 500)
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True, truncate_args=True)
@@ -777,7 +791,8 @@ def func1():
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1)
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1()
excinfo.traceback = excinfo.traceback.filter(excinfo)
p = FormattedExcinfo()
reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
@@ -810,7 +825,8 @@ def func1(m, x, y, z):
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1("m" * 90, 5, 13, "z" * 120)
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True)
@@ -837,7 +853,8 @@ def func1(x, *y, **z):
raise ValueError("hello\\nworld")
"""
)
- excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d")
+ with pytest.raises(ValueError) as excinfo:
+ mod.func1("a", "b", c="d")
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True)
@@ -863,7 +880,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(style="short")
reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
lines = reprtb.lines
@@ -898,7 +916,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ZeroDivisionError, mod.entry)
+ with pytest.raises(ZeroDivisionError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(style="short")
reprtb = p.repr_traceback_entry(excinfo.traceback[-3])
assert len(reprtb.lines) == 1
@@ -923,7 +942,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(style="no")
p.repr_traceback_entry(excinfo.traceback[-2])
@@ -934,6 +954,7 @@ def entry():
assert not lines[1:]
def test_repr_traceback_tbfilter(self, importasmod):
+ __tracebackhide__ = True
mod = importasmod(
"""
def f(x):
@@ -942,7 +963,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(tbfilter=True)
reprtb = p.repr_traceback(excinfo)
assert len(reprtb.reprentries) == 2
@@ -963,7 +985,8 @@ def entry():
func1()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
from _pytest._code.code import Code
with monkeypatch.context() as mp:
@@ -980,6 +1003,7 @@ def entry():
assert last_lines[1] == "E ValueError: hello"
def test_repr_traceback_and_excinfo(self, importasmod) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def f(x):
@@ -988,7 +1012,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
styles: tuple[TracebackStyle, ...] = ("long", "short")
for style in styles:
@@ -1008,6 +1033,7 @@ def entry():
assert repr.reprcrash.message == "ValueError: 0"
def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def f(x):
@@ -1016,7 +1042,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
p = FormattedExcinfo(abspath=False)
@@ -1065,7 +1092,8 @@ def entry():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
repr = excinfo.getrepr()
repr.addsection("title", "content")
repr.toterminal(tw_mock)
@@ -1079,7 +1107,8 @@ def entry():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
repr = excinfo.getrepr()
assert repr.reprcrash is not None
assert repr.reprcrash.path.endswith("mod.py")
@@ -1098,7 +1127,8 @@ def entry():
rec1(42)
"""
)
- excinfo = pytest.raises(RuntimeError, mod.entry)
+ with pytest.raises(RuntimeError) as excinfo:
+ mod.entry()
for style in ("short", "long", "no"):
p = FormattedExcinfo(style="short")
@@ -1115,7 +1145,8 @@ def entry():
f(0)
"""
)
- excinfo = pytest.raises(ValueError, mod.entry)
+ with pytest.raises(ValueError) as excinfo:
+ mod.entry()
styles: tuple[TracebackStyle, ...] = ("short", "long", "no")
for style in styles:
@@ -1138,6 +1169,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
assert x == "я"
def test_toterminal_long(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
def g(x):
@@ -1146,7 +1178,8 @@ def f():
g(3)
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr()
repr.toterminal(tw_mock)
@@ -1171,6 +1204,7 @@ def f():
def test_toterminal_long_missing_source(
self, importasmod, tmp_path: Path, tw_mock
) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def g(x):
@@ -1179,7 +1213,8 @@ def f():
g(3)
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
tmp_path.joinpath("mod.py").unlink()
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr()
@@ -1203,6 +1238,7 @@ def f():
def test_toterminal_long_incomplete_source(
self, importasmod, tmp_path: Path, tw_mock
) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def g(x):
@@ -1211,7 +1247,8 @@ def f():
g(3)
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8")
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr()
@@ -1235,13 +1272,15 @@ def f():
def test_toterminal_long_filenames(
self, importasmod, tw_mock, monkeypatch: MonkeyPatch
) -> None:
+ __tracebackhide__ = True
mod = importasmod(
"""
def f():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
path = Path(mod.__file__)
monkeypatch.chdir(path.parent)
repr = excinfo.getrepr(abspath=False)
@@ -1268,7 +1307,8 @@ def f():
g('some_value')
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr(style="value")
repr.toterminal(tw_mock)
@@ -1312,6 +1352,7 @@ def foo():
assert file.getvalue()
def test_traceback_repr_style(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
def f():
@@ -1324,7 +1365,8 @@ def i():
raise ValueError()
"""
)
- excinfo = pytest.raises(ValueError, mod.f)
+ with pytest.raises(ValueError) as excinfo:
+ mod.f()
excinfo.traceback = excinfo.traceback.filter(excinfo)
excinfo.traceback = _pytest._code.Traceback(
entry if i not in (1, 2) else entry.with_repr_style("short")
@@ -1359,6 +1401,7 @@ def i():
assert tw_mock.lines[20] == ":9: ValueError"
def test_exc_chain_repr(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
class Err(Exception):
@@ -1377,7 +1420,8 @@ def h():
if True: raise AttributeError()
"""
)
- excinfo = pytest.raises(AttributeError, mod.f)
+ with pytest.raises(AttributeError) as excinfo:
+ mod.f()
r = excinfo.getrepr(style="long")
r.toterminal(tw_mock)
for line in tw_mock.lines:
@@ -1458,6 +1502,7 @@ def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock):
- When the exception is raised with "from None"
- Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
"""
+ __tracebackhide__ = True
raise_suffix = " from None" if mode == "from_none" else ""
mod = importasmod(
f"""
@@ -1470,7 +1515,8 @@ def g():
raise ValueError()
"""
)
- excinfo = pytest.raises(AttributeError, mod.f)
+ with pytest.raises(AttributeError) as excinfo:
+ mod.f()
r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
r.toterminal(tw_mock)
for line in tw_mock.lines:
@@ -1547,6 +1593,7 @@ def g():
)
def test_exc_chain_repr_cycle(self, importasmod, tw_mock):
+ __tracebackhide__ = True
mod = importasmod(
"""
class Err(Exception):
@@ -1565,7 +1612,8 @@ def unreraise():
raise e.__cause__
"""
)
- excinfo = pytest.raises(ZeroDivisionError, mod.unreraise)
+ with pytest.raises(ZeroDivisionError) as excinfo:
+ mod.unreraise()
r = excinfo.getrepr(style="short")
r.toterminal(tw_mock)
out = "\n".join(line for line in tw_mock.lines if isinstance(line, str))
diff --git a/testing/code/test_source.py b/testing/code/test_source.py
index 3512a86f9a8..58005b340b6 100644
--- a/testing/code/test_source.py
+++ b/testing/code/test_source.py
@@ -211,7 +211,8 @@ def test_getstatementrange_out_of_bounds_py3(self) -> None:
def test_getstatementrange_with_syntaxerror_issue7(self) -> None:
source = Source(":")
- pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
+ with pytest.raises(SyntaxError):
+ source.getstatementrange(0)
def test_getstartingblock_singleline() -> None:
@@ -380,7 +381,8 @@ def test_code_of_object_instance_with_call() -> None:
class A:
pass
- pytest.raises(TypeError, lambda: Source(A()))
+ with pytest.raises(TypeError):
+ Source(A())
class WithCall:
def __call__(self) -> None:
@@ -393,7 +395,8 @@ class Hello:
def __call__(self) -> None:
pass
- pytest.raises(TypeError, lambda: Code.from_function(Hello))
+ with pytest.raises(TypeError):
+ Code.from_function(Hello)
def getstatement(lineno: int, source) -> Source:
diff --git a/testing/python/collect.py b/testing/python/collect.py
index d1901684527..2aa3aae7b1a 100644
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -20,7 +20,8 @@
class TestModule:
def test_failing_import(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol("import alksdjalskdjalkjals")
- pytest.raises(Collector.CollectError, modcol.collect)
+ with pytest.raises(Collector.CollectError):
+ modcol.collect()
def test_import_duplicate(self, pytester: Pytester) -> None:
a = pytester.mkdir("a")
@@ -72,12 +73,15 @@ def test():
def test_syntax_error_in_module(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol("this is a syntax error")
- pytest.raises(modcol.CollectError, modcol.collect)
- pytest.raises(modcol.CollectError, modcol.collect)
+ with pytest.raises(modcol.CollectError):
+ modcol.collect()
+ with pytest.raises(modcol.CollectError):
+ modcol.collect()
def test_module_considers_pluginmanager_at_import(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol("pytest_plugins='xasdlkj',")
- pytest.raises(ImportError, lambda: modcol.obj)
+ with pytest.raises(ImportError):
+ modcol.obj()
def test_invalid_test_module_name(self, pytester: Pytester) -> None:
a = pytester.mkdir("a")
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 38d219a547b..a0f2981e1ba 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -3591,8 +3591,8 @@ def myscoped(request):
for x in {ok.split()}:
assert hasattr(request, x)
for x in {error.split()}:
- pytest.raises(AttributeError, lambda:
- getattr(request, x))
+ with pytest.raises(AttributeError):
+ getattr(request, x)
assert request.session
assert request.config
def test_func():
@@ -3611,8 +3611,8 @@ def arg(request):
for x in {ok.split()!r}:
assert hasattr(request, x)
for x in {error.split()!r}:
- pytest.raises(AttributeError, lambda:
- getattr(request, x))
+ with pytest.raises(AttributeError):
+ getattr(request, x)
assert request.session
assert request.config
def test_func(arg):
diff --git a/testing/python/raises.py b/testing/python/raises.py
index 43a10d8a4f6..5ba0c0e1c89 100644
--- a/testing/python/raises.py
+++ b/testing/python/raises.py
@@ -21,11 +21,13 @@ def test_check_callable(self) -> None:
pytest.raises(RuntimeError, "int('qwe')") # type: ignore[call-overload]
def test_raises(self):
- excinfo = pytest.raises(ValueError, int, "qwe")
+ with pytest.raises(ValueError) as excinfo:
+ int("qwe")
assert "invalid literal" in str(excinfo.value)
def test_raises_function(self):
- excinfo = pytest.raises(ValueError, int, "hello")
+ with pytest.raises(ValueError) as excinfo:
+ int("hello")
assert "invalid literal" in str(excinfo.value)
def test_raises_does_not_allow_none(self):
@@ -179,7 +181,8 @@ def test_invalid_regex():
def test_noclass(self) -> None:
with pytest.raises(TypeError):
- pytest.raises("wrong", lambda: None) # type: ignore[call-overload]
+ with pytest.raises("wrong"): # type: ignore[call-overload]
+ ... # pragma: no cover
def test_invalid_arguments_to_raises(self) -> None:
with pytest.raises(TypeError, match="unknown"):
@@ -192,7 +195,8 @@ def test_tuple(self):
def test_no_raise_message(self) -> None:
try:
- pytest.raises(ValueError, int, "0")
+ with pytest.raises(ValueError):
+ int("0")
except pytest.fail.Exception as e:
assert e.msg == f"DID NOT RAISE {ValueError!r}"
else:
@@ -266,7 +270,7 @@ def test_raises_match(self) -> None:
pytest.raises(ValueError, int, "asdf").match(msg)
assert str(excinfo.value) == expr
- pytest.raises(TypeError, int, match="invalid")
+ pytest.raises(TypeError, int, match="invalid") # type: ignore[call-overload]
def tfunc(match):
raise ValueError(f"match={match}")
@@ -323,10 +327,10 @@ def test_raises_match_wrong_type(self):
def test_raises_exception_looks_iterable(self):
class Meta(type):
def __getitem__(self, item):
- return 1 / 0
+ return 1 / 0 # pragma: no cover
def __len__(self):
- return 1
+ return 1 # pragma: no cover
class ClassLooksIterableException(Exception, metaclass=Meta):
pass
@@ -335,7 +339,8 @@ class ClassLooksIterableException(Exception, metaclass=Meta):
Failed,
match=r"DID NOT RAISE ",
):
- pytest.raises(ClassLooksIterableException, lambda: None)
+ with pytest.raises(ClassLooksIterableException):
+ ... # pragma: no cover
def test_raises_with_raising_dunder_class(self) -> None:
"""Test current behavior with regard to exceptions via __class__ (#4284)."""
diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py
index 35ea85f10f5..7ac3f38ab64 100644
--- a/testing/test_cacheprovider.py
+++ b/testing/test_cacheprovider.py
@@ -51,7 +51,8 @@ def test_config_cache_dataerror(self, pytester: Pytester) -> None:
config = pytester.parseconfigure()
assert config.cache is not None
cache = config.cache
- pytest.raises(TypeError, lambda: cache.set("key/name", cache))
+ with pytest.raises(TypeError):
+ cache.set("key/name", cache)
config.cache.set("key/name", 0)
config.cache._getvaluepath("key/name").write_bytes(b"123invalid")
val = config.cache.get("key/name", -2)
@@ -143,7 +144,8 @@ def test_cachefuncarg(cache):
val = cache.get("some/thing", None)
assert val is None
cache.set("some/thing", [1])
- pytest.raises(TypeError, lambda: cache.get("some/thing"))
+ with pytest.raises(TypeError):
+ cache.get("some/thing")
val = cache.get("some/thing", [])
assert val == [1]
"""
diff --git a/testing/test_capture.py b/testing/test_capture.py
index 88368fadaae..7aaba99fe43 100644
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -96,7 +96,8 @@ def test_init_capturing(self):
try:
capman = CaptureManager("fd")
capman.start_global_capturing()
- pytest.raises(AssertionError, capman.start_global_capturing)
+ with pytest.raises(AssertionError):
+ capman.start_global_capturing()
capman.stop_global_capturing()
finally:
capouter.stop_capturing()
@@ -885,7 +886,8 @@ def test_text(self) -> None:
def test_unicode_and_str_mixture(self) -> None:
f = capture.CaptureIO()
f.write("\u00f6")
- pytest.raises(TypeError, f.write, b"hello")
+ with pytest.raises(TypeError):
+ f.write(b"hello") # type: ignore[arg-type]
def test_write_bytes_to_buffer(self) -> None:
"""In python3, stdout / stderr are text io wrappers (exposing a buffer
@@ -912,7 +914,8 @@ def test_unicode_and_str_mixture(self) -> None:
sio = io.StringIO()
f = capture.TeeCaptureIO(sio)
f.write("\u00f6")
- pytest.raises(TypeError, f.write, b"hello")
+ with pytest.raises(TypeError):
+ f.write(b"hello") # type: ignore[arg-type]
def test_dontreadfrominput() -> None:
@@ -921,19 +924,29 @@ def test_dontreadfrominput() -> None:
f = DontReadFromInput()
assert f.buffer is f # type: ignore[comparison-overlap]
assert not f.isatty() # type: ignore[unreachable]
- pytest.raises(OSError, f.read)
- pytest.raises(OSError, f.readlines)
+ with pytest.raises(OSError):
+ f.read()
+ with pytest.raises(OSError):
+ f.readlines()
iter_f = iter(f)
- pytest.raises(OSError, next, iter_f)
- pytest.raises(UnsupportedOperation, f.fileno)
- pytest.raises(UnsupportedOperation, f.flush)
+ with pytest.raises(OSError):
+ next(iter_f)
+ with pytest.raises(UnsupportedOperation):
+ f.fileno()
+ with pytest.raises(UnsupportedOperation):
+ f.flush()
assert not f.readable()
- pytest.raises(UnsupportedOperation, f.seek, 0)
+ with pytest.raises(UnsupportedOperation):
+ f.seek(0)
assert not f.seekable()
- pytest.raises(UnsupportedOperation, f.tell)
- pytest.raises(UnsupportedOperation, f.truncate, 0)
- pytest.raises(UnsupportedOperation, f.write, b"")
- pytest.raises(UnsupportedOperation, f.writelines, [])
+ with pytest.raises(UnsupportedOperation):
+ f.tell()
+ with pytest.raises(UnsupportedOperation):
+ f.truncate(0)
+ with pytest.raises(UnsupportedOperation):
+ f.write(b"")
+ with pytest.raises(UnsupportedOperation):
+ f.writelines([])
assert not f.writable()
assert isinstance(f.encoding, str)
f.close() # just for completeness
@@ -1005,7 +1018,8 @@ def test_simple(self, tmpfile: BinaryIO) -> None:
cap = capture.FDCapture(fd)
data = b"hello"
os.write(fd, data)
- pytest.raises(AssertionError, cap.snap)
+ with pytest.raises(AssertionError):
+ cap.snap()
cap.done()
cap = capture.FDCapture(fd)
cap.start()
@@ -1027,7 +1041,8 @@ def test_simple_fail_second_start(self, tmpfile: BinaryIO) -> None:
fd = tmpfile.fileno()
cap = capture.FDCapture(fd)
cap.done()
- pytest.raises(AssertionError, cap.start)
+ with pytest.raises(AssertionError):
+ cap.start()
def test_stderr(self) -> None:
cap = capture.FDCapture(2)
@@ -1078,7 +1093,8 @@ def test_simple_resume_suspend(self) -> None:
assert s == "but now yes\n"
cap.suspend()
cap.done()
- pytest.raises(AssertionError, cap.suspend)
+ with pytest.raises(AssertionError):
+ cap.suspend()
assert repr(cap) == (
f""
@@ -1160,7 +1176,8 @@ def test_reset_twice_error(self) -> None:
with self.getcapture() as cap:
print("hello")
out, err = cap.readouterr()
- pytest.raises(ValueError, cap.stop_capturing)
+ with pytest.raises(ValueError):
+ cap.stop_capturing()
assert out == "hello\n"
assert not err
@@ -1218,7 +1235,8 @@ def test_stdin_nulled_by_default(self) -> None:
print("XXX which indicates an error in the underlying capturing")
print("XXX mechanisms")
with self.getcapture():
- pytest.raises(OSError, sys.stdin.read)
+ with pytest.raises(OSError):
+ sys.stdin.read()
class TestTeeStdCapture(TestStdCapture):
@@ -1672,9 +1690,8 @@ def test_encodedfile_writelines(tmpfile: BinaryIO) -> None:
def test__get_multicapture() -> None:
assert isinstance(_get_multicapture("no"), MultiCapture)
- pytest.raises(ValueError, _get_multicapture, "unknown").match(
- r"^unknown capturing method: 'unknown'"
- )
+ with pytest.raises(ValueError, match=r"^unknown capturing method: 'unknown'$"):
+ _get_multicapture("unknown") # type: ignore[arg-type]
def test_logging_while_collecting(pytester: Pytester) -> None:
diff --git a/testing/test_config.py b/testing/test_config.py
index de11e3fa13a..296461c12fc 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -692,7 +692,8 @@ def test_args_source_testpaths(self, pytester: Pytester):
class TestConfigCmdlineParsing:
def test_parsing_again_fails(self, pytester: Pytester) -> None:
config = pytester.parseconfig()
- pytest.raises(AssertionError, lambda: config.parse([]))
+ with pytest.raises(AssertionError):
+ config.parse([])
def test_explicitly_specified_config_file_is_loaded(
self, pytester: Pytester
@@ -777,7 +778,8 @@ def pytest_addoption(parser):
config = pytester.parseconfig("--hello=this")
for x in ("hello", "--hello", "-X"):
assert config.getoption(x) == "this"
- pytest.raises(ValueError, config.getoption, "qweqwe")
+ with pytest.raises(ValueError):
+ config.getoption("qweqwe")
config_novalue = pytester.parseconfig()
assert config_novalue.getoption("hello") is None
@@ -803,7 +805,8 @@ def pytest_addoption(parser):
def test_config_getvalueorskip(self, pytester: Pytester) -> None:
config = pytester.parseconfig()
- pytest.raises(pytest.skip.Exception, config.getvalueorskip, "hello")
+ with pytest.raises(pytest.skip.Exception):
+ config.getvalueorskip("hello")
verbose = config.getvalueorskip("verbose")
assert verbose == config.option.verbose
@@ -851,7 +854,8 @@ def pytest_addoption(parser):
config = pytester.parseconfig()
val = config.getini("myname")
assert val == "hello"
- pytest.raises(ValueError, config.getini, "other")
+ with pytest.raises(ValueError):
+ config.getini("other")
@pytest.mark.parametrize("config_type", ["ini", "pyproject"])
def test_addini_paths(self, pytester: Pytester, config_type: str) -> None:
@@ -881,7 +885,8 @@ def pytest_addoption(parser):
assert len(values) == 2
assert values[0] == inipath.parent.joinpath("hello")
assert values[1] == inipath.parent.joinpath("world/sub.py")
- pytest.raises(ValueError, config.getini, "other")
+ with pytest.raises(ValueError):
+ config.getini("other")
def make_conftest_for_args(self, pytester: Pytester) -> None:
pytester.makeconftest(
diff --git a/testing/test_debugging.py b/testing/test_debugging.py
index 08ebf600253..950386a4923 100644
--- a/testing/test_debugging.py
+++ b/testing/test_debugging.py
@@ -324,12 +324,13 @@ def test_pdb_interaction_exception(self, pytester: Pytester) -> None:
def globalfunc():
pass
def test_1():
- pytest.raises(ValueError, globalfunc)
+ with pytest.raises(ValueError):
+ globalfunc()
"""
)
child = pytester.spawn_pytest(f"--pdb {p1}")
child.expect(".*def test_1")
- child.expect(".*pytest.raises.*globalfunc")
+ child.expect(r"with pytest.raises\(ValueError\)")
child.expect("Pdb")
child.sendline("globalfunc")
child.expect(".*function")
diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py
index d1f2255f30f..3e71e6b190c 100644
--- a/testing/test_legacypath.py
+++ b/testing/test_legacypath.py
@@ -141,7 +141,8 @@ def pytest_addoption(parser):
assert len(values) == 2
assert values[0] == inipath.parent.joinpath("hello")
assert values[1] == inipath.parent.joinpath("world/sub.py")
- pytest.raises(ValueError, config.getini, "other")
+ with pytest.raises(ValueError):
+ config.getini("other")
def test_override_ini_paths(pytester: pytest.Pytester) -> None:
diff --git a/testing/test_mark.py b/testing/test_mark.py
index 67219313183..7ae82fefd3c 100644
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -735,8 +735,8 @@ def pytest_collection_modifyitems(session):
session.add_marker("mark1")
session.add_marker(pytest.mark.mark2)
session.add_marker(pytest.mark.mark3)
- pytest.raises(ValueError, lambda:
- session.add_marker(10))
+ with pytest.raises(ValueError):
+ session.add_marker(10)
"""
)
pytester.makepyfile(
diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py
index c321439e398..988790268d2 100644
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -28,7 +28,8 @@ class A:
x = 1
monkeypatch = MonkeyPatch()
- pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2)
+ with pytest.raises(AttributeError):
+ monkeypatch.setattr(A, "notexists", 2)
monkeypatch.setattr(A, "y", 2, raising=False)
assert A.y == 2 # type: ignore
monkeypatch.undo()
@@ -109,7 +110,8 @@ class A:
monkeypatch = MonkeyPatch()
monkeypatch.delattr(A, "x")
- pytest.raises(AttributeError, monkeypatch.delattr, A, "y")
+ with pytest.raises(AttributeError):
+ monkeypatch.delattr(A, "y")
monkeypatch.delattr(A, "y", raising=False)
monkeypatch.setattr(A, "x", 5, raising=False)
assert A.x == 5
@@ -166,7 +168,8 @@ def test_delitem() -> None:
monkeypatch.delitem(d, "x")
assert "x" not in d
monkeypatch.delitem(d, "y", raising=False)
- pytest.raises(KeyError, monkeypatch.delitem, d, "y")
+ with pytest.raises(KeyError):
+ monkeypatch.delitem(d, "y")
assert not d
monkeypatch.setitem(d, "y", 1700)
assert d["y"] == 1700
@@ -192,7 +195,8 @@ def test_delenv() -> None:
name = "xyz1234"
assert name not in os.environ
monkeypatch = MonkeyPatch()
- pytest.raises(KeyError, monkeypatch.delenv, name, raising=True)
+ with pytest.raises(KeyError):
+ monkeypatch.delenv(name, raising=True)
monkeypatch.delenv(name, raising=False)
monkeypatch.undo()
os.environ[name] = "1"
diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py
index 4b721cb96f6..f56deed8b5d 100644
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -24,7 +24,8 @@ def parser() -> parseopt.Parser:
class TestParser:
def test_no_help_by_default(self) -> None:
parser = parseopt.Parser(usage="xyz", _ispytest=True)
- pytest.raises(UsageError, lambda: parser.parse(["-h"]))
+ with pytest.raises(UsageError):
+ parser.parse(["-h"])
def test_custom_prog(self, parser: parseopt.Parser) -> None:
"""Custom prog can be set for `argparse.ArgumentParser`."""
diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py
index 24700c07c80..385d907d481 100644
--- a/testing/test_pluginmanager.py
+++ b/testing/test_pluginmanager.py
@@ -268,8 +268,10 @@ def test_register_imported_modules(self) -> None:
assert pm.is_registered(mod)
values = pm.get_plugins()
assert mod in values
- pytest.raises(ValueError, pm.register, mod)
- pytest.raises(ValueError, lambda: pm.register(mod))
+ with pytest.raises(ValueError):
+ pm.register(mod)
+ with pytest.raises(ValueError):
+ pm.register(mod)
# assert not pm.is_registered(mod2)
assert pm.get_plugins() == values
@@ -376,8 +378,10 @@ def test_hello(pytestconfig):
def test_import_plugin_importname(
self, pytester: Pytester, pytestpm: PytestPluginManager
) -> None:
- pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
- pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("qweqwex.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("pytest_qweqwx.y")
pytester.syspathinsert()
pluginname = "pytest_hello"
@@ -396,8 +400,10 @@ def test_import_plugin_importname(
def test_import_plugin_dotted_name(
self, pytester: Pytester, pytestpm: PytestPluginManager
) -> None:
- pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
- pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("qweqwex.y")
+ with pytest.raises(ImportError):
+ pytestpm.import_plugin("pytest_qweqwex.y")
pytester.syspathinsert()
pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3", encoding="utf-8")
@@ -423,9 +429,8 @@ def test_consider_conftest_deps(
class TestPytestPluginManagerBootstrapping:
def test_preparse_args(self, pytestpm: PytestPluginManager) -> None:
- pytest.raises(
- ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])
- )
+ with pytest.raises(ImportError):
+ pytestpm.consider_preparse(["xyz", "-p", "hello123"])
# Handles -p without space (#3532).
with pytest.raises(ImportError) as excinfo:
diff --git a/testing/test_pytester.py b/testing/test_pytester.py
index 5e2e22f111b..f641e9ee8bb 100644
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -71,7 +71,8 @@ class rep2:
recorder.unregister() # type: ignore[attr-defined]
recorder.clear()
recorder.hook.pytest_runtest_logreport(report=rep3) # type: ignore[attr-defined]
- pytest.raises(ValueError, recorder.getfailures)
+ with pytest.raises(ValueError):
+ recorder.getfailures()
def test_parseconfig(pytester: Pytester) -> None:
@@ -196,7 +197,8 @@ def test_hookrecorder_basic(holder) -> None:
call = rec.popcall("pytest_xyz")
assert call.arg == 123
assert call._name == "pytest_xyz"
- pytest.raises(pytest.fail.Exception, rec.popcall, "abc")
+ with pytest.raises(pytest.fail.Exception):
+ rec.popcall("abc")
pm.hook.pytest_xyz_noarg()
call = rec.popcall("pytest_xyz_noarg")
assert call._name == "pytest_xyz_noarg"
diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py
index 3f89a8ed21c..f05fc6e4871 100644
--- a/testing/test_recwarn.py
+++ b/testing/test_recwarn.py
@@ -97,7 +97,8 @@ def test_recording(self) -> None:
rec.clear()
assert len(rec.list) == 0
assert values is rec.list
- pytest.raises(AssertionError, rec.pop)
+ with pytest.raises(AssertionError):
+ rec.pop()
def test_warn_stacklevel(self) -> None:
"""#4243"""
@@ -145,10 +146,12 @@ def dep_explicit(self, i: int) -> None:
def test_deprecated_call_raises(self) -> None:
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
- pytest.deprecated_call(self.dep, 3, 5)
+ with pytest.deprecated_call():
+ self.dep(3, 5)
def test_deprecated_call(self) -> None:
- pytest.deprecated_call(self.dep, 0, 5)
+ with pytest.deprecated_call():
+ self.dep(0, 5)
def test_deprecated_call_ret(self) -> None:
ret = pytest.deprecated_call(self.dep, 0)
@@ -170,11 +173,14 @@ def test_deprecated_call_preserves(self) -> None:
def test_deprecated_explicit_call_raises(self) -> None:
with pytest.raises(pytest.fail.Exception):
- pytest.deprecated_call(self.dep_explicit, 3)
+ with pytest.deprecated_call():
+ self.dep_explicit(3)
def test_deprecated_explicit_call(self) -> None:
- pytest.deprecated_call(self.dep_explicit, 0)
- pytest.deprecated_call(self.dep_explicit, 0)
+ with pytest.deprecated_call():
+ self.dep_explicit(0)
+ with pytest.deprecated_call():
+ self.dep_explicit(0)
@pytest.mark.parametrize("mode", ["context_manager", "call"])
def test_deprecated_call_no_warning(self, mode) -> None:
@@ -198,7 +204,7 @@ def f():
)
@pytest.mark.parametrize("mode", ["context_manager", "call"])
@pytest.mark.parametrize("call_f_first", [True, False])
- @pytest.mark.filterwarnings("ignore")
+ @pytest.mark.filterwarnings("ignore:hi")
def test_deprecated_call_modes(self, warning_type, mode, call_f_first) -> None:
"""Ensure deprecated_call() captures a deprecation warning as expected inside its
block/function.
@@ -258,11 +264,14 @@ def test_check_callable(self) -> None:
def test_several_messages(self) -> None:
# different messages, b/c Python suppresses multiple identical warnings
- pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
+ with pytest.warns(RuntimeWarning):
+ warnings.warn("w1", RuntimeWarning)
with pytest.warns(RuntimeWarning):
with pytest.raises(pytest.fail.Exception):
- pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
- pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
+ with pytest.warns(UserWarning):
+ warnings.warn("w2", RuntimeWarning)
+ with pytest.warns(RuntimeWarning):
+ warnings.warn("w3", RuntimeWarning)
def test_function(self) -> None:
pytest.warns(
@@ -270,20 +279,14 @@ def test_function(self) -> None:
)
def test_warning_tuple(self) -> None:
- pytest.warns(
- (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning)
- )
- pytest.warns(
- (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning)
- )
- with pytest.warns():
- pytest.raises(
- pytest.fail.Exception,
- lambda: pytest.warns(
- (RuntimeWarning, SyntaxWarning),
- lambda: warnings.warn("w3", UserWarning),
- ),
- )
+ with pytest.warns((RuntimeWarning, SyntaxWarning)):
+ warnings.warn("w1", RuntimeWarning)
+ with pytest.warns((RuntimeWarning, SyntaxWarning)):
+ warnings.warn("w2", SyntaxWarning)
+ with pytest.warns(UserWarning, match="^w3$"):
+ with pytest.raises(pytest.fail.Exception):
+ with pytest.warns((RuntimeWarning, SyntaxWarning)):
+ warnings.warn("w3", UserWarning)
def test_as_contextmanager(self) -> None:
with pytest.warns(RuntimeWarning):
diff --git a/testing/test_runner.py b/testing/test_runner.py
index 6ecdfe7e62c..3cb3c3a3841 100644
--- a/testing/test_runner.py
+++ b/testing/test_runner.py
@@ -779,8 +779,10 @@ def f():
# check that importorskip reports the actual call
# in this test the test_runner.py file
assert path.stem == "test_runner"
- pytest.raises(SyntaxError, pytest.importorskip, "x y z")
- pytest.raises(SyntaxError, pytest.importorskip, "x=y")
+ with pytest.raises(SyntaxError):
+ pytest.importorskip("x y z")
+ with pytest.raises(SyntaxError):
+ pytest.importorskip("x=y")
mod = types.ModuleType("hello123")
mod.__version__ = "1.3" # type: ignore
monkeypatch.setitem(sys.modules, "hello123", mod)
diff --git a/testing/test_session.py b/testing/test_session.py
index e3db9a1b690..be1e66112d7 100644
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -63,7 +63,8 @@ def test_raises_output(self, pytester: Pytester) -> None:
"""
import pytest
def test_raises_doesnt():
- pytest.raises(ValueError, int, "3")
+ with pytest.raises(ValueError):
+ int("3")
"""
)
_passed, _skipped, failed = reprec.listoutcomes()
diff --git a/testing/test_skipping.py b/testing/test_skipping.py
index e1e25e45468..5bb641aed3c 100644
--- a/testing/test_skipping.py
+++ b/testing/test_skipping.py
@@ -919,7 +919,8 @@ def test_func():
pass
"""
)
- pytest.raises(pytest.skip.Exception, lambda: pytest_runtest_setup(item))
+ with pytest.raises(pytest.skip.Exception):
+ pytest_runtest_setup(item)
@pytest.mark.parametrize(
"marker, msg1, msg2",
From d42cef1e234c7ffedfe4dd38796b8829c955d345 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 15:26:12 +0200
Subject: [PATCH 2/3] doc: undocument legacy forms of `raises`, `warns`,
`deprecated_call`
We don't intend to deprecate the legacy forms, but there is no reason to
use them anymore, and they add complexity/confusion, so let's just
mostly pretend they don't exist in the docs.
---
doc/en/how-to/assert.rst | 4 ----
doc/en/how-to/capture-warnings.rst | 7 -------
src/_pytest/raises.py | 19 -------------------
src/_pytest/recwarn.py | 17 ++++++++---------
4 files changed, 8 insertions(+), 39 deletions(-)
diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst
index 006cf475b02..5ae676cf59b 100644
--- a/doc/en/how-to/assert.rst
+++ b/doc/en/how-to/assert.rst
@@ -322,13 +322,9 @@ will then execute the function with those arguments and assert that the given ex
pytest.raises(ValueError, func, x=-1)
-The reporter will provide you with helpful output in case of failures such as *no
-exception* or *wrong exception*.
-
This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was
added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``)
being considered more readable.
-Nonetheless, this form is fully supported and not deprecated in any way.
xfail mark and pytest.raises
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst
index e5bc4d27874..daaf8937e1a 100644
--- a/doc/en/how-to/capture-warnings.rst
+++ b/doc/en/how-to/capture-warnings.rst
@@ -371,13 +371,6 @@ Some examples:
... warnings.warn("issue with foo() func")
...
-You can also call :func:`pytest.warns` on a function or code string:
-
-.. code-block:: python
-
- pytest.warns(expected_warning, func, *args, **kwargs)
- pytest.warns(expected_warning, "func(*args, **kwargs)")
-
The function also returns a list of all raised warnings (as
``warnings.WarningMessage`` objects), which you can query for
additional information:
diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py
index 76199e3df02..948f6109cbe 100644
--- a/src/_pytest/raises.py
+++ b/src/_pytest/raises.py
@@ -237,25 +237,6 @@ def raises(
:ref:`assertraises` for more examples and detailed discussion.
- **Legacy form**
-
- It is possible to specify a callable by passing a to-be-called lambda::
-
- >>> raises(ZeroDivisionError, lambda: 1/0)
-
-
- or you can specify an arbitrary callable with arguments::
-
- >>> def f(x): return 1/x
- ...
- >>> raises(ZeroDivisionError, f, 0)
-
- >>> raises(ZeroDivisionError, f, x=0)
-
-
- The form above is fully supported but discouraged for new code because the
- context manager form is regarded as more readable and less error-prone.
-
.. note::
Similar to caught exception objects in Python, explicitly clearing
local references to returned ``ExceptionInfo`` objects can
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index 3351e9ed395..dd1164b0e91 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -67,16 +67,15 @@ def deprecated_call(
>>> import pytest
>>> with pytest.deprecated_call():
... assert api_call_v2() == 200
+ >>> with pytest.deprecated_call(match="^use v3 of this api$") as warning_messages:
+ ... assert api_call_v2() == 200
- It can also be used by passing a function and ``*args`` and ``**kwargs``,
- in which case it will ensure calling ``func(*args, **kwargs)`` produces one of
- the warnings types above. The return value is the return value of the function.
-
- In the context manager form you may use the keyword argument ``match`` to assert
+ You may use the keyword argument ``match`` to assert
that the warning matches a text or regex.
- The context manager produces a list of :class:`warnings.WarningMessage` objects,
- one for each warning raised.
+ The return value is a list of :class:`warnings.WarningMessage` objects,
+ one for each warning emitted
+ (regardless of whether it is an ``expected_warning`` or not).
"""
__tracebackhide__ = True
if func is not None:
@@ -119,13 +118,13 @@ def warns(
each warning emitted (regardless of whether it is an ``expected_warning`` or not).
Since pytest 8.0, unmatched warnings are also re-emitted when the context closes.
- This function can be used as a context manager::
+ This function should be used as a context manager::
>>> import pytest
>>> with pytest.warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)
- In the context manager form you may use the keyword argument ``match`` to assert
+ The ``match`` keyword argument can be used to assert
that the warning matches a text or regex::
>>> with pytest.warns(UserWarning, match='must be 0 or None'):
From f7f0889be2be5f63557a8c564ab7d94b3a8636c0 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 4 Jun 2025 15:26:12 +0200
Subject: [PATCH 3/3] Add `ParamSpec` to legacy callable forms of
`raises`/`warns`/`deprecated_call`
`pytest.raises`, `warns` & `deprecated_call` previously typed `*args`
and `**kwargs` as `Any` in the legacy callable form, so this did not
raise errors:
```py
def foo(x: int) -> None:
raise ValueError
raises(ValueError, foo, None)
```
but now it will give call-overload.
It also makes it possible to pass `func` as a kwarg, which the type
hints previously showed as possible, but it didn't work.
It's possible that `func` (and the expected type?) should be pos-only,
as this looks quite weird:
```py
raises(1, 2, kwarg1=3, func=my_func, kwarg2=4, expected_exception=ValueError)
```
but if somebody is dynamically generating parameters to send to `raises`
then we probably shouldn't ban it needlessly; and we can't make `func`
pos-only without making `expected_exception` pos-only, and that could
break backwards compatibility.
---
changelog/13241.improvement.rst | 2 ++
doc/en/conf.py | 4 ++++
pyproject.toml | 1 +
src/_pytest/raises.py | 12 ++++++------
src/_pytest/recwarn.py | 30 +++++++++++++++++-------------
5 files changed, 30 insertions(+), 19 deletions(-)
create mode 100644 changelog/13241.improvement.rst
diff --git a/changelog/13241.improvement.rst b/changelog/13241.improvement.rst
new file mode 100644
index 00000000000..41ba55e280a
--- /dev/null
+++ b/changelog/13241.improvement.rst
@@ -0,0 +1,2 @@
+:func:`pytest.raises`, :func:`pytest.warns` and :func:`pytest.deprecated_call` now uses :class:`ParamSpec` for the type hint to the (old and not recommended) callable overload, instead of :class:`Any`. This allows type checkers to raise errors when passing incorrect function parameters.
+``func`` can now also be passed as a kwarg, which the type hint previously showed as possible but didn't accept.
diff --git a/doc/en/conf.py b/doc/en/conf.py
index 25dc526c4da..84b1c99e181 100644
--- a/doc/en/conf.py
+++ b/doc/en/conf.py
@@ -102,6 +102,10 @@
# TypeVars
("py:class", "_pytest._code.code.E"),
("py:class", "E"), # due to delayed annotation
+ ("py:class", "T"),
+ ("py:class", "P"),
+ ("py:class", "P.args"),
+ ("py:class", "P.kwargs"),
("py:class", "_pytest.fixtures.FixtureFunction"),
("py:class", "_pytest.nodes._NodeType"),
("py:class", "_NodeType"), # due to delayed annotation
diff --git a/pyproject.toml b/pyproject.toml
index e51cafbd747..83d367d7024 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -453,6 +453,7 @@ exclude_lines = [
'^\s*case unreachable:',
'^\s*assert_never\(',
'^\s*if TYPE_CHECKING:',
+ '^\s*(el)?if TYPE_CHECKING:',
'^\s*@overload( |$)',
'^\s*def .+: \.\.\.$',
'^\s*@pytest\.mark\.xfail',
diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py
index 948f6109cbe..23690a00470 100644
--- a/src/_pytest/raises.py
+++ b/src/_pytest/raises.py
@@ -95,14 +95,15 @@ def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException
@overload
def raises(
expected_exception: type[E] | tuple[type[E], ...],
- func: Callable[..., Any],
- *args: Any,
- **kwargs: Any,
+ func: Callable[P, object],
+ *args: P.args,
+ **kwargs: P.kwargs,
) -> ExceptionInfo[E]: ...
def raises(
expected_exception: type[E] | tuple[type[E], ...] | None = None,
+ func: Callable[P, object] | None = None,
*args: Any,
**kwargs: Any,
) -> RaisesExc[BaseException] | ExceptionInfo[E]:
@@ -253,7 +254,7 @@ def raises(
"""
__tracebackhide__ = True
- if not args:
+ if func is None and not args:
if set(kwargs) - {"match", "check", "expected_exception"}:
msg = "Unexpected keyword arguments passed to pytest.raises: "
msg += ", ".join(sorted(kwargs))
@@ -270,11 +271,10 @@ def raises(
f"Raising exceptions is already understood as failing the test, so you don't need "
f"any special code to say 'this should never raise an exception'."
)
- func = args[0]
if not callable(func):
raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
with RaisesExc(expected_exception) as excinfo:
- func(*args[1:], **kwargs)
+ func(*args, **kwargs)
try:
return excinfo
finally:
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index dd1164b0e91..c3cb10b7f08 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -17,8 +17,11 @@
if TYPE_CHECKING:
+ from typing_extensions import ParamSpec
from typing_extensions import Self
+ P = ParamSpec("P")
+
import warnings
from _pytest.deprecated import check_ispytest
@@ -49,7 +52,7 @@ def deprecated_call(
@overload
-def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ...
+def deprecated_call(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: ...
def deprecated_call(
@@ -78,11 +81,12 @@ def deprecated_call(
(regardless of whether it is an ``expected_warning`` or not).
"""
__tracebackhide__ = True
- if func is not None:
- args = (func, *args)
- return warns(
- (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs
- )
+ dep_warnings = (DeprecationWarning, PendingDeprecationWarning, FutureWarning)
+ if func is None:
+ return warns(dep_warnings, *args, **kwargs)
+
+ with warns(dep_warnings):
+ return func(*args, **kwargs)
@overload
@@ -96,16 +100,16 @@ def warns(
@overload
def warns(
expected_warning: type[Warning] | tuple[type[Warning], ...],
- func: Callable[..., T],
- *args: Any,
- **kwargs: Any,
+ func: Callable[P, T],
+ *args: P.args,
+ **kwargs: P.kwargs,
) -> T: ...
def warns(
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
+ func: Callable[..., object] | None = None,
*args: Any,
- match: str | re.Pattern[str] | None = None,
**kwargs: Any,
) -> WarningsChecker | Any:
r"""Assert that code raises a particular class of warning.
@@ -152,7 +156,8 @@ def warns(
"""
__tracebackhide__ = True
- if not args:
+ if func is None and not args:
+ match: str | re.Pattern[str] | None = kwargs.pop("match", None)
if kwargs:
argnames = ", ".join(sorted(kwargs))
raise TypeError(
@@ -161,11 +166,10 @@ def warns(
)
return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
else:
- func = args[0]
if not callable(func):
raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
with WarningsChecker(expected_warning, _ispytest=True):
- return func(*args[1:], **kwargs)
+ return func(*args, **kwargs)
class WarningsRecorder(warnings.catch_warnings):