introduce warning system with this API:

- node.warn() for a node-specific warning
- config.warn() for a global non-node specific warning

Each warning is accompanied by a "warning number" so that we can later
introduce mechanisms for surpressing them.

Each warning will trigger a call to pytest_report_warn(number, node, message)
which is by default implemented by the TerminalReporter which introduces
a new option "-rw" to show details about warnings.
This commit is contained in:
holger krekel 2014-03-11 22:10:17 +01:00
parent b96559149c
commit 1b387bea62
5 changed files with 90 additions and 1 deletions

View File

@ -613,6 +613,11 @@ class Config(object):
self.hook.pytest_unconfigure(config=self) self.hook.pytest_unconfigure(config=self)
self.pluginmanager.ensure_shutdown() self.pluginmanager.ensure_shutdown()
def warn(self, code, message):
""" generate a warning for this test session. """
self.hook.pytest_logwarning(code=code, message=message,
fslocation=None, nodeid=None)
def pytest_cmdline_parse(self, pluginmanager, args): def pytest_cmdline_parse(self, pluginmanager, args):
assert self == pluginmanager.config, (self, pluginmanager.config) assert self == pluginmanager.config, (self, pluginmanager.config)
self.parse(args) self.parse(args)

View File

@ -227,6 +227,11 @@ pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter): def pytest_terminal_summary(terminalreporter):
""" add additional section in terminal summary reporting. """ """ add additional section in terminal summary reporting. """
def pytest_logwarning(message, code, nodeid, fslocation):
""" process a warning specified by a message, a code string,
a nodeid and fslocation (both of which may be None
if the warning is not tied to a partilar node/location)."""
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# doctest hooks # doctest hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------

View File

@ -263,6 +263,20 @@ class Node(object):
return "<%s %r>" %(self.__class__.__name__, return "<%s %r>" %(self.__class__.__name__,
getattr(self, 'name', None)) getattr(self, 'name', None))
def warn(self, code, message):
""" generate a warning with the given code and message for this
item. """
assert isinstance(code, str)
fslocation = getattr(self, "location", None)
if fslocation is None:
fslocation = getattr(self, "fspath", None)
else:
fslocation = "%s:%s" % fslocation[:2]
self.ihook.pytest_logwarning(code=code, message=message,
nodeid=self.nodeid,
fslocation=fslocation)
# methods for ordering nodes # methods for ordering nodes
@property @property
def nodeid(self): def nodeid(self):

View File

@ -75,6 +75,14 @@ def pytest_report_teststatus(report):
letter = "f" letter = "f"
return report.outcome, letter, report.outcome.upper() return report.outcome, letter, report.outcome.upper()
class WarningReport:
def __init__(self, code, message, nodeid=None, fslocation=None):
self.code = code
self.message = message
self.nodeid = nodeid
self.fslocation = fslocation
class TerminalReporter: class TerminalReporter:
def __init__(self, config, file=None): def __init__(self, config, file=None):
self.config = config self.config = config
@ -151,6 +159,12 @@ class TerminalReporter:
self.write_line("INTERNALERROR> " + line) self.write_line("INTERNALERROR> " + line)
return 1 return 1
def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
warning = WarningReport(code=code, fslocation=fslocation,
message=message, nodeid=nodeid)
warnings.append(warning)
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,)
@ -335,6 +349,7 @@ class TerminalReporter:
self.summary_errors() self.summary_errors()
self.summary_failures() self.summary_failures()
self.summary_hints() self.summary_hints()
self.summary_warnings()
self.config.hook.pytest_terminal_summary(terminalreporter=self) self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2: if exitstatus == 2:
self._report_keyboardinterrupt() self._report_keyboardinterrupt()
@ -405,6 +420,16 @@ class TerminalReporter:
for hint in self.config.pluginmanager._hints: for hint in self.config.pluginmanager._hints:
self._tw.line("hint: %s" % hint) self._tw.line("hint: %s" % hint)
def summary_warnings(self):
if self.hasopt("w"):
warnings = self.stats.get("warnings")
if not warnings:
return
self.write_sep("=", "warning summary")
for w in warnings:
self._tw.line("W%s %s %s" % (w.code,
w.fslocation, w.message))
def summary_failures(self): def summary_failures(self):
if self.config.option.tbstyle != "no": if self.config.option.tbstyle != "no":
reports = self.getreports('failed') reports = self.getreports('failed')
@ -449,7 +474,8 @@ class TerminalReporter:
def summary_stats(self): def summary_stats(self):
session_duration = py.std.time.time() - self._sessionstarttime session_duration = py.std.time.time() - self._sessionstarttime
keys = "failed passed skipped deselected xfailed xpassed".split() keys = ("failed passed skipped deselected "
"xfailed xpassed warnings").split()
for key in self.stats.keys(): for key in self.stats.keys():
if key not in keys: if key not in keys:
keys.append(key) keys.append(key)

View File

@ -360,4 +360,43 @@ def test_load_initial_conftest_last_ordering(testdir):
assert l[-2] == m.pytest_load_initial_conftests assert l[-2] == m.pytest_load_initial_conftests
assert l[-3].__module__ == "_pytest.config" assert l[-3].__module__ == "_pytest.config"
class TestWarning:
def test_warn_config(self, testdir):
testdir.makeconftest("""
l = []
def pytest_configure(config):
config.warn("C1", "hello")
def pytest_logwarning(code, message):
assert code == "C1"
assert message == "hello"
l.append(1)
""")
testdir.makepyfile("""
def test_proper(pytestconfig):
import conftest
assert conftest.l == [1]
""")
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
def test_warn_on_test_item_from_request(self, testdir):
testdir.makepyfile("""
import pytest
@pytest.fixture
def fix(request):
request.node.warn("T1", "hello")
def test_hello(fix):
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
*1 warning*
""")
assert "hello" not in result.stdout.str()
result = testdir.runpytest("-rw")
result.stdout.fnmatch_lines("""
===*warning summary*===
*WT1*test_warn_on_test_item*:5*hello*
*1 warning*
""")