diff --git a/doc/test/funcargs.txt b/doc/test/funcargs.txt index 406ac9fce..29a574219 100644 --- a/doc/test/funcargs.txt +++ b/doc/test/funcargs.txt @@ -108,8 +108,6 @@ encapsulate a request for a function argument for a specific test function. Request objects allow providers to access test configuration and test context: -``request.argname``: name of the requested function argument - ``request.function``: python function object requesting the argument ``request.cls``: class object where the test function is defined in or None. @@ -120,6 +118,7 @@ to access test configuration and test context: ``request.param``: if exists was passed by a `parametrizing test generator`_ + perform scoped setup and teardown --------------------------------------------- @@ -171,6 +170,21 @@ object that is to be closed when the test function finishes. request.addfinalizer(lambda: myfile.close()) return myfile +requesting values of other funcargs +--------------------------------------------- + +While setting up one function argument you may +want to retrieve another function argument. + +.. sourcecode:: python + + def getfuncargvalue(name): + """ Lookup and call function argument provider for the given name. + Each function argument is only requested once per function setup. + """ + +Note that it does not matter if the test function +specifies the requested function argument. decorating other funcarg providers ++++++++++++++++++++++++++++++++++++++++ diff --git a/py/test/funcargs.py b/py/test/funcargs.py index ef70b442e..dbe4f7075 100644 --- a/py/test/funcargs.py +++ b/py/test/funcargs.py @@ -10,16 +10,8 @@ def getfuncargnames(function): def fillfuncargs(function): """ fill missing funcargs. """ - argnames = getfuncargnames(function.obj) - if argnames: - assert not function._args, "yielded functions cannot have funcargs" - for argname in argnames: - if argname not in function.funcargs: - request = FuncargRequest(pyfuncitem=function, argname=argname) - try: - function.funcargs[argname] = request.call_next_provider() - except request.Error: - request._raiselookupfailed() + request = FuncargRequest(pyfuncitem=function) + request._fillfuncargs() _notexists = object() @@ -78,13 +70,13 @@ class FunctionCollector(py.test.collect.Collector): class FuncargRequest: _argprefix = "pytest_funcarg__" + _argname = None class Error(LookupError): """ error on performing funcarg request. """ - def __init__(self, pyfuncitem, argname): + def __init__(self, pyfuncitem): self._pyfuncitem = pyfuncitem - self.argname = argname self.function = pyfuncitem.obj self.module = pyfuncitem.getparent(py.test.collect.Module).obj self.cls = getattr(self.function, 'im_class', None) @@ -97,14 +89,20 @@ class FuncargRequest: self._plugins.append(self.module) if self.instance is not None: self._plugins.append(self.instance) - self._provider = self.config.pluginmanager.listattr( - plugins=self._plugins, - attrname=self._argprefix + str(argname) - ) + self._funcargs = self._pyfuncitem.funcargs.copy() + self._provider = {} + + def _fillfuncargs(self): + argnames = getfuncargnames(self.function) + if argnames: + assert not self._pyfuncitem._args, "yielded functions cannot have funcargs" + for argname in argnames: + if argname not in self._pyfuncitem.funcargs: + self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname) def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): if not hasattr(self.config, '_setupcache'): - self.config._setupcache = {} + self.config._setupcache = {} # XXX weakref? cachekey = (self._getscopeitem(scope), extrakey) cache = self.config._setupcache try: @@ -117,11 +115,34 @@ class FuncargRequest: return val def call_next_provider(self): - if not self._provider: - raise self.Error("no provider methods left") - next_provider = self._provider.pop() + if not self._provider[self._argname]: + raise self.Error("no provider methods left for %r" % self._argname) + next_provider = self._provider[self._argname].pop() return next_provider(request=self) + def getfuncargvalue(self, argname): + try: + return self._funcargs[argname] + except KeyError: + pass + assert argname not in self._provider + self._provider[argname] = self.config.pluginmanager.listattr( + plugins=self._plugins, + attrname=self._argprefix + str(argname) + ) + # during call_next_provider() we keep state about the current + # argument on the request object - we may go for instantiating + # request objects per each funcargname if neccessary + oldname = self._argname + self._argname = argname + try: + self._funcargs[argname] = res = self.call_next_provider() + except self.Error: + self._raiselookupfailed(argname) + if oldname: + self._argname = oldname + return res + def _getscopeitem(self, scope): if scope == "function": return self._pyfuncitem @@ -134,9 +155,9 @@ class FuncargRequest: self.config._setupstate.addfinalizer(finalizer=finalizer, colitem=colitem) def __repr__(self): - return "" %(self.argname, self._pyfuncitem) + return "" %(self._pyfuncitem) - def _raiselookupfailed(self): + def _raiselookupfailed(self, argname): available = [] for plugin in self._plugins: for name in vars(plugin): @@ -146,7 +167,7 @@ class FuncargRequest: available.append(name) fspath, lineno, msg = self._pyfuncitem.reportinfo() line = "%s:%s" %(fspath, lineno) - msg = "funcargument %r not found for: %s" %(self.argname, line) + msg = "funcargument %r not found for: %s" %(argname, line) msg += "\n available funcargs: %s" %(", ".join(available),) raise LookupError(msg) diff --git a/py/test/plugin/pytest_tmpdir.py b/py/test/plugin/pytest_tmpdir.py index 7c77eb308..bc6cbc61e 100644 --- a/py/test/plugin/pytest_tmpdir.py +++ b/py/test/plugin/pytest_tmpdir.py @@ -27,7 +27,7 @@ def test_generic(plugintester): def test_funcarg(testdir): from py.__.test.funcargs import FuncargRequest item = testdir.getitem("def test_func(tmpdir): pass") - p = pytest_funcarg__tmpdir(FuncargRequest(item, "tmpdir")) + p = pytest_funcarg__tmpdir(FuncargRequest(item)) assert p.check() bn = p.basename.strip("0123456789-") assert bn.endswith("test_func") diff --git a/py/test/testing/test_funcargs.py b/py/test/testing/test_funcargs.py index 6b3940826..5ffd86e9b 100644 --- a/py/test/testing/test_funcargs.py +++ b/py/test/testing/test_funcargs.py @@ -85,8 +85,7 @@ class TestRequest: def pytest_funcarg__something(request): pass def test_func(something): pass """) - req = funcargs.FuncargRequest(item, argname="other") - assert req.argname == "other" + req = funcargs.FuncargRequest(item) assert req.function == item.obj assert hasattr(req.module, 'test_func') assert req.cls is None @@ -100,46 +99,86 @@ class TestRequest: def test_func(self, something): pass """) - req = funcargs.FuncargRequest(item, argname="something") + req = funcargs.FuncargRequest(item) assert req.cls.__name__ == "TestB" assert req.instance.__class__ == req.cls - - def test_request_contains_funcargs_provider(self, testdir): + + def XXXtest_request_contains_funcargs_provider(self, testdir): modcol = testdir.getmodulecol(""" def pytest_funcarg__something(request): pass class TestClass: def test_method(self, something): - pass + pass """) item1, = testdir.genitems([modcol]) assert item1.name == "test_method" - provider = funcargs.FuncargRequest(item1, "something")._provider + provider = funcargs.FuncargRequest(item1)._provider assert len(provider) == 1 assert provider[0].__name__ == "pytest_funcarg__something" def test_request_call_next_provider(self, testdir): item = testdir.getitem(""" - def pytest_funcarg__something(request): pass + def pytest_funcarg__something(request): return 1 def test_func(something): pass """) - req = funcargs.FuncargRequest(item, "something") - val = req.call_next_provider() - assert val is None + req = funcargs.FuncargRequest(item) + val = req.getfuncargvalue("something") + assert val == 1 py.test.raises(req.Error, "req.call_next_provider()") + def test_getfuncargvalue(self, testdir): + item = testdir.getitem(""" + l = [2] + def pytest_funcarg__something(request): return 1 + def pytest_funcarg__other(request): + return l.pop() + def test_func(something): pass + """) + req = funcargs.FuncargRequest(item) + val = req.getfuncargvalue("something") + assert val == 1 + val = req.getfuncargvalue("something") + assert val == 1 + val2 = req.getfuncargvalue("other") + assert val2 == 2 + val2 = req.getfuncargvalue("other") # see about caching + assert val2 == 2 + req._fillfuncargs() + assert item.funcargs == {'something': 1} + def test_request_addfinalizer(self, testdir): item = testdir.getitem(""" def pytest_funcarg__something(request): pass def test_func(something): pass """) - req = funcargs.FuncargRequest(item, "something") + req = funcargs.FuncargRequest(item) py.test.raises(ValueError, "req.addfinalizer(None, scope='xyz')") l = [1] req.addfinalizer(l.pop) req.config._setupstate._teardown(item) assert not l + def test_request_getmodulepath(self, testdir): + modcol = testdir.getmodulecol("def test_somefunc(): pass") + item, = testdir.genitems([modcol]) + req = funcargs.FuncargRequest(item) + assert req.fspath == modcol.fspath + +class TestRequestProtocol: + @py.test.mark.xfail + def test_protocol(self, testdir): + item = testdir.getitem(""" + def pytest_funcarg_arg1(request): return 1 + def pytest_funcarg_arg2(request): return 2 + def test_func(arg1, arg2): pass + """) + req = funcargs.FuncargRequest(item) + req._fillargs() + #assert item.funcreq. + + +class TestRequestCachedSetup: def test_request_cachedsetup(self, testdir): item1,item2 = testdir.getitems(""" class TestClass: @@ -148,7 +187,7 @@ class TestRequest: def test_func2(self, something): pass """) - req1 = funcargs.FuncargRequest(item1, "something") + req1 = funcargs.FuncargRequest(item1) l = ["hello"] def setup(): return l.pop() @@ -156,13 +195,13 @@ class TestRequest: assert ret1 == "hello" ret1b = req1.cached_setup(setup) assert ret1 == ret1b - req2 = funcargs.FuncargRequest(item2, "something") + req2 = funcargs.FuncargRequest(item2) ret2 = req2.cached_setup(setup) assert ret2 == ret1 def test_request_cachedsetup_extrakey(self, testdir): item1 = testdir.getitem("def test_func(): pass") - req1 = funcargs.FuncargRequest(item1, "something") + req1 = funcargs.FuncargRequest(item1) l = ["hello", "world"] def setup(): return l.pop() @@ -202,11 +241,6 @@ class TestRequest: "*3 passed*" ]) - def test_request_getmodulepath(self, testdir): - modcol = testdir.getmodulecol("def test_somefunc(): pass") - item, = testdir.genitems([modcol]) - req = funcargs.FuncargRequest(item, "xxx") - assert req.fspath == modcol.fspath class TestMetafunc: def test_no_funcargs(self, testdir): diff --git a/py/test/testing/test_parseopt.py b/py/test/testing/test_parseopt.py index 756fe0523..c608f9df5 100644 --- a/py/test/testing/test_parseopt.py +++ b/py/test/testing/test_parseopt.py @@ -4,10 +4,10 @@ from py.__.test import parseopt pytest_plugins = 'pytest_iocapture' class TestParser: - def test_init(self, stdcapture): + def test_init(self, capsys): parser = parseopt.Parser(usage="xyz") py.test.raises(SystemExit, 'parser.parse(["-h"])') - out, err = stdcapture.reset() + out, err = capsys.reset() assert out.find("xyz") != -1 def test_group_add_and_get(self):