diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index 580ab2799..898ffee2a 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -115,6 +115,9 @@ pytest_pyfunc_call.firstresult = True def pytest_generate_tests(metafunc): """ generate (multiple) parametrized calls to a test function.""" +def pytest_configure_funcargs(request): + """ configure funcargs """ + # ------------------------------------------------------------------------- # generic runtest related hooks # ------------------------------------------------------------------------- diff --git a/_pytest/python.py b/_pytest/python.py index 5378dc095..f05aa1447 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -500,6 +500,16 @@ def fillfuncargs(function): request = FuncargRequest(pyfuncitem=function) request._fillfuncargs() +def pytest_configure_funcargs(request): + argnames = getfuncargnames(request.function) + if argnames: + item = request._pyfuncitem + assert not getattr(item, '_args', None), ( + "yielded functions cannot have funcargs") + for argname in argnames: + if argname not in item.funcargs: + item.funcargs[argname] = request.getfuncargvalue(argname) + _notexists = object() class CallSpec: def __init__(self, funcargs, id, param): @@ -621,14 +631,6 @@ class FuncargRequest: """ the file system path of the test module which collected this test. """ return self._pyfuncitem.fspath - def _fillfuncargs(self): - argnames = getfuncargnames(self.function) - if argnames: - assert not getattr(self._pyfuncitem, '_args', None), ( - "yielded functions cannot have funcargs") - for argname in argnames: - if argname not in self._pyfuncitem.funcargs: - self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname) def applymarker(self, marker): @@ -700,6 +702,9 @@ class FuncargRequest: self._currentarg = oldarg return res + def _fillfuncargs(self): + self.config.hook.pytest_configure_funcargs.pcall(self._plugins, request=self) + def _getscopeitem(self, scope): if scope == "function": return self._pyfuncitem diff --git a/doc/funcargs.txt b/doc/funcargs.txt index 1ccc3de41..996b59211 100644 --- a/doc/funcargs.txt +++ b/doc/funcargs.txt @@ -115,6 +115,9 @@ think of as "resources"). .. _`funcarg factory`: .. _factory: + + + The funcarg **request** object ============================================= @@ -140,6 +143,16 @@ factory and provides access to test configuration and context: .. _`parametrizing-tests`: .. _`parametrized test functions`: + +Reconfiguring funcargs in a test's setup +======================================== + +Sometimes there is need to do additional funcarg setup steps +which are outside of the normal setup and involve more than just one funcarg. +For that reason the ``pytest_configure_funcargs(request)`` hook +is called to implement and extend the funcarg filling mechanism. + + Parametrizing multiple calls to a test function =========================================================== diff --git a/testing/test_python.py b/testing/test_python.py index 5c5ced648..8675bca0f 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -606,6 +606,22 @@ class TestFillFuncArgs: fillfuncargs(item) assert len(item.funcargs) == 1 + def test_configure_hook(self, testdir): + item = testdir.getitem("def test_func(some, other=20): pass") + class Provider: + def pytest_funcarg__some(self, request): + return [] + def pytest_configure_funcargs(self, request): + request.getfuncargvalue('some').append(1) + item.config.pluginmanager.register(Provider()) + if hasattr(item, '_args'): + del item._args + from _pytest.python import fillfuncargs + fillfuncargs(item) + assert len(item.funcargs) == 1 + assert item.funcargs['some'] == [1] + + class TestRequest: def test_request_attributes(self, testdir): item = testdir.getitem("""