From 3fa261564bfe95347a5151f38e560e37ccaeec62 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 31 Aug 2015 21:54:12 +0200 Subject: [PATCH 1/2] replay initial warnings when terminalreporter is loaded --- _pytest/config.py | 42 ++++++++++++++++++++++++++++++++---------- _pytest/helpconfig.py | 11 +++++------ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index aea6a98e7..e73452b25 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -136,6 +136,7 @@ class PytestPluginManager(PluginManager): def __init__(self): super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") self._warnings = [] + self._pushwarning = self._warnings.append self._conftest_plugins = set() # state related to local conftest plugins @@ -166,7 +167,7 @@ class PytestPluginManager(PluginManager): fslocation=py.code.getfslineno(sys._getframe(1)), message="use pluginmanager.add_hookspecs instead of " "deprecated addhooks() method.") - self._warnings.append(warning) + self._warn(warning) return self.add_hookspecs(module_or_class) def parse_hookimpl_opts(self, plugin, name): @@ -196,9 +197,10 @@ class PytestPluginManager(PluginManager): fslineno = py.code.getfslineno(hookmethod.function) warning = dict(code="I1", fslocation=fslineno, + nodeid=None, message="%r hook uses deprecated __multicall__ " "argument" % (hook.name)) - self._warnings.append(warning) + self._warn(warning) def register(self, plugin, name=None): ret = super(PytestPluginManager, self).register(plugin, name) @@ -224,11 +226,31 @@ class PytestPluginManager(PluginManager): config.addinivalue_line("markers", "trylast: mark a hook implementation function such that the " "plugin machinery will try to call it last/as late as possible.") - for warning in self._warnings: - if isinstance(warning, dict): - config.warn(**warning) - else: - config.warn(code="I1", message=warning) + + def pytest_plugin_registered(self, plugin): + if self.get_name(plugin) == 'terminalreporter': + warnings = self._warnings + self._warnings = None + config = self.get_plugin('pytestconfig') + + def pushwarning(args): + assert args.pop('nodeid', None) is None + config.warn(**args) + + self._pushwarning = pushwarning + for warning in warnings: + config.hook.pytest_logwarning(**warning) + + def _warn(self, message): + if isinstance(message, dict): + self._pushwarning(message) + else: + self._pushwarning({ + 'code': 'I1', + 'message': message, + 'fslocation': None, + 'nodeid': None, + }) # # internal API for local conftest plugin handling @@ -381,7 +403,7 @@ class PytestPluginManager(PluginManager): import pytest if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception): raise - self._warnings.append("skipped plugin %r: %s" %((modname, e.msg))) + self._warn("skipped plugin %r: %s" %((modname, e.msg))) else: mod = sys.modules[importspec] self.register(mod, modname) @@ -793,6 +815,7 @@ class Config(object): self._inicache = {} self._opt2dest = {} self._cleanup = [] + self._warn = self.pluginmanager._warn self.pluginmanager.register(self, "pytestconfig") self._configured = False def do_setns(dic): @@ -914,8 +937,7 @@ class Config(object): if ns.help or ns.version: # we don't want to prevent --help/--version to work # so just let is pass and print a warning at the end - self.pluginmanager._warnings.append( - "could not load initial conftests (%s)\n" % e.path) + self._warn("could not load initial conftests (%s)\n" % e.path) else: raise diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index af35e241e..5ede8b371 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -62,8 +62,8 @@ def pytest_cmdline_main(config): return 0 def showhelp(config): - import _pytest.config - tw = _pytest.config.create_terminal_writer(config) + reporter = config.pluginmanager.get_plugin('terminalreporter') + tw = reporter._tw tw.write(config._parser.optparser.format_help()) tw.line() tw.line() @@ -86,8 +86,9 @@ def showhelp(config): tw.line("to see available fixtures type: py.test --fixtures") tw.line("(shown according to specified file_or_dir or current dir " "if not specified)") - for warning in config.pluginmanager._warnings: - tw.line("warning: %s" % (warning,), red=True) + tw.line(str(reporter.stats)) + for warningreport in reporter.stats.get('warnings', []): + tw.line("warning : " + warningreport.message, red=True) return @@ -126,5 +127,3 @@ def pytest_report_header(config): r = repr(plugin) lines.append(" %-20s: %s" %(name, r)) return lines - - From 76f09885517abaf4fc3741827b439a434a61136d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 2 Sep 2015 18:49:49 +0200 Subject: [PATCH 2/2] turn the pytest_logwarning hook historic --- _pytest/config.py | 40 ++++++++++++----------------------- _pytest/hookspec.py | 2 ++ _pytest/main.py | 8 +++---- testing/test_pluginmanager.py | 13 +++++++++--- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index e73452b25..c9af4d030 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -16,6 +16,8 @@ hookspec = HookspecMarker("pytest") # pytest startup # + + class ConftestImportFailure(Exception): def __init__(self, path, excinfo): Exception.__init__(self, path, excinfo) @@ -135,8 +137,6 @@ class PytestPluginManager(PluginManager): """ def __init__(self): super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") - self._warnings = [] - self._pushwarning = self._warnings.append self._conftest_plugins = set() # state related to local conftest plugins @@ -227,30 +227,14 @@ class PytestPluginManager(PluginManager): "trylast: mark a hook implementation function such that the " "plugin machinery will try to call it last/as late as possible.") - def pytest_plugin_registered(self, plugin): - if self.get_name(plugin) == 'terminalreporter': - warnings = self._warnings - self._warnings = None - config = self.get_plugin('pytestconfig') - - def pushwarning(args): - assert args.pop('nodeid', None) is None - config.warn(**args) - - self._pushwarning = pushwarning - for warning in warnings: - config.hook.pytest_logwarning(**warning) - def _warn(self, message): - if isinstance(message, dict): - self._pushwarning(message) - else: - self._pushwarning({ - 'code': 'I1', - 'message': message, - 'fslocation': None, - 'nodeid': None, - }) + kwargs = message if isinstance(message, dict) else { + 'code': 'I1', + 'message': message, + 'fslocation': None, + 'nodeid': None, + } + self.hook.pytest_logwarning.call_historic(kwargs=kwargs) # # internal API for local conftest plugin handling @@ -726,6 +710,7 @@ class MyOptionParser(argparse.ArgumentParser): getattr(args, FILE_OR_DIR).extend(argv) return args + class DropShorterLongHelpFormatter(argparse.HelpFormatter): """shorten help for long options that differ only in extra hyphens @@ -845,8 +830,9 @@ class Config(object): def warn(self, code, message, fslocation=None): """ generate a warning for this test session. """ - self.hook.pytest_logwarning(code=code, message=message, - fslocation=fslocation, nodeid=None) + self.hook.pytest_logwarning.call_historic(kwargs=dict( + code=code, message=message, + fslocation=fslocation, nodeid=None)) def get_terminal_writer(self): return self.pluginmanager.get_plugin("terminalreporter")._tw diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index c75f563f4..a3a481d6a 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -249,6 +249,8 @@ def pytest_report_teststatus(report): def pytest_terminal_summary(terminalreporter): """ add additional section in terminal summary reporting. """ + +@hookspec(historic=True) 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 diff --git a/_pytest/main.py b/_pytest/main.py index 4f3d2625f..6b2ccb6b7 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -279,9 +279,9 @@ class Node(object): else: fslocation = "%s:%s" % fslocation[:2] - self.ihook.pytest_logwarning(code=code, message=message, - nodeid=self.nodeid, - fslocation=fslocation) + self.ihook.pytest_logwarning.call_historic(kwargs=dict( + code=code, message=message, + nodeid=self.nodeid, fslocation=fslocation)) # methods for ordering nodes @property @@ -742,5 +742,3 @@ class Session(FSCollector): for x in self.genitems(subnode): yield x node.ihook.pytest_collectreport(report=rep) - - diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 92afba9bc..c49938100 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -129,14 +129,21 @@ class TestPytestPluginInteractions: undo() def test_warn_on_deprecated_multicall(self, pytestpm): + warnings = [] + + class get_warnings: + def pytest_logwarning(self, message): + warnings.append(message) + class Plugin: def pytest_configure(self, __multicall__): pass - before = list(pytestpm._warnings) + pytestpm.register(get_warnings()) + before = list(warnings) pytestpm.register(Plugin()) - assert len(pytestpm._warnings) == len(before) + 1 - assert "deprecated" in pytestpm._warnings[-1]["message"] + assert len(warnings) == len(before) + 1 + assert "deprecated" in warnings[-1] def test_namespace_has_default_and_env_plugins(testdir):