diff --git a/CHANGELOG b/CHANGELOG index 53e6437ae..1511229c8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,20 @@ ----------------------------- +2.7.1.dev (compared to 2.7.0) +----------------------------- + +- fix issue660: properly report scope-mismatch-access errors + independently from ordering of fixture arguments. Also + avoid the pytest internal traceback which does not provide + information to the user. Thanks Holger Krekel. + +- streamlined and documented release process. Also all versions + (in setup.py and documentation generation) are now read + from _pytest/__init__.py. Thanks Holger Krekel. + +- fixed docs to remove the notion that yield-fixtures are experimental. + They are here to stay :) Thanks Bruno Oliveira. - Support building wheels by using environment markers for the requirements. Thanks Ionel Maries Cristian. diff --git a/_pytest/python.py b/_pytest/python.py index 5766768ed..292054a0c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1356,12 +1356,7 @@ class FixtureRequest(FuncargnamesCompatAttr): try: val = cache[cachekey] except KeyError: - __tracebackhide__ = True - if scopemismatch(self.scope, scope): - raise ScopeMismatchError("You tried to access a %r scoped " - "resource with a %r scoped request object" %( - (scope, self.scope))) - __tracebackhide__ = False + self._check_scope(self.fixturename, self.scope, scope) val = setup() cache[cachekey] = val if teardown is not None: @@ -1392,6 +1387,7 @@ class FixtureRequest(FuncargnamesCompatAttr): if argname == "request": class PseudoFixtureDef: cached_result = (self, [0], None) + scope = "function" return PseudoFixtureDef raise # remove indent to prevent the python3 exception @@ -1435,16 +1431,7 @@ class FixtureRequest(FuncargnamesCompatAttr): subrequest = SubRequest(self, scope, param, param_index, fixturedef) # check if a higher-level scoped fixture accesses a lower level one - if scope is not None: - __tracebackhide__ = True - if scopemismatch(self.scope, scope): - # try to report something helpful - lines = subrequest._factorytraceback() - raise ScopeMismatchError("You tried to access the %r scoped " - "fixture %r with a %r scoped request object, " - "involved factories\n%s" %( - (scope, argname, self.scope, "\n".join(lines)))) - __tracebackhide__ = False + subrequest._check_scope(argname, self.scope, scope) # clear sys.exc_info before invoking the fixture (python bug?) # if its not explicitly cleared it will leak into the call @@ -1458,6 +1445,18 @@ class FixtureRequest(FuncargnamesCompatAttr): subrequest.node) return val + def _check_scope(self, argname, invoking_scope, requested_scope): + if argname == "request": + return + if scopemismatch(invoking_scope, requested_scope): + # try to report something helpful + lines = self._factorytraceback() + pytest.fail("ScopeMismatch: you tried to access the %r scoped " + "fixture %r with a %r scoped request object, " + "involved factories\n%s" %( + (requested_scope, argname, invoking_scope, "\n".join(lines))), + pytrace=False) + def _factorytraceback(self): lines = [] for fixturedef in self._get_fixturestack(): @@ -1518,6 +1517,7 @@ scopenum_function = scopes.index("function") def scopemismatch(currentscope, newscope): return scopes.index(newscope) > scopes.index(currentscope) + class FixtureLookupError(LookupError): """ could not return a requested Fixture (missing or invalid). """ def __init__(self, argname, request, msg=None): @@ -1867,6 +1867,7 @@ class FixtureDef: for argname in self.argnames: fixturedef = request._get_active_fixturedef(argname) result, arg_cache_key, exc = fixturedef.cached_result + request._check_scope(argname, request.scope, fixturedef.scope) kwargs[argname] = result if argname != "request": fixturedef.addfinalizer(self.finish) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 1de2de920..ef43744d5 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -906,6 +906,27 @@ class TestFixtureUsages: "*1 error*" ]) + def test_receives_funcargs_scope_mismatch_issue660(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope="function") + def arg1(): + return 1 + + @pytest.fixture(scope="module") + def arg2(arg1): + return arg1 + 1 + + def test_add(arg1, arg2): + assert arg2 == 2 + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*ScopeMismatch*involved factories*", + "* def arg2*", + "*1 error*" + ]) + def test_funcarg_parametrized_and_used_twice(self, testdir): testdir.makepyfile(""" import pytest