diff --git a/_pytest/capture.py b/_pytest/capture.py index f260e2413..e948713e5 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -187,7 +187,7 @@ def pytest_funcarg__capsys(request): captured output available via ``capsys.readouterr()`` method calls which return a ``(out, err)`` tuple. """ - if "capfd" in request.funcargs: + if "capfd" in request._funcargs: raise request.LookupError(error_capsysfderror) return CaptureFuncarg(py.io.StdCapture) @@ -196,7 +196,7 @@ def pytest_funcarg__capfd(request): captured output available via ``capsys.readouterr()`` method calls which return a ``(out, err)`` tuple. """ - if "capsys" in request.funcargs: + if "capsys" in request._funcargs: raise request.LookupError(error_capsysfderror) if not hasattr(os, 'dup'): pytest.skip("capfd funcarg needs os.dup") diff --git a/_pytest/impl b/_pytest/impl new file mode 100644 index 000000000..be79a6819 --- /dev/null +++ b/_pytest/impl @@ -0,0 +1,97 @@ + +Implementation plan for resources +------------------------------------------ + +1. Revert FuncargRequest to the old form, unmerge item/request +2. make setup functions be discovered at collection time +3. make funcarg factories be discovered at collection time +4. Introduce funcarg marker +5. Introduce funcarg scope parameter +6. Introduce funcarg parametrize parameter +7. (Introduce a pytest_fixture_protocol/setup_funcargs hook) + +methods and data structures +-------------------------------- + +A FuncarcDB holds all information about funcarg definitions, +parametrization and the places where funcargs are required. It can +answer the following questions: + +* given a node and a funcargname, return a paramlist so that collection + can perform parametrization (parametrized nodes?) +* given a node (possibly containing a param), perform a funcargrequest + and return the value +* if funcargname is an empty string, it matches general setup. + +pytest could perform 2-pass collection: +- first perform normal collection (no parametrization at all!), populate + FuncargDB +- walk through the node tree and ask FuncargDB for each node for + required funcargs and their parameters - clone subtrees (deepcopy) and + substitute the un-parametrized node with parametrized ones + +as a simple example, let's consider a tree where a test function requires +a "abc" funcarg and its factory defines it as parametrized and scoped +for Modules. When the 2nd collection pass asks FuncargDB to return +params for the test module, it will know that the test functions in it +requires "abc" and that is it parametrized and defined for module scope. +Therefore parametrization of the module node is performed, substituting +the node with multiple module nodes ("test_module.py[1]", ...). +When test_module.py[1] is setup() it will call all its (parametrized) +factories and populate a funcargs dictionary, mapping funcargnames to values. +When a test function below test_module.py[1] is executed, it looks up +its required arguments from the thus populated funcargs dictionary. + +Let's add to this example a second funcarg "def" that has a per-function parametrization. When the 2nd collection pass asks FuncargDB to return +params for the test function, it will know that the test functions in it +requires "def" and that is it parametrized and defined for function scope. +Therefore parametrization of the function node is performed, substituting +the node with multiple function nodes ("test_function[1]", ...). + +When test_function[1] is setup() it will call all its (parametrized) +factories and populate a funcargs dictionary. The "def" will only appear +in the funcargs dict seen by test_function[1]. When test_function[1] +executes, it will use its funcargs. + + + + +where + +* ``nodeidbase`` is a basestring; for all nodeids matching + startswith(nodeidbase) it defines a (scopecls, factorylist) tuple +* ``scopecls`` is a node class for the which the factorylist s defined +* ``param`` is a parametrizing parameter for the factorylist +* ``factorylist`` is a list of factories which will be used to perform + a funcarg request +* the whole list is sorted by length of nodeidbase (longest first) + +conftest loading: + each funcarg-factory will populate FuncargDefs which keeps references + to all definitions the funcarg2 marked function or pytest_funcarg__ + + +scope can be a string or a nodenames-tuple. + + scopestring -> list of (funcargname, factorylist) + + nodenames -> (funcargname, list of factories) + +It needs to be a list because factories can decorate + +For any given node and a required funcarg it is thus +easy to lookup a list of matching factories. + +When a test item is collected, it grows a dictionary +(funcargname2factorycalllist). A factory lookup is performed +for each required funcarg. The resulting factory call is stored +with the item. If a function is parametrized multiple items are +created with respective factory calls. Else if a factory is parametrized +multiple items and calls to the factory function are created as well. + +At setup time, an item populates a funcargs mapping, mapping names +to values. If a value is funcarg factories are queried for a given item +test functions and setup functions are put in a class +which looks up required funcarg factories. + + diff --git a/_pytest/main.py b/_pytest/main.py index 5e9e38bc8..a51d02966 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -177,11 +177,11 @@ class Node(object): #: fspath sensitive hook proxy used to call pytest hooks self.ihook = self.session.gethookproxy(self.fspath) - self.extrainit() + #self.extrainit() - def extrainit(self): - """"extra initialization after Node is initialized. Implemented - by some subclasses. """ + #def extrainit(self): + # """"extra initialization after Node is initialized. Implemented + # by some subclasses. """ Module = compatproperty("Module") Class = compatproperty("Class") diff --git a/_pytest/python.py b/_pytest/python.py index 2b3a4b06d..230d1cbfb 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -33,126 +33,6 @@ def pyobj_property(name): name.lower(),) return property(get, None, None, doc) -class Request(object): - _argprefix = "pytest_funcarg__" - - class LookupError(LookupError): - """ error while performing funcarg factory lookup. """ - - def extrainit(self): - self._name2factory = {} - self._currentarg = None - self.funcargs = None # later set to a dict from fillfuncargs() or - # from getfuncargvalue(). Setting it to - # None prevents users from performing - # "name in item.funcargs" checks too early. - - @property - def _plugins(self): - extra = [obj for obj in (self.module, self.instance) if obj] - return self.getplugins() + extra - - def _getscopeitem(self, scope): - if scope == "function": - return self - elif scope == "session": - return None - elif scope == "class": - x = self.getparent(pytest.Class) - if x is not None: - return x - scope = "module" - if scope == "module": - return self.getparent(pytest.Module) - raise ValueError("unknown scope %r" %(scope,)) - - def getfuncargvalue(self, argname): - """ Retrieve a named function argument value. - - This function looks up a matching factory and invokes - it to obtain the return value. The factory receives - can itself perform recursive calls to this method, - either for using multiple other funcarg values under the hood - or to decorate values from other factories matching the same name. - """ - try: - return self.funcargs[argname] - except KeyError: - pass - except TypeError: - self.funcargs = getattr(self, "_funcargs", {}) - if argname not in self._name2factory: - self._name2factory[argname] = self.config.pluginmanager.listattr( - plugins=self._plugins, - attrname=self._argprefix + str(argname) - ) - #else: we are called recursively - if not self._name2factory[argname]: - self._raiselookupfailed(argname) - funcargfactory = self._name2factory[argname].pop() - oldarg = self._currentarg - mp = monkeypatch() - mp.setattr(self, '_currentarg', argname) - try: - param = self.callspec.getparam(argname) - except (AttributeError, ValueError): - pass - else: - mp.setattr(self, 'param', param, raising=False) - try: - self.funcargs[argname] = res = funcargfactory(self) - finally: - mp.undo() - return res - - def addfinalizer(self, finalizer): - """ add a no-args finalizer function to be called when the underlying - node is torn down.""" - self.session._setupstate.addfinalizer(finalizer, self) - - def cached_setup(self, setup, teardown=None, - scope="module", extrakey=None): - """ Return a cached testing resource created by ``setup`` & - detroyed by a respective ``teardown(resource)`` call. - - :arg teardown: function receiving a previously setup resource. - :arg setup: a no-argument function creating a resource. - :arg scope: a string value out of ``function``, ``class``, ``module`` - or ``session`` indicating the caching lifecycle of the resource. - :arg extrakey: added to internal caching key. - """ - if not hasattr(self.config, '_setupcache'): - self.config._setupcache = {} # XXX weakref? - colitem = self._getscopeitem(scope) - cachekey = (self._currentarg, colitem, extrakey) - cache = self.config._setupcache - try: - val = cache[cachekey] - except KeyError: - val = setup() - cache[cachekey] = val - if teardown is not None: - def finalizer(): - del cache[cachekey] - teardown(val) - self.session._setupstate.addfinalizer(finalizer, colitem) - return val - - def _raiselookupfailed(self, argname): - available = [] - for plugin in self._plugins: - for name in vars(plugin): - if name.startswith(self._argprefix): - name = name[len(self._argprefix):] - if name not in available: - available.append(name) - fspath, lineno, msg = self.reportinfo() - msg = "LookupError: no factory found for function argument %r" % (argname,) - msg += "\n available funcargs: %s" %(", ".join(available),) - msg += "\n use 'py.test --funcargs [testpath]' for help on them." - raise self.LookupError(msg) - - def pytest_addoption(parser): group = parser.getgroup("general") @@ -222,6 +102,15 @@ def pytest_pyfunc_call(__multicall__, pyfuncitem): funcargs[name] = pyfuncitem.funcargs[name] testfunction(**funcargs) +def pytest_pyfunc_call(__multicall__, pyfuncitem): + if not __multicall__.execute(): + testfunction = pyfuncitem.obj + if pyfuncitem._isyieldedfunction(): + testfunction(*pyfuncitem._args) + else: + funcargs = pyfuncitem.funcargs + testfunction(**funcargs) + def pytest_collect_file(path, parent): ext = path.ext pb = path.purebasename @@ -267,7 +156,7 @@ class PyobjContext(object): cls = pyobj_property("Class") instance = pyobj_property("Instance") -class PyobjMixin(Request, PyobjContext): +class PyobjMixin(PyobjContext): def obj(): def fget(self): try: @@ -390,14 +279,12 @@ class PyCollector(PyobjMixin, pytest.Collector): gentesthook.pcall(plugins, metafunc=metafunc) Function = self._getcustomclass("Function") if not metafunc._calls: - return Function(name, parent=self, - funcargnames=metafunc.funcargnames) + return Function(name, parent=self) l = [] for callspec in metafunc._calls: subname = "%s[%s]" %(name, callspec.id) function = Function(name=subname, parent=self, callspec=callspec, callobj=funcobj, - funcargnames=metafunc.funcargnames, keywords={callspec.id:True}) l.append(function) return l @@ -494,6 +381,7 @@ class Instance(PyCollector): class FunctionMixin(PyobjMixin): """ mixin for the code common to Function and Generator. """ + def setup(self): """ perform setup for this test function. """ if hasattr(self, '_preservedparent'): @@ -535,7 +423,7 @@ class FunctionMixin(PyobjMixin): excinfo.traceback = ntraceback.filter() def _repr_failure_py(self, excinfo, style="long"): - if excinfo.errisinstance(Request.LookupError): + if excinfo.errisinstance(FuncargRequest.LookupError): fspath, lineno, msg = self.reportinfo() lines, _ = inspect.getsourcelines(self.obj) for i, line in enumerate(lines): @@ -626,10 +514,21 @@ def getfuncargnames(function, startindex=None): return argnames[startindex:-numdefaults] return argnames[startindex:] -def fillfuncargs(node): +def fillfuncargs(function): """ fill missing funcargs. """ - if not isinstance(node, Function): - node = OldFuncargRequest(pyfuncitem=node) + #if not getattr(function, "_args", None) is not None: + # request = FuncargRequest(pyfuncitem=function) + # request._fillfuncargs() + if getattr(function, "_args", None) is None: + try: + request = function._request + except AttributeError: + request = FuncargRequest(function) + request._fillfuncargs() + +def XXXfillfuncargs(node): + """ fill missing funcargs. """ + node = FuncargRequest(node) if node.funcargs is None: node.funcargs = getattr(node, "_funcargs", {}) if not isinstance(node, Function) or not node._isyieldedfunction(): @@ -815,8 +714,8 @@ def _showfuncargs_main(config, session): for plugin in plugins: available = [] for name, factory in vars(plugin).items(): - if name.startswith(Request._argprefix): - name = name[len(Request._argprefix):] + if name.startswith(FuncargRequest._argprefix): + name = name[len(FuncargRequest._argprefix):] if name not in available: available.append([name, factory]) if available: @@ -931,11 +830,9 @@ class Function(FunctionMixin, pytest.Item): """ _genid = None def __init__(self, name, parent=None, args=None, config=None, - callspec=None, callobj=_dummy, keywords=None, - session=None, funcargnames=()): + callspec=None, callobj=_dummy, keywords=None, session=None): super(Function, self).__init__(name, parent, config=config, session=session) - self.funcargnames = funcargnames self._args = args if self._isyieldedfunction(): assert not callspec, ( @@ -943,12 +840,17 @@ class Function(FunctionMixin, pytest.Item): else: if callspec is not None: self.callspec = callspec - self._funcargs = callspec.funcargs or {} + self.funcargs = callspec.funcargs or {} self._genid = callspec.id if hasattr(callspec, "param"): self.param = callspec.param + else: + self.funcargs = {} + self._request = req = FuncargRequest(self) if callobj is not _dummy: - self._obj = callobj + self.obj = callobj + startindex = int(self.cls is not None) + self.funcargnames = getfuncargnames(self.obj, startindex=startindex) self.keywords.update(py.builtin._getfuncdict(self.obj) or {}) if keywords: @@ -1002,49 +904,196 @@ class Function(FunctionMixin, pytest.Item): return hash((self.parent, self.name)) -def itemapi_property(name, set=False): - prop = getattr(Function, name, None) - doc = getattr(prop, "__doc__", None) - def get(self): - return getattr(self._pyfuncitem, name) - if set: - def set(self, value): - setattr(self._pyfuncitem, name, value) - else: - set = None - return property(get, set, None, doc) +class FuncargRequest: + """ A request for function arguments from a test function. - -class OldFuncargRequest(Request, PyobjContext): - """ (deprecated) helper interactions with a test function invocation. - - Note that there is an optional ``param`` attribute in case - there was an invocation to metafunc.addcall(param=...). - If no such call was done in a ``pytest_generate_tests`` - hook, the attribute will not be present. + Note that there is an optional ``param`` attribute in case + there was an invocation to metafunc.addcall(param=...). + If no such call was done in a ``pytest_generate_tests`` + hook, the attribute will not be present. """ + _argprefix = "pytest_funcarg__" + _argname = None + + class LookupError(LookupError): + """ error on performing funcarg request. """ + def __init__(self, pyfuncitem): self._pyfuncitem = pyfuncitem - Request.extrainit(self) - self.funcargs = pyfuncitem.funcargs - self.getplugins = self._pyfuncitem.getplugins - self.reportinfo = self._pyfuncitem.reportinfo - self.getparent = self._pyfuncitem.getparent - try: - self.param = self._pyfuncitem.param - except AttributeError: - pass + if hasattr(pyfuncitem, '_requestparam'): + self.param = pyfuncitem._requestparam + self.getparent = pyfuncitem.getparent + self._funcargs = self._pyfuncitem.funcargs.copy() + self._name2factory = {} + self._currentarg = None - def __repr__(self): - return "" % (self._pyfuncitem.name) + @cached_property + def _plugins(self): + extra = [obj for obj in (self.module, self.instance) if obj] + return self._pyfuncitem.getplugins() + extra - _getscopeitem = itemapi_property("_getscopeitem") - funcargs = itemapi_property("funcargs", set=True) - keywords = itemapi_property("keywords") - config = itemapi_property("config") - session = itemapi_property("session") - fspath = itemapi_property("fspath") - applymarker = itemapi_property("applymarker") @property def function(self): + """ function object of the test invocation. """ return self._pyfuncitem.obj + + @property + def keywords(self): + """ keywords of the test function item. + + .. versionadded:: 2.0 + """ + return self._pyfuncitem.keywords + + @property + def module(self): + """ module where the test function was collected. """ + return self._pyfuncitem.getparent(pytest.Module).obj + + @property + def cls(self): + """ class (can be None) where the test function was collected. """ + clscol = self._pyfuncitem.getparent(pytest.Class) + if clscol: + return clscol.obj + @property + def instance(self): + """ instance (can be None) on which test function was collected. """ + return py.builtin._getimself(self.function) + + @property + def config(self): + """ the pytest config object associated with this request. """ + return self._pyfuncitem.config + + @property + def fspath(self): + """ 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): + """ Apply a marker to a single test function invocation. + This method is useful if you don't want to have a keyword/marker + on all function invocations. + + :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object + created by a call to ``py.test.mark.NAME(...)``. + """ + if not isinstance(marker, py.test.mark.XYZ.__class__): + raise ValueError("%r is not a py.test.mark.* object") + self._pyfuncitem.keywords[marker.markname] = marker + + def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): + """ Return a testing resource managed by ``setup`` & + ``teardown`` calls. ``scope`` and ``extrakey`` determine when the + ``teardown`` function will be called so that subsequent calls to + ``setup`` would recreate the resource. + + :arg teardown: function receiving a previously setup resource. + :arg setup: a no-argument function creating a resource. + :arg scope: a string value out of ``function``, ``class``, ``module`` + or ``session`` indicating the caching lifecycle of the resource. + :arg extrakey: added to internal caching key of (funcargname, scope). + """ + if not hasattr(self.config, '_setupcache'): + self.config._setupcache = {} # XXX weakref? + cachekey = (self._currentarg, self._getscopeitem(scope), extrakey) + cache = self.config._setupcache + try: + val = cache[cachekey] + except KeyError: + val = setup() + cache[cachekey] = val + if teardown is not None: + def finalizer(): + del cache[cachekey] + teardown(val) + self._addfinalizer(finalizer, scope=scope) + return val + + def getfuncargvalue(self, argname): + """ Retrieve a function argument by name for this test + function invocation. This allows one function argument factory + to call another function argument factory. If there are two + funcarg factories for the same test function argument the first + factory may use ``getfuncargvalue`` to call the second one and + do something additional with the resource. + """ + try: + return self._funcargs[argname] + except KeyError: + pass + if argname not in self._name2factory: + self._name2factory[argname] = self.config.pluginmanager.listattr( + plugins=self._plugins, + attrname=self._argprefix + str(argname) + ) + #else: we are called recursively + if not self._name2factory[argname]: + self._raiselookupfailed(argname) + funcargfactory = self._name2factory[argname].pop() + oldarg = self._currentarg + mp = monkeypatch() + mp.setattr(self, '_currentarg', argname) + try: + param = self._pyfuncitem.callspec.getparam(argname) + except (AttributeError, ValueError): + pass + else: + mp.setattr(self, 'param', param, raising=False) + try: + self._funcargs[argname] = res = funcargfactory(request=self) + finally: + mp.undo() + return res + + def _getscopeitem(self, scope): + if scope == "function": + return self._pyfuncitem + elif scope == "session": + return None + elif scope == "class": + x = self._pyfuncitem.getparent(pytest.Class) + if x is not None: + return x + scope = "module" + if scope == "module": + return self._pyfuncitem.getparent(pytest.Module) + raise ValueError("unknown finalization scope %r" %(scope,)) + + def addfinalizer(self, finalizer): + """add finalizer function to be called after test function + finished execution. """ + self._addfinalizer(finalizer, scope="function") + + def _addfinalizer(self, finalizer, scope): + colitem = self._getscopeitem(scope) + self._pyfuncitem.session._setupstate.addfinalizer( + finalizer=finalizer, colitem=colitem) + + def __repr__(self): + return "" %(self._pyfuncitem) + + def _raiselookupfailed(self, argname): + available = [] + for plugin in self._plugins: + for name in vars(plugin): + if name.startswith(self._argprefix): + name = name[len(self._argprefix):] + if name not in available: + available.append(name) + fspath, lineno, msg = self._pyfuncitem.reportinfo() + msg = "LookupError: no factory found for function argument %r" % (argname,) + msg += "\n available funcargs: %s" %(", ".join(available),) + msg += "\n use 'py.test --funcargs [testpath]' for help on them." + raise self.LookupError(msg) diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 0d0cf4bf3..c67f4389e 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -54,15 +54,15 @@ def pytest_configure(config): mp.setattr(config, '_tmpdirhandler', t, raising=False) mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False) -def pytest_funcarg__tmpdir(item): +def pytest_funcarg__tmpdir(request): """return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ path object. """ - name = item.name + name = request._pyfuncitem.name name = py.std.re.sub("[\W]", "_", name) - x = item.config._tmpdirhandler.mktemp(name, numbered=True) + x = request.config._tmpdirhandler.mktemp(name, numbered=True) return x diff --git a/doc/en/funcargs.txt b/doc/en/funcargs.txt index 1dfa1a838..e53c49098 100644 --- a/doc/en/funcargs.txt +++ b/doc/en/funcargs.txt @@ -110,7 +110,7 @@ with a list of available function arguments. The request object passed to factories ----------------------------------------- -Each funcarg factory receives a :py:class:`~_pytest.python.Request` object which +Each funcarg factory receives a :py:class:`~_pytest.python.FuncargRequest` object which provides methods to manage caching and finalization in the context of the test invocation as well as several attributes of the the underlying test item. In fact, as of version pytest-2.3, the request API is implemented on all Item objects and therefore the request object has general :py:class:`Node attributes and methods <_pytest.main.Node>` attributes. This is a backward compatible diff --git a/doc/en/plugins.txt b/doc/en/plugins.txt index 531221e47..e884dd414 100644 --- a/doc/en/plugins.txt +++ b/doc/en/plugins.txt @@ -331,7 +331,7 @@ test execution: Reference of objects involved in hooks =========================================================== -.. autoclass:: _pytest.python.Request() +.. autoclass:: _pytest.python.FuncargRequest() :members: .. autoclass:: _pytest.config.Config() diff --git a/testing/test_python.py b/testing/test_python.py index 0a46b8e9a..01a8a746a 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -277,14 +277,18 @@ class TestFunction: def test_function_equality(self, testdir, tmpdir): config = testdir.parseconfigure() session = testdir.Session(config) + def func1(): + pass + def func2(): + pass f1 = pytest.Function(name="name", config=config, - args=(1,), callobj=isinstance, session=session) + args=(1,), callobj=func1, session=session) f2 = pytest.Function(name="name",config=config, - args=(1,), callobj=py.builtin.callable, session=session) + args=(1,), callobj=func2, session=session) assert not f1 == f2 assert f1 != f2 f3 = pytest.Function(name="name", config=config, - args=(1,2), callobj=py.builtin.callable, session=session) + args=(1,2), callobj=func2, session=session) assert not f3 == f2 assert f3 != f2 @@ -292,7 +296,7 @@ class TestFunction: assert f3 != f1 f1_b = pytest.Function(name="name", config=config, - args=(1,), callobj=isinstance, session=session) + args=(1,), callobj=func1, session=session) assert f1 == f1_b assert not f1 != f1_b @@ -307,10 +311,12 @@ class TestFunction: funcargs = {} id = "world" session = testdir.Session(config) + def func(): + pass f5 = pytest.Function(name="name", config=config, - callspec=callspec1, callobj=isinstance, session=session) + callspec=callspec1, callobj=func, session=session) f5b = pytest.Function(name="name", config=config, - callspec=callspec2, callobj=isinstance, session=session) + callspec=callspec2, callobj=func, session=session) assert f5 != f5b assert not (f5 == f5b) @@ -549,7 +555,7 @@ class TestFillFuncArgs: return 42 """) item = testdir.getitem("def test_func(some): pass") - exc = pytest.raises(funcargs.OldFuncargRequest.LookupError, + exc = pytest.raises(funcargs.FuncargRequest.LookupError, "funcargs.fillfuncargs(item)") s = str(exc.value) assert s.find("xyzsomething") != -1 @@ -624,7 +630,7 @@ class TestRequest: def pytest_funcarg__something(request): pass def test_func(something): pass """) - req = funcargs.OldFuncargRequest(item) + req = funcargs.FuncargRequest(item) assert req.function == item.obj assert req.keywords is item.keywords assert hasattr(req.module, 'test_func') @@ -639,7 +645,7 @@ class TestRequest: def test_func(self, something): pass """) - req = funcargs.OldFuncargRequest(item) + req = funcargs.FuncargRequest(item) assert req.cls.__name__ == "TestB" assert req.instance.__class__ == req.cls @@ -653,7 +659,7 @@ class TestRequest: """) item1, = testdir.genitems([modcol]) assert item1.name == "test_method" - name2factory = funcargs.OldFuncargRequest(item1)._name2factory + name2factory = funcargs.FuncargRequest(item1)._name2factory assert len(name2factory) == 1 assert name2factory[0].__name__ == "pytest_funcarg__something" @@ -668,7 +674,7 @@ class TestRequest: def test_func(something): assert something == 2 """) - req = funcargs.OldFuncargRequest(item) + req = funcargs.FuncargRequest(item) val = req.getfuncargvalue("something") assert val == 2 @@ -680,7 +686,7 @@ class TestRequest: return l.pop() def test_func(something): pass """) - req = funcargs.OldFuncargRequest(item) + req = funcargs.FuncargRequest(item) pytest.raises(req.LookupError, req.getfuncargvalue, "notexists") val = req.getfuncargvalue("something") assert val == 1 @@ -691,7 +697,8 @@ class TestRequest: val2 = req.getfuncargvalue("other") # see about caching assert val2 == 2 pytest._fillfuncargs(item) - assert item.funcargs == {'something': 1, "other": 2} + assert item.funcargs == {'something': 1} + #assert item.funcargs == {'something': 1, "other": 2} def test_request_addfinalizer(self, testdir): item = testdir.getitem(""" @@ -728,7 +735,7 @@ class TestRequest: def test_request_getmodulepath(self, testdir): modcol = testdir.getmodulecol("def test_somefunc(): pass") item, = testdir.genitems([modcol]) - req = funcargs.OldFuncargRequest(item) + req = funcargs.FuncargRequest(item) assert req.fspath == modcol.fspath def test_applymarker(testdir): @@ -739,7 +746,7 @@ def test_applymarker(testdir): def test_func2(self, something): pass """) - req1 = funcargs.OldFuncargRequest(item1) + req1 = funcargs.FuncargRequest(item1) assert 'xfail' not in item1.keywords req1.applymarker(pytest.mark.xfail) assert 'xfail' in item1.keywords @@ -757,7 +764,7 @@ class TestRequestCachedSetup: def test_func2(self, something): pass """) - req1 = funcargs.OldFuncargRequest(item1) + req1 = funcargs.FuncargRequest(item1) l = ["hello"] def setup(): return l.pop() @@ -766,7 +773,7 @@ class TestRequestCachedSetup: assert ret1 == "hello" ret1b = req1.cached_setup(setup) assert ret1 == ret1b - req2 = funcargs.OldFuncargRequest(item2) + req2 = funcargs.FuncargRequest(item2) ret2 = req2.cached_setup(setup) assert ret2 == ret1 @@ -782,7 +789,7 @@ class TestRequestCachedSetup: def test_func2b(self, something): pass """) - req1 = funcargs.OldFuncargRequest(item2) + req1 = funcargs.FuncargRequest(item2) l = ["hello2", "hello"] def setup(): return l.pop() @@ -791,22 +798,22 @@ class TestRequestCachedSetup: # automatically turn "class" to "module" scope ret1 = req1.cached_setup(setup, scope="class") assert ret1 == "hello" - req2 = funcargs.OldFuncargRequest(item2) + req2 = funcargs.FuncargRequest(item2) ret2 = req2.cached_setup(setup, scope="class") assert ret2 == "hello" - req3 = funcargs.OldFuncargRequest(item3) + req3 = funcargs.FuncargRequest(item3) ret3a = req3.cached_setup(setup, scope="class") ret3b = req3.cached_setup(setup, scope="class") assert ret3a == "hello2" assert ret3b == "hello2" - req4 = funcargs.OldFuncargRequest(item4) + req4 = funcargs.FuncargRequest(item4) ret4 = req4.cached_setup(setup, scope="class") assert ret4 == ret3a def test_request_cachedsetup_extrakey(self, testdir): item1 = testdir.getitem("def test_func(): pass") - req1 = funcargs.OldFuncargRequest(item1) + req1 = funcargs.FuncargRequest(item1) l = ["hello", "world"] def setup(): return l.pop() @@ -821,7 +828,7 @@ class TestRequestCachedSetup: def test_request_cachedsetup_cache_deletion(self, testdir): item1 = testdir.getitem("def test_func(): pass") - req1 = funcargs.OldFuncargRequest(item1) + req1 = funcargs.FuncargRequest(item1) l = [] def setup(): l.append("setup") @@ -1093,9 +1100,9 @@ class TestMetafuncFunctional: def pytest_generate_tests(metafunc): metafunc.addcall(param=metafunc) - def pytest_funcarg__metafunc(item): - assert item._genid == "0" - return item.param + def pytest_funcarg__metafunc(request): + assert request._pyfuncitem._genid == "0" + return request.param def test_function(metafunc, pytestconfig): assert metafunc.config == pytestconfig @@ -1591,6 +1598,7 @@ def test_issue117_sessionscopeteardown(testdir): ]) class TestRequestAPI: + @pytest.mark.xfail(reason="reverted refactoring") def test_addfinalizer_cachedsetup_getfuncargvalue(self, testdir): testdir.makeconftest(""" l = [] @@ -1615,10 +1623,11 @@ class TestRequestAPI: "*2 passed*", ]) + @pytest.mark.xfail(reason="consider item's funcarg access and error conditions") def test_runtest_setup_sees_filled_funcargs(self, testdir): testdir.makeconftest(""" def pytest_runtest_setup(item): - assert item.funcargs is None + assert not hasattr(item, "_request") """) testdir.makepyfile(""" def pytest_funcarg__a(request):