Skip to content

Commit 032ce8b

Browse files
Switch setuponly and setupplan options to a hook-based implementation.
1 parent c6af737 commit 032ce8b

File tree

5 files changed

+117
-75
lines changed

5 files changed

+117
-75
lines changed

_pytest/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class UsageError(Exception):
6565
default_plugins = (
6666
"mark main terminal runner python pdb unittest capture skipping "
6767
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
68-
"junitxml resultlog doctest cacheprovider").split()
68+
"junitxml resultlog doctest cacheprovider setuponly setupplan").split()
6969

7070
builtin_plugins = set(default_plugins)
7171
builtin_plugins.add("pytester")

_pytest/hookspec.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,19 @@ def pytest_runtest_logreport(report):
218218
""" process a test setup/call/teardown report relating to
219219
the respective phase of executing a test. """
220220

221+
# -------------------------------------------------------------------------
222+
# Fixture related hooks
223+
# -------------------------------------------------------------------------
224+
225+
@hookspec(firstresult=True)
226+
def pytest_fixture_setup(fixturedef, request):
227+
""" performs fixture setup execution. """
228+
229+
def pytest_fixture_post_finalizer(fixturedef):
230+
""" called after fixture teardown, but before the cache is cleared so
231+
the fixture result cache ``fixturedef.cached_result`` can
232+
still be accessed."""
233+
221234
# -------------------------------------------------------------------------
222235
# test session related hooks
223236
# -------------------------------------------------------------------------

_pytest/python.py

Lines changed: 35 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,25 +2475,18 @@ def finish(self):
24752475
func = self._finalizer.pop()
24762476
func()
24772477
finally:
2478+
ihook = self._fixturemanager.session.ihook
2479+
ihook.pytest_fixture_post_finalizer(fixturedef=self)
24782480
# even if finalization fails, we invalidate
24792481
# the cached fixture value
24802482
if hasattr(self, "cached_result"):
2481-
config = self._fixturemanager.config
2482-
if config.option.setuponly or config.option.setupplan:
2483-
self._show_fixture_action('TEARDOWN')
2484-
if hasattr(self, "cached_param"):
2485-
del self.cached_param
24862483
del self.cached_result
24872484

24882485
def execute(self, request):
24892486
# get required arguments and register our own finish()
24902487
# with their finalization
2491-
kwargs = {}
24922488
for argname in self.argnames:
24932489
fixturedef = request._get_active_fixturedef(argname)
2494-
result, arg_cache_key, exc = fixturedef.cached_result
2495-
request._check_scope(argname, request.scope, fixturedef.scope)
2496-
kwargs[argname] = result
24972490
if argname != "request":
24982491
fixturedef.addfinalizer(self.finish)
24992492

@@ -2511,76 +2504,44 @@ def execute(self, request):
25112504
self.finish()
25122505
assert not hasattr(self, "cached_result")
25132506

2514-
fixturefunc = self.func
2515-
2516-
if self.unittest:
2517-
if request.instance is not None:
2518-
# bind the unbound method to the TestCase instance
2519-
fixturefunc = self.func.__get__(request.instance)
2520-
else:
2521-
# the fixture function needs to be bound to the actual
2522-
# request.instance so that code working with "self" behaves
2523-
# as expected.
2524-
if request.instance is not None:
2525-
fixturefunc = getimfunc(self.func)
2526-
if fixturefunc != self.func:
2527-
fixturefunc = fixturefunc.__get__(request.instance)
2528-
2529-
try:
2530-
config = request.config
2531-
if config.option.setupplan:
2532-
result = None
2533-
else:
2534-
result = call_fixture_func(fixturefunc, request, kwargs)
2535-
if config.option.setuponly or config.option.setupplan:
2536-
if hasattr(request, 'param'):
2537-
# Save the fixture parameter so ._show_fixture_action() can
2538-
# display it now and during the teardown (in .finish()).
2539-
if self.ids:
2540-
if callable(self.ids):
2541-
self.cached_param = self.ids(request.param)
2542-
else:
2543-
self.cached_param = self.ids[request.param_index]
2544-
else:
2545-
self.cached_param = request.param
2546-
self._show_fixture_action('SETUP')
2547-
except Exception:
2548-
self.cached_result = (None, my_cache_key, sys.exc_info())
2549-
raise
2550-
self.cached_result = (result, my_cache_key, None)
2551-
return result
2552-
2553-
def _show_fixture_action(self, what):
2554-
config = self._fixturemanager.config
2555-
capman = config.pluginmanager.getplugin('capturemanager')
2556-
if capman:
2557-
out, err = capman.suspendcapture()
2558-
2559-
tw = config.get_terminal_writer()
2560-
tw.line()
2561-
tw.write(' ' * 2 * self.scopenum)
2562-
tw.write('{step} {scope} {fixture}'.format(
2563-
step=what.ljust(8), # align the output to TEARDOWN
2564-
scope=self.scope[0].upper(),
2565-
fixture=self.argname))
2566-
2567-
if what == 'SETUP':
2568-
deps = sorted(arg for arg in self.argnames if arg != 'request')
2569-
if deps:
2570-
tw.write(' (fixtures used: {0})'.format(', '.join(deps)))
2571-
2572-
if hasattr(self, 'cached_param'):
2573-
tw.write('[{0}]'.format(self.cached_param))
2574-
2575-
if capman:
2576-
capman.resumecapture()
2577-
sys.stdout.write(out)
2578-
sys.stderr.write(err)
2507+
ihook = self._fixturemanager.session.ihook
2508+
ihook.pytest_fixture_setup(fixturedef=self, request=request)
25792509

25802510
def __repr__(self):
25812511
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
25822512
(self.argname, self.scope, self.baseid))
25832513

2514+
def pytest_fixture_setup(fixturedef, request):
2515+
""" Execution of fixture setup. """
2516+
kwargs = {}
2517+
for argname in fixturedef.argnames:
2518+
fixdef = request._get_active_fixturedef(argname)
2519+
result, arg_cache_key, exc = fixdef.cached_result
2520+
request._check_scope(argname, request.scope, fixdef.scope)
2521+
kwargs[argname] = result
2522+
2523+
fixturefunc = fixturedef.func
2524+
if fixturedef.unittest:
2525+
if request.instance is not None:
2526+
# bind the unbound method to the TestCase instance
2527+
fixturefunc = fixturedef.func.__get__(request.instance)
2528+
else:
2529+
# the fixture function needs to be bound to the actual
2530+
# request.instance so that code working with "fixturedef" behaves
2531+
# as expected.
2532+
if request.instance is not None:
2533+
fixturefunc = getimfunc(fixturedef.func)
2534+
if fixturefunc != fixturedef.func:
2535+
fixturefunc = fixturefunc.__get__(request.instance)
2536+
my_cache_key = request.param_index
2537+
try:
2538+
result = call_fixture_func(fixturefunc, request, kwargs)
2539+
except Exception:
2540+
fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
2541+
raise
2542+
fixturedef.cached_result = (result, my_cache_key, None)
2543+
return result
2544+
25842545
def num_mock_patch_args(function):
25852546
""" return number of arguments used up by mock arguments (if any) """
25862547
patchings = getattr(function, "patchings", None)

_pytest/setuponly.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import pytest
2+
import sys
3+
4+
@pytest.hookimpl(hookwrapper=True)
5+
def pytest_fixture_setup(fixturedef, request):
6+
yield
7+
config = request.config
8+
if config.option.setuponly:
9+
if hasattr(request, 'param'):
10+
# Save the fixture parameter so ._show_fixture_action() can
11+
# display it now and during the teardown (in .finish()).
12+
if fixturedef.ids:
13+
if callable(fixturedef.ids):
14+
fixturedef.cached_param = fixturedef.ids(request.param)
15+
else:
16+
fixturedef.cached_param = fixturedef.ids[request.param_index]
17+
else:
18+
fixturedef.cached_param = request.param
19+
_show_fixture_action(fixturedef, 'SETUP')
20+
21+
def pytest_fixture_post_finalizer(fixturedef):
22+
if hasattr(fixturedef, "cached_result"):
23+
config = fixturedef._fixturemanager.config
24+
if config.option.setuponly:
25+
_show_fixture_action(fixturedef, 'TEARDOWN')
26+
if hasattr(fixturedef, "cached_param"):
27+
del fixturedef.cached_param
28+
29+
def _show_fixture_action(fixturedef, msg):
30+
config = fixturedef._fixturemanager.config
31+
capman = config.pluginmanager.getplugin('capturemanager')
32+
if capman:
33+
out, err = capman.suspendcapture()
34+
35+
tw = config.get_terminal_writer()
36+
tw.line()
37+
tw.write(' ' * 2 * fixturedef.scopenum)
38+
tw.write('{step} {scope} {fixture}'.format(
39+
step=msg.ljust(8), # align the output to TEARDOWN
40+
scope=fixturedef.scope[0].upper(),
41+
fixture=fixturedef.argname))
42+
43+
if msg == 'SETUP':
44+
deps = sorted(arg for arg in fixturedef.argnames if arg != 'request')
45+
if deps:
46+
tw.write(' (fixtures used: {0})'.format(', '.join(deps)))
47+
48+
if hasattr(fixturedef, 'cached_param'):
49+
tw.write('[{0}]'.format(fixturedef.cached_param))
50+
51+
if capman:
52+
capman.resumecapture()
53+
sys.stdout.write(out)
54+
sys.stderr.write(err)

_pytest/setupplan.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import pytest
2+
3+
4+
@pytest.hookimpl(tryfirst=True)
5+
def pytest_fixture_setup(fixturedef, request):
6+
# Will return a dummy fixture if the setuponly option is provided.
7+
if request.config.option.setupplan:
8+
fixturedef.cached_result = (None, None, None)
9+
return fixturedef.cached_result
10+
11+
@pytest.hookimpl(tryfirst=True)
12+
def pytest_cmdline_main(config):
13+
if config.option.setupplan:
14+
config.option.setuponly = True

0 commit comments

Comments
 (0)