Display node ids and the warnings generated by it

The rationale of using node ids is that users can copy/paste it to run a chosen test
This commit is contained in:
Bruno Oliveira 2017-03-04 20:53:42 -03:00
parent bddb922f7b
commit 272afa9422
3 changed files with 44 additions and 21 deletions

View File

@ -2,6 +2,9 @@
This is a good source for looking at the various reporting hooks. 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, \ from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest import pytest
@ -28,7 +31,7 @@ def pytest_addoption(parser):
"--disable-warnings is set") "--disable-warnings is set")
group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
dest='disable_warnings', action='store_true', dest='disable_warnings', action='store_true',
help='disable warnings summary, overrides -r w flag') help='disable warnings summary')
group._addoption('-l', '--showlocals', group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False, action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).") help="show locals in tracebacks (disabled by default).")
@ -438,16 +441,15 @@ class TerminalReporter(object):
def summary_warnings(self): def summary_warnings(self):
if self.hasopt("w"): if self.hasopt("w"):
warnings = self.stats.get("warnings") all_warnings = self.stats.get("warnings")
if not warnings: if not all_warnings:
return return
self.write_sep("=", "warnings summary") self.write_sep("=", "warnings summary", yellow=True, bold=False)
for w in warnings: grouped = itertools.groupby(all_warnings, key=operator.attrgetter('nodeid'))
msg = '' for nodeid, warnings in grouped:
if w.fslocation: self._tw.line(str(nodeid))
msg += str(w.fslocation) + ' ' for w in warnings:
msg += w.message self._tw.line(w.message)
self._tw.line(msg)
self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
def summary_passes(self): def summary_passes(self):

View File

@ -63,7 +63,7 @@ def catch_warnings_for_item(item):
msg = warnings.formatwarning( msg = warnings.formatwarning(
warning.message, warning.category, warning.message, warning.category,
warning.filename, warning.lineno, warning.line) warning.filename, warning.lineno, warning.line)
item.config.warn("unused", msg, fslocation=None) item.warn("unused", msg)
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)

View File

@ -4,27 +4,48 @@ import pytest
WARNINGS_SUMMARY_HEADER = 'warnings summary' WARNINGS_SUMMARY_HEADER = 'warnings summary'
@pytest.fixture @pytest.fixture
def pyfile_with_warnings(testdir): def pyfile_with_warnings(testdir, request):
testdir.makepyfile(''' """
import warnings Create a test file which calls a function in a module which generates warnings.
def test_func(): """
warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) testdir.syspathinsert()
warnings.warn(DeprecationWarning("functionality is deprecated")) test_name = request.function.__name__
''') module_name = test_name.lstrip('test_') + '_module'
testdir.makepyfile(**{
module_name: '''
import warnings
def foo():
warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))
warnings.warn(DeprecationWarning("functionality is deprecated"))
return 1
''',
test_name: '''
import {module_name}
def test_func():
assert {module_name}.foo() == 1
'''.format(module_name=module_name)
})
def test_normal_flow(testdir, pyfile_with_warnings): def test_normal_flow(testdir, pyfile_with_warnings):
"""
Check that the warnings section is displayed, containing test node ids followed by
all warnings generated by that test node.
"""
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'*== %s ==*' % WARNINGS_SUMMARY_HEADER, '*== %s ==*' % WARNINGS_SUMMARY_HEADER,
'*test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', '*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"))',
'*test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', '*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*', '* 1 passed, 2 warnings*',
]) ])
assert result.stdout.str().count('test_normal_flow.py::test_func') == 1
def test_setup_teardown_warnings(testdir, pyfile_with_warnings): def test_setup_teardown_warnings(testdir, pyfile_with_warnings):
@ -66,7 +87,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method):
result = testdir.runpytest(*args) result = testdir.runpytest(*args)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'E PendingDeprecationWarning: functionality is pending deprecation', 'E PendingDeprecationWarning: functionality is pending deprecation',
'test_as_errors.py:3: PendingDeprecationWarning', 'as_errors_module.py:3: PendingDeprecationWarning',
'* 1 failed in *', '* 1 failed in *',
]) ])