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.
|
from ``inline_run()`` to allow temporary modules to be reloaded.
|
||||||
Thanks Eduardo Schettino.
|
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)
|
2.7.1.dev (compared to 2.7.0)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|
|
@ -70,12 +70,11 @@ def pytest_configure(config):
|
||||||
config._assertstate = AssertionState(config, mode)
|
config._assertstate = AssertionState(config, mode)
|
||||||
config._assertstate.hook = hook
|
config._assertstate.hook = hook
|
||||||
config._assertstate.trace("configured with mode set to %r" % (mode,))
|
config._assertstate.trace("configured with mode set to %r" % (mode,))
|
||||||
|
def undo():
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
|
||||||
hook = config._assertstate.hook
|
hook = config._assertstate.hook
|
||||||
if hook is not None and hook in sys.meta_path:
|
if hook is not None and hook in sys.meta_path:
|
||||||
sys.meta_path.remove(hook)
|
sys.meta_path.remove(hook)
|
||||||
|
config.add_cleanup(undo)
|
||||||
|
|
||||||
|
|
||||||
def pytest_collection(session):
|
def pytest_collection(session):
|
||||||
|
|
|
@ -37,13 +37,13 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
pluginmanager.register(capman, "capturemanager")
|
pluginmanager.register(capman, "capturemanager")
|
||||||
|
|
||||||
# make sure that capturemanager is properly reset at final shutdown
|
# 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
|
# make sure logging does not raise exceptions at the end
|
||||||
def silence_logging_at_shutdown():
|
def silence_logging_at_shutdown():
|
||||||
if "logging" in sys.modules:
|
if "logging" in sys.modules:
|
||||||
sys.modules["logging"].raiseExceptions = False
|
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)
|
# finally trigger conftest loading but while capturing (issue93)
|
||||||
capman.init_capturings()
|
capman.init_capturings()
|
||||||
|
|
|
@ -77,20 +77,17 @@ def _prepareconfig(args=None, plugins=None):
|
||||||
raise ValueError("not a string or argument list: %r" % (args,))
|
raise ValueError("not a string or argument list: %r" % (args,))
|
||||||
args = shlex.split(args)
|
args = shlex.split(args)
|
||||||
pluginmanager = get_plugin_manager()
|
pluginmanager = get_plugin_manager()
|
||||||
try:
|
|
||||||
if plugins:
|
if plugins:
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
pluginmanager.register(plugin)
|
pluginmanager.register(plugin)
|
||||||
return pluginmanager.hook.pytest_cmdline_parse(
|
return pluginmanager.hook.pytest_cmdline_parse(
|
||||||
pluginmanager=pluginmanager, args=args)
|
pluginmanager=pluginmanager, args=args)
|
||||||
except Exception:
|
|
||||||
pluginmanager.ensure_shutdown()
|
|
||||||
raise
|
|
||||||
|
|
||||||
def exclude_pytest_names(name):
|
def exclude_pytest_names(name):
|
||||||
return not name.startswith(name) or name == "pytest_plugins" or \
|
return not name.startswith(name) or name == "pytest_plugins" or \
|
||||||
name.startswith("pytest_funcarg__")
|
name.startswith("pytest_funcarg__")
|
||||||
|
|
||||||
|
|
||||||
class PytestPluginManager(PluginManager):
|
class PytestPluginManager(PluginManager):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PytestPluginManager, self).__init__(prefix="pytest_",
|
super(PytestPluginManager, self).__init__(prefix="pytest_",
|
||||||
|
@ -723,16 +720,23 @@ class Config(object):
|
||||||
if self._configured:
|
if self._configured:
|
||||||
call_plugin(plugin, "pytest_configure", {'config': self})
|
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
|
assert not self._configured
|
||||||
self._configured = True
|
self._configured = True
|
||||||
self.hook.pytest_configure(config=self)
|
self.hook.pytest_configure(config=self)
|
||||||
|
|
||||||
def do_unconfigure(self):
|
def _ensure_unconfigure(self):
|
||||||
assert self._configured
|
if self._configured:
|
||||||
self._configured = False
|
self._configured = False
|
||||||
self.hook.pytest_unconfigure(config=self)
|
self.hook.pytest_unconfigure(config=self)
|
||||||
self.pluginmanager.ensure_shutdown()
|
while self._cleanup:
|
||||||
|
fin = self._cleanup.pop()
|
||||||
|
fin()
|
||||||
|
|
||||||
def warn(self, code, message):
|
def warn(self, code, message):
|
||||||
""" generate a warning for this test session. """
|
""" generate a warning for this test session. """
|
||||||
|
@ -747,11 +751,6 @@ class Config(object):
|
||||||
self.parse(args)
|
self.parse(args)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
|
||||||
while config._cleanup:
|
|
||||||
fin = config._cleanup.pop()
|
|
||||||
fin()
|
|
||||||
|
|
||||||
def notify_exception(self, excinfo, option=None):
|
def notify_exception(self, excinfo, option=None):
|
||||||
if option and option.fulltrace:
|
if option and option.fulltrace:
|
||||||
style = "long"
|
style = "long"
|
||||||
|
|
|
@ -135,6 +135,21 @@ class CallOutcome:
|
||||||
|
|
||||||
|
|
||||||
class PluginManager(object):
|
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):
|
def __init__(self, prefix, excludefunc=None):
|
||||||
self._prefix = prefix
|
self._prefix = prefix
|
||||||
self._excludefunc = excludefunc
|
self._excludefunc = excludefunc
|
||||||
|
@ -142,10 +157,11 @@ class PluginManager(object):
|
||||||
self._plugins = []
|
self._plugins = []
|
||||||
self._plugin2hookcallers = {}
|
self._plugin2hookcallers = {}
|
||||||
self.trace = TagTracer().get("pluginmanage")
|
self.trace = TagTracer().get("pluginmanage")
|
||||||
self._shutdown = []
|
|
||||||
self.hook = HookRelay(pm=self)
|
self.hook = HookRelay(pm=self)
|
||||||
|
|
||||||
def set_tracing(self, writer):
|
def set_tracing(self, writer):
|
||||||
|
""" turn on tracing to the given writer method and
|
||||||
|
return an undo function. """
|
||||||
self.trace.root.setwriter(writer)
|
self.trace.root.setwriter(writer)
|
||||||
# reconfigure HookCalling to perform tracing
|
# reconfigure HookCalling to perform tracing
|
||||||
assert not hasattr(self, "_wrapping")
|
assert not hasattr(self, "_wrapping")
|
||||||
|
@ -160,12 +176,7 @@ class PluginManager(object):
|
||||||
trace("finish", self.name, "-->", box.result)
|
trace("finish", self.name, "-->", box.result)
|
||||||
trace.root.indent -= 1
|
trace.root.indent -= 1
|
||||||
|
|
||||||
undo = add_method_wrapper(HookCaller, _docall)
|
return add_method_wrapper(HookCaller, _docall)
|
||||||
self.add_shutdown(undo)
|
|
||||||
|
|
||||||
def do_configure(self, config):
|
|
||||||
# backward compatibility
|
|
||||||
config.do_configure()
|
|
||||||
|
|
||||||
def make_hook_caller(self, name, plugins):
|
def make_hook_caller(self, name, plugins):
|
||||||
caller = getattr(self.hook, name)
|
caller = getattr(self.hook, name)
|
||||||
|
@ -233,16 +244,6 @@ class PluginManager(object):
|
||||||
for hookcaller in hookcallers:
|
for hookcaller in hookcallers:
|
||||||
hookcaller.scan_methods()
|
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):
|
def addhooks(self, module_or_class):
|
||||||
isclass = int(inspect.isclass(module_or_class))
|
isclass = int(inspect.isclass(module_or_class))
|
||||||
names = []
|
names = []
|
||||||
|
|
|
@ -28,24 +28,20 @@ def pytest_cmdline_parse():
|
||||||
config = outcome.get_result()
|
config = outcome.get_result()
|
||||||
if config.option.debug:
|
if config.option.debug:
|
||||||
path = os.path.abspath("pytestdebug.log")
|
path = os.path.abspath("pytestdebug.log")
|
||||||
f = open(path, 'w')
|
debugfile = open(path, 'w')
|
||||||
config._debugfile = f
|
debugfile.write("versions pytest-%s, py-%s, "
|
||||||
f.write("versions pytest-%s, py-%s, "
|
|
||||||
"python-%s\ncwd=%s\nargs=%s\n\n" %(
|
"python-%s\ncwd=%s\nargs=%s\n\n" %(
|
||||||
pytest.__version__, py.__version__,
|
pytest.__version__, py.__version__,
|
||||||
".".join(map(str, sys.version_info)),
|
".".join(map(str, sys.version_info)),
|
||||||
os.getcwd(), config._origargs))
|
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)
|
sys.stderr.write("writing pytestdebug information to %s\n" % path)
|
||||||
|
def unset_tracing():
|
||||||
@pytest.mark.trylast
|
debugfile.close()
|
||||||
def pytest_unconfigure(config):
|
|
||||||
if hasattr(config, '_debugfile'):
|
|
||||||
config._debugfile.close()
|
|
||||||
sys.stderr.write("wrote pytestdebug information to %s\n" %
|
sys.stderr.write("wrote pytestdebug information to %s\n" %
|
||||||
config._debugfile.name)
|
debugfile.name)
|
||||||
config.trace.root.setwriter(None)
|
config.trace.root.setwriter(None)
|
||||||
|
config.add_cleanup(unset_tracing)
|
||||||
|
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
if config.option.version:
|
if config.option.version:
|
||||||
|
@ -58,9 +54,9 @@ def pytest_cmdline_main(config):
|
||||||
sys.stderr.write(line + "\n")
|
sys.stderr.write(line + "\n")
|
||||||
return 0
|
return 0
|
||||||
elif config.option.help:
|
elif config.option.help:
|
||||||
config.do_configure()
|
config._do_configure()
|
||||||
showhelp(config)
|
showhelp(config)
|
||||||
config.do_unconfigure()
|
config._ensure_unconfigure()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def showhelp(config):
|
def showhelp(config):
|
||||||
|
|
|
@ -77,7 +77,7 @@ def wrap_session(config, doit):
|
||||||
initstate = 0
|
initstate = 0
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
config.do_configure()
|
config._do_configure()
|
||||||
initstate = 1
|
initstate = 1
|
||||||
config.hook.pytest_sessionstart(session=session)
|
config.hook.pytest_sessionstart(session=session)
|
||||||
initstate = 2
|
initstate = 2
|
||||||
|
@ -107,9 +107,7 @@ def wrap_session(config, doit):
|
||||||
config.hook.pytest_sessionfinish(
|
config.hook.pytest_sessionfinish(
|
||||||
session=session,
|
session=session,
|
||||||
exitstatus=session.exitstatus)
|
exitstatus=session.exitstatus)
|
||||||
if initstate >= 1:
|
config._ensure_unconfigure()
|
||||||
config.do_unconfigure()
|
|
||||||
config.pluginmanager.ensure_shutdown()
|
|
||||||
return session.exitstatus
|
return session.exitstatus
|
||||||
|
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
|
|
|
@ -44,14 +44,14 @@ def pytest_addoption(parser):
|
||||||
|
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
if config.option.markers:
|
if config.option.markers:
|
||||||
config.do_configure()
|
config._do_configure()
|
||||||
tw = py.io.TerminalWriter()
|
tw = py.io.TerminalWriter()
|
||||||
for line in config.getini("markers"):
|
for line in config.getini("markers"):
|
||||||
name, rest = line.split(":", 1)
|
name, rest = line.split(":", 1)
|
||||||
tw.write("@pytest.mark.%s:" % name, bold=True)
|
tw.write("@pytest.mark.%s:" % name, bold=True)
|
||||||
tw.line(rest)
|
tw.line(rest)
|
||||||
tw.line()
|
tw.line()
|
||||||
config.do_unconfigure()
|
config._ensure_unconfigure()
|
||||||
return 0
|
return 0
|
||||||
pytest_cmdline_main.tryfirst = True
|
pytest_cmdline_main.tryfirst = True
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,8 @@ class HookRecorder:
|
||||||
self.calls.append(ParsedCall(hookcaller.name, kwargs))
|
self.calls.append(ParsedCall(hookcaller.name, kwargs))
|
||||||
yield
|
yield
|
||||||
self._undo_wrapping = add_method_wrapper(HookCaller, _docall)
|
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):
|
def finish_recording(self):
|
||||||
self._undo_wrapping()
|
self._undo_wrapping()
|
||||||
|
@ -571,12 +572,7 @@ class TmpTestdir:
|
||||||
# we don't know what the test will do with this half-setup config
|
# 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
|
# object and thus we make sure it gets unconfigured properly in any
|
||||||
# case (otherwise capturing could still be active, for example)
|
# case (otherwise capturing could still be active, for example)
|
||||||
def ensure_unconfigure():
|
self.request.addfinalizer(config._ensure_unconfigure)
|
||||||
if hasattr(config.pluginmanager, "_config"):
|
|
||||||
config.pluginmanager.do_unconfigure(config)
|
|
||||||
config.pluginmanager.ensure_shutdown()
|
|
||||||
|
|
||||||
self.request.addfinalizer(ensure_unconfigure)
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def parseconfigure(self, *args):
|
def parseconfigure(self, *args):
|
||||||
|
@ -588,8 +584,8 @@ class TmpTestdir:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = self.parseconfig(*args)
|
config = self.parseconfig(*args)
|
||||||
config.do_configure()
|
config._do_configure()
|
||||||
self.request.addfinalizer(config.do_unconfigure)
|
self.request.addfinalizer(config._ensure_unconfigure)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def getitem(self, source, funcname="test_func"):
|
def getitem(self, source, funcname="test_func"):
|
||||||
|
|
|
@ -66,6 +66,7 @@ def check_open_files(config):
|
||||||
error.append(error[0])
|
error.append(error[0])
|
||||||
raise AssertionError("\n".join(error))
|
raise AssertionError("\n".join(error))
|
||||||
|
|
||||||
|
@pytest.mark.trylast
|
||||||
def pytest_runtest_teardown(item, __multicall__):
|
def pytest_runtest_teardown(item, __multicall__):
|
||||||
item.config._basedir.chdir()
|
item.config._basedir.chdir()
|
||||||
if hasattr(item.config, '_openfiles'):
|
if hasattr(item.config, '_openfiles'):
|
||||||
|
|
|
@ -155,13 +155,13 @@ class TestPytestPluginInteractions:
|
||||||
|
|
||||||
config.pluginmanager.register(A())
|
config.pluginmanager.register(A())
|
||||||
assert len(l) == 0
|
assert len(l) == 0
|
||||||
config.do_configure()
|
config._do_configure()
|
||||||
assert len(l) == 1
|
assert len(l) == 1
|
||||||
config.pluginmanager.register(A()) # leads to a configured() plugin
|
config.pluginmanager.register(A()) # leads to a configured() plugin
|
||||||
assert len(l) == 2
|
assert len(l) == 2
|
||||||
assert l[0] != l[1]
|
assert l[0] != l[1]
|
||||||
|
|
||||||
config.do_unconfigure()
|
config._ensure_unconfigure()
|
||||||
config.pluginmanager.register(A())
|
config.pluginmanager.register(A())
|
||||||
assert len(l) == 2
|
assert len(l) == 2
|
||||||
|
|
||||||
|
|
|
@ -214,8 +214,8 @@ def test_plugin_specify(testdir):
|
||||||
def test_plugin_already_exists(testdir):
|
def test_plugin_already_exists(testdir):
|
||||||
config = testdir.parseconfig("-p", "terminal")
|
config = testdir.parseconfig("-p", "terminal")
|
||||||
assert config.option.plugins == ['terminal']
|
assert config.option.plugins == ['terminal']
|
||||||
config.do_configure()
|
config._do_configure()
|
||||||
config.do_unconfigure()
|
config._ensure_unconfigure()
|
||||||
|
|
||||||
def test_exclude(testdir):
|
def test_exclude(testdir):
|
||||||
hellodir = testdir.mkdir("hello")
|
hellodir = testdir.mkdir("hello")
|
||||||
|
|
Loading…
Reference in New Issue