Make Node.warn support two forms, new and deprecated

As suggested during review, it now accepts two forms:

Node.warn(warning_instance)  (recommended)

Node.warn(code, message)  (deprecated)
This commit is contained in:
Bruno Oliveira 2018-09-04 15:07:52 -03:00
parent 5ef51262f7
commit 47bf58d69e
10 changed files with 62 additions and 22 deletions

View File

@ -1,5 +1,12 @@
The functions ``Node.warn`` and ``Config.warn`` have been deprecated. Instead of ``Node.warn`` users should now use ``Config.warn`` has been deprecated, it should be replaced by calls to the standard ``warnings.warn``.
``Node.std_warn``, while ``Config.warn`` should be replaced by the standard ``warnings.warn``.
``Node.warn`` now supports two signatures:
* ``node.warn(PytestWarning("some message"))``: is now the recommended way to call this function. The warning
instance must be a ``PytestWarning`` or subclass instance.
* ``node.warn("CI", "some message")``: this code/message form is now deprecated and should be converted to
the warning instance form above.
``RemovedInPytest4Warning`` and ``PytestExperimentalApiWarning`` are now part of the public API and should be accessed ``RemovedInPytest4Warning`` and ``PytestExperimentalApiWarning`` are now part of the public API and should be accessed
using ``pytest.RemovedInPytest4Warning`` and ``pytest.PytestExperimentalApiWarning``. using ``pytest.RemovedInPytest4Warning`` and ``pytest.PytestExperimentalApiWarning``.

View File

@ -54,7 +54,7 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
) )
NODE_WARN = RemovedInPytest4Warning( NODE_WARN = RemovedInPytest4Warning(
"Node.warn has been deprecated, use Node.std_warn instead" "Node.warn(code, message) form has been deprecated, use Node.warn(warning_instance) instead."
) )
RECORD_XML_PROPERTY = RemovedInPytest4Warning( RECORD_XML_PROPERTY = RemovedInPytest4Warning(

View File

@ -262,7 +262,7 @@ def record_xml_property(record_property, request):
"""(Deprecated) use record_property.""" """(Deprecated) use record_property."""
from _pytest import deprecated from _pytest import deprecated
request.node.std_warn(deprecated.RECORD_XML_PROPERTY) request.node.warn(deprecated.RECORD_XML_PROPERTY)
return record_property return record_property
@ -275,9 +275,7 @@ def record_xml_attribute(request):
""" """
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
request.node.std_warn( request.node.warn(PytestWarning("record_xml_attribute is an experimental feature"))
PytestWarning("record_xml_attribute is an experimental feature")
)
xml = getattr(request.config, "_xml", None) xml = getattr(request.config, "_xml", None)
if xml is not None: if xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid) node_reporter = xml.node_reporter(request.node.nodeid)

View File

@ -95,7 +95,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
argval = (argval,) argval = (argval,)
if newmarks and belonging_definition is not None: if newmarks and belonging_definition is not None:
belonging_definition.std_warn(MARK_PARAMETERSET_UNPACKING) belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None) return cls(argval, marks=newmarks, id=None)

View File

@ -136,7 +136,42 @@ class Node(object):
def __repr__(self): def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None)) return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None))
def warn(self, code, message): def warn(self, code_or_warning, message=None):
"""Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed.
This can be called in two forms:
**Warning instance**
This was introduced in pytest 3.8 and uses the standard warning mechanism to issue warnings.
.. code-block:: python
node.warn(PytestWarning("some message"))
The warning instance must be a subclass of :class:`pytest.PytestWarning`.
**code/message (deprecated)**
This form was used in pytest prior to 3.8 and is considered deprecated. Using this form will emit another
warning about the deprecation:
.. code-block:: python
node.warn("CI", "some message")
:param Union[Warning,str] code_or_warning: warning instance or warning code (legacy).
:param Union[str,None] message: message to display when called in the legacy form.
:return:
"""
if message is None:
self._std_warn(code_or_warning)
else:
self._legacy_warn(code_or_warning, message)
def _legacy_warn(self, code, message):
""" """
.. deprecated:: 3.8 .. deprecated:: 3.8
@ -146,7 +181,7 @@ class Node(object):
""" """
from _pytest.deprecated import NODE_WARN from _pytest.deprecated import NODE_WARN
self.std_warn(NODE_WARN) self._std_warn(NODE_WARN)
assert isinstance(code, str) assert isinstance(code, str)
fslocation = get_fslocation_from_item(self) fslocation = get_fslocation_from_item(self)
@ -156,7 +191,7 @@ class Node(object):
) )
) )
def std_warn(self, warning): def _std_warn(self, warning):
"""Issue a warning for this item. """Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed Warnings will be displayed after the test session, unless explicitly suppressed
@ -175,8 +210,8 @@ class Node(object):
) )
path, lineno = get_fslocation_from_item(self) path, lineno = get_fslocation_from_item(self)
warnings.warn_explicit( warnings.warn_explicit(
six.text_type(warning), warning,
type(warning), category=None,
filename=str(path), filename=str(path),
lineno=lineno + 1 if lineno is not None else None, lineno=lineno + 1 if lineno is not None else None,
) )

View File

@ -126,7 +126,7 @@ class LsofFdLeakChecker(object):
error.append(error[0]) error.append(error[0])
error.append("*** function %s:%s: %s " % item.location) error.append("*** function %s:%s: %s " % item.location)
error.append("See issue #2366") error.append("See issue #2366")
item.std_warn(pytest.PytestWarning("\n".join(error))) item.warn(pytest.PytestWarning("\n".join(error)))
# XXX copied from execnet's conftest.py - needs to be merged # XXX copied from execnet's conftest.py - needs to be merged

View File

@ -659,7 +659,7 @@ class Class(PyCollector):
if not safe_getattr(self.obj, "__test__", True): if not safe_getattr(self.obj, "__test__", True):
return [] return []
if hasinit(self.obj): if hasinit(self.obj):
self.std_warn( self.warn(
PytestWarning( PytestWarning(
"cannot collect test class %r because it has a " "cannot collect test class %r because it has a "
"__init__ constructor" % self.obj.__name__ "__init__ constructor" % self.obj.__name__
@ -667,7 +667,7 @@ class Class(PyCollector):
) )
return [] return []
elif hasnew(self.obj): elif hasnew(self.obj):
self.std_warn( self.warn(
PytestWarning( PytestWarning(
"cannot collect test class %r because it has a " "cannot collect test class %r because it has a "
"__new__ constructor" % self.obj.__name__ "__new__ constructor" % self.obj.__name__
@ -799,7 +799,7 @@ class Generator(FunctionMixin, PyCollector):
) )
seen[name] = True seen[name] = True
values.append(self.Function(name, self, args=args, callobj=call)) values.append(self.Function(name, self, args=args, callobj=call))
self.std_warn(deprecated.YIELD_TESTS) self.warn(deprecated.YIELD_TESTS)
return values return values
def getcallargs(self, obj): def getcallargs(self, obj):
@ -1106,7 +1106,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
invocation through the ``request.param`` attribute. invocation through the ``request.param`` attribute.
""" """
if self.config: if self.config:
self.definition.std_warn(deprecated.METAFUNC_ADD_CALL) self.definition.warn(deprecated.METAFUNC_ADD_CALL)
assert funcargs is None or isinstance(funcargs, dict) assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None: if funcargs is not None:
@ -1170,7 +1170,7 @@ def _idval(val, argname, idx, idfn, item, config=None):
) )
msg += " {}: {}\n".format(type(e).__name__, e) msg += " {}: {}\n".format(type(e).__name__, e)
msg += "This warning will be an error error in pytest-4.0." msg += "This warning will be an error error in pytest-4.0."
item.std_warn(RemovedInPytest4Warning(msg)) item.warn(RemovedInPytest4Warning(msg))
if s: if s:
return ascii_escaped(s) return ascii_escaped(s)

View File

@ -831,7 +831,7 @@ class TestLegacyWarning(object):
===*warnings summary*=== ===*warnings summary*===
*test_warn_on_test_item_from_request.py::test_hello* *test_warn_on_test_item_from_request.py::test_hello*
*hello* *hello*
*test_warn_on_test_item_from_request.py:7:*Node.warn has been deprecated, use Node.std_warn instead* *test_warn_on_test_item_from_request.py:7:*Node.warn(code, message) form has been deprecated*
""" """
) )

View File

@ -1042,7 +1042,7 @@ class TestKeywordSelection(object):
@pytest.mark.filterwarnings("ignore") @pytest.mark.filterwarnings("ignore")
def test_parameterset_extractfrom(argval, expected): def test_parameterset_extractfrom(argval, expected):
class DummyItem: class DummyItem:
def std_warn(self, warning): def warn(self, warning):
pass pass
extracted = ParameterSet.extract_from(argval, belonging_definition=DummyItem()) extracted = ParameterSet.extract_from(argval, belonging_definition=DummyItem())

View File

@ -29,4 +29,4 @@ def test_std_warn_not_pytestwarning(testdir):
""" """
) )
with pytest.raises(ValueError, match=".*instance of PytestWarning.*"): with pytest.raises(ValueError, match=".*instance of PytestWarning.*"):
items[0].std_warn(UserWarning("some warning")) items[0].warn(UserWarning("some warning"))