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``. | ||||||
|  | 
 | ||||||
|  |     :ivar str message: user friendly message about the warning | ||||||
|  |     :ivar str|None nodeid: node id that generated the warning (see ``get_location``). | ||||||
|  |     :ivar tuple|py.path.local fslocation: | ||||||
|  |         file system location of the source of the warning (see ``get_location``). | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, code, message, nodeid=None, fslocation=None): |     message = attr.ib() | ||||||
|         """ |     nodeid = attr.ib(default=None) | ||||||
|         :param code: unused |     fslocation = attr.ib(default=None) | ||||||
|         :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``). |  | ||||||
|         """ |  | ||||||
|         self.code = code |  | ||||||
|         self.message = message |  | ||||||
|         self.nodeid = nodeid |  | ||||||
|         self.fslocation = fslocation |  | ||||||
| 
 | 
 | ||||||
|     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