Improve warning representation in terminal plugin and fix tests
This commit is contained in:
parent
be5db6fa22
commit
78194093af
|
@ -910,11 +910,11 @@ class Config(object):
|
|||
fin = self._cleanup.pop()
|
||||
fin()
|
||||
|
||||
def warn(self, code, message, fslocation=None):
|
||||
def warn(self, code, message, fslocation=None, nodeid=None):
|
||||
""" generate a warning for this test session. """
|
||||
self.hook.pytest_logwarning.call_historic(kwargs=dict(
|
||||
code=code, message=message,
|
||||
fslocation=fslocation, nodeid=None))
|
||||
fslocation=fslocation, nodeid=nodeid))
|
||||
|
||||
def get_terminal_writer(self):
|
||||
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
||||
|
|
|
@ -1080,7 +1080,7 @@ class FixtureManager(object):
|
|||
continue
|
||||
marker = defaultfuncargprefixmarker
|
||||
from _pytest import deprecated
|
||||
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name))
|
||||
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid)
|
||||
name = name[len(self._argprefix):]
|
||||
elif not isinstance(marker, FixtureFunctionMarker):
|
||||
# magic globals with __getattr__ might have got us a wrong
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
This is a good source for looking at the various reporting hooks.
|
||||
"""
|
||||
import operator
|
||||
import itertools
|
||||
|
||||
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
|
||||
|
@ -83,13 +82,40 @@ def pytest_report_teststatus(report):
|
|||
letter = "f"
|
||||
return report.outcome, letter, report.outcome.upper()
|
||||
|
||||
|
||||
class WarningReport(object):
|
||||
"""
|
||||
Simple structure to hold warnings information captured by ``pytest_logwarning``.
|
||||
"""
|
||||
def __init__(self, code, message, nodeid=None, fslocation=None):
|
||||
"""
|
||||
:param code: unused
|
||||
: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):
|
||||
"""
|
||||
Returns the more user-friendly information about the location
|
||||
of a warning, or None.
|
||||
"""
|
||||
if self.nodeid:
|
||||
return self.nodeid
|
||||
if self.fslocation:
|
||||
if isinstance(self.fslocation, tuple) and len(self.fslocation) == 2:
|
||||
filename, linenum = self.fslocation
|
||||
relpath = py.path.local(filename).relto(config.invocation_dir)
|
||||
return '%s:%d' % (relpath, linenum)
|
||||
else:
|
||||
return str(self.fslocation)
|
||||
return None
|
||||
|
||||
|
||||
class TerminalReporter(object):
|
||||
def __init__(self, config, file=None):
|
||||
|
@ -169,8 +195,6 @@ class TerminalReporter(object):
|
|||
|
||||
def pytest_logwarning(self, code, fslocation, message, nodeid):
|
||||
warnings = self.stats.setdefault("warnings", [])
|
||||
if isinstance(fslocation, tuple):
|
||||
fslocation = "%s:%d" % fslocation
|
||||
warning = WarningReport(code=code, fslocation=fslocation,
|
||||
message=message, nodeid=nodeid)
|
||||
warnings.append(warning)
|
||||
|
@ -444,12 +468,17 @@ class TerminalReporter(object):
|
|||
all_warnings = self.stats.get("warnings")
|
||||
if not all_warnings:
|
||||
return
|
||||
|
||||
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
|
||||
|
||||
self.write_sep("=", "warnings summary", yellow=True, bold=False)
|
||||
grouped = itertools.groupby(all_warnings, key=operator.attrgetter('nodeid'))
|
||||
for nodeid, warnings in grouped:
|
||||
self._tw.line(str(nodeid))
|
||||
for location, warnings in grouped:
|
||||
self._tw.line(str(location) or '<undetermined location>')
|
||||
for w in warnings:
|
||||
self._tw.line(w.message)
|
||||
lines = w.message.splitlines()
|
||||
indented = '\n'.join(' ' + x for x in lines)
|
||||
self._tw.line(indented)
|
||||
self._tw.line()
|
||||
self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
|
||||
|
||||
def summary_passes(self):
|
||||
|
|
|
@ -26,7 +26,7 @@ def test_funcarg_prefix_deprecation(testdir):
|
|||
""")
|
||||
result = testdir.runpytest('-ra')
|
||||
result.stdout.fnmatch_lines([
|
||||
('pytest_funcarg__value: '
|
||||
('*pytest_funcarg__value: '
|
||||
'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
||||
'and scheduled to be removed in pytest 4.0. '
|
||||
'Please remove the prefix and use the @pytest.fixture decorator instead.'),
|
||||
|
|
|
@ -113,9 +113,9 @@ class TestClass(object):
|
|||
pass
|
||||
""")
|
||||
result = testdir.runpytest("-rw")
|
||||
result.stdout.fnmatch_lines_random("""
|
||||
WC1*test_class_with_init_warning.py*__init__*
|
||||
""")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*cannot collect test class 'TestClass1' because it has a __init__ constructor",
|
||||
])
|
||||
|
||||
def test_class_subclassobject(self, testdir):
|
||||
testdir.getmodulecol("""
|
||||
|
@ -1241,8 +1241,8 @@ def test_dont_collect_non_function_callable(testdir):
|
|||
result = testdir.runpytest('-rw')
|
||||
result.stdout.fnmatch_lines([
|
||||
'*collected 1 item*',
|
||||
'WC2 *',
|
||||
'*1 passed, 1 pytest-warnings in *',
|
||||
"*cannot collect 'test_a' because it is not a function*",
|
||||
'*1 passed, 1 warnings in *',
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -975,7 +975,10 @@ def test_assert_tuple_warning(testdir):
|
|||
assert(False, 'you shall not pass')
|
||||
""")
|
||||
result = testdir.runpytest('-rw')
|
||||
result.stdout.fnmatch_lines('*test_assert_tuple_warning.py:2 assertion is always true*')
|
||||
result.stdout.fnmatch_lines([
|
||||
'*test_assert_tuple_warning.py:2',
|
||||
'*assertion is always true*',
|
||||
])
|
||||
|
||||
def test_assert_indirect_tuple_no_warning(testdir):
|
||||
testdir.makepyfile("""
|
||||
|
|
|
@ -638,7 +638,7 @@ class TestWarning(object):
|
|||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("""
|
||||
===*warnings summary*===
|
||||
*test_warn_on_test_item_from_request::test_hello*
|
||||
*test_warn_on_test_item_from_request.py::test_hello*
|
||||
*hello*
|
||||
""")
|
||||
|
||||
|
|
|
@ -854,7 +854,7 @@ def test_record_property(testdir):
|
|||
pnodes[1].assert_attr(name="foo", value="<1")
|
||||
result.stdout.fnmatch_lines([
|
||||
'test_record_property.py::test_record',
|
||||
'record_xml_property*experimental*',
|
||||
'*record_xml_property*experimental*',
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -39,10 +39,10 @@ def test_normal_flow(testdir, pyfile_with_warnings):
|
|||
'*test_normal_flow.py::test_func',
|
||||
|
||||
'*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation',
|
||||
' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))',
|
||||
'* warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))',
|
||||
|
||||
'*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated',
|
||||
' warnings.warn(DeprecationWarning("functionality is deprecated"))',
|
||||
'* warnings.warn(DeprecationWarning("functionality is deprecated"))',
|
||||
'* 1 passed, 2 warnings*',
|
||||
])
|
||||
assert result.stdout.str().count('test_normal_flow.py::test_func') == 1
|
||||
|
@ -67,10 +67,10 @@ def test_setup_teardown_warnings(testdir, pyfile_with_warnings):
|
|||
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||
|
||||
'*test_setup_teardown_warnings.py:6: UserWarning: warning during setup',
|
||||
' warnings.warn(UserWarning("warning during setup"))',
|
||||
'*warnings.warn(UserWarning("warning during setup"))',
|
||||
|
||||
'*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown',
|
||||
' warnings.warn(UserWarning("warning during teardown"))',
|
||||
'*warnings.warn(UserWarning("warning during teardown"))',
|
||||
'* 1 passed, 2 warnings*',
|
||||
])
|
||||
|
||||
|
|
Loading…
Reference in New Issue