remove shutdown logic from PluginManager and add a add_cleanup() API
for the already existing cleanup logic of the config object. This simplifies lifecycle management as we don't keep two layers of shutdown functions and also simplifies the pluginmanager interface. also add some docstrings. --HG-- branch : plugin_no_pytest
This commit is contained in:
parent
f746c190ac
commit
715a235b45
|
@ -18,6 +18,15 @@
|
|||
from ``inline_run()`` to allow temporary modules to be reloaded.
|
||||
Thanks Eduardo Schettino.
|
||||
|
||||
- internally refactor pluginmanager API and code so that there
|
||||
is a clear distinction between a pytest-agnostic rather simple
|
||||
pluginmanager and the PytestPluginManager which adds a lot of
|
||||
behaviour, among it handling of the local conftest files.
|
||||
In terms of documented methods this is a backward compatible
|
||||
change but it might still break 3rd party plugins which relied on
|
||||
details like especially the pluginmanager.add_shutdown() API.
|
||||
Thanks Holger Krekel.
|
||||
|
||||
2.7.1.dev (compared to 2.7.0)
|
||||
-----------------------------
|
||||
|
||||
|
|
|
@ -70,12 +70,11 @@ def pytest_configure(config):
|
|||
config._assertstate = AssertionState(config, mode)
|
||||
config._assertstate.hook = hook
|
||||
config._assertstate.trace("configured with mode set to %r" % (mode,))
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
def undo():
|
||||
hook = config._assertstate.hook
|
||||
if hook is not None and hook in sys.meta_path:
|
||||
sys.meta_path.remove(hook)
|
||||
config.add_cleanup(undo)
|
||||
|
||||
|
||||
def pytest_collection(session):
|
||||
|
|
|
@ -37,13 +37,13 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
|||
pluginmanager.register(capman, "capturemanager")
|
||||
|
||||
# make sure that capturemanager is properly reset at final shutdown
|
||||
pluginmanager.add_shutdown(capman.reset_capturings)
|
||||
early_config.add_cleanup(capman.reset_capturings)
|
||||
|
||||
# make sure logging does not raise exceptions at the end
|
||||
def silence_logging_at_shutdown():
|
||||
if "logging" in sys.modules:
|
||||
sys.modules["logging"].raiseExceptions = False
|
||||
pluginmanager.add_shutdown(silence_logging_at_shutdown)
|
||||
early_config.add_cleanup(silence_logging_at_shutdown)
|
||||
|
||||
# finally trigger conftest loading but while capturing (issue93)
|
||||
capman.init_capturings()
|
||||
|
|
|
@ -77,20 +77,17 @@ def _prepareconfig(args=None, plugins=None):
|
|||
raise ValueError("not a string or argument list: %r" % (args,))
|
||||
args = shlex.split(args)
|
||||
pluginmanager = get_plugin_manager()
|
||||
try:
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
pluginmanager.register(plugin)
|
||||
return pluginmanager.hook.pytest_cmdline_parse(
|
||||
pluginmanager=pluginmanager, args=args)
|
||||
except Exception:
|
||||
pluginmanager.ensure_shutdown()
|
||||
raise
|
||||
|
||||
def exclude_pytest_names(name):
|
||||
return not name.startswith(name) or name == "pytest_plugins" or \
|
||||
name.startswith("pytest_funcarg__")
|
||||
|
||||
|
||||
class PytestPluginManager(PluginManager):
|
||||
def __init__(self):
|
||||
super(PytestPluginManager, self).__init__(prefix="pytest_",
|
||||
|
@ -723,16 +720,23 @@ class Config(object):
|
|||
if self._configured:
|
||||
call_plugin(plugin, "pytest_configure", {'config': self})
|
||||
|
||||
def do_configure(self):
|
||||
def add_cleanup(self, func):
|
||||
""" Add a function to be called when the config object gets out of
|
||||
use (usually coninciding with pytest_unconfigure)."""
|
||||
self._cleanup.append(func)
|
||||
|
||||
def _do_configure(self):
|
||||
assert not self._configured
|
||||
self._configured = True
|
||||
self.hook.pytest_configure(config=self)
|
||||
|
||||
def do_unconfigure(self):
|
||||
assert self._configured
|
||||
def _ensure_unconfigure(self):
|
||||
if self._configured:
|
||||
self._configured = False
|
||||
self.hook.pytest_unconfigure(config=self)
|
||||
self.pluginmanager.ensure_shutdown()
|
||||
while self._cleanup:
|
||||
fin = self._cleanup.pop()
|
||||
fin()
|
||||
|
||||
def warn(self, code, message):
|
||||
""" generate a warning for this test session. """
|
||||
|
@ -747,11 +751,6 @@ class Config(object):
|
|||
self.parse(args)
|
||||
return self
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
while config._cleanup:
|
||||
fin = config._cleanup.pop()
|
||||
fin()
|
||||
|
||||
def notify_exception(self, excinfo, option=None):
|
||||
if option and option.fulltrace:
|
||||
style = "long"
|
||||
|
|
|
@ -135,6 +135,21 @@ class CallOutcome:
|
|||
|
||||
|
||||
class PluginManager(object):
|
||||
""" Core Pluginmanager class which manages registration
|
||||
of plugin objects and 1:N hook calling.
|
||||
|
||||
You can register new hooks by calling ``addhooks(module_or_class)``.
|
||||
You can register plugin objects (which contain hooks) by calling
|
||||
``register(plugin)``. The Pluginmanager is initialized with a
|
||||
prefix that is searched for in the names of the dict of registered
|
||||
plugin objects. An optional excludefunc allows to blacklist names which
|
||||
are not considered as hooks despite a matching prefix.
|
||||
|
||||
For debugging purposes you can call ``set_tracing(writer)``
|
||||
which will subsequently send debug information to the specified
|
||||
write function.
|
||||
"""
|
||||
|
||||
def __init__(self, prefix, excludefunc=None):
|
||||
self._prefix = prefix
|
||||
self._excludefunc = excludefunc
|
||||
|
@ -142,10 +157,11 @@ class PluginManager(object):
|
|||
self._plugins = []
|
||||
self._plugin2hookcallers = {}
|
||||
self.trace = TagTracer().get("pluginmanage")
|
||||
self._shutdown = []
|
||||
self.hook = HookRelay(pm=self)
|
||||
|
||||
def set_tracing(self, writer):
|
||||
""" turn on tracing to the given writer method and
|
||||
return an undo function. """
|
||||
self.trace.root.setwriter(writer)
|
||||
# reconfigure HookCalling to perform tracing
|
||||
assert not hasattr(self, "_wrapping")
|
||||
|
@ -160,12 +176,7 @@ class PluginManager(object):
|
|||
trace("finish", self.name, "-->", box.result)
|
||||
trace.root.indent -= 1
|
||||
|
||||
undo = add_method_wrapper(HookCaller, _docall)
|
||||
self.add_shutdown(undo)
|
||||
|
||||
def do_configure(self, config):
|
||||
# backward compatibility
|
||||
config.do_configure()
|
||||
return add_method_wrapper(HookCaller, _docall)
|
||||
|
||||
def make_hook_caller(self, name, plugins):
|
||||
caller = getattr(self.hook, name)
|
||||
|
@ -233,16 +244,6 @@ class PluginManager(object):
|
|||
for hookcaller in hookcallers:
|
||||
hookcaller.scan_methods()
|
||||
|
||||
def add_shutdown(self, func):
|
||||
self._shutdown.append(func)
|
||||
|
||||
def ensure_shutdown(self):
|
||||
while self._shutdown:
|
||||
func = self._shutdown.pop()
|
||||
func()
|
||||
self._plugins = []
|
||||
self._name2plugin.clear()
|
||||
|
||||
def addhooks(self, module_or_class):
|
||||
isclass = int(inspect.isclass(module_or_class))
|
||||
names = []
|
||||
|
|
|
@ -28,24 +28,20 @@ def pytest_cmdline_parse():
|
|||
config = outcome.get_result()
|
||||
if config.option.debug:
|
||||
path = os.path.abspath("pytestdebug.log")
|
||||
f = open(path, 'w')
|
||||
config._debugfile = f
|
||||
f.write("versions pytest-%s, py-%s, "
|
||||
debugfile = open(path, 'w')
|
||||
debugfile.write("versions pytest-%s, py-%s, "
|
||||
"python-%s\ncwd=%s\nargs=%s\n\n" %(
|
||||
pytest.__version__, py.__version__,
|
||||
".".join(map(str, sys.version_info)),
|
||||
os.getcwd(), config._origargs))
|
||||
config.pluginmanager.set_tracing(f.write)
|
||||
config.pluginmanager.set_tracing(debugfile.write)
|
||||
sys.stderr.write("writing pytestdebug information to %s\n" % path)
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_unconfigure(config):
|
||||
if hasattr(config, '_debugfile'):
|
||||
config._debugfile.close()
|
||||
def unset_tracing():
|
||||
debugfile.close()
|
||||
sys.stderr.write("wrote pytestdebug information to %s\n" %
|
||||
config._debugfile.name)
|
||||
debugfile.name)
|
||||
config.trace.root.setwriter(None)
|
||||
|
||||
config.add_cleanup(unset_tracing)
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
if config.option.version:
|
||||
|
@ -58,9 +54,9 @@ def pytest_cmdline_main(config):
|
|||
sys.stderr.write(line + "\n")
|
||||
return 0
|
||||
elif config.option.help:
|
||||
config.do_configure()
|
||||
config._do_configure()
|
||||
showhelp(config)
|
||||
config.do_unconfigure()
|
||||
config._ensure_unconfigure()
|
||||
return 0
|
||||
|
||||
def showhelp(config):
|
||||
|
|
|
@ -77,7 +77,7 @@ def wrap_session(config, doit):
|
|||
initstate = 0
|
||||
try:
|
||||
try:
|
||||
config.do_configure()
|
||||
config._do_configure()
|
||||
initstate = 1
|
||||
config.hook.pytest_sessionstart(session=session)
|
||||
initstate = 2
|
||||
|
@ -107,9 +107,7 @@ def wrap_session(config, doit):
|
|||
config.hook.pytest_sessionfinish(
|
||||
session=session,
|
||||
exitstatus=session.exitstatus)
|
||||
if initstate >= 1:
|
||||
config.do_unconfigure()
|
||||
config.pluginmanager.ensure_shutdown()
|
||||
config._ensure_unconfigure()
|
||||
return session.exitstatus
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
|
|
|
@ -44,14 +44,14 @@ def pytest_addoption(parser):
|
|||
|
||||
def pytest_cmdline_main(config):
|
||||
if config.option.markers:
|
||||
config.do_configure()
|
||||
config._do_configure()
|
||||
tw = py.io.TerminalWriter()
|
||||
for line in config.getini("markers"):
|
||||
name, rest = line.split(":", 1)
|
||||
tw.write("@pytest.mark.%s:" % name, bold=True)
|
||||
tw.line(rest)
|
||||
tw.line()
|
||||
config.do_unconfigure()
|
||||
config._ensure_unconfigure()
|
||||
return 0
|
||||
pytest_cmdline_main.tryfirst = True
|
||||
|
||||
|
|
|
@ -65,7 +65,8 @@ class HookRecorder:
|
|||
self.calls.append(ParsedCall(hookcaller.name, kwargs))
|
||||
yield
|
||||
self._undo_wrapping = add_method_wrapper(HookCaller, _docall)
|
||||
pluginmanager.add_shutdown(self._undo_wrapping)
|
||||
#if hasattr(pluginmanager, "config"):
|
||||
# pluginmanager.add_shutdown(self._undo_wrapping)
|
||||
|
||||
def finish_recording(self):
|
||||
self._undo_wrapping()
|
||||
|
@ -571,12 +572,7 @@ class TmpTestdir:
|
|||
# we don't know what the test will do with this half-setup config
|
||||
# object and thus we make sure it gets unconfigured properly in any
|
||||
# case (otherwise capturing could still be active, for example)
|
||||
def ensure_unconfigure():
|
||||
if hasattr(config.pluginmanager, "_config"):
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
config.pluginmanager.ensure_shutdown()
|
||||
|
||||
self.request.addfinalizer(ensure_unconfigure)
|
||||
self.request.addfinalizer(config._ensure_unconfigure)
|
||||
return config
|
||||
|
||||
def parseconfigure(self, *args):
|
||||
|
@ -588,8 +584,8 @@ class TmpTestdir:
|
|||
|
||||
"""
|
||||
config = self.parseconfig(*args)
|
||||
config.do_configure()
|
||||
self.request.addfinalizer(config.do_unconfigure)
|
||||
config._do_configure()
|
||||
self.request.addfinalizer(config._ensure_unconfigure)
|
||||
return config
|
||||
|
||||
def getitem(self, source, funcname="test_func"):
|
||||
|
|
|
@ -66,6 +66,7 @@ def check_open_files(config):
|
|||
error.append(error[0])
|
||||
raise AssertionError("\n".join(error))
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_runtest_teardown(item, __multicall__):
|
||||
item.config._basedir.chdir()
|
||||
if hasattr(item.config, '_openfiles'):
|
||||
|
|
|
@ -155,13 +155,13 @@ class TestPytestPluginInteractions:
|
|||
|
||||
config.pluginmanager.register(A())
|
||||
assert len(l) == 0
|
||||
config.do_configure()
|
||||
config._do_configure()
|
||||
assert len(l) == 1
|
||||
config.pluginmanager.register(A()) # leads to a configured() plugin
|
||||
assert len(l) == 2
|
||||
assert l[0] != l[1]
|
||||
|
||||
config.do_unconfigure()
|
||||
config._ensure_unconfigure()
|
||||
config.pluginmanager.register(A())
|
||||
assert len(l) == 2
|
||||
|
||||
|
|
|
@ -214,8 +214,8 @@ def test_plugin_specify(testdir):
|
|||
def test_plugin_already_exists(testdir):
|
||||
config = testdir.parseconfig("-p", "terminal")
|
||||
assert config.option.plugins == ['terminal']
|
||||
config.do_configure()
|
||||
config.do_unconfigure()
|
||||
config._do_configure()
|
||||
config._ensure_unconfigure()
|
||||
|
||||
def test_exclude(testdir):
|
||||
hellodir = testdir.mkdir("hello")
|
||||
|
|
Loading…
Reference in New Issue