diff --git a/changelog/5180.removal.rst b/changelog/5180.removal.rst index 952a75c82..bde3e03ff 100644 --- a/changelog/5180.removal.rst +++ b/changelog/5180.removal.rst @@ -2,3 +2,9 @@ As per our policy, the following features have been deprecated in the 4.X series removed: * ``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 `__ in the docs. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index e2399dd41..344e1dd72 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -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 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 `__. + +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`` 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`` 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 `__. - -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 4b68fd107..77c7c30ff 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -54,14 +54,6 @@ RESULT_LOG = PytestDeprecationWarning( "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 = ( "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported " diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 374fa598f..f709057a3 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,7 +1,6 @@ import inspect import math import pprint -import sys import warnings from collections.abc import Iterable from collections.abc import Mapping @@ -667,23 +666,12 @@ def raises(expected_exception, *args, **kwargs): msg += ", ".join(sorted(kwargs)) raise TypeError(msg) 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: func = args[0] + if not callable(func): + raise TypeError( + "{!r} object (type: {}) must be callable".format(func, type(func)) + ) try: func(*args[1:], **kwargs) except expected_exception: diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 006d97e7f..8733a893a 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,12 +1,9 @@ """ recording warnings during test function execution. """ import inspect import re -import sys import warnings -import _pytest._code from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS -from _pytest.deprecated import WARNS_EXEC from _pytest.fixtures import yield_fixture 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 ) 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: func = args[0] + if not callable(func): + raise TypeError( + "{!r} object (type: {}) must be callable".format(func, type(func)) + ) with WarningsChecker(expected_warning): return func(*args[1:], **kwargs) diff --git a/testing/python/raises.py b/testing/python/raises.py index c9ede412a..3ace96e7a 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -6,31 +6,17 @@ from _pytest.warning_types import PytestDeprecationWarning 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): - source = "int('qwe')" - with pytest.warns(PytestDeprecationWarning): - 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__ + excinfo = pytest.raises(ValueError, int, "qwe") + assert "invalid literal" in str(excinfo.value) 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): class A: diff --git a/testing/test_mark.py b/testing/test_mark.py index c22e9dbb5..1544ffe56 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -25,7 +25,8 @@ class TestMark: def test_pytest_mark_notcallable(self): mark = Mark() - pytest.raises((AttributeError, TypeError), mark) + with pytest.raises(TypeError): + mark() def test_mark_with_param(self): def some_function(abc): diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 1c68b3787..65fdd1682 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -3,7 +3,6 @@ import warnings import pytest from _pytest.recwarn import WarningsRecorder -from _pytest.warning_types import PytestDeprecationWarning def test_recwarn_stacklevel(recwarn): @@ -206,22 +205,17 @@ class TestDeprecatedCall: 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 - source1 = "warnings.warn('w1', RuntimeWarning)" - source2 = "warnings.warn('w2', RuntimeWarning)" - source3 = "warnings.warn('w3', RuntimeWarning)" - with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg - 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") + pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning)) + with pytest.raises(pytest.fail.Exception): + pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning)) + pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning)) def test_function(self): pytest.warns(