Skip to content

Commit eb6c449

Browse files
symonknicoddemus
andauthored
Deprecation of msg= for both pytest.skip() and pytest.fail(). (#8950)
* porting pytest.skip() to use reason=, adding tests * avoid adding **kwargs, it breaks other functionality, use optional msg= instead * deprecation of `pytest.fail(msg=...)` * fix bug with not capturing the returned reason value * pass reason= in acceptance async tests instead of msg= * finalising deprecations of `msg` in `pytest.skip()` and `pytest.fail()` * Update doc/en/deprecations.rst Co-authored-by: Bruno Oliveira <[email protected]> * Update doc/en/deprecations.rst Co-authored-by: Bruno Oliveira <[email protected]> * fix failing test after upstream merge * adding deprecation to `pytest.exit(msg=...)` * add docs for pytest.exit deprecations * finalising deprecation of msg for pytest.skip, pytest.exit and pytest.fail * hold a reference to the Scope instance to please mypy Co-authored-by: Bruno Oliveira <[email protected]>
1 parent b7603fa commit eb6c449

File tree

11 files changed

+288
-20
lines changed

11 files changed

+288
-20
lines changed

changelog/8948.deprecation.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:func:`pytest.skip(msg=...) <pytest.skip>`, :func:`pytest.fail(msg=...) <pytest.fail>` and :func:`pytest.exit(msg=...) <pytest.exit>`
2+
signatures now accept a ``reason`` argument instead of ``msg``. Using ``msg`` still works, but is deprecated and will be removed in a future release.
3+
4+
This was changed for consistency with :func:`pytest.mark.skip <pytest.mark.skip>` and :func:`pytest.mark.xfail <pytest.mark.xfail>` which both accept
5+
``reason`` as an argument.

doc/en/deprecations.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,38 @@ In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the
5656
The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
5757

5858

59+
Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
60+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61+
62+
.. deprecated:: 7.0
63+
64+
Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit`
65+
is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these
66+
functions and the``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
67+
68+
.. code-block:: python
69+
70+
def test_fail_example():
71+
# old
72+
pytest.fail(msg="foo")
73+
# new
74+
pytest.fail(reason="bar")
75+
76+
77+
def test_skip_example():
78+
# old
79+
pytest.skip(msg="foo")
80+
# new
81+
pytest.skip(reason="bar")
82+
83+
84+
def test_exit_example():
85+
# old
86+
pytest.exit(msg="foo")
87+
# new
88+
pytest.exit(reason="bar")
89+
90+
5991
Implementing the ``pytest_cmdline_preparse`` hook
6092
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6193

doc/en/reference/reference.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ pytest.fail
2222

2323
**Tutorial**: :ref:`skipping`
2424

25-
.. autofunction:: pytest.fail
25+
.. autofunction:: pytest.fail(reason, [pytrace=True, msg=None])
2626

2727
pytest.skip
2828
~~~~~~~~~~~
2929

30-
.. autofunction:: pytest.skip(msg, [allow_module_level=False])
30+
.. autofunction:: pytest.skip(reason, [allow_module_level=False, msg=None])
3131

3232
.. _`pytest.importorskip ref`:
3333

@@ -44,7 +44,7 @@ pytest.xfail
4444
pytest.exit
4545
~~~~~~~~~~~
4646

47-
.. autofunction:: pytest.exit
47+
.. autofunction:: pytest.exit(reason, [returncode=False, msg=None])
4848

4949
pytest.main
5050
~~~~~~~~~~~

src/_pytest/compat.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020
import attr
2121
import py
2222

23-
from _pytest.outcomes import fail
24-
from _pytest.outcomes import TEST_OUTCOME
25-
2623
if TYPE_CHECKING:
2724
from typing import NoReturn
2825
from typing_extensions import Final
@@ -152,6 +149,8 @@ def getfuncargnames(
152149
try:
153150
parameters = signature(function).parameters
154151
except (ValueError, TypeError) as e:
152+
from _pytest.outcomes import fail
153+
155154
fail(
156155
f"Could not determine arguments of {function!r}: {e}",
157156
pytrace=False,
@@ -324,6 +323,8 @@ def safe_getattr(object: Any, name: str, default: Any) -> Any:
324323
are derived from BaseException instead of Exception (for more details
325324
check #2707).
326325
"""
326+
from _pytest.outcomes import TEST_OUTCOME
327+
327328
try:
328329
return getattr(object, name, default)
329330
except TEST_OUTCOME:

src/_pytest/deprecated.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@
114114
" Replace pytest.warns(None) by simply pytest.warns()."
115115
)
116116

117+
KEYWORD_MSG_ARG = UnformattedWarning(
118+
PytestDeprecationWarning,
119+
"pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead",
120+
)
121+
117122
# You want to make some `__init__` or function "private".
118123
#
119124
# def my_private_function(some, args):

src/_pytest/outcomes.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
"""Exception classes and constants handling test outcomes as well as
22
functions creating them."""
33
import sys
4+
import warnings
45
from typing import Any
56
from typing import Callable
67
from typing import cast
78
from typing import Optional
89
from typing import Type
910
from typing import TypeVar
1011

12+
from _pytest.deprecated import KEYWORD_MSG_ARG
13+
1114
TYPE_CHECKING = False # Avoid circular import through compat.
1215

1316
if TYPE_CHECKING:
@@ -110,28 +113,56 @@ def decorate(func: _F) -> _WithException[_F, _ET]:
110113

111114

112115
@_with_exception(Exit)
113-
def exit(msg: str, returncode: Optional[int] = None) -> "NoReturn":
116+
def exit(
117+
reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None
118+
) -> "NoReturn":
114119
"""Exit testing process.
115120
116-
:param str msg: Message to display upon exit.
117-
:param int returncode: Return code to be used when exiting pytest.
121+
:param reason:
122+
The message to show as the reason for exiting pytest. reason has a default value
123+
only because `msg` is deprecated.
124+
125+
:param returncode:
126+
Return code to be used when exiting pytest.
127+
128+
:param msg:
129+
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
118130
"""
119131
__tracebackhide__ = True
120-
raise Exit(msg, returncode)
132+
from _pytest.config import UsageError
133+
134+
if reason and msg:
135+
raise UsageError(
136+
"cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`."
137+
)
138+
if not reason:
139+
if msg is None:
140+
raise UsageError("exit() requires a reason argument")
141+
warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2)
142+
reason = msg
143+
raise Exit(reason, returncode)
121144

122145

123146
@_with_exception(Skipped)
124-
def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn":
147+
def skip(
148+
reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None
149+
) -> "NoReturn":
125150
"""Skip an executing test with the given message.
126151
127152
This function should be called only during testing (setup, call or teardown) or
128153
during collection by using the ``allow_module_level`` flag. This function can
129154
be called in doctests as well.
130155
131-
:param bool allow_module_level:
156+
:param reason:
157+
The message to show the user as reason for the skip.
158+
159+
:param allow_module_level:
132160
Allows this function to be called at module level, skipping the rest
133161
of the module. Defaults to False.
134162
163+
:param msg:
164+
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
165+
135166
.. note::
136167
It is better to use the :ref:`pytest.mark.skipif ref` marker when
137168
possible to declare a test to be skipped under certain conditions
@@ -140,21 +171,66 @@ def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn":
140171
to skip a doctest statically.
141172
"""
142173
__tracebackhide__ = True
143-
raise Skipped(msg=msg, allow_module_level=allow_module_level)
174+
reason = _resolve_msg_to_reason("skip", reason, msg)
175+
raise Skipped(msg=reason, allow_module_level=allow_module_level)
144176

145177

146178
@_with_exception(Failed)
147-
def fail(msg: str = "", pytrace: bool = True) -> "NoReturn":
179+
def fail(
180+
reason: str = "", pytrace: bool = True, msg: Optional[str] = None
181+
) -> "NoReturn":
148182
"""Explicitly fail an executing test with the given message.
149183
150-
:param str msg:
184+
:param reason:
151185
The message to show the user as reason for the failure.
152-
:param bool pytrace:
186+
187+
:param pytrace:
153188
If False, msg represents the full failure information and no
154189
python traceback will be reported.
190+
191+
:param msg:
192+
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
155193
"""
156194
__tracebackhide__ = True
157-
raise Failed(msg=msg, pytrace=pytrace)
195+
reason = _resolve_msg_to_reason("fail", reason, msg)
196+
raise Failed(msg=reason, pytrace=pytrace)
197+
198+
199+
def _resolve_msg_to_reason(
200+
func_name: str, reason: str, msg: Optional[str] = None
201+
) -> str:
202+
"""
203+
Handles converting the deprecated msg parameter if provided into
204+
reason, raising a deprecation warning. This function will be removed
205+
when the optional msg argument is removed from here in future.
206+
207+
:param str func_name:
208+
The name of the offending function, this is formatted into the deprecation message.
209+
210+
:param str reason:
211+
The reason= passed into either pytest.fail() or pytest.skip()
212+
213+
:param str msg:
214+
The msg= passed into either pytest.fail() or pytest.skip(). This will
215+
be converted into reason if it is provided to allow pytest.skip(msg=) or
216+
pytest.fail(msg=) to continue working in the interim period.
217+
218+
:returns:
219+
The value to use as reason.
220+
221+
"""
222+
__tracebackhide__ = True
223+
if msg is not None:
224+
225+
if reason:
226+
from pytest import UsageError
227+
228+
raise UsageError(
229+
f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted."
230+
)
231+
warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3)
232+
reason = msg
233+
return reason
158234

159235

160236
class XFailed(Failed):

src/_pytest/python.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def async_warn_and_skip(nodeid: str) -> None:
175175
msg += " - pytest-trio\n"
176176
msg += " - pytest-twisted"
177177
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))
178-
skip(msg="async def function and no async plugin installed (see warnings)")
178+
skip(reason="async def function and no async plugin installed (see warnings)")
179179

180180

181181
@hookimpl(trylast=True)

src/_pytest/scope.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,16 @@ def from_user(
7171
from _pytest.outcomes import fail
7272

7373
try:
74-
return Scope(scope_name)
74+
# Holding this reference is necessary for mypy at the moment.
75+
scope = Scope(scope_name)
7576
except ValueError:
7677
fail(
7778
"{} {}got an unexpected scope value '{}'".format(
7879
descr, f"from {where} " if where else "", scope_name
7980
),
8081
pytrace=False,
8182
)
83+
return scope
8284

8385

8486
_ALL_SCOPES = list(Scope)

testing/deprecated_test.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,64 @@ def test_warns_none_is_deprecated():
200200
pass
201201

202202

203+
class TestSkipMsgArgumentDeprecated:
204+
def test_skip_with_msg_is_deprecated(self, pytester: Pytester) -> None:
205+
p = pytester.makepyfile(
206+
"""
207+
import pytest
208+
209+
def test_skipping_msg():
210+
pytest.skip(msg="skippedmsg")
211+
"""
212+
)
213+
result = pytester.runpytest(p)
214+
result.stdout.fnmatch_lines(
215+
[
216+
"*PytestDeprecationWarning: pytest.skip(msg=...) is now deprecated, "
217+
"use pytest.skip(reason=...) instead",
218+
'*pytest.skip(msg="skippedmsg")*',
219+
]
220+
)
221+
result.assert_outcomes(skipped=1, warnings=1)
222+
223+
def test_fail_with_msg_is_deprecated(self, pytester: Pytester) -> None:
224+
p = pytester.makepyfile(
225+
"""
226+
import pytest
227+
228+
def test_failing_msg():
229+
pytest.fail(msg="failedmsg")
230+
"""
231+
)
232+
result = pytester.runpytest(p)
233+
result.stdout.fnmatch_lines(
234+
[
235+
"*PytestDeprecationWarning: pytest.fail(msg=...) is now deprecated, "
236+
"use pytest.fail(reason=...) instead",
237+
'*pytest.fail(msg="failedmsg")',
238+
]
239+
)
240+
result.assert_outcomes(failed=1, warnings=1)
241+
242+
def test_exit_with_msg_is_deprecated(self, pytester: Pytester) -> None:
243+
p = pytester.makepyfile(
244+
"""
245+
import pytest
246+
247+
def test_exit_msg():
248+
pytest.exit(msg="exitmsg")
249+
"""
250+
)
251+
result = pytester.runpytest(p)
252+
result.stdout.fnmatch_lines(
253+
[
254+
"*PytestDeprecationWarning: pytest.exit(msg=...) is now deprecated, "
255+
"use pytest.exit(reason=...) instead",
256+
]
257+
)
258+
result.assert_outcomes(warnings=1)
259+
260+
203261
def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None:
204262
pytester.makeconftest(
205263
"""

testing/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_wrap_session_exit_sessionfinish(
7070
"""
7171
import pytest
7272
def pytest_sessionfinish():
73-
pytest.exit(msg="exit_pytest_sessionfinish", returncode={returncode})
73+
pytest.exit(reason="exit_pytest_sessionfinish", returncode={returncode})
7474
""".format(
7575
returncode=returncode
7676
)

0 commit comments

Comments
 (0)