Make terminal capture pytest_warning_capture
pytest_logwarning is no longer emitted by the warnings plugin, only ever emitted from .warn() functions in config and item
This commit is contained in:
parent
ffd47ceefc
commit
3fcc4cdbd5
|
@ -536,13 +536,13 @@ def pytest_logwarning(message, code, nodeid, fslocation):
|
||||||
|
|
||||||
|
|
||||||
@hookspec(historic=True)
|
@hookspec(historic=True)
|
||||||
def pytest_warning_captured(warning, when, item):
|
def pytest_warning_captured(warning_message, when, item):
|
||||||
"""
|
"""
|
||||||
Process a warning captured by the internal pytest plugin.
|
Process a warning captured by the internal pytest plugin.
|
||||||
|
|
||||||
:param warnings.WarningMessage warning:
|
:param warnings.WarningMessage warning_message:
|
||||||
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
|
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
|
||||||
the same attributes as :py:func:`warnings.showwarning`.
|
the same attributes as the parameters of :py:func:`warnings.showwarning`.
|
||||||
|
|
||||||
:param str when:
|
:param str when:
|
||||||
Indicates when the warning was captured. Possible values:
|
Indicates when the warning was captured. Possible values:
|
||||||
|
|
|
@ -138,9 +138,7 @@ class Node(object):
|
||||||
""" generate a warning with the given code and message for this
|
""" generate a warning with the given code and message for this
|
||||||
item. """
|
item. """
|
||||||
assert isinstance(code, str)
|
assert isinstance(code, str)
|
||||||
fslocation = getattr(self, "location", None)
|
fslocation = get_fslocation_from_item(self)
|
||||||
if fslocation is None:
|
|
||||||
fslocation = getattr(self, "fspath", None)
|
|
||||||
self.ihook.pytest_logwarning.call_historic(
|
self.ihook.pytest_logwarning.call_historic(
|
||||||
kwargs=dict(
|
kwargs=dict(
|
||||||
code=code, message=message, nodeid=self.nodeid, fslocation=fslocation
|
code=code, message=message, nodeid=self.nodeid, fslocation=fslocation
|
||||||
|
@ -310,6 +308,18 @@ class Node(object):
|
||||||
repr_failure = _repr_failure_py
|
repr_failure = _repr_failure_py
|
||||||
|
|
||||||
|
|
||||||
|
def get_fslocation_from_item(item):
|
||||||
|
"""Tries to extract the actual location from an item, depending on available attributes:
|
||||||
|
|
||||||
|
* "fslocation": a pair (path, lineno)
|
||||||
|
* "fspath": just a path
|
||||||
|
"""
|
||||||
|
fslocation = getattr(item, "location", None)
|
||||||
|
if fslocation is None:
|
||||||
|
fslocation = getattr(item, "fspath", None)
|
||||||
|
return fslocation
|
||||||
|
|
||||||
|
|
||||||
class Collector(Node):
|
class Collector(Node):
|
||||||
""" Collector instances create children through collect()
|
""" Collector instances create children through collect()
|
||||||
and thus iteratively build a tree.
|
and thus iteratively build a tree.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import platform
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import attr
|
||||||
import pluggy
|
import pluggy
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
|
@ -184,23 +185,20 @@ def pytest_report_teststatus(report):
|
||||||
return report.outcome, letter, report.outcome.upper()
|
return report.outcome, letter, report.outcome.upper()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class WarningReport(object):
|
class WarningReport(object):
|
||||||
"""
|
"""
|
||||||
Simple structure to hold warnings information captured by ``pytest_logwarning``.
|
Simple structure to hold warnings information captured by ``pytest_logwarning``.
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, code, message, nodeid=None, fslocation=None):
|
:ivar str message: user friendly message about the warning
|
||||||
"""
|
:ivar str|None nodeid: node id that generated the warning (see ``get_location``).
|
||||||
:param code: unused
|
:ivar tuple|py.path.local fslocation:
|
||||||
:param str message: user friendly message about the warning
|
|
||||||
:param str|None nodeid: node id that generated the warning (see ``get_location``).
|
|
||||||
:param tuple|py.path.local fslocation:
|
|
||||||
file system location of the source of the warning (see ``get_location``).
|
file system location of the source of the warning (see ``get_location``).
|
||||||
"""
|
"""
|
||||||
self.code = code
|
|
||||||
self.message = message
|
message = attr.ib()
|
||||||
self.nodeid = nodeid
|
nodeid = attr.ib(default=None)
|
||||||
self.fslocation = fslocation
|
fslocation = attr.ib(default=None)
|
||||||
|
|
||||||
def get_location(self, config):
|
def get_location(self, config):
|
||||||
"""
|
"""
|
||||||
|
@ -327,13 +325,25 @@ class TerminalReporter(object):
|
||||||
self.write_line("INTERNALERROR> " + line)
|
self.write_line("INTERNALERROR> " + line)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def pytest_logwarning(self, code, fslocation, message, nodeid):
|
def pytest_logwarning(self, fslocation, message, nodeid):
|
||||||
warnings = self.stats.setdefault("warnings", [])
|
warnings = self.stats.setdefault("warnings", [])
|
||||||
warning = WarningReport(
|
warning = WarningReport(fslocation=fslocation, message=message, nodeid=nodeid)
|
||||||
code=code, fslocation=fslocation, message=message, nodeid=nodeid
|
|
||||||
)
|
|
||||||
warnings.append(warning)
|
warnings.append(warning)
|
||||||
|
|
||||||
|
def pytest_warning_captured(self, warning_message, item):
|
||||||
|
from _pytest.nodes import get_fslocation_from_item
|
||||||
|
from _pytest.warnings import warning_record_to_str
|
||||||
|
|
||||||
|
warnings = self.stats.setdefault("warnings", [])
|
||||||
|
|
||||||
|
fslocation = get_fslocation_from_item(item)
|
||||||
|
message = warning_record_to_str(warning_message)
|
||||||
|
|
||||||
|
warning_report = WarningReport(
|
||||||
|
fslocation=fslocation, message=message, nodeid=item.nodeid
|
||||||
|
)
|
||||||
|
warnings.append(warning_report)
|
||||||
|
|
||||||
def pytest_plugin_registered(self, plugin):
|
def pytest_plugin_registered(self, plugin):
|
||||||
if self.config.option.traceconfig:
|
if self.config.option.traceconfig:
|
||||||
msg = "PLUGIN registered: %s" % (plugin,)
|
msg = "PLUGIN registered: %s" % (plugin,)
|
||||||
|
|
|
@ -58,7 +58,7 @@ def pytest_configure(config):
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def deprecated_catch_warnings_for_item(item):
|
def catch_warnings_for_item(item):
|
||||||
"""
|
"""
|
||||||
catches the warnings generated during setup/call/teardown execution
|
catches the warnings generated during setup/call/teardown execution
|
||||||
of the given item and after it is done posts them as warnings to this
|
of the given item and after it is done posts them as warnings to this
|
||||||
|
@ -79,18 +79,18 @@ def deprecated_catch_warnings_for_item(item):
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
for warning in log:
|
for warning_message in log:
|
||||||
item.ihook.pytest_warning_captured.call_historic(
|
item.ihook.pytest_warning_captured.call_historic(
|
||||||
kwargs=dict(warning=warning, when="runtest", item=item)
|
kwargs=dict(warning_message=warning_message, when="runtest", item=item)
|
||||||
)
|
)
|
||||||
deprecated_emit_warning(item, warning)
|
|
||||||
|
|
||||||
|
|
||||||
def deprecated_emit_warning(item, warning):
|
def warning_record_to_str(warning_message):
|
||||||
|
"""Convert a warnings.WarningMessage to a string, taking in account a lot of unicode shenaningans in Python 2.
|
||||||
|
|
||||||
|
When Python 2 support is tropped this function can be greatly simplified.
|
||||||
"""
|
"""
|
||||||
Emits the deprecated ``pytest_logwarning`` for the given warning and item.
|
warn_msg = warning_message.message
|
||||||
"""
|
|
||||||
warn_msg = warning.message
|
|
||||||
unicode_warning = False
|
unicode_warning = False
|
||||||
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
|
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
|
||||||
new_args = []
|
new_args = []
|
||||||
|
@ -102,18 +102,22 @@ def deprecated_emit_warning(item, warning):
|
||||||
warn_msg.args = new_args
|
warn_msg.args = new_args
|
||||||
|
|
||||||
msg = warnings.formatwarning(
|
msg = warnings.formatwarning(
|
||||||
warn_msg, warning.category, warning.filename, warning.lineno, warning.line
|
warn_msg,
|
||||||
|
warning_message.category,
|
||||||
|
warning_message.filename,
|
||||||
|
warning_message.lineno,
|
||||||
|
warning_message.line,
|
||||||
)
|
)
|
||||||
item.warn("unused", msg)
|
|
||||||
if unicode_warning:
|
if unicode_warning:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Warning is using unicode non convertible to ascii, "
|
"Warning is using unicode non convertible to ascii, "
|
||||||
"converting to a safe representation:\n %s" % msg,
|
"converting to a safe representation:\n %s" % msg,
|
||||||
UnicodeWarning,
|
UnicodeWarning,
|
||||||
)
|
)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
with deprecated_catch_warnings_for_item(item):
|
with catch_warnings_for_item(item):
|
||||||
yield
|
yield
|
||||||
|
|
|
@ -310,8 +310,8 @@ def test_warning_captured_hook(testdir, pyfile_with_warnings):
|
||||||
collected = []
|
collected = []
|
||||||
|
|
||||||
class WarningCollector:
|
class WarningCollector:
|
||||||
def pytest_warning_captured(self, warning, when, item):
|
def pytest_warning_captured(self, warning_message, when, item):
|
||||||
collected.append((warning.category, when, item.name))
|
collected.append((warning_message.category, when, item.name))
|
||||||
|
|
||||||
result = testdir.runpytest(plugins=[WarningCollector()])
|
result = testdir.runpytest(plugins=[WarningCollector()])
|
||||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||||
|
|
Loading…
Reference in New Issue