diff --git a/.coveragerc b/.coveragerc index d39d3d5f02b..4e13efa8066 100644 --- a/.coveragerc +++ b/.coveragerc @@ -27,7 +27,7 @@ exclude_lines = ^\s*assert False(,|$) ^\s*assert_never\( - ^\s*if TYPE_CHECKING: + ^\s*(el)?if TYPE_CHECKING: ^\s*@overload( |$) ^\s*def .+: \.\.\.$ 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 c89e14d07fa..9deee5230bd 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -97,6 +97,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/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst index 6bc8f6fed33..2366cbc598c 100644 --- a/doc/en/how-to/assert.rst +++ b/doc/en/how-to/assert.rst @@ -295,13 +295,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 a9bd894b6fd..efbc4ac49ec 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -338,13 +338,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 480ae33647f..f48932f7989 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]: @@ -237,25 +238,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 @@ -272,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)) @@ -289,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 e3db717bfe4..6fdb6d6eb80 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( @@ -67,23 +70,23 @@ 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: - 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 @@ -97,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. @@ -119,13 +122,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'): @@ -151,7 +154,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( @@ -160,11 +164,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): diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 7064d1daa9b..94a0403c1be 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -625,7 +625,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) @@ -998,8 +999,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__) @@ -1099,7 +1102,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 @@ -1397,7 +1401,8 @@ def test_stat_helpers(self, tmpdir, monkeypatch): def test_stat_non_raising(self, tmpdir): path1 = tmpdir.join("file") - pytest.raises(error.ENOENT, lambda: 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 7ae5ad46100..d1e7efdc678 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: + with pytest.raises(Exception) as excinfo: raise Exception(value) - - excinfo = pytest.raises(Exception, f) str(excinfo) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 555645030fc..8d64f14a537 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 321372d4b59..bf7ed38cefe 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -210,7 +210,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: @@ -379,7 +380,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: @@ -392,7 +394,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/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index 112d1e05f27..678dd06d907 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -6,4 +6,5 @@ @pytest.fixture def arg2(request): - pytest.raises(Exception, request.getfixturevalue, "arg1") + with pytest.raises(Exception): # noqa: B017 # too general exception + request.getfixturevalue("arg1") diff --git a/testing/python/collect.py b/testing/python/collect.py index 530f1c340ff..0834204be84 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 fb76fe6cf96..6e420cd9c51 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3447,8 +3447,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(): @@ -3467,8 +3467,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 40f9afea3ba..dee922546ba 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}") @@ -320,10 +324,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 @@ -332,7 +336,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 ca417e86ee5..32065308550 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 d9dacebd938..330050589f1 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() - 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"") # type: ignore[arg-type] + with pytest.raises(UnsupportedOperation): + f.writelines([]) assert not f.writable() assert isinstance(f.encoding, str) f.close() # just for completeness @@ -1000,7 +1013,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() @@ -1022,7 +1036,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) @@ -1073,7 +1088,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"" @@ -1155,7 +1171,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 @@ -1213,7 +1230,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): @@ -1667,9 +1685,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 bb08c40fef4..a40ef6e36d7 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -556,7 +556,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 @@ -647,7 +648,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 @@ -673,7 +675,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 @@ -721,7 +724,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: @@ -751,7 +755,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 72854e4e5c0..ba7f93b1016 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 1e51f9db18f..a088586612c 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -724,8 +724,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 0e992e298ec..4aa822a9878 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -29,7 +29,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() @@ -110,7 +111,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 @@ -167,7 +169,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 @@ -193,7 +196,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 36db7b13989..2abbd5d6dbb 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 db85124bf0d..d6f21167b7e 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 721e8c19d8b..780d4ff4b3d 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 384f2b66a15..14b5d10e89e 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. @@ -256,11 +262,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( @@ -268,20 +277,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): @@ -420,7 +423,7 @@ def test_none_of_multiple_warns(self) -> None: warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("cccccccccc", UserWarning) - @pytest.mark.filterwarnings("ignore") + @pytest.mark.filterwarnings("ignore:ohai") def test_can_capture_previously_warned(self) -> None: def f() -> int: warnings.warn(UserWarning("ohai")) diff --git a/testing/test_runner.py b/testing/test_runner.py index 0245438a47d..a0d0ca2c614 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -780,8 +780,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 ba904916033..c6b5717ca83 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 9a6c2c4b6aa..1d27c39ba0f 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",