Skip to content

Commit e1a75bd

Browse files
committed
Reset the hook proxy cache if the number of conftests changes
#2016
1 parent 7574033 commit e1a75bd

File tree

5 files changed

+65
-3
lines changed

5 files changed

+65
-3
lines changed

CHANGELOG.rst

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,23 @@ Changes
2727
* Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError``
2828
to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR.
2929

30+
* Fix an internal caching issue which could cause hooks from ``conftest.py`` files in
31+
sub-directories to be called in other directories incorrectly (`#2016`_).
32+
Thanks `@d-b-w`_ for the report and `@nicoddemus`_ for the PR.
3033

31-
* fix `#2013`_: turn RecordedWarning into namedtupe,
32-
to give it a comprehensible repr while preventing unwarranted modification
34+
* fix `#2013`_: turn RecordedWarning into ``namedtuple``
35+
to give it a comprehensible repr while preventing unwarranted modification.
3336

3437
.. _@davidszotten: https://github.com/davidszotten
38+
.. _@d-b-w: https://github.com/d-b-w
3539
.. _@fushi: https://github.com/fushi
3640
.. _@mattduck: https://github.com/mattduck
3741

3842
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
3943
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
4044
.. _#1952: https://github.com/pytest-dev/pytest/pull/1952
4145
.. _#2013: https://github.com/pytest-dev/pytest/issues/2013
46+
.. _#2016: https://github.com/pytest-dev/pytest/issues/2016
4247

4348

4449
3.0.5.dev0

_pytest/main.py

+13
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,20 @@ def pytest_runtest_logreport(self, report):
561561
def isinitpath(self, path):
562562
return path in self._initialpaths
563563

564+
def _maybe_clear_hook_proxy_cache(self):
565+
"""
566+
Reset the hook proxy cache if the number of conftests has changed
567+
since the last time it was used (#2016).
568+
"""
569+
pm = self.config.pluginmanager
570+
total_conftests = len(pm._conftest_plugins)
571+
last_conftests = getattr(self, 'last_conftests', 0)
572+
if total_conftests != last_conftests:
573+
self._fs2hookproxy.clear()
574+
self.last_conftests = total_conftests
575+
564576
def gethookproxy(self, fspath):
577+
self._maybe_clear_hook_proxy_cache()
565578
try:
566579
return self._fs2hookproxy[fspath]
567580
except KeyError:

_pytest/pytester.py

+1
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ def _makefile(self, ext, args, kwargs):
481481
ret = None
482482
for name, value in items:
483483
p = self.tmpdir.join(name).new(ext=ext)
484+
p.dirpath().ensure_dir()
484485
source = Source(value)
485486
def my_totext(s, encoding="utf-8"):
486487
if py.builtin._isbytes(s):

testing/test_conftest.py

+23
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,26 @@ def test_some():
421421
res = testdir.runpytest()
422422
assert res.ret == 4
423423
assert 'raise ValueError()' in [line.strip() for line in res.errlines]
424+
425+
426+
def test_hook_proxy_cache(testdir):
427+
"""Session's gethookproxy() could cache incorrectly conftests (#2016)."""
428+
testdir.makepyfile(**{
429+
'root/demo-0/test_foo1.py': "def test1(): pass",
430+
431+
'root/demo-a/test_foo2.py': "def test1(): pass",
432+
'root/demo-a/conftest.py': """
433+
def pytest_ignore_collect(path, config):
434+
return True
435+
""",
436+
437+
'root/demo-b/test_foo3.py': "def test1(): pass",
438+
'root/demo-c/test_foo4.py': "def test1(): pass",
439+
})
440+
result = testdir.runpytest()
441+
result.stdout.fnmatch_lines([
442+
'*test_foo1.py*',
443+
'*test_foo3.py*',
444+
'*test_foo4.py*',
445+
'*3 passed*',
446+
])

testing/test_pluginmanager.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import os
55

66
from _pytest.config import get_config, PytestPluginManager
7-
from _pytest.main import EXIT_NOTESTSCOLLECTED
7+
from _pytest.main import EXIT_NOTESTSCOLLECTED, Session
8+
89

910
@pytest.fixture
1011
def pytestpm():
@@ -129,6 +130,25 @@ def pytest_plugin_registered(self):
129130
finally:
130131
undo()
131132

133+
def test_hook_proxy_cache_reset(self, testdir):
134+
"""Importing new conftest files should reset the internal hook proxy cache (#2016)"""
135+
config = testdir.parseconfig()
136+
session = Session(config)
137+
testdir.makepyfile(**{
138+
'tests/conftest.py': '',
139+
'tests/subdir/conftest.py': '',
140+
})
141+
142+
conftest1 = testdir.tmpdir.join('tests/conftest.py')
143+
conftest2 = testdir.tmpdir.join('tests/subdir/conftest.py')
144+
145+
config.pluginmanager._importconftest(conftest1)
146+
ihook_a = session.gethookproxy(testdir.tmpdir.join('tests'))
147+
assert ihook_a is not None
148+
config.pluginmanager._importconftest(conftest2)
149+
ihook_b = session.gethookproxy(testdir.tmpdir.join('tests'))
150+
assert ihook_a is not ihook_b
151+
132152
def test_warn_on_deprecated_multicall(self, pytestpm):
133153
warnings = []
134154

0 commit comments

Comments
 (0)