From 2c0d2eef40bf6393643f045ec1766e1b60ccc999 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Aug 2018 16:35:24 -0300 Subject: [PATCH] Only consider actual functions when considering hooks Fix #3775 --- changelog/3775.bugfix.rst | 1 + src/_pytest/config/__init__.py | 5 +++++ .../config/collect_pytest_prefix/conftest.py | 2 ++ .../config/collect_pytest_prefix/test_foo.py | 2 ++ testing/test_config.py | 18 ++++++++++++++++++ 5 files changed, 28 insertions(+) create mode 100644 changelog/3775.bugfix.rst create mode 100644 testing/example_scripts/config/collect_pytest_prefix/conftest.py create mode 100644 testing/example_scripts/config/collect_pytest_prefix/test_foo.py diff --git a/changelog/3775.bugfix.rst b/changelog/3775.bugfix.rst new file mode 100644 index 00000000000..dd5263f743a --- /dev/null +++ b/changelog/3775.bugfix.rst @@ -0,0 +1 @@ +Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 421d124e9a9..921b3ef2026 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,6 +1,7 @@ """ command line options, ini-file and conftest.py processing. """ from __future__ import absolute_import, division, print_function import argparse +import inspect import shlex import traceback import types @@ -252,6 +253,10 @@ def parse_hookimpl_opts(self, plugin, name): method = getattr(plugin, name) opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) + # consider only actual functions for hooks (#3775) + if not inspect.isroutine(method): + return + # collect unmarked hooks as long as they have the `pytest_' prefix if opts is None and name.startswith("pytest_"): opts = {} diff --git a/testing/example_scripts/config/collect_pytest_prefix/conftest.py b/testing/example_scripts/config/collect_pytest_prefix/conftest.py new file mode 100644 index 00000000000..56a4c71d358 --- /dev/null +++ b/testing/example_scripts/config/collect_pytest_prefix/conftest.py @@ -0,0 +1,2 @@ +class pytest_something(object): + pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py new file mode 100644 index 00000000000..8f2d73cfa4f --- /dev/null +++ b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py @@ -0,0 +1,2 @@ +def test_foo(): + pass diff --git a/testing/test_config.py b/testing/test_config.py index b507bb8e823..ef9dacd9c5b 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -745,6 +745,24 @@ def test_get_plugin_specs_as_list(): assert _get_plugin_specs_as_list(("foo", "bar")) == ["foo", "bar"] +def test_collect_pytest_prefix_bug_integration(testdir): + """Integration test for issue #3775""" + p = testdir.copy_example("config/collect_pytest_prefix") + result = testdir.runpytest(p) + result.stdout.fnmatch_lines("* 1 passed *") + + +def test_collect_pytest_prefix_bug(pytestconfig): + """Ensure we collect only actual functions from conftest files (#3775)""" + + class Dummy(object): + class pytest_something(object): + pass + + pm = pytestconfig.pluginmanager + assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None + + class TestWarning(object): def test_warn_config(self, testdir): testdir.makeconftest(