diff --git a/CHANGELOG b/CHANGELOG index 864c211b0..771de3f4f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ 2.8.1.dev --------- +- fix issue #1073: avoid calling __getattr__ on potential plugin objects. + This fixes an incompatibility with pytest-django. Thanks Andreas Pelme, + Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel + for the fix. + - Fix issue #704: handle versionconflict during plugin loading more gracefully. Thanks Bruno Oliveira for the PR. diff --git a/_pytest/config.py b/_pytest/config.py index def26a02b..8be167a21 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -120,12 +120,6 @@ def _prepareconfig(args=None, plugins=None): raise -def exclude_pytest_names(name): - return not name.startswith(name) or name == "pytest_plugins" or \ - name.startswith("pytest_funcarg__") - - - class PytestPluginManager(PluginManager): """ Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific @@ -171,8 +165,14 @@ class PytestPluginManager(PluginManager): return self.add_hookspecs(module_or_class) def parse_hookimpl_opts(self, plugin, name): - if exclude_pytest_names(name): - return None + # pytest hooks are always prefixed with pytest_ + # so we avoid accessing possibly non-readable attributes + # (see issue #1073) + if not name.startswith("pytest_"): + return + # ignore some historic special names which can not be hooks anyway + if name == "pytest_plugins" or name.startswith("pytest_funcarg__"): + return method = getattr(plugin, name) opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) diff --git a/_pytest/python.py b/_pytest/python.py index 6548cdbf5..0ad18b3ef 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1918,14 +1918,14 @@ class FixtureManager: autousenames = [] for name in dir(holderobj): obj = getattr(holderobj, name, None) - if not callable(obj): - continue # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # or are "@pytest.fixture" marked marker = getfixturemarker(obj) if marker is None: if not name.startswith(self._argprefix): continue + if not callable(obj): + continue marker = defaultfuncargprefixmarker name = name[len(self._argprefix):] elif not isinstance(marker, FixtureFunctionMarker): diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 6700502c4..a0b77cfa5 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -388,3 +388,19 @@ def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error): if error: match += '*%d error*' % error result.stdout.fnmatch_lines(match) + + +def test_issue1073_conftest_special_objects(testdir): + testdir.makeconftest(""" + class DontTouchMe: + def __getattr__(self, x): + raise Exception('cant touch me') + + x = DontTouchMe() + """) + testdir.makepyfile(""" + def test_some(): + pass + """) + res = testdir.runpytest() + assert res.ret == 0