2024-01-28 21:12:42 +08:00
|
|
|
# mypy: allow-untyped-defs
|
2024-02-08 08:47:56 +08:00
|
|
|
import sys
|
2023-07-02 21:35:04 +08:00
|
|
|
from typing import List
|
2019-12-03 20:34:41 +08:00
|
|
|
from typing import Optional
|
2023-07-02 21:35:04 +08:00
|
|
|
from typing import Type
|
2024-02-10 04:13:41 +08:00
|
|
|
from typing import Union
|
2018-10-25 15:01:29 +08:00
|
|
|
import warnings
|
2017-05-30 05:59:34 +08:00
|
|
|
|
2015-07-29 07:01:11 +08:00
|
|
|
import pytest
|
2024-02-05 04:25:49 +08:00
|
|
|
from pytest import ExitCode
|
|
|
|
from pytest import Pytester
|
|
|
|
from pytest import WarningsRecorder
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2009-09-06 22:59:39 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None:
|
2018-10-18 10:36:41 +08:00
|
|
|
warnings.warn("hello")
|
|
|
|
warn = recwarn.pop()
|
|
|
|
assert warn.filename == __file__
|
|
|
|
|
|
|
|
|
2020-11-09 12:23:31 +08:00
|
|
|
def test_recwarn_functional(pytester: Pytester) -> None:
|
|
|
|
pytester.makepyfile(
|
2009-09-06 22:59:39 +08:00
|
|
|
"""
|
|
|
|
import warnings
|
|
|
|
def test_method(recwarn):
|
|
|
|
warnings.warn("hello")
|
|
|
|
warn = recwarn.pop()
|
|
|
|
assert isinstance(warn.message, UserWarning)
|
|
|
|
"""
|
|
|
|
)
|
2020-11-09 12:23:31 +08:00
|
|
|
reprec = pytester.inline_run()
|
2018-09-05 04:02:56 +08:00
|
|
|
reprec.assertoutcome(passed=1)
|
2010-07-27 03:15:15 +08:00
|
|
|
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2021-06-12 22:38:24 +08:00
|
|
|
@pytest.mark.filterwarnings("")
|
|
|
|
def test_recwarn_captures_deprecation_warning(recwarn: WarningsRecorder) -> None:
|
|
|
|
"""
|
|
|
|
Check that recwarn can capture DeprecationWarning by default
|
|
|
|
without custom filterwarnings (see #8666).
|
|
|
|
"""
|
|
|
|
warnings.warn(DeprecationWarning("some deprecation"))
|
|
|
|
assert len(recwarn) == 1
|
|
|
|
assert recwarn.pop(DeprecationWarning)
|
|
|
|
|
|
|
|
|
2023-07-02 20:15:50 +08:00
|
|
|
class TestSubclassWarningPop:
|
|
|
|
class ParentWarning(Warning):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class ChildWarning(ParentWarning):
|
|
|
|
pass
|
|
|
|
|
2023-07-02 21:35:04 +08:00
|
|
|
class ChildOfChildWarning(ChildWarning):
|
2023-07-02 21:03:15 +08:00
|
|
|
pass
|
|
|
|
|
2023-07-02 21:35:04 +08:00
|
|
|
@staticmethod
|
|
|
|
def raise_warnings_from_list(_warnings: List[Type[Warning]]):
|
|
|
|
for warn in _warnings:
|
|
|
|
warnings.warn(f"Warning {warn().__repr__()}", warn)
|
2023-07-02 20:15:50 +08:00
|
|
|
|
2023-07-05 01:00:29 +08:00
|
|
|
def test_pop_finds_exact_match(self):
|
2023-07-02 20:15:50 +08:00
|
|
|
with pytest.warns((self.ParentWarning, self.ChildWarning)) as record:
|
2023-07-02 21:35:04 +08:00
|
|
|
self.raise_warnings_from_list(
|
|
|
|
[self.ChildWarning, self.ParentWarning, self.ChildOfChildWarning]
|
|
|
|
)
|
2023-07-02 20:15:50 +08:00
|
|
|
|
2023-07-02 21:35:04 +08:00
|
|
|
assert len(record) == 3
|
2023-07-02 20:15:50 +08:00
|
|
|
_warn = record.pop(self.ParentWarning)
|
|
|
|
assert _warn.category is self.ParentWarning
|
|
|
|
|
2023-07-05 01:00:29 +08:00
|
|
|
def test_pop_raises_if_no_match(self):
|
2023-07-02 21:35:04 +08:00
|
|
|
with pytest.raises(AssertionError):
|
|
|
|
with pytest.warns(self.ParentWarning) as record:
|
|
|
|
self.raise_warnings_from_list([self.ParentWarning])
|
|
|
|
record.pop(self.ChildOfChildWarning)
|
|
|
|
|
2023-07-05 01:00:29 +08:00
|
|
|
def test_pop_finds_best_inexact_match(self):
|
2023-07-02 21:35:04 +08:00
|
|
|
with pytest.warns(self.ParentWarning) as record:
|
2023-07-04 16:20:50 +08:00
|
|
|
self.raise_warnings_from_list(
|
|
|
|
[self.ChildOfChildWarning, self.ChildWarning, self.ChildOfChildWarning]
|
|
|
|
)
|
2023-07-02 21:35:04 +08:00
|
|
|
|
|
|
|
_warn = record.pop(self.ParentWarning)
|
2023-07-04 16:20:50 +08:00
|
|
|
assert _warn.category is self.ChildWarning
|
2023-07-02 21:35:04 +08:00
|
|
|
|
2023-07-02 20:15:50 +08:00
|
|
|
|
2015-07-29 07:01:11 +08:00
|
|
|
class TestWarningsRecorderChecker:
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_recording(self) -> None:
|
2020-09-28 03:20:31 +08:00
|
|
|
rec = WarningsRecorder(_ispytest=True)
|
2015-07-29 07:01:11 +08:00
|
|
|
with rec:
|
|
|
|
assert not rec.list
|
2017-12-27 11:47:26 +08:00
|
|
|
warnings.warn_explicit("hello", UserWarning, "xyz", 13)
|
2015-07-29 07:01:11 +08:00
|
|
|
assert len(rec.list) == 1
|
2017-12-27 11:47:26 +08:00
|
|
|
warnings.warn(DeprecationWarning("hello"))
|
2015-07-29 07:01:11 +08:00
|
|
|
assert len(rec.list) == 2
|
|
|
|
warn = rec.pop()
|
|
|
|
assert str(warn.message) == "hello"
|
2017-11-04 23:17:20 +08:00
|
|
|
values = rec.list
|
2015-07-29 07:01:11 +08:00
|
|
|
rec.clear()
|
|
|
|
assert len(rec.list) == 0
|
2017-11-04 23:17:20 +08:00
|
|
|
assert values is rec.list
|
2018-11-23 02:05:10 +08:00
|
|
|
pytest.raises(AssertionError, rec.pop)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_warn_stacklevel(self) -> None:
|
2019-04-27 22:25:37 +08:00
|
|
|
"""#4243"""
|
2020-09-28 03:20:31 +08:00
|
|
|
rec = WarningsRecorder(_ispytest=True)
|
2018-10-26 15:01:25 +08:00
|
|
|
with rec:
|
|
|
|
warnings.warn("test", DeprecationWarning, 2)
|
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_typechecking(self) -> None:
|
2015-08-05 06:27:13 +08:00
|
|
|
from _pytest.recwarn import WarningsChecker
|
2018-05-23 22:48:46 +08:00
|
|
|
|
2015-07-29 07:01:11 +08:00
|
|
|
with pytest.raises(TypeError):
|
2020-09-28 03:20:31 +08:00
|
|
|
WarningsChecker(5, _ispytest=True) # type: ignore[arg-type]
|
2015-07-29 07:01:11 +08:00
|
|
|
with pytest.raises(TypeError):
|
2020-09-28 03:20:31 +08:00
|
|
|
WarningsChecker(("hi", RuntimeWarning), _ispytest=True) # type: ignore[arg-type]
|
2015-07-29 07:01:11 +08:00
|
|
|
with pytest.raises(TypeError):
|
2020-09-28 03:20:31 +08:00
|
|
|
WarningsChecker([DeprecationWarning, RuntimeWarning], _ispytest=True) # type: ignore[arg-type]
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_invalid_enter_exit(self) -> None:
|
2015-07-29 07:01:11 +08:00
|
|
|
# wrap this test in WarningsRecorder to ensure warning state gets reset
|
2020-09-28 03:20:31 +08:00
|
|
|
with WarningsRecorder(_ispytest=True):
|
2015-07-29 07:01:11 +08:00
|
|
|
with pytest.raises(RuntimeError):
|
2020-09-28 03:20:31 +08:00
|
|
|
rec = WarningsRecorder(_ispytest=True)
|
2015-07-29 07:01:11 +08:00
|
|
|
rec.__exit__(None, None, None) # can't exit before entering
|
|
|
|
|
|
|
|
with pytest.raises(RuntimeError):
|
2020-09-28 03:20:31 +08:00
|
|
|
rec = WarningsRecorder(_ispytest=True)
|
2015-07-29 07:01:11 +08:00
|
|
|
with rec:
|
|
|
|
with rec:
|
|
|
|
pass # can't enter twice
|
|
|
|
|
2009-09-06 22:59:39 +08:00
|
|
|
|
2015-11-27 02:27:20 +08:00
|
|
|
class TestDeprecatedCall:
|
|
|
|
"""test pytest.deprecated_call()"""
|
2009-09-06 22:59:39 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def dep(self, i: int, j: Optional[int] = None) -> int:
|
2015-11-27 02:27:20 +08:00
|
|
|
if i == 0:
|
2017-12-27 11:47:26 +08:00
|
|
|
warnings.warn("is deprecated", DeprecationWarning, stacklevel=1)
|
2015-11-27 02:27:20 +08:00
|
|
|
return 42
|
2009-09-06 22:59:39 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def dep_explicit(self, i: int) -> None:
|
2015-11-27 02:27:20 +08:00
|
|
|
if i == 0:
|
2017-12-27 11:47:26 +08:00
|
|
|
warnings.warn_explicit(
|
|
|
|
"dep_explicit", category=DeprecationWarning, filename="hello", lineno=3
|
|
|
|
)
|
2015-09-21 21:18:29 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_raises(self) -> None:
|
Use a hack to make typing of pytest.fail.Exception & co work
Mypy currently is unable to handle assigning attributes on function:
https://github.com/python/mypy/issues/2087.
pytest uses this for the outcome exceptions -- `pytest.fail.Exception`,
`pytest.exit.Exception` etc, and this is the canonical name by which they
are referred.
Initially we started working around this with type: ignores, and later
by switching e.g. `pytest.fail.Exception` with the direct exception
`Failed`. But this causes a lot of churn and is not as nice. And I also
found that some code relies on it, in skipping.py:
def pytest_configure(config):
if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old))
def nop(*args, **kwargs):
pass
nop.Exception = xfail.Exception
setattr(pytest, "xfail", nop)
...
So it seems better to support it. Use a hack to make it work. The rest
of the commit rolls back all of the workarounds we added up to now.
`pytest.raises.Exception` also exists, but it's not used much so I kept
it as-is for now.
Hopefully in the future mypy supports this and this ugliness can be
removed.
2020-02-17 03:46:11 +08:00
|
|
|
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
|
2015-12-09 08:40:05 +08:00
|
|
|
pytest.deprecated_call(self.dep, 3, 5)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call(self) -> None:
|
2015-12-09 08:40:05 +08:00
|
|
|
pytest.deprecated_call(self.dep, 0, 5)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_ret(self) -> None:
|
2015-11-27 02:27:20 +08:00
|
|
|
ret = pytest.deprecated_call(self.dep, 0)
|
2015-07-29 07:01:11 +08:00
|
|
|
assert ret == 42
|
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_preserves(self) -> None:
|
|
|
|
# Type ignored because `onceregistry` and `filters` are not
|
|
|
|
# documented API.
|
|
|
|
onceregistry = warnings.onceregistry.copy() # type: ignore
|
2021-12-22 09:42:32 +08:00
|
|
|
filters = warnings.filters[:]
|
2017-12-27 11:47:26 +08:00
|
|
|
warn = warnings.warn
|
|
|
|
warn_explicit = warnings.warn_explicit
|
2015-07-29 07:01:11 +08:00
|
|
|
self.test_deprecated_call_raises()
|
|
|
|
self.test_deprecated_call()
|
2019-12-03 20:34:41 +08:00
|
|
|
assert onceregistry == warnings.onceregistry # type: ignore
|
2021-12-22 09:42:32 +08:00
|
|
|
assert filters == warnings.filters
|
2017-12-27 11:47:26 +08:00
|
|
|
assert warn is warnings.warn
|
|
|
|
assert warn_explicit is warnings.warn_explicit
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_explicit_call_raises(self) -> None:
|
Use a hack to make typing of pytest.fail.Exception & co work
Mypy currently is unable to handle assigning attributes on function:
https://github.com/python/mypy/issues/2087.
pytest uses this for the outcome exceptions -- `pytest.fail.Exception`,
`pytest.exit.Exception` etc, and this is the canonical name by which they
are referred.
Initially we started working around this with type: ignores, and later
by switching e.g. `pytest.fail.Exception` with the direct exception
`Failed`. But this causes a lot of churn and is not as nice. And I also
found that some code relies on it, in skipping.py:
def pytest_configure(config):
if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old))
def nop(*args, **kwargs):
pass
nop.Exception = xfail.Exception
setattr(pytest, "xfail", nop)
...
So it seems better to support it. Use a hack to make it work. The rest
of the commit rolls back all of the workarounds we added up to now.
`pytest.raises.Exception` also exists, but it's not used much so I kept
it as-is for now.
Hopefully in the future mypy supports this and this ugliness can be
removed.
2020-02-17 03:46:11 +08:00
|
|
|
with pytest.raises(pytest.fail.Exception):
|
2015-11-27 02:27:20 +08:00
|
|
|
pytest.deprecated_call(self.dep_explicit, 3)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_explicit_call(self) -> None:
|
2015-11-27 02:27:20 +08:00
|
|
|
pytest.deprecated_call(self.dep_explicit, 0)
|
|
|
|
pytest.deprecated_call(self.dep_explicit, 0)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2017-06-07 09:25:15 +08:00
|
|
|
@pytest.mark.parametrize("mode", ["context_manager", "call"])
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_no_warning(self, mode) -> None:
|
2017-06-07 09:25:15 +08:00
|
|
|
"""Ensure deprecated_call() raises the expected failure when its block/function does
|
|
|
|
not raise a deprecation warning.
|
|
|
|
"""
|
2018-05-23 22:48:46 +08:00
|
|
|
|
2017-06-07 09:25:15 +08:00
|
|
|
def f():
|
|
|
|
pass
|
|
|
|
|
2018-10-10 23:00:59 +08:00
|
|
|
msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)"
|
Use a hack to make typing of pytest.fail.Exception & co work
Mypy currently is unable to handle assigning attributes on function:
https://github.com/python/mypy/issues/2087.
pytest uses this for the outcome exceptions -- `pytest.fail.Exception`,
`pytest.exit.Exception` etc, and this is the canonical name by which they
are referred.
Initially we started working around this with type: ignores, and later
by switching e.g. `pytest.fail.Exception` with the direct exception
`Failed`. But this causes a lot of churn and is not as nice. And I also
found that some code relies on it, in skipping.py:
def pytest_configure(config):
if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old))
def nop(*args, **kwargs):
pass
nop.Exception = xfail.Exception
setattr(pytest, "xfail", nop)
...
So it seems better to support it. Use a hack to make it work. The rest
of the commit rolls back all of the workarounds we added up to now.
`pytest.raises.Exception` also exists, but it's not used much so I kept
it as-is for now.
Hopefully in the future mypy supports this and this ugliness can be
removed.
2020-02-17 03:46:11 +08:00
|
|
|
with pytest.raises(pytest.fail.Exception, match=msg):
|
2017-06-07 09:25:15 +08:00
|
|
|
if mode == "call":
|
|
|
|
pytest.deprecated_call(f)
|
|
|
|
else:
|
|
|
|
with pytest.deprecated_call():
|
|
|
|
f()
|
2015-09-21 21:18:29 +08:00
|
|
|
|
2017-05-30 09:46:15 +08:00
|
|
|
@pytest.mark.parametrize(
|
2023-09-18 20:34:05 +08:00
|
|
|
"warning_type", [PendingDeprecationWarning, DeprecationWarning, FutureWarning]
|
2017-05-30 09:46:15 +08:00
|
|
|
)
|
|
|
|
@pytest.mark.parametrize("mode", ["context_manager", "call"])
|
2017-06-07 09:25:15 +08:00
|
|
|
@pytest.mark.parametrize("call_f_first", [True, False])
|
2017-07-21 10:11:14 +08:00
|
|
|
@pytest.mark.filterwarnings("ignore")
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_modes(self, warning_type, mode, call_f_first) -> None:
|
2017-06-07 09:25:15 +08:00
|
|
|
"""Ensure deprecated_call() captures a deprecation warning as expected inside its
|
|
|
|
block/function.
|
|
|
|
"""
|
2018-05-23 22:48:46 +08:00
|
|
|
|
2015-11-27 02:27:20 +08:00
|
|
|
def f():
|
2017-05-30 09:46:15 +08:00
|
|
|
warnings.warn(warning_type("hi"))
|
2017-06-07 09:25:15 +08:00
|
|
|
return 10
|
|
|
|
|
|
|
|
# ensure deprecated_call() can capture the warning even if it has already been triggered
|
|
|
|
if call_f_first:
|
|
|
|
assert f() == 10
|
2017-05-30 09:46:15 +08:00
|
|
|
if mode == "call":
|
2017-06-07 09:25:15 +08:00
|
|
|
assert pytest.deprecated_call(f) == 10
|
2017-05-30 09:46:15 +08:00
|
|
|
else:
|
|
|
|
with pytest.deprecated_call():
|
2017-06-07 09:25:15 +08:00
|
|
|
assert f() == 10
|
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_specificity(self) -> None:
|
2015-09-29 00:24:20 +08:00
|
|
|
other_warnings = [
|
|
|
|
Warning,
|
|
|
|
UserWarning,
|
|
|
|
SyntaxWarning,
|
|
|
|
RuntimeWarning,
|
|
|
|
ImportWarning,
|
|
|
|
UnicodeWarning,
|
|
|
|
]
|
|
|
|
for warning in other_warnings:
|
2018-05-23 22:48:46 +08:00
|
|
|
|
2015-11-27 02:27:20 +08:00
|
|
|
def f():
|
2024-02-03 04:26:49 +08:00
|
|
|
warnings.warn(warning("hi")) # noqa: B023
|
2017-06-07 09:25:15 +08:00
|
|
|
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns(warning):
|
|
|
|
with pytest.raises(pytest.fail.Exception):
|
|
|
|
pytest.deprecated_call(f)
|
|
|
|
with pytest.raises(pytest.fail.Exception):
|
|
|
|
with pytest.deprecated_call():
|
|
|
|
f()
|
2015-09-29 00:24:20 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_deprecated_call_supports_match(self) -> None:
|
2018-10-10 23:00:59 +08:00
|
|
|
with pytest.deprecated_call(match=r"must be \d+$"):
|
|
|
|
warnings.warn("value must be 42", DeprecationWarning)
|
|
|
|
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.deprecated_call():
|
|
|
|
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
|
|
|
|
with pytest.deprecated_call(match=r"must be \d+$"):
|
|
|
|
warnings.warn("this is not here", DeprecationWarning)
|
2018-10-10 23:00:59 +08:00
|
|
|
|
2015-07-29 07:01:11 +08:00
|
|
|
|
|
|
|
class TestWarns:
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_check_callable(self) -> None:
|
2019-06-30 22:40:24 +08:00
|
|
|
source = "warnings.warn('w1', RuntimeWarning)"
|
|
|
|
with pytest.raises(TypeError, match=r".* must be callable"):
|
2019-12-03 20:34:41 +08:00
|
|
|
pytest.warns(RuntimeWarning, source) # type: ignore
|
2019-06-30 22:40:24 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_several_messages(self) -> None:
|
2015-07-29 07:01:11 +08:00
|
|
|
# different messages, b/c Python suppresses multiple identical warnings
|
2019-06-30 22:40:24 +08:00
|
|
|
pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns(RuntimeWarning):
|
|
|
|
with pytest.raises(pytest.fail.Exception):
|
|
|
|
pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
|
2019-06-30 22:40:24 +08:00
|
|
|
pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_function(self) -> None:
|
2015-07-29 07:01:11 +08:00
|
|
|
pytest.warns(
|
|
|
|
SyntaxWarning, lambda msg: warnings.warn(msg, SyntaxWarning), "syntax"
|
|
|
|
)
|
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_warning_tuple(self) -> None:
|
2015-07-29 07:01:11 +08:00
|
|
|
pytest.warns(
|
|
|
|
(RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning)
|
2018-05-23 22:48:46 +08:00
|
|
|
)
|
2015-07-29 07:01:11 +08:00
|
|
|
pytest.warns(
|
|
|
|
(RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning)
|
2018-05-23 22:48:46 +08:00
|
|
|
)
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
pytest.raises(
|
|
|
|
pytest.fail.Exception,
|
|
|
|
lambda: pytest.warns(
|
|
|
|
(RuntimeWarning, SyntaxWarning),
|
|
|
|
lambda: warnings.warn("w3", UserWarning),
|
|
|
|
),
|
|
|
|
)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_as_contextmanager(self) -> None:
|
2015-07-29 07:01:11 +08:00
|
|
|
with pytest.warns(RuntimeWarning):
|
|
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
|
|
|
2016-12-20 20:36:57 +08:00
|
|
|
with pytest.warns(UserWarning):
|
|
|
|
warnings.warn("user", UserWarning)
|
|
|
|
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
|
|
with pytest.warns(RuntimeWarning):
|
|
|
|
warnings.warn("user", UserWarning)
|
2016-12-20 20:36:57 +08:00
|
|
|
excinfo.match(
|
2022-03-21 10:32:39 +08:00
|
|
|
r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted.\n"
|
2023-07-01 06:29:03 +08:00
|
|
|
r" Emitted warnings: \[UserWarning\('user',?\)\]."
|
2018-01-31 06:01:50 +08:00
|
|
|
)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
|
|
with pytest.warns(UserWarning):
|
|
|
|
warnings.warn("runtime", RuntimeWarning)
|
2016-12-20 20:36:57 +08:00
|
|
|
excinfo.match(
|
2022-03-21 10:32:39 +08:00
|
|
|
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
|
2023-07-01 06:29:03 +08:00
|
|
|
r" Emitted warnings: \[RuntimeWarning\('runtime',?\)]."
|
2018-01-31 06:01:50 +08:00
|
|
|
)
|
2016-12-20 20:36:57 +08:00
|
|
|
|
Use a hack to make typing of pytest.fail.Exception & co work
Mypy currently is unable to handle assigning attributes on function:
https://github.com/python/mypy/issues/2087.
pytest uses this for the outcome exceptions -- `pytest.fail.Exception`,
`pytest.exit.Exception` etc, and this is the canonical name by which they
are referred.
Initially we started working around this with type: ignores, and later
by switching e.g. `pytest.fail.Exception` with the direct exception
`Failed`. But this causes a lot of churn and is not as nice. And I also
found that some code relies on it, in skipping.py:
def pytest_configure(config):
if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old))
def nop(*args, **kwargs):
pass
nop.Exception = xfail.Exception
setattr(pytest, "xfail", nop)
...
So it seems better to support it. Use a hack to make it work. The rest
of the commit rolls back all of the workarounds we added up to now.
`pytest.raises.Exception` also exists, but it's not used much so I kept
it as-is for now.
Hopefully in the future mypy supports this and this ugliness can be
removed.
2020-02-17 03:46:11 +08:00
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
2016-12-20 20:36:57 +08:00
|
|
|
with pytest.warns(UserWarning):
|
|
|
|
pass
|
|
|
|
excinfo.match(
|
2022-03-21 10:32:39 +08:00
|
|
|
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
|
2023-07-01 06:29:03 +08:00
|
|
|
r" Emitted warnings: \[\]."
|
2016-12-20 20:36:57 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
warning_classes = (UserWarning, FutureWarning)
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
|
|
with pytest.warns(warning_classes) as warninfo:
|
|
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
|
|
warnings.warn("import", ImportWarning)
|
2016-12-20 20:36:57 +08:00
|
|
|
|
2022-03-21 10:32:39 +08:00
|
|
|
messages = [each.message for each in warninfo]
|
|
|
|
expected_str = (
|
|
|
|
f"DID NOT WARN. No warnings of type {warning_classes} were emitted.\n"
|
2023-07-01 06:29:03 +08:00
|
|
|
f" Emitted warnings: {messages}."
|
2018-05-23 22:48:46 +08:00
|
|
|
)
|
2015-07-29 07:01:11 +08:00
|
|
|
|
2022-03-21 10:32:39 +08:00
|
|
|
assert str(excinfo.value) == expected_str
|
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_record(self) -> None:
|
2015-07-29 07:01:11 +08:00
|
|
|
with pytest.warns(UserWarning) as record:
|
|
|
|
warnings.warn("user", UserWarning)
|
|
|
|
|
|
|
|
assert len(record) == 1
|
|
|
|
assert str(record[0].message) == "user"
|
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_record_only(self) -> None:
|
2021-05-15 22:15:43 +08:00
|
|
|
with pytest.warns() as record:
|
2015-07-29 07:01:11 +08:00
|
|
|
warnings.warn("user", UserWarning)
|
|
|
|
warnings.warn("runtime", RuntimeWarning)
|
2009-09-06 22:59:39 +08:00
|
|
|
|
2015-07-29 07:01:11 +08:00
|
|
|
assert len(record) == 2
|
|
|
|
assert str(record[0].message) == "user"
|
|
|
|
assert str(record[1].message) == "runtime"
|
2021-05-15 22:15:43 +08:00
|
|
|
|
2024-01-01 19:32:01 +08:00
|
|
|
def test_record_only_none_type_error(self) -> None:
|
|
|
|
with pytest.raises(TypeError):
|
|
|
|
pytest.warns(None) # type: ignore[call-overload]
|
2015-08-07 03:05:01 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_record_by_subclass(self) -> None:
|
2016-12-20 21:57:48 +08:00
|
|
|
with pytest.warns(Warning) as record:
|
|
|
|
warnings.warn("user", UserWarning)
|
|
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
|
|
|
|
|
|
assert len(record) == 2
|
|
|
|
assert str(record[0].message) == "user"
|
|
|
|
assert str(record[1].message) == "runtime"
|
|
|
|
|
2017-07-17 07:25:10 +08:00
|
|
|
class MyUserWarning(UserWarning):
|
|
|
|
pass
|
2016-12-29 17:18:33 +08:00
|
|
|
|
2017-07-17 07:25:10 +08:00
|
|
|
class MyRuntimeWarning(RuntimeWarning):
|
|
|
|
pass
|
2016-12-29 16:34:21 +08:00
|
|
|
|
|
|
|
with pytest.warns((UserWarning, RuntimeWarning)) as record:
|
|
|
|
warnings.warn("user", MyUserWarning)
|
|
|
|
warnings.warn("runtime", MyRuntimeWarning)
|
|
|
|
|
|
|
|
assert len(record) == 2
|
|
|
|
assert str(record[0].message) == "user"
|
|
|
|
assert str(record[1].message) == "runtime"
|
|
|
|
|
2020-11-09 12:23:31 +08:00
|
|
|
def test_double_test(self, pytester: Pytester) -> None:
|
2015-08-07 03:05:01 +08:00
|
|
|
"""If a test is run again, the warning should still be raised"""
|
2020-11-09 12:23:31 +08:00
|
|
|
pytester.makepyfile(
|
2018-05-23 22:48:46 +08:00
|
|
|
"""
|
2015-08-07 09:30:01 +08:00
|
|
|
import pytest
|
|
|
|
import warnings
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('run', [1, 2])
|
|
|
|
def test(run):
|
|
|
|
with pytest.warns(RuntimeWarning):
|
|
|
|
warnings.warn("runtime", RuntimeWarning)
|
2018-05-23 22:48:46 +08:00
|
|
|
"""
|
2015-08-07 09:30:01 +08:00
|
|
|
)
|
2020-11-09 12:23:31 +08:00
|
|
|
result = pytester.runpytest()
|
2015-08-07 09:30:01 +08:00
|
|
|
result.stdout.fnmatch_lines(["*2 passed in*"])
|
2017-08-22 19:48:29 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_match_regex(self) -> None:
|
2017-08-22 19:48:29 +08:00
|
|
|
with pytest.warns(UserWarning, match=r"must be \d+$"):
|
|
|
|
warnings.warn("value must be 42", UserWarning)
|
|
|
|
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception):
|
|
|
|
with pytest.warns(UserWarning, match=r"must be \d+$"):
|
|
|
|
warnings.warn("this is not here", UserWarning)
|
2017-08-22 19:48:29 +08:00
|
|
|
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception):
|
|
|
|
with pytest.warns(FutureWarning, match=r"must be \d+$"):
|
|
|
|
warnings.warn("value must be 42", UserWarning)
|
2017-09-06 07:13:08 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_one_from_multiple_warns(self) -> None:
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
|
2023-07-01 06:29:02 +08:00
|
|
|
with pytest.warns(UserWarning, match=r"aaa"):
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns(UserWarning, match=r"aaa"):
|
|
|
|
warnings.warn("cccccccccc", UserWarning)
|
|
|
|
warnings.warn("bbbbbbbbbb", UserWarning)
|
|
|
|
warnings.warn("aaaaaaaaaa", UserWarning)
|
2017-08-22 19:48:29 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_none_of_multiple_warns(self) -> None:
|
2023-07-01 06:29:03 +08:00
|
|
|
with pytest.warns():
|
|
|
|
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
|
|
|
|
with pytest.warns(UserWarning, match=r"aaa"):
|
|
|
|
warnings.warn("bbbbbbbbbb", UserWarning)
|
|
|
|
warnings.warn("cccccccccc", UserWarning)
|
2018-10-11 00:36:19 +08:00
|
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore")
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_can_capture_previously_warned(self) -> None:
|
2020-06-25 19:08:47 +08:00
|
|
|
def f() -> int:
|
2018-10-11 00:36:19 +08:00
|
|
|
warnings.warn(UserWarning("ohai"))
|
|
|
|
return 10
|
|
|
|
|
|
|
|
assert f() == 10
|
|
|
|
assert pytest.warns(UserWarning, f) == 10
|
|
|
|
assert pytest.warns(UserWarning, f) == 10
|
2020-06-25 19:08:47 +08:00
|
|
|
assert pytest.warns(UserWarning, f) != "10" # type: ignore[comparison-overlap]
|
2019-07-14 16:39:30 +08:00
|
|
|
|
2019-12-03 20:34:41 +08:00
|
|
|
def test_warns_context_manager_with_kwargs(self) -> None:
|
2019-07-14 16:39:30 +08:00
|
|
|
with pytest.raises(TypeError) as excinfo:
|
2019-12-03 20:34:41 +08:00
|
|
|
with pytest.warns(UserWarning, foo="bar"): # type: ignore
|
2019-07-14 16:39:30 +08:00
|
|
|
pass
|
|
|
|
assert "Unexpected keyword arguments" in str(excinfo.value)
|
2023-07-01 06:29:02 +08:00
|
|
|
|
|
|
|
def test_re_emit_single(self) -> None:
|
|
|
|
with pytest.warns(DeprecationWarning):
|
|
|
|
with pytest.warns(UserWarning):
|
|
|
|
warnings.warn("user warning", UserWarning)
|
|
|
|
warnings.warn("some deprecation warning", DeprecationWarning)
|
|
|
|
|
|
|
|
def test_re_emit_multiple(self) -> None:
|
|
|
|
with pytest.warns(UserWarning):
|
|
|
|
warnings.warn("first warning", UserWarning)
|
|
|
|
warnings.warn("second warning", UserWarning)
|
|
|
|
|
|
|
|
def test_re_emit_match_single(self) -> None:
|
|
|
|
with pytest.warns(DeprecationWarning):
|
|
|
|
with pytest.warns(UserWarning, match="user warning"):
|
|
|
|
warnings.warn("user warning", UserWarning)
|
|
|
|
warnings.warn("some deprecation warning", DeprecationWarning)
|
|
|
|
|
|
|
|
def test_re_emit_match_multiple(self) -> None:
|
2023-07-01 06:29:02 +08:00
|
|
|
with warnings.catch_warnings():
|
|
|
|
warnings.simplefilter("error") # if anything is re-emitted
|
|
|
|
with pytest.warns(UserWarning, match="user warning"):
|
|
|
|
warnings.warn("first user warning", UserWarning)
|
|
|
|
warnings.warn("second user warning", UserWarning)
|
2023-07-01 06:29:02 +08:00
|
|
|
|
|
|
|
def test_re_emit_non_match_single(self) -> None:
|
|
|
|
with pytest.warns(UserWarning, match="v2 warning"):
|
|
|
|
with pytest.warns(UserWarning, match="v1 warning"):
|
|
|
|
warnings.warn("v1 warning", UserWarning)
|
|
|
|
warnings.warn("non-matching v2 warning", UserWarning)
|
2023-06-22 23:48:20 +08:00
|
|
|
|
|
|
|
def test_catch_warning_within_raise(self) -> None:
|
|
|
|
# warns-in-raises works since https://github.com/pytest-dev/pytest/pull/11129
|
|
|
|
with pytest.raises(ValueError, match="some exception"):
|
|
|
|
with pytest.warns(FutureWarning, match="some warning"):
|
|
|
|
warnings.warn("some warning", category=FutureWarning)
|
|
|
|
raise ValueError("some exception")
|
|
|
|
# and raises-in-warns has always worked but we'll check for symmetry.
|
|
|
|
with pytest.warns(FutureWarning, match="some warning"):
|
|
|
|
with pytest.raises(ValueError, match="some exception"):
|
|
|
|
warnings.warn("some warning", category=FutureWarning)
|
|
|
|
raise ValueError("some exception")
|
2024-02-08 08:47:56 +08:00
|
|
|
|
2024-02-05 04:25:49 +08:00
|
|
|
def test_skip_within_warns(self, pytester: Pytester) -> None:
|
|
|
|
"""Regression test for #11907."""
|
|
|
|
pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
def test_it():
|
|
|
|
with pytest.warns(Warning):
|
|
|
|
pytest.skip("this is OK")
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
|
|
|
|
result = pytester.runpytest()
|
|
|
|
assert result.ret == ExitCode.OK
|
|
|
|
result.assert_outcomes(skipped=1)
|
|
|
|
|
|
|
|
def test_fail_within_warns(self, pytester: Pytester) -> None:
|
|
|
|
"""Regression test for #11907."""
|
|
|
|
pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
def test_it():
|
|
|
|
with pytest.warns(Warning):
|
|
|
|
pytest.fail("BOOM")
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
|
|
|
|
result = pytester.runpytest()
|
|
|
|
assert result.ret == ExitCode.TESTS_FAILED
|
|
|
|
result.assert_outcomes(failed=1)
|
|
|
|
assert "DID NOT WARN" not in str(result.stdout)
|
|
|
|
|
|
|
|
def test_exit_within_warns(self, pytester: Pytester) -> None:
|
|
|
|
"""Regression test for #11907."""
|
|
|
|
pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
def test_it():
|
|
|
|
with pytest.warns(Warning):
|
|
|
|
pytest.exit()
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
|
|
|
|
result = pytester.runpytest()
|
|
|
|
assert result.ret == ExitCode.INTERRUPTED
|
|
|
|
result.assert_outcomes()
|
|
|
|
|
|
|
|
def test_keyboard_interrupt_within_warns(self, pytester: Pytester) -> None:
|
|
|
|
"""Regression test for #11907."""
|
|
|
|
pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
def test_it():
|
|
|
|
with pytest.warns(Warning):
|
|
|
|
raise KeyboardInterrupt()
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
|
|
|
|
result = pytester.runpytest_subprocess()
|
|
|
|
assert result.ret == ExitCode.INTERRUPTED
|
|
|
|
result.assert_outcomes()
|
|
|
|
|
2024-02-08 08:47:56 +08:00
|
|
|
|
2024-02-10 04:13:41 +08:00
|
|
|
def test_raise_type_error_on_invalid_warning() -> None:
|
|
|
|
"""Check pytest.warns validates warning messages are strings (#10865) or
|
|
|
|
Warning instances (#11959)."""
|
|
|
|
with pytest.raises(TypeError, match="Warning must be str or Warning"):
|
2024-02-08 08:47:56 +08:00
|
|
|
with pytest.warns(UserWarning):
|
|
|
|
warnings.warn(1) # type: ignore
|
|
|
|
|
|
|
|
|
2024-02-10 04:13:41 +08:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"message",
|
|
|
|
[
|
|
|
|
pytest.param("Warning", id="str"),
|
|
|
|
pytest.param(UserWarning(), id="UserWarning"),
|
|
|
|
pytest.param(Warning(), id="Warning"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_no_raise_type_error_on_valid_warning(message: Union[str, Warning]) -> None:
|
|
|
|
"""Check pytest.warns validates warning messages are strings (#10865) or
|
|
|
|
Warning instances (#11959)."""
|
|
|
|
with pytest.warns(Warning):
|
|
|
|
warnings.warn(message)
|
2024-02-08 08:47:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
|
|
hasattr(sys, "pypy_version_info"),
|
|
|
|
reason="Not for pypy",
|
|
|
|
)
|
2024-02-10 04:13:41 +08:00
|
|
|
def test_raise_type_error_on_invalid_warning_message_cpython() -> None:
|
2024-02-08 08:47:56 +08:00
|
|
|
# Check that we get the same behavior with the stdlib, at least if filtering
|
|
|
|
# (see https://github.com/python/cpython/issues/103577 for details)
|
|
|
|
with pytest.raises(TypeError):
|
|
|
|
with warnings.catch_warnings():
|
|
|
|
warnings.filterwarnings("ignore", "test")
|
|
|
|
warnings.warn(1) # type: ignore
|
2024-02-16 20:01:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
def test_multiple_arg_custom_warning() -> None:
|
|
|
|
"""Test for issue #11906."""
|
|
|
|
|
|
|
|
class CustomWarning(UserWarning):
|
|
|
|
def __init__(self, a, b):
|
|
|
|
pass
|
|
|
|
|
|
|
|
with pytest.warns(CustomWarning):
|
|
|
|
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
|
|
|
|
with pytest.warns(CustomWarning, match="not gonna match"):
|
|
|
|
a, b = 1, 2
|
|
|
|
warnings.warn(CustomWarning(a, b))
|