From 2cf22e312412748bb75f0979f07331ce588d36e6 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 25 Sep 2010 18:23:26 +0200 Subject: [PATCH] shift all python related testing functioanlity to a dedicated pytest_python plugin which incorporates pytest's logic of python function testing (including funcargs). --HG-- branch : trunk --- CHANGELOG | 1 + py/__init__.py | 6 - py/_plugin/pytest_default.py | 72 +-- .../pycollect.py => _plugin/pytest_python.py} | 316 ++++++++++- py/_plugin/pytest_terminal.py | 3 - py/_test/funcargs.py | 228 -------- py/_test/pluginmanager.py | 2 +- .../test_pytest_python.py} | 518 +++++++++++++++++- testing/plugin/test_pytest_tmpdir.py | 2 +- testing/test_pycollect.py | 517 ----------------- 10 files changed, 830 insertions(+), 835 deletions(-) rename py/{_test/pycollect.py => _plugin/pytest_python.py} (55%) delete mode 100644 py/_test/funcargs.py rename testing/{test_funcargs.py => plugin/test_pytest_python.py} (53%) delete mode 100644 testing/test_pycollect.py diff --git a/CHANGELOG b/CHANGELOG index d9cf05000..a865f0cd6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Changes between 1.3.4 and 1.4.0a1 ================================================== - major refactoring of internal collection handling +- majorly reduce py.test core code, shift function/python testing to own plugin - fix issue88 (finding custom test nodes from command line arg) Changes between 1.3.3 and 1.3.4 diff --git a/py/__init__.py b/py/__init__.py index 0a831a41c..e62e1a72a 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -45,12 +45,6 @@ py.apipkg.initpkg(__name__, dict( 'Directory' : '._test.collect:Directory', 'File' : '._test.collect:File', 'Item' : '._test.collect:Item', - 'Module' : '._test.pycollect:Module', - 'Class' : '._test.pycollect:Class', - 'Instance' : '._test.pycollect:Instance', - 'Generator' : '._test.pycollect:Generator', - 'Function' : '._test.pycollect:Function', - '_fillfuncargs' : '._test.funcargs:fillfuncargs', }, 'cmdline': { 'main' : '._test.session:main', # backward compat diff --git a/py/_plugin/pytest_default.py b/py/_plugin/pytest_default.py index 41f320754..45f13376a 100644 --- a/py/_plugin/pytest_default.py +++ b/py/_plugin/pytest_default.py @@ -5,45 +5,15 @@ import py def pytest_cmdline_main(config): from py._test.session import Session, Collection - exitstatus = 0 - if config.option.showfuncargs: - from py._test.funcargs import showfuncargs - session = showfuncargs(config) - else: - collection = Collection(config) - # instantiate session already because it - # records failures and implements maxfail handling - session = Session(config, collection) - exitstatus = collection.do_collection() - if not exitstatus: - exitstatus = session.main() + collection = Collection(config) + # instantiate session already because it + # records failures and implements maxfail handling + session = Session(config, collection) + exitstatus = collection.do_collection() + if not exitstatus: + exitstatus = session.main() return exitstatus -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 - if pb.startswith("test_") or pb.endswith("_test") or \ - path in parent.collection._argfspaths: - if ext == ".py": - return parent.ihook.pytest_pycollect_makemodule( - path=path, parent=parent) - -def pytest_pycollect_makemodule(path, parent): - return parent.Module(path, parent) - -def pytest_funcarg__pytestconfig(request): - """ the pytest config object with access to command line opts.""" - return request.config - def pytest_ignore_collect(path, config): ignore_paths = config.getconftest_pathlist("collect_ignore", path=path) ignore_paths = ignore_paths or [] @@ -57,7 +27,6 @@ def pytest_ignore_collect(path, config): if path == p or path.relto(p): return True - def pytest_collect_directory(path, parent): # XXX reconsider the following comment # not use parent.Directory here as we generally @@ -105,30 +74,3 @@ def pytest_configure(config): if config.getvalue("exitfirst"): config.option.maxfail = 1 -# pycollect related hooks and code, should move to pytest_pycollect.py - -def pytest_pycollect_makeitem(__multicall__, collector, name, obj): - res = __multicall__.execute() - if res is not None: - return res - if collector._istestclasscandidate(name, obj): - res = collector._deprecated_join(name) - if res is not None: - return res - return collector.Class(name, parent=collector) - elif collector.funcnamefilter(name) and hasattr(obj, '__call__'): - res = collector._deprecated_join(name) - if res is not None: - return res - if is_generator(obj): - # XXX deprecation warning - return collector.Generator(name, parent=collector) - else: - return collector._genfunctions(name, obj) - -def is_generator(func): - try: - return py.code.getrawcode(func).co_flags & 32 # generator function - except AttributeError: # builtin functions have no bytecode - # assume them to not be generators - return False diff --git a/py/_test/pycollect.py b/py/_plugin/pytest_python.py similarity index 55% rename from py/_test/pycollect.py rename to py/_plugin/pytest_python.py index e4514fc31..337d4919f 100644 --- a/py/_test/pycollect.py +++ b/py/_plugin/pytest_python.py @@ -5,9 +5,80 @@ import py import inspect import sys from py._test.collect import configproperty, warnoldcollect -from py._test import funcargs from py._code.code import TerminalRepr +def pytest_addoption(parser): + group = parser.getgroup("terminal reporting") + group._addoption('--funcargs', + action="store_true", dest="showfuncargs", default=False, + help="show available function arguments, sorted by plugin") + +def pytest_cmdline_main(config): + if config.option.showfuncargs: + showfuncargs(config) + return 0 + +def pytest_namespace(): + # XXX rather return than set directly + py.test.collect.Module = Module + py.test.collect.Class = Class + py.test.collect.Instance = Instance + py.test.collect.Function = Function + py.test.collect.Generator = Generator + py.test.collect._fillfuncargs = fillfuncargs + +def pytest_funcarg__pytestconfig(request): + """ the pytest config object with access to command line opts.""" + return request.config + +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 + if pb.startswith("test_") or pb.endswith("_test") or \ + path in parent.collection._argfspaths: + if ext == ".py": + return parent.ihook.pytest_pycollect_makemodule( + path=path, parent=parent) + +def pytest_pycollect_makemodule(path, parent): + return parent.Module(path, parent) + + +def pytest_pycollect_makeitem(__multicall__, collector, name, obj): + res = __multicall__.execute() + if res is not None: + return res + if collector._istestclasscandidate(name, obj): + res = collector._deprecated_join(name) + if res is not None: + return res + return collector.Class(name, parent=collector) + elif collector.funcnamefilter(name) and hasattr(obj, '__call__'): + res = collector._deprecated_join(name) + if res is not None: + return res + if is_generator(obj): + # XXX deprecation warning + return collector.Generator(name, parent=collector) + else: + return collector._genfunctions(name, obj) + +def is_generator(func): + try: + return py.code.getrawcode(func).co_flags & 32 # generator function + except AttributeError: # builtin functions have no bytecode + # assume them to not be generators + return False + class PyobjMixin(object): def obj(): def fget(self): @@ -120,10 +191,10 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector): module = self.getparent(Module).obj clscol = self.getparent(Class) cls = clscol and clscol.obj or None - metafunc = funcargs.Metafunc(funcobj, config=self.config, + metafunc = Metafunc(funcobj, config=self.config, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests - plugins = funcargs.getplugins(self, withpy=True) + plugins = getplugins(self, withpy=True) gentesthook.pcall(plugins, metafunc=metafunc) if not metafunc._calls: return self.Function(name, parent=self) @@ -212,16 +283,9 @@ class Class(PyCollectorMixin, py.test.collect.Collector): class Instance(PyCollectorMixin, py.test.collect.Collector): def _getobj(self): return self.parent.obj() - def Function(self): - return getattr(self.obj, 'Function', - PyCollectorMixin.Function.__get__(self)) # XXX for python 2.2 + def _keywords(self): return [] - Function = property(Function) - - #def __repr__(self): - # return "<%s of '%s'>" %(self.__class__.__name__, - # self.parent.obj.__name__) def newinstance(self): self.obj = self._getobj() @@ -270,7 +334,7 @@ class FunctionMixin(PyobjMixin): return traceback def _repr_failure_py(self, excinfo, style="long"): - if excinfo.errisinstance(funcargs.FuncargRequest.LookupError): + if excinfo.errisinstance(FuncargRequest.LookupError): fspath, lineno, msg = self.reportinfo() lines, _ = inspect.getsourcelines(self.obj) for i, line in enumerate(lines): @@ -384,7 +448,7 @@ class Function(FunctionMixin, py.test.collect.Item): def setup(self): super(Function, self).setup() if hasattr(self, 'funcargs'): - funcargs.fillfuncargs(self) + fillfuncargs(self) def __eq__(self, other): try: @@ -410,3 +474,229 @@ def hasinit(obj): if init: if init != object.__init__: return True + + +def getfuncargnames(function): + argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0] + startindex = py.std.inspect.ismethod(function) and 1 or 0 + defaults = getattr(function, 'func_defaults', + getattr(function, '__defaults__', None)) or () + numdefaults = len(defaults) + if numdefaults: + return argnames[startindex:-numdefaults] + return argnames[startindex:] + +def fillfuncargs(function): + """ fill missing funcargs. """ + request = FuncargRequest(pyfuncitem=function) + request._fillfuncargs() + +def getplugins(node, withpy=False): # might by any node + plugins = node.config._getmatchingplugins(node.fspath) + if withpy: + mod = node.getparent(py.test.collect.Module) + if mod is not None: + plugins.append(mod.obj) + inst = node.getparent(py.test.collect.Instance) + if inst is not None: + plugins.append(inst.obj) + return plugins + +_notexists = object() +class CallSpec: + def __init__(self, funcargs, id, param): + self.funcargs = funcargs + self.id = id + if param is not _notexists: + self.param = param + def __repr__(self): + return "" %( + self.id, getattr(self, 'param', '?'), self.funcargs) + +class Metafunc: + def __init__(self, function, config=None, cls=None, module=None): + self.config = config + self.module = module + self.function = function + self.funcargnames = getfuncargnames(function) + self.cls = cls + self.module = module + self._calls = [] + self._ids = py.builtin.set() + + def addcall(self, funcargs=None, id=_notexists, param=_notexists): + assert funcargs is None or isinstance(funcargs, dict) + if id is None: + raise ValueError("id=None not allowed") + if id is _notexists: + id = len(self._calls) + id = str(id) + if id in self._ids: + raise ValueError("duplicate id %r" % id) + self._ids.add(id) + self._calls.append(CallSpec(funcargs, id, param)) + +class FuncargRequest: + _argprefix = "pytest_funcarg__" + _argname = None + + class LookupError(LookupError): + """ error on performing funcarg request. """ + + def __init__(self, pyfuncitem): + self._pyfuncitem = pyfuncitem + self.function = pyfuncitem.obj + self.module = pyfuncitem.getparent(py.test.collect.Module).obj + clscol = pyfuncitem.getparent(py.test.collect.Class) + self.cls = clscol and clscol.obj or None + self.instance = py.builtin._getimself(self.function) + self.config = pyfuncitem.config + self.fspath = pyfuncitem.fspath + if hasattr(pyfuncitem, '_requestparam'): + self.param = pyfuncitem._requestparam + self._plugins = getplugins(pyfuncitem, withpy=True) + self._funcargs = self._pyfuncitem.funcargs.copy() + self._name2factory = {} + self._currentarg = None + + 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 test function invocation. + + The 'marker' must be created with py.test.mark.* XYZ. + """ + 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): + """ cache and return result of calling setup(). + + The requested argument name, the scope and the ``extrakey`` + determine the cache key. The scope also determines when + teardown(result) will be called. valid scopes are: + scope == 'function': when the single test function run finishes. + scope == 'module': when tests in a different module are run + scope == 'session': when tests of the session have run. + """ + 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): + 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 + self._currentarg = argname + try: + self._funcargs[argname] = res = funcargfactory(request=self) + finally: + self._currentarg = oldarg + return res + + def _getscopeitem(self, scope): + if scope == "function": + return self._pyfuncitem + elif scope == "module": + return self._pyfuncitem.getparent(py.test.collect.Module) + elif scope == "session": + return None + raise ValueError("unknown finalization scope %r" %(scope,)) + + def _addfinalizer(self, finalizer, scope): + colitem = self._getscopeitem(scope) + self.config._setupstate.addfinalizer( + finalizer=finalizer, colitem=colitem) + + def addfinalizer(self, finalizer): + """ call the given finalizer after test function finished execution. """ + self._addfinalizer(finalizer, scope="function") + + 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) + +def showfuncargs(config): + from py._test.session import Collection + collection = Collection(config) + colitem = collection.getinitialnodes()[0] + curdir = py.path.local() + tw = py.io.TerminalWriter() + plugins = getplugins(colitem, withpy=True) + verbose = config.getvalue("verbose") + for plugin in plugins: + available = [] + for name, factory in vars(plugin).items(): + if name.startswith(FuncargRequest._argprefix): + name = name[len(FuncargRequest._argprefix):] + if name not in available: + available.append([name, factory]) + if available: + pluginname = plugin.__name__ + for name, factory in available: + loc = getlocation(factory, curdir) + if verbose: + funcargspec = "%s -- %s" %(name, loc,) + else: + funcargspec = name + tw.line(funcargspec, green=True) + doc = factory.__doc__ or "" + if doc: + for line in doc.split("\n"): + tw.line(" " + line.strip()) + else: + tw.line(" %s: no docstring available" %(loc,), + red=True) + +def getlocation(function, curdir): + import inspect + fn = py.path.local(inspect.getfile(function)) + lineno = py.builtin._getcode(function).co_firstlineno + if fn.relto(curdir): + fn = fn.relto(curdir) + return "%s:%d" %(fn, lineno+1) diff --git a/py/_plugin/pytest_terminal.py b/py/_plugin/pytest_terminal.py index 7fac140bb..bed5630e3 100644 --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -27,9 +27,6 @@ def pytest_addoption(parser): group._addoption('--fulltrace', action="store_true", dest="fulltrace", default=False, help="don't cut any tracebacks (default is to cut).") - group._addoption('--funcargs', - action="store_true", dest="showfuncargs", default=False, - help="show available function arguments, sorted by plugin") def pytest_configure(config): if config.option.collectonly: diff --git a/py/_test/funcargs.py b/py/_test/funcargs.py deleted file mode 100644 index 2b4f9a0ca..000000000 --- a/py/_test/funcargs.py +++ /dev/null @@ -1,228 +0,0 @@ -import py - -def getfuncargnames(function): - argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0] - startindex = py.std.inspect.ismethod(function) and 1 or 0 - defaults = getattr(function, 'func_defaults', - getattr(function, '__defaults__', None)) or () - numdefaults = len(defaults) - if numdefaults: - return argnames[startindex:-numdefaults] - return argnames[startindex:] - -def fillfuncargs(function): - """ fill missing funcargs. """ - request = FuncargRequest(pyfuncitem=function) - request._fillfuncargs() - -def getplugins(node, withpy=False): # might by any node - plugins = node.config._getmatchingplugins(node.fspath) - if withpy: - mod = node.getparent(py.test.collect.Module) - if mod is not None: - plugins.append(mod.obj) - inst = node.getparent(py.test.collect.Instance) - if inst is not None: - plugins.append(inst.obj) - return plugins - -_notexists = object() -class CallSpec: - def __init__(self, funcargs, id, param): - self.funcargs = funcargs - self.id = id - if param is not _notexists: - self.param = param - def __repr__(self): - return "" %( - self.id, getattr(self, 'param', '?'), self.funcargs) - -class Metafunc: - def __init__(self, function, config=None, cls=None, module=None): - self.config = config - self.module = module - self.function = function - self.funcargnames = getfuncargnames(function) - self.cls = cls - self.module = module - self._calls = [] - self._ids = py.builtin.set() - - def addcall(self, funcargs=None, id=_notexists, param=_notexists): - assert funcargs is None or isinstance(funcargs, dict) - if id is None: - raise ValueError("id=None not allowed") - if id is _notexists: - id = len(self._calls) - id = str(id) - if id in self._ids: - raise ValueError("duplicate id %r" % id) - self._ids.add(id) - self._calls.append(CallSpec(funcargs, id, param)) - -class FuncargRequest: - _argprefix = "pytest_funcarg__" - _argname = None - - class LookupError(LookupError): - """ error on performing funcarg request. """ - - def __init__(self, pyfuncitem): - self._pyfuncitem = pyfuncitem - self.function = pyfuncitem.obj - self.module = pyfuncitem.getparent(py.test.collect.Module).obj - clscol = pyfuncitem.getparent(py.test.collect.Class) - self.cls = clscol and clscol.obj or None - self.instance = py.builtin._getimself(self.function) - self.config = pyfuncitem.config - self.fspath = pyfuncitem.fspath - if hasattr(pyfuncitem, '_requestparam'): - self.param = pyfuncitem._requestparam - self._plugins = getplugins(pyfuncitem, withpy=True) - self._funcargs = self._pyfuncitem.funcargs.copy() - self._name2factory = {} - self._currentarg = None - - 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 test function invocation. - - The 'marker' must be created with py.test.mark.* XYZ. - """ - 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): - """ cache and return result of calling setup(). - - The requested argument name, the scope and the ``extrakey`` - determine the cache key. The scope also determines when - teardown(result) will be called. valid scopes are: - scope == 'function': when the single test function run finishes. - scope == 'module': when tests in a different module are run - scope == 'session': when tests of the session have run. - """ - 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): - 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 - self._currentarg = argname - try: - self._funcargs[argname] = res = funcargfactory(request=self) - finally: - self._currentarg = oldarg - return res - - def _getscopeitem(self, scope): - if scope == "function": - return self._pyfuncitem - elif scope == "module": - return self._pyfuncitem.getparent(py.test.collect.Module) - elif scope == "session": - return None - raise ValueError("unknown finalization scope %r" %(scope,)) - - def _addfinalizer(self, finalizer, scope): - colitem = self._getscopeitem(scope) - self.config._setupstate.addfinalizer( - finalizer=finalizer, colitem=colitem) - - def addfinalizer(self, finalizer): - """ call the given finalizer after test function finished execution. """ - self._addfinalizer(finalizer, scope="function") - - 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) - -def showfuncargs(config): - from py._test.session import Collection - collection = Collection(config) - colitem = collection.getinitialnodes()[0] - curdir = py.path.local() - tw = py.io.TerminalWriter() - #from py._test.funcargs import getplugins - #from py._test.funcargs import FuncargRequest - plugins = getplugins(colitem, withpy=True) - verbose = config.getvalue("verbose") - for plugin in plugins: - available = [] - for name, factory in vars(plugin).items(): - if name.startswith(FuncargRequest._argprefix): - name = name[len(FuncargRequest._argprefix):] - if name not in available: - available.append([name, factory]) - if available: - pluginname = plugin.__name__ - for name, factory in available: - loc = getlocation(factory, curdir) - if verbose: - funcargspec = "%s -- %s" %(name, loc,) - else: - funcargspec = name - tw.line(funcargspec, green=True) - doc = factory.__doc__ or "" - if doc: - for line in doc.split("\n"): - tw.line(" " + line.strip()) - else: - tw.line(" %s: no docstring available" %(loc,), - red=True) - -def getlocation(function, curdir): - import inspect - fn = py.path.local(inspect.getfile(function)) - lineno = py.builtin._getcode(function).co_firstlineno - if fn.relto(curdir): - fn = fn.relto(curdir) - return "%s:%d" %(fn, lineno+1) diff --git a/py/_test/pluginmanager.py b/py/_test/pluginmanager.py index b4ed48528..32abedf65 100644 --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -6,7 +6,7 @@ import inspect from py._plugin import hookspec default_plugins = ( - "default runner pdb capture mark terminal skipping tmpdir monkeypatch " + "default python runner pdb capture mark terminal skipping tmpdir monkeypatch " "recwarn pastebin unittest helpconfig nose assertion genscript " "junitxml doctest keyword").split() diff --git a/testing/test_funcargs.py b/testing/plugin/test_pytest_python.py similarity index 53% rename from testing/test_funcargs.py rename to testing/plugin/test_pytest_python.py index 5b90ffe4f..cfc5a054f 100644 --- a/testing/test_funcargs.py +++ b/testing/plugin/test_pytest_python.py @@ -1,5 +1,521 @@ import py, sys -from py._test import funcargs +from py._plugin import pytest_python as funcargs + +class TestModule: + def test_module_file_not_found(self, testdir): + tmpdir = testdir.tmpdir + fn = tmpdir.join('nada','no') + col = py.test.collect.Module(fn, config=testdir.Config()) + col.config = testdir.parseconfig(tmpdir) + py.test.raises(py.error.ENOENT, col.collect) + + def test_failing_import(self, testdir): + modcol = testdir.getmodulecol("import alksdjalskdjalkjals") + py.test.raises(ImportError, modcol.collect) + py.test.raises(ImportError, modcol.collect) + py.test.raises(ImportError, modcol.run) + + def test_import_duplicate(self, testdir): + a = testdir.mkdir("a") + b = testdir.mkdir("b") + p = a.ensure("test_whatever.py") + p.pyimport() + del py.std.sys.modules['test_whatever'] + b.ensure("test_whatever.py") + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*import*mismatch*", + "*imported*test_whatever*", + "*%s*" % a.join("test_whatever.py"), + "*not the same*", + "*%s*" % b.join("test_whatever.py"), + "*HINT*", + ]) + + def test_syntax_error_in_module(self, testdir): + modcol = testdir.getmodulecol("this is a syntax error") + py.test.raises(modcol.CollectError, modcol.collect) + py.test.raises(modcol.CollectError, modcol.collect) + py.test.raises(modcol.CollectError, modcol.run) + + def test_module_considers_pluginmanager_at_import(self, testdir): + modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") + py.test.raises(ImportError, "modcol.obj") + +class TestClass: + def test_class_with_init_not_collected(self, testdir): + modcol = testdir.getmodulecol(""" + class TestClass1: + def __init__(self): + pass + class TestClass2(object): + def __init__(self): + pass + """) + l = modcol.collect() + assert len(l) == 0 + +if py.std.sys.version_info > (3, 0): + _func_name_attr = "__name__" +else: + _func_name_attr = "func_name" + +class TestGenerator: + def test_generative_functions(self, testdir): + modcol = testdir.getmodulecol(""" + def func1(arg, arg2): + assert arg == arg2 + + def test_gen(): + yield func1, 17, 3*5 + yield func1, 42, 6*7 + """) + colitems = modcol.collect() + assert len(colitems) == 1 + gencol = colitems[0] + assert isinstance(gencol, py.test.collect.Generator) + gencolitems = gencol.collect() + assert len(gencolitems) == 2 + assert isinstance(gencolitems[0], py.test.collect.Function) + assert isinstance(gencolitems[1], py.test.collect.Function) + assert gencolitems[0].name == '[0]' + assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + + def test_generative_methods(self, testdir): + modcol = testdir.getmodulecol(""" + def func1(arg, arg2): + assert arg == arg2 + class TestGenMethods: + def test_gen(self): + yield func1, 17, 3*5 + yield func1, 42, 6*7 + """) + gencol = modcol.collect()[0].collect()[0].collect()[0] + assert isinstance(gencol, py.test.collect.Generator) + gencolitems = gencol.collect() + assert len(gencolitems) == 2 + assert isinstance(gencolitems[0], py.test.collect.Function) + assert isinstance(gencolitems[1], py.test.collect.Function) + assert gencolitems[0].name == '[0]' + assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + + def test_generative_functions_with_explicit_names(self, testdir): + modcol = testdir.getmodulecol(""" + def func1(arg, arg2): + assert arg == arg2 + + def test_gen(): + yield "seventeen", func1, 17, 3*5 + yield "fortytwo", func1, 42, 6*7 + """) + colitems = modcol.collect() + assert len(colitems) == 1 + gencol = colitems[0] + assert isinstance(gencol, py.test.collect.Generator) + gencolitems = gencol.collect() + assert len(gencolitems) == 2 + assert isinstance(gencolitems[0], py.test.collect.Function) + assert isinstance(gencolitems[1], py.test.collect.Function) + assert gencolitems[0].name == "['seventeen']" + assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + assert gencolitems[1].name == "['fortytwo']" + assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1' + + def test_generative_functions_unique_explicit_names(self, testdir): + # generative + modcol = testdir.getmodulecol(""" + def func(): pass + def test_gen(): + yield "name", func + yield "name", func + """) + colitems = modcol.collect() + assert len(colitems) == 1 + gencol = colitems[0] + assert isinstance(gencol, py.test.collect.Generator) + py.test.raises(ValueError, "gencol.collect()") + + def test_generative_methods_with_explicit_names(self, testdir): + modcol = testdir.getmodulecol(""" + def func1(arg, arg2): + assert arg == arg2 + class TestGenMethods: + def test_gen(self): + yield "m1", func1, 17, 3*5 + yield "m2", func1, 42, 6*7 + """) + gencol = modcol.collect()[0].collect()[0].collect()[0] + assert isinstance(gencol, py.test.collect.Generator) + gencolitems = gencol.collect() + assert len(gencolitems) == 2 + assert isinstance(gencolitems[0], py.test.collect.Function) + assert isinstance(gencolitems[1], py.test.collect.Function) + assert gencolitems[0].name == "['m1']" + assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' + assert gencolitems[1].name == "['m2']" + assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1' + + def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): + o = testdir.makepyfile(""" + def test_generative_order_of_execution(): + import py + test_list = [] + expected_list = list(range(6)) + + def list_append(item): + test_list.append(item) + + def assert_order_of_execution(): + py.builtin.print_('expected order', expected_list) + py.builtin.print_('but got ', test_list) + assert test_list == expected_list + + for i in expected_list: + yield list_append, i + yield assert_order_of_execution + """) + reprec = testdir.inline_run(o) + passed, skipped, failed = reprec.countoutcomes() + assert passed == 7 + assert not skipped and not failed + + def test_order_of_execution_generator_different_codeline(self, testdir): + o = testdir.makepyfile(""" + def test_generative_tests_different_codeline(): + import py + test_list = [] + expected_list = list(range(3)) + + def list_append_2(): + test_list.append(2) + + def list_append_1(): + test_list.append(1) + + def list_append_0(): + test_list.append(0) + + def assert_order_of_execution(): + py.builtin.print_('expected order', expected_list) + py.builtin.print_('but got ', test_list) + assert test_list == expected_list + + yield list_append_0 + yield list_append_1 + yield list_append_2 + yield assert_order_of_execution + """) + reprec = testdir.inline_run(o) + passed, skipped, failed = reprec.countoutcomes() + assert passed == 4 + assert not skipped and not failed + +class TestFunction: + def test_getmodulecollector(self, testdir): + item = testdir.getitem("def test_func(): pass") + modcol = item.getparent(py.test.collect.Module) + assert isinstance(modcol, py.test.collect.Module) + assert hasattr(modcol.obj, 'test_func') + + def test_function_equality(self, testdir, tmpdir): + config = testdir.reparseconfig() + f1 = py.test.collect.Function(name="name", config=config, + args=(1,), callobj=isinstance) + f2 = py.test.collect.Function(name="name",config=config, + args=(1,), callobj=py.builtin.callable) + assert not f1 == f2 + assert f1 != f2 + f3 = py.test.collect.Function(name="name", config=config, + args=(1,2), callobj=py.builtin.callable) + assert not f3 == f2 + assert f3 != f2 + + assert not f3 == f1 + assert f3 != f1 + + f1_b = py.test.collect.Function(name="name", config=config, + args=(1,), callobj=isinstance) + assert f1 == f1_b + assert not f1 != f1_b + + def test_function_equality_with_callspec(self, testdir, tmpdir): + config = testdir.reparseconfig() + class callspec1: + param = 1 + funcargs = {} + id = "hello" + class callspec2: + param = 1 + funcargs = {} + id = "world" + collection = object() + f5 = py.test.collect.Function(name="name", config=config, + callspec=callspec1, callobj=isinstance, collection=collection) + f5b = py.test.collect.Function(name="name", config=config, + callspec=callspec2, callobj=isinstance, collection=collection) + assert f5 != f5b + assert not (f5 == f5b) + + def test_pyfunc_call(self, testdir): + item = testdir.getitem("def test_func(): raise ValueError") + config = item.config + class MyPlugin1: + def pytest_pyfunc_call(self, pyfuncitem): + raise ValueError + class MyPlugin2: + def pytest_pyfunc_call(self, pyfuncitem): + return True + config.pluginmanager.register(MyPlugin1()) + config.pluginmanager.register(MyPlugin2()) + config.hook.pytest_pyfunc_call(pyfuncitem=item) + +class TestSorting: + def test_check_equality(self, testdir): + modcol = testdir.getmodulecol(""" + def test_pass(): pass + def test_fail(): assert 0 + """) + fn1 = modcol.collect_by_name("test_pass") + assert isinstance(fn1, py.test.collect.Function) + fn2 = modcol.collect_by_name("test_pass") + assert isinstance(fn2, py.test.collect.Function) + + assert fn1 == fn2 + assert fn1 != modcol + if py.std.sys.version_info < (3, 0): + assert cmp(fn1, fn2) == 0 + assert hash(fn1) == hash(fn2) + + fn3 = modcol.collect_by_name("test_fail") + assert isinstance(fn3, py.test.collect.Function) + assert not (fn1 == fn3) + assert fn1 != fn3 + + for fn in fn1,fn2,fn3: + assert fn != 3 + assert fn != modcol + assert fn != [1,2,3] + assert [1,2,3] != fn + assert modcol != fn + + def test_allow_sane_sorting_for_decorators(self, testdir): + modcol = testdir.getmodulecol(""" + def dec(f): + g = lambda: f(2) + g.place_as = f + return g + + + def test_b(y): + pass + test_b = dec(test_b) + + def test_a(y): + pass + test_a = dec(test_a) + """) + colitems = modcol.collect() + assert len(colitems) == 2 + assert [item.name for item in colitems] == ['test_b', 'test_a'] + + +class TestConftestCustomization: + def test_pytest_pycollect_module(self, testdir): + testdir.makeconftest(""" + import py + class MyModule(py.test.collect.Module): + pass + def pytest_pycollect_makemodule(path, parent): + if path.basename == "test_xyz.py": + return MyModule(path, parent) + """) + testdir.makepyfile("def some(): pass") + testdir.makepyfile(test_xyz="") + result = testdir.runpytest("--collectonly") + result.stdout.fnmatch_lines([ + "*3 + + def test_traceback_error_during_import(self, testdir): + testdir.makepyfile(""" + x = 1 + x = 2 + x = 17 + asd + """) + result = testdir.runpytest() + assert result.ret != 0 + out = result.stdout.str() + assert "x = 1" not in out + assert "x = 2" not in out + result.stdout.fnmatch_lines([ + ">*asd*", + "E*NameError*", + ]) + result = testdir.runpytest("--fulltrace") + out = result.stdout.str() + assert "x = 1" in out + assert "x = 2" in out + result.stdout.fnmatch_lines([ + ">*asd*", + "E*NameError*", + ]) def test_getfuncargnames(): def f(): pass diff --git a/testing/plugin/test_pytest_tmpdir.py b/testing/plugin/test_pytest_tmpdir.py index 692956f45..d19088d91 100644 --- a/testing/plugin/test_pytest_tmpdir.py +++ b/testing/plugin/test_pytest_tmpdir.py @@ -1,7 +1,7 @@ from py._plugin.pytest_tmpdir import pytest_funcarg__tmpdir +from py._plugin.pytest_python import FuncargRequest def test_funcarg(testdir): - from py._test.funcargs import FuncargRequest item = testdir.getitem("def test_func(tmpdir): pass") p = pytest_funcarg__tmpdir(FuncargRequest(item)) assert p.check() diff --git a/testing/test_pycollect.py b/testing/test_pycollect.py deleted file mode 100644 index bc5a8b068..000000000 --- a/testing/test_pycollect.py +++ /dev/null @@ -1,517 +0,0 @@ -import py - -class TestModule: - def test_module_file_not_found(self, testdir): - tmpdir = testdir.tmpdir - fn = tmpdir.join('nada','no') - col = py.test.collect.Module(fn, config=testdir.Config()) - col.config = testdir.parseconfig(tmpdir) - py.test.raises(py.error.ENOENT, col.collect) - - def test_failing_import(self, testdir): - modcol = testdir.getmodulecol("import alksdjalskdjalkjals") - py.test.raises(ImportError, modcol.collect) - py.test.raises(ImportError, modcol.collect) - py.test.raises(ImportError, modcol.run) - - def test_import_duplicate(self, testdir): - a = testdir.mkdir("a") - b = testdir.mkdir("b") - p = a.ensure("test_whatever.py") - p.pyimport() - del py.std.sys.modules['test_whatever'] - b.ensure("test_whatever.py") - result = testdir.runpytest() - result.stdout.fnmatch_lines([ - "*import*mismatch*", - "*imported*test_whatever*", - "*%s*" % a.join("test_whatever.py"), - "*not the same*", - "*%s*" % b.join("test_whatever.py"), - "*HINT*", - ]) - - def test_syntax_error_in_module(self, testdir): - modcol = testdir.getmodulecol("this is a syntax error") - py.test.raises(modcol.CollectError, modcol.collect) - py.test.raises(modcol.CollectError, modcol.collect) - py.test.raises(modcol.CollectError, modcol.run) - - def test_module_considers_pluginmanager_at_import(self, testdir): - modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") - py.test.raises(ImportError, "modcol.obj") - -class TestClass: - def test_class_with_init_not_collected(self, testdir): - modcol = testdir.getmodulecol(""" - class TestClass1: - def __init__(self): - pass - class TestClass2(object): - def __init__(self): - pass - """) - l = modcol.collect() - assert len(l) == 0 - -if py.std.sys.version_info > (3, 0): - _func_name_attr = "__name__" -else: - _func_name_attr = "func_name" - -class TestGenerator: - def test_generative_functions(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - - def test_gen(): - yield func1, 17, 3*5 - yield func1, 42, 6*7 - """) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, py.test.collect.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], py.test.collect.Function) - assert isinstance(gencolitems[1], py.test.collect.Function) - assert gencolitems[0].name == '[0]' - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' - - def test_generative_methods(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - class TestGenMethods: - def test_gen(self): - yield func1, 17, 3*5 - yield func1, 42, 6*7 - """) - gencol = modcol.collect()[0].collect()[0].collect()[0] - assert isinstance(gencol, py.test.collect.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], py.test.collect.Function) - assert isinstance(gencolitems[1], py.test.collect.Function) - assert gencolitems[0].name == '[0]' - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' - - def test_generative_functions_with_explicit_names(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - - def test_gen(): - yield "seventeen", func1, 17, 3*5 - yield "fortytwo", func1, 42, 6*7 - """) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, py.test.collect.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], py.test.collect.Function) - assert isinstance(gencolitems[1], py.test.collect.Function) - assert gencolitems[0].name == "['seventeen']" - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' - assert gencolitems[1].name == "['fortytwo']" - assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1' - - def test_generative_functions_unique_explicit_names(self, testdir): - # generative - modcol = testdir.getmodulecol(""" - def func(): pass - def test_gen(): - yield "name", func - yield "name", func - """) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, py.test.collect.Generator) - py.test.raises(ValueError, "gencol.collect()") - - def test_generative_methods_with_explicit_names(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - class TestGenMethods: - def test_gen(self): - yield "m1", func1, 17, 3*5 - yield "m2", func1, 42, 6*7 - """) - gencol = modcol.collect()[0].collect()[0].collect()[0] - assert isinstance(gencol, py.test.collect.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], py.test.collect.Function) - assert isinstance(gencolitems[1], py.test.collect.Function) - assert gencolitems[0].name == "['m1']" - assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1' - assert gencolitems[1].name == "['m2']" - assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1' - - def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): - o = testdir.makepyfile(""" - def test_generative_order_of_execution(): - import py - test_list = [] - expected_list = list(range(6)) - - def list_append(item): - test_list.append(item) - - def assert_order_of_execution(): - py.builtin.print_('expected order', expected_list) - py.builtin.print_('but got ', test_list) - assert test_list == expected_list - - for i in expected_list: - yield list_append, i - yield assert_order_of_execution - """) - reprec = testdir.inline_run(o) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 7 - assert not skipped and not failed - - def test_order_of_execution_generator_different_codeline(self, testdir): - o = testdir.makepyfile(""" - def test_generative_tests_different_codeline(): - import py - test_list = [] - expected_list = list(range(3)) - - def list_append_2(): - test_list.append(2) - - def list_append_1(): - test_list.append(1) - - def list_append_0(): - test_list.append(0) - - def assert_order_of_execution(): - py.builtin.print_('expected order', expected_list) - py.builtin.print_('but got ', test_list) - assert test_list == expected_list - - yield list_append_0 - yield list_append_1 - yield list_append_2 - yield assert_order_of_execution - """) - reprec = testdir.inline_run(o) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 4 - assert not skipped and not failed - -class TestFunction: - def test_getmodulecollector(self, testdir): - item = testdir.getitem("def test_func(): pass") - modcol = item.getparent(py.test.collect.Module) - assert isinstance(modcol, py.test.collect.Module) - assert hasattr(modcol.obj, 'test_func') - - def test_function_equality(self, testdir, tmpdir): - config = testdir.reparseconfig() - f1 = py.test.collect.Function(name="name", config=config, - args=(1,), callobj=isinstance) - f2 = py.test.collect.Function(name="name",config=config, - args=(1,), callobj=py.builtin.callable) - assert not f1 == f2 - assert f1 != f2 - f3 = py.test.collect.Function(name="name", config=config, - args=(1,2), callobj=py.builtin.callable) - assert not f3 == f2 - assert f3 != f2 - - assert not f3 == f1 - assert f3 != f1 - - f1_b = py.test.collect.Function(name="name", config=config, - args=(1,), callobj=isinstance) - assert f1 == f1_b - assert not f1 != f1_b - - def test_function_equality_with_callspec(self, testdir, tmpdir): - config = testdir.reparseconfig() - class callspec1: - param = 1 - funcargs = {} - id = "hello" - class callspec2: - param = 1 - funcargs = {} - id = "world" - collection = object() - f5 = py.test.collect.Function(name="name", config=config, - callspec=callspec1, callobj=isinstance, collection=collection) - f5b = py.test.collect.Function(name="name", config=config, - callspec=callspec2, callobj=isinstance, collection=collection) - assert f5 != f5b - assert not (f5 == f5b) - - def test_pyfunc_call(self, testdir): - item = testdir.getitem("def test_func(): raise ValueError") - config = item.config - class MyPlugin1: - def pytest_pyfunc_call(self, pyfuncitem): - raise ValueError - class MyPlugin2: - def pytest_pyfunc_call(self, pyfuncitem): - return True - config.pluginmanager.register(MyPlugin1()) - config.pluginmanager.register(MyPlugin2()) - config.hook.pytest_pyfunc_call(pyfuncitem=item) - -class TestSorting: - def test_check_equality(self, testdir): - modcol = testdir.getmodulecol(""" - def test_pass(): pass - def test_fail(): assert 0 - """) - fn1 = modcol.collect_by_name("test_pass") - assert isinstance(fn1, py.test.collect.Function) - fn2 = modcol.collect_by_name("test_pass") - assert isinstance(fn2, py.test.collect.Function) - - assert fn1 == fn2 - assert fn1 != modcol - if py.std.sys.version_info < (3, 0): - assert cmp(fn1, fn2) == 0 - assert hash(fn1) == hash(fn2) - - fn3 = modcol.collect_by_name("test_fail") - assert isinstance(fn3, py.test.collect.Function) - assert not (fn1 == fn3) - assert fn1 != fn3 - - for fn in fn1,fn2,fn3: - assert fn != 3 - assert fn != modcol - assert fn != [1,2,3] - assert [1,2,3] != fn - assert modcol != fn - - def test_allow_sane_sorting_for_decorators(self, testdir): - modcol = testdir.getmodulecol(""" - def dec(f): - g = lambda: f(2) - g.place_as = f - return g - - - def test_b(y): - pass - test_b = dec(test_b) - - def test_a(y): - pass - test_a = dec(test_a) - """) - colitems = modcol.collect() - assert len(colitems) == 2 - assert [item.name for item in colitems] == ['test_b', 'test_a'] - - -class TestConftestCustomization: - def test_pytest_pycollect_module(self, testdir): - testdir.makeconftest(""" - import py - class MyModule(py.test.collect.Module): - pass - def pytest_pycollect_makemodule(path, parent): - if path.basename == "test_xyz.py": - return MyModule(path, parent) - """) - testdir.makepyfile("def some(): pass") - testdir.makepyfile(test_xyz="") - result = testdir.runpytest("--collectonly") - result.stdout.fnmatch_lines([ - "*3 - - def test_traceback_error_during_import(self, testdir): - testdir.makepyfile(""" - x = 1 - x = 2 - x = 17 - asd - """) - result = testdir.runpytest() - assert result.ret != 0 - out = result.stdout.str() - assert "x = 1" not in out - assert "x = 2" not in out - result.stdout.fnmatch_lines([ - ">*asd*", - "E*NameError*", - ]) - result = testdir.runpytest("--fulltrace") - out = result.stdout.str() - assert "x = 1" in out - assert "x = 2" in out - result.stdout.fnmatch_lines([ - ">*asd*", - "E*NameError*", - ])