From 44984a5af8cec43466ed5c8d77caa2235d334106 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Mar 2017 21:02:52 -0300 Subject: [PATCH] Verify hooks after collection completes Fix #1821 --- CHANGELOG.rst | 12 +++++++++--- _pytest/config.py | 1 - _pytest/main.py | 1 + testing/acceptance_test.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 44ac7baf375..2f05085adbc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -41,11 +41,16 @@ Changes * Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError`` to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR. -* fix `#2013`_: turn RecordedWarning into namedtupe, - to give it a comprehensible repr while preventing unwarranted modification +* fix `#2013`_: turn RecordedWarning into ``namedtuple``, + to give it a comprehensible repr while preventing unwarranted modification. * fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func. - Thanks `@RonnyPfannschmidt`_ for the Report and PR + Thanks `@RonnyPfannschmidt`_ for the report and PR. + +* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This + makes it easy to write hooks for plugins which will be loaded during collection, for example using the + ``pytest_plugins`` special variable (`#1821`_). + Thanks `@nicoddemus`_ for the PR. * Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an additional parameter. @@ -75,6 +80,7 @@ Bug Fixes .. _#1407: https://github.com/pytest-dev/pytest/issues/1407 .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 +.. _#1821: https://github.com/pytest-dev/pytest/issues/1821 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1952: https://github.com/pytest-dev/pytest/pull/1952 .. _#2007: https://github.com/pytest-dev/pytest/issues/2007 diff --git a/_pytest/config.py b/_pytest/config.py index 81f39cda485..36a396067f8 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -53,7 +53,6 @@ def main(args=None, plugins=None): return 4 else: try: - config.pluginmanager.check_pending() return config.hook.pytest_cmdline_main(config=config) finally: config._ensure_unconfigure() diff --git a/_pytest/main.py b/_pytest/main.py index 73858e0994b..249e3b4df64 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -594,6 +594,7 @@ def perform_collect(self, args=None, genitems=True): hook = self.config.hook try: items = self._perform_collect(args, genitems) + self.config.pluginmanager.check_pending() hook.pytest_collection_modifyitems(session=self, config=self.config, items=items) finally: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 0d3fc101623..7ba4c790623 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -781,3 +781,32 @@ def main(): assert result.ret == 0 result.stderr.fnmatch_lines(['*not found*foo*']) assert 'INTERNALERROR>' not in result.stdout.str() + + +def test_deferred_hook_checking(testdir): + """ + Check hooks as late as possible (#1821). + """ + testdir.syspathinsert() + testdir.makepyfile(**{ + 'plugin.py': """ + class Hooks: + def pytest_my_hook(self, config): + pass + + def pytest_configure(config): + config.pluginmanager.add_hookspecs(Hooks) + """, + 'conftest.py': """ + pytest_plugins = ['plugin'] + def pytest_my_hook(config): + return 40 + """, + 'test_foo.py': """ + def test(request): + assert request.config.hook.pytest_my_hook(config=request.config) == [40] + """ + }) + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 passed *']) +