Skip to content

Commit 685387a

Browse files
authored
Merge pull request #2127 from malinoff/fix-2124
Use session.config.hook instead of ihook. Fixes #2124
2 parents d1af369 + a6f2d2d commit 685387a

File tree

9 files changed

+77
-25
lines changed

9 files changed

+77
-25
lines changed

_pytest/fixtures.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from __future__ import absolute_import, division, print_function
22

3+
import functools
34
import inspect
45
import sys
56
import warnings
7+
from collections import OrderedDict
68

9+
import attr
710
import py
811
from py._code.code import FormattedExcinfo
912

10-
import attr
1113
import _pytest
1214
from _pytest import nodes
1315
from _pytest._code.code import TerminalRepr
@@ -22,9 +24,6 @@
2224
from _pytest.outcomes import fail, TEST_OUTCOME
2325

2426

25-
from collections import OrderedDict
26-
27-
2827
def pytest_sessionstart(session):
2928
import _pytest.python
3029

@@ -519,7 +518,7 @@ def _getfixturevalue(self, fixturedef):
519518
val = fixturedef.execute(request=subrequest)
520519
finally:
521520
# if fixture function failed it might have registered finalizers
522-
self.session._setupstate.addfinalizer(fixturedef.finish,
521+
self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest),
523522
subrequest.node)
524523
return val
525524

@@ -573,7 +572,6 @@ def __init__(self, request, scope, param, param_index, fixturedef):
573572
self.param_index = param_index
574573
self.scope = scope
575574
self._fixturedef = fixturedef
576-
self.addfinalizer = fixturedef.addfinalizer
577575
self._pyfuncitem = request._pyfuncitem
578576
self._fixture_values = request._fixture_values
579577
self._fixture_defs = request._fixture_defs
@@ -584,6 +582,9 @@ def __init__(self, request, scope, param, param_index, fixturedef):
584582
def __repr__(self):
585583
return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
586584

585+
def addfinalizer(self, finalizer):
586+
self._fixturedef.addfinalizer(finalizer)
587+
587588

588589
class ScopeMismatchError(Exception):
589590
""" A fixture function tries to use a different fixture function which
@@ -734,17 +735,17 @@ def __init__(self, fixturemanager, baseid, argname, func, scope, params,
734735
self.argnames = getfuncargnames(func, is_method=unittest)
735736
self.unittest = unittest
736737
self.ids = ids
737-
self._finalizer = []
738+
self._finalizers = []
738739

739740
def addfinalizer(self, finalizer):
740-
self._finalizer.append(finalizer)
741+
self._finalizers.append(finalizer)
741742

742-
def finish(self):
743+
def finish(self, request):
743744
exceptions = []
744745
try:
745-
while self._finalizer:
746+
while self._finalizers:
746747
try:
747-
func = self._finalizer.pop()
748+
func = self._finalizers.pop()
748749
func()
749750
except: # noqa
750751
exceptions.append(sys.exc_info())
@@ -754,20 +755,23 @@ def finish(self):
754755
py.builtin._reraise(*e)
755756

756757
finally:
757-
ihook = self._fixturemanager.session.ihook
758-
ihook.pytest_fixture_post_finalizer(fixturedef=self)
758+
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
759+
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
759760
# even if finalization fails, we invalidate
760-
# the cached fixture value
761+
# the cached fixture value and remove
762+
# all finalizers because they may be bound methods which will
763+
# keep instances alive
761764
if hasattr(self, "cached_result"):
762765
del self.cached_result
766+
self._finalizers = []
763767

764768
def execute(self, request):
765769
# get required arguments and register our own finish()
766770
# with their finalization
767771
for argname in self.argnames:
768772
fixturedef = request._get_active_fixturedef(argname)
769773
if argname != "request":
770-
fixturedef.addfinalizer(self.finish)
774+
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
771775

772776
my_cache_key = request.param_index
773777
cached_result = getattr(self, "cached_result", None)
@@ -780,11 +784,11 @@ def execute(self, request):
780784
return result
781785
# we have a previous but differently parametrized fixture instance
782786
# so we need to tear it down before creating a new one
783-
self.finish()
787+
self.finish(request)
784788
assert not hasattr(self, "cached_result")
785789

786-
ihook = self._fixturemanager.session.ihook
787-
return ihook.pytest_fixture_setup(fixturedef=self, request=request)
790+
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
791+
return hook.pytest_fixture_setup(fixturedef=self, request=request)
788792

789793
def __repr__(self):
790794
return ("<FixtureDef name=%r scope=%r baseid=%r >" %

_pytest/hookspec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def pytest_fixture_setup(fixturedef, request):
296296
Stops at first non-None result, see :ref:`firstresult` """
297297

298298

299-
def pytest_fixture_post_finalizer(fixturedef):
299+
def pytest_fixture_post_finalizer(fixturedef, request):
300300
""" called after fixture teardown, but before the cache is cleared so
301301
the fixture result cache ``fixturedef.cached_result`` can
302302
still be accessed."""

_pytest/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,16 @@ def __init__(self, fspath, parent=None, config=None, session=None):
499499
super(FSCollector, self).__init__(name, parent, config, session)
500500
self.fspath = fspath
501501

502+
def _check_initialpaths_for_relpath(self):
503+
for initialpath in self.session._initialpaths:
504+
if self.fspath.common(initialpath) == initialpath:
505+
return self.fspath.relto(initialpath.dirname)
506+
502507
def _makeid(self):
503508
relpath = self.fspath.relto(self.config.rootdir)
509+
510+
if not relpath:
511+
relpath = self._check_initialpaths_for_relpath()
504512
if os.sep != nodes.SEP:
505513
relpath = relpath.replace(os.sep, nodes.SEP)
506514
return relpath

_pytest/runner.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ def pytest_sessionfinish(session):
5656
session._setupstate.teardown_all()
5757

5858

59-
class NodeInfo:
60-
def __init__(self, location):
61-
self.location = location
62-
63-
6459
def pytest_runtest_protocol(item, nextitem):
6560
item.ihook.pytest_runtest_logstart(
6661
nodeid=item.nodeid, location=item.location,

changelog/2124.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now called for all ``conftest.py`` files.

changelog/2124.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``pytest_fixture_post_finalizer`` hook can now receive a ``request`` argument.

changelog/2775.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix the bug where running pytest with "--pyargs" will result in Items with empty "parent.nodeid" if run from a different root directory.

testing/acceptance_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,10 @@ def join_pythonpath(*dirs):
624624
for p in search_path:
625625
monkeypatch.syspath_prepend(p)
626626

627+
os.chdir('world')
627628
# mixed module and filenames:
628-
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "world/ns_pkg")
629+
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
630+
testdir.chdir()
629631
assert result.ret == 0
630632
result.stdout.fnmatch_lines([
631633
"*test_hello.py::test_hello*PASSED",

testing/python/fixture.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3121,3 +3121,43 @@ def test_foo(request):
31213121
E*{1}:5
31223122
*1 failed*
31233123
""".format(fixfile.strpath, testfile.basename))
3124+
3125+
3126+
def test_pytest_fixture_setup_and_post_finalizer_hook(testdir):
3127+
testdir.makeconftest("""
3128+
from __future__ import print_function
3129+
def pytest_fixture_setup(fixturedef, request):
3130+
print('ROOT setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
3131+
def pytest_fixture_post_finalizer(fixturedef, request):
3132+
print('ROOT finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
3133+
""")
3134+
testdir.makepyfile(**{
3135+
'tests/conftest.py': """
3136+
from __future__ import print_function
3137+
def pytest_fixture_setup(fixturedef, request):
3138+
print('TESTS setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
3139+
def pytest_fixture_post_finalizer(fixturedef, request):
3140+
print('TESTS finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
3141+
""",
3142+
'tests/test_hooks.py': """
3143+
from __future__ import print_function
3144+
import pytest
3145+
3146+
@pytest.fixture()
3147+
def my_fixture():
3148+
return 'some'
3149+
3150+
def test_func(my_fixture):
3151+
print('TEST test_func')
3152+
assert my_fixture == 'some'
3153+
"""
3154+
})
3155+
result = testdir.runpytest("-s")
3156+
assert result.ret == 0
3157+
result.stdout.fnmatch_lines([
3158+
"*TESTS setup hook called for my_fixture from test_func*",
3159+
"*ROOT setup hook called for my_fixture from test_func*",
3160+
"*TEST test_func*",
3161+
"*TESTS finalizer hook called for my_fixture from test_func*",
3162+
"*ROOT finalizer hook called for my_fixture from test_func*",
3163+
])

0 commit comments

Comments
 (0)