2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/05ea7094569f/ Changeset: 05ea7094569f User: hpk42 Date: 2013-11-21 12:21:52 Summary: refactor internal FixtureRequest handling to avoid monkeypatching. One of the positive user-facing effects is that the "request" object can now be used in closures. Affected #: 3 files
diff -r dcbe5c70239930fafdc762d0a427f8814c1d4799 -r 05ea7094569fe3f3098fa8e965014d236bc3c907 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -65,6 +65,10 @@ - fix issue221 - handle importing of namespace-package with no __init__.py properly. +- refactor internal FixtureRequest handling to avoid monkeypatching. + One of the positive user-facing effects is that the "request" object + can now be used in closures. + Changes between 2.4.1 and 2.4.2 ----------------------------------- diff -r dcbe5c70239930fafdc762d0a427f8814c1d4799 -r 05ea7094569fe3f3098fa8e965014d236bc3c907 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -4,7 +4,6 @@ import sys import pytest from _pytest.mark import MarkDecorator -from _pytest.monkeypatch import monkeypatch from py._code.code import TerminalRepr import _pytest @@ -1083,14 +1082,12 @@ self.fixturename = None #: Scope string, one of "function", "cls", "module", "session" self.scope = "function" - self.getparent = pyfuncitem.getparent self._funcargs = self._pyfuncitem.funcargs.copy() - self._fixtureinfo = fi = pyfuncitem._fixtureinfo - self._arg2fixturedefs = fi.name2fixturedefs + fixtureinfo = pyfuncitem._fixtureinfo + self._arg2fixturedefs = fixtureinfo.name2fixturedefs self._arg2index = {} - self.fixturenames = self._fixtureinfo.names_closure + self.fixturenames = fixtureinfo.names_closure self._fixturemanager = pyfuncitem.session._fixturemanager - self._parentid = pyfuncitem.parent.nodeid self._fixturestack = [] @property @@ -1104,7 +1101,7 @@ # we arrive here because of a getfuncargvalue(argname) usage which # was naturally not knowable at parsing/collection time fixturedefs = self._fixturemanager.getfixturedefs( - argname, self._parentid) + argname, self._pyfuncitem.parent.nodeid) self._arg2fixturedefs[argname] = fixturedefs # fixturedefs is immutable so we maintain a decreasing index index = self._arg2index.get(argname, 0) - 1 @@ -1270,29 +1267,21 @@ return fixturedef.cached_result # set by fixturedef.execute() except AttributeError: pass - - # prepare request fixturename and param attributes before - # calling into fixture function + # prepare a subrequest object before calling fixture function + # (latter managed by fixturedef) argname = fixturedef.argname node = self._pyfuncitem - mp = monkeypatch() - mp.setattr(self, 'fixturename', argname) + scope = fixturedef.scope try: param = node.callspec.getparam(argname) except (AttributeError, ValueError): - pass + param = notset else: - mp.setattr(self, 'param', param, raising=False) - - # if a parametrize invocation set a scope it will override - # the static scope defined with the fixture function - scope = fixturedef.scope - try: - paramscopenum = node.callspec._arg2scopenum[argname] - except (KeyError, AttributeError): - pass - else: - if paramscopenum != scopenum_subfunction: + # if a parametrize invocation set a scope it will override + # the static scope defined with the fixture function + paramscopenum = node.callspec._arg2scopenum.get(argname) + if paramscopenum is not None and \ + paramscopenum != scopenum_subfunction: scope = scopes[paramscopenum] # check if a higher-level scoped fixture accesses a lower level one @@ -1302,31 +1291,29 @@ # try to report something helpful lines = self._factorytraceback() raise ScopeMismatchError("You tried to access the %r scoped " - "funcarg %r with a %r scoped request object, " + "fixture %r with a %r scoped request object, " "involved factories\n%s" %( (scope, argname, self.scope, "\n".join(lines)))) __tracebackhide__ = False - mp.setattr(self, "scope", scope) + else: + scope = self.scope - # route request.addfinalizer to fixturedef - mp.setattr(self, "addfinalizer", fixturedef.addfinalizer) - + subrequest = SubRequest(self, argname, scope, param, + fixturedef.addfinalizer) try: # perform the fixture call - val = fixturedef.execute(request=self) + val = fixturedef.execute(request=subrequest) finally: # if the fixture function failed it might still have # registered finalizers so we can register - # prepare finalization according to scope # (XXX analyse exact finalizing mechanics / cleanup) self.session._setupstate.addfinalizer(fixturedef.finish, - self.node) + subrequest.node) self._fixturemanager.addargfinalizer(fixturedef.finish, argname) for subargname in fixturedef.argnames: # XXX all deps? self._fixturemanager.addargfinalizer(fixturedef.finish, subargname) - mp.undo() return val def _factorytraceback(self): @@ -1358,6 +1345,28 @@ def __repr__(self): return "<FixtureRequest for %r>" %(self.node) +notset = object() +class SubRequest(FixtureRequest): + """ a sub request for handling getting a fixture from a + test function/fixture. """ + def __init__(self, request, argname, scope, param, addfinalizer): + self._parent_request = request + self.fixturename = argname + if param is not notset: + self.param = param + self.scope = scope + self.addfinalizer = addfinalizer + self._pyfuncitem = request._pyfuncitem + self._funcargs = request._funcargs + self._arg2fixturedefs = request._arg2fixturedefs + self._arg2index = request._arg2index + self.fixturenames = request.fixturenames + self._fixturemanager = request._fixturemanager + self._fixturestack = request._fixturestack + + def __repr__(self): + return "<SubRequest %r for %r>" % (self.fixturename, self.node) + class ScopeMismatchError(Exception): """ A fixture function tries to use a different fixture function which which has a lower scope (e.g. a Session one calls a function one) @@ -1399,8 +1408,8 @@ fm = self.request._fixturemanager available = [] for name, fixturedef in fm._arg2fixturedefs.items(): - faclist = list(fm._matchfactories(fixturedef, - self.request._parentid)) + parentid = self.request._pyfuncitem.parent.nodeid + faclist = list(fm._matchfactories(fixturedef, parentid)) if faclist: available.append(name) msg = "fixture %r not found" % (self.argname,) @@ -1744,7 +1753,6 @@ func() # check neccesity of next commented call self._fixturemanager.removefinalizer(self.finish) - #print "finished", self try: del self.cached_result except AttributeError: diff -r dcbe5c70239930fafdc762d0a427f8814c1d4799 -r 05ea7094569fe3f3098fa8e965014d236bc3c907 testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -473,7 +473,7 @@ assert l == ["module", "function", "class", "function", "method", "function"] """) - reprec = testdir.inline_run() + reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=3) def test_fixtures_sub_subdir_normalize_sep(self, testdir): @@ -1792,6 +1792,20 @@ for test in ['test_a', 'test_b', 'test_c']: assert reprec.matchreport(test).passed + def test_request_is_clean(self, testdir): + testdir.makepyfile(""" + import pytest + l = [] + @pytest.fixture(params=[1, 2]) + def fix(request): + request.addfinalizer(lambda: l.append(request.param)) + def test_fix(fix): + pass + """) + reprec = testdir.inline_run("-s") + l = reprec.getcalls("pytest_runtest_call")[0].item.module.l + assert l == [1,2] + def test_parametrize_separated_lifecycle(self, testdir): testdir.makepyfile(""" import pytest https://bitbucket.org/hpk42/pytest/commits/7c1e760976b2/ Changeset: 7c1e760976b2 User: hpk42 Date: 2013-11-21 12:31:22 Summary: fix py25 compat Affected #: 1 file diff -r 05ea7094569fe3f3098fa8e965014d236bc3c907 -r 7c1e760976b2e7097588da6d7d9cdc892330722f testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,3 +1,5 @@ +from __future__ import with_statement + import pytest, py, sys, os from _pytest import runner, main Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit