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 <nicoddemus@gmail.com>

* Update doc/en/deprecations.rst

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>

* 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 <nicoddemus@gmail.com>
This commit is contained in:
Simon K
2021-11-08 14:31:14 +00:00
committed by GitHub
parent b7603fa730
commit eb6c4493b2
11 changed files with 288 additions and 20 deletions

View File

@@ -20,9 +20,6 @@ from typing import Union
import attr
import py
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
if TYPE_CHECKING:
from typing import NoReturn
from typing_extensions import Final
@@ -152,6 +149,8 @@ def getfuncargnames(
try:
parameters = signature(function).parameters
except (ValueError, TypeError) as e:
from _pytest.outcomes import fail
fail(
f"Could not determine arguments of {function!r}: {e}",
pytrace=False,
@@ -324,6 +323,8 @@ def safe_getattr(object: Any, name: str, default: Any) -> Any:
are derived from BaseException instead of Exception (for more details
check #2707).
"""
from _pytest.outcomes import TEST_OUTCOME
try:
return getattr(object, name, default)
except TEST_OUTCOME:

View File

@@ -114,6 +114,11 @@ WARNS_NONE_ARG = PytestDeprecationWarning(
" Replace pytest.warns(None) by simply pytest.warns()."
)
KEYWORD_MSG_ARG = UnformattedWarning(
PytestDeprecationWarning,
"pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead",
)
# You want to make some `__init__` or function "private".
#
# def my_private_function(some, args):

View File

@@ -1,6 +1,7 @@
"""Exception classes and constants handling test outcomes as well as
functions creating them."""
import sys
import warnings
from typing import Any
from typing import Callable
from typing import cast
@@ -8,6 +9,8 @@ from typing import Optional
from typing import Type
from typing import TypeVar
from _pytest.deprecated import KEYWORD_MSG_ARG
TYPE_CHECKING = False # Avoid circular import through compat.
if TYPE_CHECKING:
@@ -110,28 +113,56 @@ def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _E
@_with_exception(Exit)
def exit(msg: str, returncode: Optional[int] = None) -> "NoReturn":
def exit(
reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None
) -> "NoReturn":
"""Exit testing process.
:param str msg: Message to display upon exit.
:param int returncode: Return code to be used when exiting pytest.
:param reason:
The message to show as the reason for exiting pytest. reason has a default value
only because `msg` is deprecated.
:param returncode:
Return code to be used when exiting pytest.
:param msg:
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
"""
__tracebackhide__ = True
raise Exit(msg, returncode)
from _pytest.config import UsageError
if reason and msg:
raise UsageError(
"cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`."
)
if not reason:
if msg is None:
raise UsageError("exit() requires a reason argument")
warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2)
reason = msg
raise Exit(reason, returncode)
@_with_exception(Skipped)
def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn":
def skip(
reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None
) -> "NoReturn":
"""Skip an executing test with the given message.
This function should be called only during testing (setup, call or teardown) or
during collection by using the ``allow_module_level`` flag. This function can
be called in doctests as well.
:param bool allow_module_level:
:param reason:
The message to show the user as reason for the skip.
:param allow_module_level:
Allows this function to be called at module level, skipping the rest
of the module. Defaults to False.
:param msg:
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
.. note::
It is better to use the :ref:`pytest.mark.skipif ref` marker when
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":
to skip a doctest statically.
"""
__tracebackhide__ = True
raise Skipped(msg=msg, allow_module_level=allow_module_level)
reason = _resolve_msg_to_reason("skip", reason, msg)
raise Skipped(msg=reason, allow_module_level=allow_module_level)
@_with_exception(Failed)
def fail(msg: str = "", pytrace: bool = True) -> "NoReturn":
def fail(
reason: str = "", pytrace: bool = True, msg: Optional[str] = None
) -> "NoReturn":
"""Explicitly fail an executing test with the given message.
:param str msg:
:param reason:
The message to show the user as reason for the failure.
:param bool pytrace:
:param pytrace:
If False, msg represents the full failure information and no
python traceback will be reported.
:param msg:
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
reason = _resolve_msg_to_reason("fail", reason, msg)
raise Failed(msg=reason, pytrace=pytrace)
def _resolve_msg_to_reason(
func_name: str, reason: str, msg: Optional[str] = None
) -> str:
"""
Handles converting the deprecated msg parameter if provided into
reason, raising a deprecation warning. This function will be removed
when the optional msg argument is removed from here in future.
:param str func_name:
The name of the offending function, this is formatted into the deprecation message.
:param str reason:
The reason= passed into either pytest.fail() or pytest.skip()
:param str msg:
The msg= passed into either pytest.fail() or pytest.skip(). This will
be converted into reason if it is provided to allow pytest.skip(msg=) or
pytest.fail(msg=) to continue working in the interim period.
:returns:
The value to use as reason.
"""
__tracebackhide__ = True
if msg is not None:
if reason:
from pytest import UsageError
raise UsageError(
f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted."
)
warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3)
reason = msg
return reason
class XFailed(Failed):

View File

@@ -175,7 +175,7 @@ def async_warn_and_skip(nodeid: str) -> None:
msg += " - pytest-trio\n"
msg += " - pytest-twisted"
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))
skip(msg="async def function and no async plugin installed (see warnings)")
skip(reason="async def function and no async plugin installed (see warnings)")
@hookimpl(trylast=True)

View File

@@ -71,7 +71,8 @@ class Scope(Enum):
from _pytest.outcomes import fail
try:
return Scope(scope_name)
# Holding this reference is necessary for mypy at the moment.
scope = Scope(scope_name)
except ValueError:
fail(
"{} {}got an unexpected scope value '{}'".format(
@@ -79,6 +80,7 @@ class Scope(Enum):
),
pytrace=False,
)
return scope
_ALL_SCOPES = list(Scope)