Remove support for 'code as string' from pytest.raises and pytest.warns
This commit is contained in:
parent
be91c4d932
commit
279733a30b
|
@ -2,3 +2,9 @@ As per our policy, the following features have been deprecated in the 4.X series
|
||||||
removed:
|
removed:
|
||||||
|
|
||||||
* ``Request.getfuncargvalue``: use ``Request.getfixturevalue`` instead.
|
* ``Request.getfuncargvalue``: use ``Request.getfixturevalue`` instead.
|
||||||
|
|
||||||
|
* ``pytest.raises`` and ``pytest.warns`` no longer support strings as the second argument.
|
||||||
|
|
||||||
|
|
||||||
|
For more information consult
|
||||||
|
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
||||||
|
|
|
@ -80,12 +80,35 @@ The ``pytest.config`` global object is deprecated. Instead use
|
||||||
use the ``pytest_configure(config)`` hook. Note that many hooks can also access
|
use the ``pytest_configure(config)`` hook. Note that many hooks can also access
|
||||||
the ``config`` object indirectly, through ``session.config`` or ``item.config`` for example.
|
the ``config`` object indirectly, through ``session.config`` or ``item.config`` for example.
|
||||||
|
|
||||||
|
|
||||||
|
Result log (``--result-log``)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 4.0
|
||||||
|
|
||||||
|
The ``--result-log`` option produces a stream of test reports which can be
|
||||||
|
analysed at runtime. It uses a custom format which requires users to implement their own
|
||||||
|
parser, but the team believes using a line-based format that can be parsed using standard
|
||||||
|
tools would provide a suitable and better alternative.
|
||||||
|
|
||||||
|
The current plan is to provide an alternative in the pytest 5.0 series and remove the ``--result-log``
|
||||||
|
option in pytest 6.0 after the new implementation proves satisfactory to all users and is deemed
|
||||||
|
stable.
|
||||||
|
|
||||||
|
The actual alternative is still being discussed in issue `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`__.
|
||||||
|
|
||||||
|
Removed Features
|
||||||
|
----------------
|
||||||
|
|
||||||
|
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
||||||
|
an appropriate period of deprecation has passed.
|
||||||
|
|
||||||
.. _raises-warns-exec:
|
.. _raises-warns-exec:
|
||||||
|
|
||||||
``raises`` / ``warns`` with a string as the second argument
|
``raises`` / ``warns`` with a string as the second argument
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. deprecated:: 4.1
|
.. versionremoved:: 5.0
|
||||||
|
|
||||||
Use the context manager form of these instead. When necessary, invoke ``exec``
|
Use the context manager form of these instead. When necessary, invoke ``exec``
|
||||||
directly.
|
directly.
|
||||||
|
@ -116,27 +139,6 @@ Becomes:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Result log (``--result-log``)
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 4.0
|
|
||||||
|
|
||||||
The ``--result-log`` option produces a stream of test reports which can be
|
|
||||||
analysed at runtime. It uses a custom format which requires users to implement their own
|
|
||||||
parser, but the team believes using a line-based format that can be parsed using standard
|
|
||||||
tools would provide a suitable and better alternative.
|
|
||||||
|
|
||||||
The current plan is to provide an alternative in the pytest 5.0 series and remove the ``--result-log``
|
|
||||||
option in pytest 6.0 after the new implementation proves satisfactory to all users and is deemed
|
|
||||||
stable.
|
|
||||||
|
|
||||||
The actual alternative is still being discussed in issue `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`__.
|
|
||||||
|
|
||||||
Removed Features
|
|
||||||
----------------
|
|
||||||
|
|
||||||
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
|
||||||
an appropriate period of deprecation has passed.
|
|
||||||
|
|
||||||
Using ``Class`` in custom Collectors
|
Using ``Class`` in custom Collectors
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -54,14 +54,6 @@ RESULT_LOG = PytestDeprecationWarning(
|
||||||
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
|
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
|
||||||
)
|
)
|
||||||
|
|
||||||
RAISES_EXEC = PytestDeprecationWarning(
|
|
||||||
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
|
|
||||||
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
|
|
||||||
)
|
|
||||||
WARNS_EXEC = PytestDeprecationWarning(
|
|
||||||
"warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n"
|
|
||||||
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
|
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
|
||||||
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
|
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import inspect
|
import inspect
|
||||||
import math
|
import math
|
||||||
import pprint
|
import pprint
|
||||||
import sys
|
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
@ -667,23 +666,12 @@ def raises(expected_exception, *args, **kwargs):
|
||||||
msg += ", ".join(sorted(kwargs))
|
msg += ", ".join(sorted(kwargs))
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
return RaisesContext(expected_exception, message, match_expr)
|
return RaisesContext(expected_exception, message, match_expr)
|
||||||
elif isinstance(args[0], str):
|
|
||||||
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
|
|
||||||
code, = args
|
|
||||||
assert isinstance(code, str)
|
|
||||||
frame = sys._getframe(1)
|
|
||||||
loc = frame.f_locals.copy()
|
|
||||||
loc.update(kwargs)
|
|
||||||
# print "raises frame scope: %r" % frame.f_locals
|
|
||||||
try:
|
|
||||||
code = _pytest._code.Source(code).compile(_genframe=frame)
|
|
||||||
exec(code, frame.f_globals, loc)
|
|
||||||
# XXX didn't mean f_globals == f_locals something special?
|
|
||||||
# this is destroyed here ...
|
|
||||||
except expected_exception:
|
|
||||||
return _pytest._code.ExceptionInfo.from_current()
|
|
||||||
else:
|
else:
|
||||||
func = args[0]
|
func = args[0]
|
||||||
|
if not callable(func):
|
||||||
|
raise TypeError(
|
||||||
|
"{!r} object (type: {}) must be callable".format(func, type(func))
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
func(*args[1:], **kwargs)
|
func(*args[1:], **kwargs)
|
||||||
except expected_exception:
|
except expected_exception:
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
""" recording warnings during test function execution. """
|
""" recording warnings during test function execution. """
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import _pytest._code
|
|
||||||
from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS
|
from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS
|
||||||
from _pytest.deprecated import WARNS_EXEC
|
|
||||||
from _pytest.fixtures import yield_fixture
|
from _pytest.fixtures import yield_fixture
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
|
||||||
|
@ -86,19 +83,12 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2
|
PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2
|
||||||
)
|
)
|
||||||
return WarningsChecker(expected_warning, match_expr=match_expr)
|
return WarningsChecker(expected_warning, match_expr=match_expr)
|
||||||
elif isinstance(args[0], str):
|
|
||||||
warnings.warn(WARNS_EXEC, stacklevel=2)
|
|
||||||
code, = args
|
|
||||||
assert isinstance(code, str)
|
|
||||||
frame = sys._getframe(1)
|
|
||||||
loc = frame.f_locals.copy()
|
|
||||||
loc.update(kwargs)
|
|
||||||
|
|
||||||
with WarningsChecker(expected_warning):
|
|
||||||
code = _pytest._code.Source(code).compile()
|
|
||||||
exec(code, frame.f_globals, loc)
|
|
||||||
else:
|
else:
|
||||||
func = args[0]
|
func = args[0]
|
||||||
|
if not callable(func):
|
||||||
|
raise TypeError(
|
||||||
|
"{!r} object (type: {}) must be callable".format(func, type(func))
|
||||||
|
)
|
||||||
with WarningsChecker(expected_warning):
|
with WarningsChecker(expected_warning):
|
||||||
return func(*args[1:], **kwargs)
|
return func(*args[1:], **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -6,31 +6,17 @@ from _pytest.warning_types import PytestDeprecationWarning
|
||||||
|
|
||||||
|
|
||||||
class TestRaises:
|
class TestRaises:
|
||||||
|
def test_check_callable(self):
|
||||||
|
with pytest.raises(TypeError, match=r".* must be callable"):
|
||||||
|
pytest.raises(RuntimeError, "int('qwe')")
|
||||||
|
|
||||||
def test_raises(self):
|
def test_raises(self):
|
||||||
source = "int('qwe')"
|
excinfo = pytest.raises(ValueError, int, "qwe")
|
||||||
with pytest.warns(PytestDeprecationWarning):
|
assert "invalid literal" in str(excinfo.value)
|
||||||
excinfo = pytest.raises(ValueError, source)
|
|
||||||
code = excinfo.traceback[-1].frame.code
|
|
||||||
s = str(code.fullsource)
|
|
||||||
assert s == source
|
|
||||||
|
|
||||||
def test_raises_exec(self):
|
|
||||||
with pytest.warns(PytestDeprecationWarning) as warninfo:
|
|
||||||
pytest.raises(ValueError, "a,x = []")
|
|
||||||
assert warninfo[0].filename == __file__
|
|
||||||
|
|
||||||
def test_raises_exec_correct_filename(self):
|
|
||||||
with pytest.warns(PytestDeprecationWarning):
|
|
||||||
excinfo = pytest.raises(ValueError, 'int("s")')
|
|
||||||
assert __file__ in excinfo.traceback[-1].path
|
|
||||||
|
|
||||||
def test_raises_syntax_error(self):
|
|
||||||
with pytest.warns(PytestDeprecationWarning) as warninfo:
|
|
||||||
pytest.raises(SyntaxError, "qwe qwe qwe")
|
|
||||||
assert warninfo[0].filename == __file__
|
|
||||||
|
|
||||||
def test_raises_function(self):
|
def test_raises_function(self):
|
||||||
pytest.raises(ValueError, int, "hello")
|
excinfo = pytest.raises(ValueError, int, "hello")
|
||||||
|
assert "invalid literal" in str(excinfo.value)
|
||||||
|
|
||||||
def test_raises_callable_no_exception(self):
|
def test_raises_callable_no_exception(self):
|
||||||
class A:
|
class A:
|
||||||
|
|
|
@ -25,7 +25,8 @@ class TestMark:
|
||||||
|
|
||||||
def test_pytest_mark_notcallable(self):
|
def test_pytest_mark_notcallable(self):
|
||||||
mark = Mark()
|
mark = Mark()
|
||||||
pytest.raises((AttributeError, TypeError), mark)
|
with pytest.raises(TypeError):
|
||||||
|
mark()
|
||||||
|
|
||||||
def test_mark_with_param(self):
|
def test_mark_with_param(self):
|
||||||
def some_function(abc):
|
def some_function(abc):
|
||||||
|
|
|
@ -3,7 +3,6 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.recwarn import WarningsRecorder
|
from _pytest.recwarn import WarningsRecorder
|
||||||
from _pytest.warning_types import PytestDeprecationWarning
|
|
||||||
|
|
||||||
|
|
||||||
def test_recwarn_stacklevel(recwarn):
|
def test_recwarn_stacklevel(recwarn):
|
||||||
|
@ -206,22 +205,17 @@ class TestDeprecatedCall:
|
||||||
|
|
||||||
|
|
||||||
class TestWarns:
|
class TestWarns:
|
||||||
def test_strings(self):
|
def test_check_callable(self):
|
||||||
|
source = "warnings.warn('w1', RuntimeWarning)"
|
||||||
|
with pytest.raises(TypeError, match=r".* must be callable"):
|
||||||
|
pytest.warns(RuntimeWarning, source)
|
||||||
|
|
||||||
|
def test_several_messages(self):
|
||||||
# different messages, b/c Python suppresses multiple identical warnings
|
# different messages, b/c Python suppresses multiple identical warnings
|
||||||
source1 = "warnings.warn('w1', RuntimeWarning)"
|
pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
|
||||||
source2 = "warnings.warn('w2', RuntimeWarning)"
|
with pytest.raises(pytest.fail.Exception):
|
||||||
source3 = "warnings.warn('w3', RuntimeWarning)"
|
pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
|
||||||
with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg
|
pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
|
||||||
pytest.warns(RuntimeWarning, source1)
|
|
||||||
pytest.raises(
|
|
||||||
pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2)
|
|
||||||
)
|
|
||||||
pytest.warns(RuntimeWarning, source3)
|
|
||||||
assert len(warninfo) == 3
|
|
||||||
for w in warninfo:
|
|
||||||
assert w.filename == __file__
|
|
||||||
msg, = w.message.args
|
|
||||||
assert msg.startswith("warns(..., 'code(as_a_string)') is deprecated")
|
|
||||||
|
|
||||||
def test_function(self):
|
def test_function(self):
|
||||||
pytest.warns(
|
pytest.warns(
|
||||||
|
|
Loading…
Reference in New Issue