Cache exception raised in fixtures according to their scope

Without this if a session scoped fixture fails it's setup it will
be re-tried each time it is requested.  Especially in case of
skip or failure exceptions this can be undesirable, but caching
makes sense for all exceptions.
This commit is contained in:
Floris Bruynooghe 2014-04-15 22:22:41 -04:00
parent 65a145e2a7
commit c46e2cbbc7
3 changed files with 35 additions and 18 deletions

View File

@ -1,6 +1,8 @@
NEXT (2.6) NEXT (2.6)
----------------------------------- -----------------------------------
- Cache exceptions from fixtures according to their scope (issue 467).
- fix issue514: teach assertion reinterpretation about private class attributes - fix issue514: teach assertion reinterpretation about private class attributes
- change -v output to include full node IDs of tests. Users can copy - change -v output to include full node IDs of tests. Users can copy
@ -59,10 +61,6 @@ NEXT (2.6)
- fix issue483: trial/py33 works now properly. Thanks Daniel Grana for PR. - fix issue483: trial/py33 works now properly. Thanks Daniel Grana for PR.
- improve example for pytest integration with "python setup.py test"
which now has a generic "-a" or "--pytest-args" option where you
can pass additional options as a quoted string. Thanks Trevor Bekolay.
- simplified internal capturing mechanism and made it more robust - simplified internal capturing mechanism and made it more robust
against tests or setups changing FD1/FD2, also better integrated against tests or setups changing FD1/FD2, also better integrated
now with pytest.pdb() in single tests. now with pytest.pdb() in single tests.
@ -89,9 +87,6 @@ NEXT (2.6)
functions, including unittest-style Classes. If set to False, the functions, including unittest-style Classes. If set to False, the
test will not be collected. test will not be collected.
- fix issue512: show "<notset>" for arguments which might not be set
in monkeypatch plugin. Improves output in documentation.
2.5.2 2.5.2
----------------------------------- -----------------------------------

View File

@ -1337,7 +1337,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
except FixtureLookupError: except FixtureLookupError:
if argname == "request": if argname == "request":
class PseudoFixtureDef: class PseudoFixtureDef:
cached_result = (self, [0]) cached_result = (self, [0], None)
return PseudoFixtureDef return PseudoFixtureDef
raise raise
result = self._getfuncargvalue(fixturedef) result = self._getfuncargvalue(fixturedef)
@ -1810,7 +1810,7 @@ class FixtureDef:
kwargs = {} kwargs = {}
for argname in self.argnames: for argname in self.argnames:
fixturedef = request._get_active_fixturedef(argname) fixturedef = request._get_active_fixturedef(argname)
result, arg_cache_key = fixturedef.cached_result result, arg_cache_key, exc = fixturedef.cached_result
kwargs[argname] = result kwargs[argname] = result
if argname != "request": if argname != "request":
fixturedef.addfinalizer(self.finish) fixturedef.addfinalizer(self.finish)
@ -1818,13 +1818,12 @@ class FixtureDef:
my_cache_key = request.param_index my_cache_key = request.param_index
cached_result = getattr(self, "cached_result", None) cached_result = getattr(self, "cached_result", None)
if cached_result is not None: if cached_result is not None:
#print argname, "Found cached_result", cached_result result, cache_key, err = cached_result
#print argname, "param_index", param_index
result, cache_key = cached_result
if my_cache_key == cache_key: if my_cache_key == cache_key:
#print request.fixturename, "CACHE HIT", repr(my_cache_key) if err is not None:
py.builtin._reraise(*err)
else:
return result return result
#print request.fixturename, "CACHE MISS"
# we have a previous but differently parametrized fixture instance # we have a previous but differently parametrized fixture instance
# so we need to tear it down before creating a new one # so we need to tear it down before creating a new one
self.finish() self.finish()
@ -1841,9 +1840,13 @@ class FixtureDef:
fixturefunc = getimfunc(self.func) fixturefunc = getimfunc(self.func)
if fixturefunc != self.func: if fixturefunc != self.func:
fixturefunc = fixturefunc.__get__(request.instance) fixturefunc = fixturefunc.__get__(request.instance)
try:
result = call_fixture_func(fixturefunc, request, kwargs, result = call_fixture_func(fixturefunc, request, kwargs,
self.yieldctx) self.yieldctx)
self.cached_result = (result, my_cache_key) except Exception:
self.cached_result = (None, my_cache_key, sys.exc_info())
raise
self.cached_result = (result, my_cache_key, None)
return result return result
def __repr__(self): def __repr__(self):

View File

@ -1430,6 +1430,25 @@ class TestFixtureMarker:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=3) reprec.assertoutcome(passed=3)
def test_scope_session_exc(self, testdir):
testdir.makepyfile("""
import pytest
l = []
@pytest.fixture(scope="session")
def fix():
l.append(1)
pytest.skip('skipping')
def test_1(fix):
pass
def test_2(fix):
pass
def test_last():
assert l == [1]
""")
reprec = testdir.inline_run()
reprec.assertoutcome(skipped=2, passed=1)
def test_scope_module_uses_session(self, testdir): def test_scope_module_uses_session(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest