From c46e2cbbc74caacfb04334d04dde5eb524bc24d6 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 15 Apr 2014 22:22:41 -0400 Subject: [PATCH] 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. --- CHANGELOG | 9 ++------- _pytest/python.py | 25 ++++++++++++++----------- testing/python/fixture.py | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 52bf9e503..dfdaa1c17 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ NEXT (2.6) ----------------------------------- +- Cache exceptions from fixtures according to their scope (issue 467). + - fix issue514: teach assertion reinterpretation about private class attributes - 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. -- 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 against tests or setups changing FD1/FD2, also better integrated now with pytest.pdb() in single tests. @@ -89,9 +87,6 @@ NEXT (2.6) functions, including unittest-style Classes. If set to False, the test will not be collected. -- fix issue512: show "" for arguments which might not be set - in monkeypatch plugin. Improves output in documentation. - 2.5.2 ----------------------------------- diff --git a/_pytest/python.py b/_pytest/python.py index 01925f539..dd3b7aaa3 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1337,7 +1337,7 @@ class FixtureRequest(FuncargnamesCompatAttr): except FixtureLookupError: if argname == "request": class PseudoFixtureDef: - cached_result = (self, [0]) + cached_result = (self, [0], None) return PseudoFixtureDef raise result = self._getfuncargvalue(fixturedef) @@ -1810,7 +1810,7 @@ class FixtureDef: kwargs = {} for argname in self.argnames: fixturedef = request._get_active_fixturedef(argname) - result, arg_cache_key = fixturedef.cached_result + result, arg_cache_key, exc = fixturedef.cached_result kwargs[argname] = result if argname != "request": fixturedef.addfinalizer(self.finish) @@ -1818,13 +1818,12 @@ class FixtureDef: my_cache_key = request.param_index cached_result = getattr(self, "cached_result", None) if cached_result is not None: - #print argname, "Found cached_result", cached_result - #print argname, "param_index", param_index - result, cache_key = cached_result + result, cache_key, err = cached_result if my_cache_key == cache_key: - #print request.fixturename, "CACHE HIT", repr(my_cache_key) - return result - #print request.fixturename, "CACHE MISS" + if err is not None: + py.builtin._reraise(*err) + else: + return result # we have a previous but differently parametrized fixture instance # so we need to tear it down before creating a new one self.finish() @@ -1841,9 +1840,13 @@ class FixtureDef: fixturefunc = getimfunc(self.func) if fixturefunc != self.func: fixturefunc = fixturefunc.__get__(request.instance) - result = call_fixture_func(fixturefunc, request, kwargs, - self.yieldctx) - self.cached_result = (result, my_cache_key) + try: + result = call_fixture_func(fixturefunc, request, kwargs, + self.yieldctx) + except Exception: + self.cached_result = (None, my_cache_key, sys.exc_info()) + raise + self.cached_result = (result, my_cache_key, None) return result def __repr__(self): diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 78a89dc0a..51354a57a 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1430,6 +1430,25 @@ class TestFixtureMarker: reprec = testdir.inline_run() 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): testdir.makepyfile(""" import pytest