From a930f44e60e7d3191004ef793a91d347012d0cae Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 28 Sep 2013 22:23:00 +0200 Subject: [PATCH] introduce pluginmanager.ensure_teardown() which allows --- _pytest/capture.py | 8 ++++++++ _pytest/core.py | 14 +++++++++++++- _pytest/main.py | 1 + _pytest/pytester.py | 33 ++++++++++++--------------------- testing/acceptance_test.py | 10 +++++----- testing/test_collection.py | 4 ++-- testing/test_doctest.py | 2 +- 7 files changed, 42 insertions(+), 30 deletions(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index 3c2874684..a372faabb 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -22,6 +22,13 @@ def pytest_cmdline_parse(pluginmanager, args): method = "sys" capman = CaptureManager(method) pluginmanager.register(capman, "capturemanager") + # make sure that capturemanager is properly reset at final shutdown + def teardown(): + try: + capman.reset_capturings() + except ValueError: + pass + pluginmanager.add_shutdown(teardown) def addouterr(rep, outerr): for secname, content in zip(["out", "err"], outerr): @@ -82,6 +89,7 @@ class CaptureManager: for name, cap in self._method2capture.items(): cap.reset() + def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) if not hasattr(item, 'outerr'): diff --git a/_pytest/core.py b/_pytest/core.py index ff506d01b..6f87276bd 100644 --- a/_pytest/core.py +++ b/_pytest/core.py @@ -80,6 +80,7 @@ class PluginManager(object): self._hints = [] self.trace = TagTracer().get("pluginmanage") self._plugin_distinfo = [] + self._shutdown = [] if os.environ.get('PYTEST_DEBUG'): err = sys.stderr encoding = getattr(err, 'encoding', 'utf8') @@ -118,6 +119,17 @@ class PluginManager(object): if value == plugin: del self._name2plugin[name] + 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() + self._listattrcache.clear() + def isregistered(self, plugin, name=None): if self.getplugin(name) is not None: return True @@ -286,7 +298,7 @@ class PluginManager(object): config = self._config del self._config config.hook.pytest_unconfigure(config=config) - config.pluginmanager.unregister(self) + config.pluginmanager.ensure_shutdown() def notify_exception(self, excinfo, option=None): if option and option.fulltrace: diff --git a/_pytest/main.py b/_pytest/main.py index 008d39c3a..680a99d3a 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -106,6 +106,7 @@ def wrap_session(config, doit): exitstatus=session.exitstatus) if initstate >= 1: config.pluginmanager.do_unconfigure(config) + config.pluginmanager.ensure_shutdown() return session.exitstatus def pytest_cmdline_main(config): diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 2b96beef9..322b11277 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -83,7 +83,8 @@ class HookRecorder: def finish_recording(self): for recorder in self._recorders.values(): - self._pluginmanager.unregister(recorder) + if self._pluginmanager.isregistered(recorder): + self._pluginmanager.unregister(recorder) self._recorders.clear() def _makecallparser(self, method): @@ -361,7 +362,7 @@ class TmpTestdir: if not plugins: plugins = [] plugins.append(Collect()) - ret = self.pytestmain(list(args), plugins=plugins) + ret = pytest.main(list(args), plugins=plugins) reprec = rec[0] reprec.ret = ret assert len(rec) == 1 @@ -376,14 +377,15 @@ class TmpTestdir: args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) import _pytest.core config = _pytest.core._prepareconfig(args, self.plugins) - # the in-process pytest invocation needs to avoid leaking FDs - # so we register a "reset_capturings" callmon the capturing manager - # and make sure it gets called - config._cleanup.append( - config.pluginmanager.getplugin("capturemanager").reset_capturings) - import _pytest.config - self.request.addfinalizer( - lambda: _pytest.config.pytest_unconfigure(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 + # 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) return config def parseconfigure(self, *args): @@ -428,17 +430,6 @@ class TmpTestdir: return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) - def pytestmain(self, *args, **kwargs): - class ResetCapturing: - @pytest.mark.trylast - def pytest_unconfigure(self, config): - capman = config.pluginmanager.getplugin("capturemanager") - capman.reset_capturings() - plugins = kwargs.setdefault("plugins", []) - rc = ResetCapturing() - plugins.append(rc) - return pytest.main(*args, **kwargs) - def run(self, *cmdargs): return self._run(*cmdargs) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 295fe3080..5ae588760 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -391,15 +391,15 @@ class TestInvocationVariants: def test_equivalence_pytest_pytest(self): assert pytest.main == py.test.cmdline.main - def test_invoke_with_string(self, testdir, capsys): - retcode = testdir.pytestmain("-h") + def test_invoke_with_string(self, capsys): + retcode = pytest.main("-h") assert not retcode out, err = capsys.readouterr() assert "--help" in out pytest.raises(ValueError, lambda: pytest.main(0)) - def test_invoke_with_path(self, testdir, capsys): - retcode = testdir.pytestmain(testdir.tmpdir) + def test_invoke_with_path(self, tmpdir, capsys): + retcode = pytest.main(tmpdir) assert not retcode out, err = capsys.readouterr() @@ -408,7 +408,7 @@ class TestInvocationVariants: def pytest_addoption(self, parser): parser.addoption("--myopt") - testdir.pytestmain(["-h"], plugins=[MyPlugin()]) + pytest.main(["-h"], plugins=[MyPlugin()]) out, err = capsys.readouterr() assert "--myopt" in out diff --git a/testing/test_collection.py b/testing/test_collection.py index f330002e9..c27bad957 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -123,7 +123,7 @@ class TestCollectPluginHookRelay: def pytest_collect_file(self, path, parent): wascalled.append(path) testdir.makefile(".abc", "xyz") - testdir.pytestmain([testdir.tmpdir], plugins=[Plugin()]) + pytest.main([testdir.tmpdir], plugins=[Plugin()]) assert len(wascalled) == 1 assert wascalled[0].ext == '.abc' @@ -134,7 +134,7 @@ class TestCollectPluginHookRelay: wascalled.append(path.basename) testdir.mkdir("hello") testdir.mkdir("world") - testdir.pytestmain(testdir.tmpdir, plugins=[Plugin()]) + pytest.main(testdir.tmpdir, plugins=[Plugin()]) assert "hello" in wascalled assert "world" in wascalled diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 15f28613e..c840e3301 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -4,7 +4,7 @@ import py, pytest import pdb xfail_if_pdbpp_installed = pytest.mark.xfail(hasattr(pdb, "__author__"), - reason="doctest/pdbpp problem: https://bitbucket.org/antocuni/pdb/issue/24/doctests-fail-when-pdbpp-is-installed") + reason="doctest/pdbpp problem: https://bitbucket.org/antocuni/pdb/issue/24/doctests-fail-when-pdbpp-is-installed", run=False) class TestDoctests: