""" Python test discovery, setup and run of test functions. """importpyimportinspectimportsysimportpytestfrompy._code.codeimportTerminalReprimport_pytestcutdir=py.path.local(_pytest.__file__).dirpath()defpytest_addoption(parser):group=parser.getgroup("general")group.addoption('--funcargs',action="store_true",dest="showfuncargs",default=False,help="show available function arguments, sorted by plugin")parser.addini("python_files",type="args",default=('test_*.py','*_test.py'),help="glob-style file patterns for Python test module discovery")parser.addini("python_classes",type="args",default=("Test",),help="prefixes for Python test class discovery")parser.addini("python_functions",type="args",default=("test",),help="prefixes for Python test function and method discovery")defpytest_cmdline_main(config):ifconfig.option.showfuncargs:showfuncargs(config)return0@pytest.mark.trylastdefpytest_namespace():raises.Exception=pytest.fail.Exceptionreturn{'raises':raises,'collect':{'Module':Module,'Class':Class,'Instance':Instance,'Function':Function,'Generator':Generator,'_fillfuncargs':fillfuncargs}}defpytest_funcarg__pytestconfig(request):""" the pytest config object with access to command line opts."""returnrequest.configdefpytest_pyfunc_call(__multicall__,pyfuncitem):ifnot__multicall__.execute():testfunction=pyfuncitem.objifpyfuncitem._isyieldedfunction():testfunction(*pyfuncitem._args)else:funcargs=pyfuncitem.funcargstestfunction(**funcargs)defpytest_collect_file(path,parent):ext=path.extpb=path.purebasenameifext==".py":ifnotparent.session.isinitpath(path):forpatinparent.config.getini('python_files'):ifpath.fnmatch(pat):breakelse:returnreturnparent.ihook.pytest_pycollect_makemodule(path=path,parent=parent)defpytest_pycollect_makemodule(path,parent):returnModule(path,parent)defpytest_pycollect_makeitem(__multicall__,collector,name,obj):res=__multicall__.execute()ifresisnotNone:returnresifinspect.isclass(obj):#if hasattr(collector.obj, 'unittest'):# return # we assume it's a mixin class for a TestCase derived oneifcollector.classnamefilter(name):ifnothasinit(obj):Class=collector._getcustomclass("Class")returnClass(name,parent=collector)elifcollector.funcnamefilter(name)andhasattr(obj,'__call__'):ifis_generator(obj):returnGenerator(name,parent=collector)else:returncollector._genfunctions(name,obj)defis_generator(func):try:returnpy.code.getrawcode(func).co_flags&32# generator functionexceptAttributeError:# builtin functions have no bytecode# assume them to not be generatorsreturnFalseclassPyobjMixin(object):defobj():deffget(self):try:returnself._objexceptAttributeError:self._obj=obj=self._getobj()returnobjdeffset(self,value):self._obj=valuereturnproperty(fget,fset,None,"underlying python object")obj=obj()def_getobj(self):returngetattr(self.parent.obj,self.name)defgetmodpath(self,stopatmodule=True,includemodule=False):""" return python path relative to the containing module. """chain=self.listchain()chain.reverse()parts=[]fornodeinchain:ifisinstance(node,Instance):continuename=node.nameifisinstance(node,Module):assertname.endswith(".py")name=name[:-3]ifstopatmodule:ifincludemodule:parts.append(name)breakparts.append(name)parts.reverse()s=".".join(parts)returns.replace(".[","[")def_getfslineno(self):try:returnself._fslinenoexceptAttributeError:passobj=self.obj# xxx let decorators etc specify a sane orderingifhasattr(obj,'place_as'):obj=obj.place_asself._fslineno=py.code.getfslineno(obj)returnself._fslinenodefreportinfo(self):# XXX caching?obj=self.objifhasattr(obj,'compat_co_firstlineno'):# nose compatibilityfspath=sys.modules[obj.__module__].__file__iffspath.endswith(".pyc"):fspath=fspath[:-1]#assert 0#fn = inspect.getsourcefile(obj) or inspect.getfile(obj)lineno=obj.compat_co_firstlinenomodpath=obj.__module__else:fspath,lineno=self._getfslineno()modpath=self.getmodpath()returnfspath,lineno,modpathclassPyCollectorMixin(PyobjMixin,pytest.Collector):deffuncnamefilter(self,name):forprefixinself.config.getini("python_functions"):ifname.startswith(prefix):returnTruedefclassnamefilter(self,name):forprefixinself.config.getini("python_classes"):ifname.startswith(prefix):returnTruedefcollect(self):# NB. we avoid random getattrs and peek in the __dict__ instead# (XXX originally introduced from a PyPy need, still true?)dicts=[getattr(self.obj,'__dict__',{})]forbaseclsininspect.getmro(self.obj.__class__):dicts.append(basecls.__dict__)seen={}l=[]fordicindicts:forname,objindic.items():ifnameinseen:continueseen[name]=Trueifname[0]!="_":res=self.makeitem(name,obj)ifresisNone:continueifnotisinstance(res,list):res=[res]l.extend(res)l.sort(key=lambdaitem:item.reportinfo()[:2])returnldefmakeitem(self,name,obj):returnself.ihook.pytest_pycollect_makeitem(collector=self,name=name,obj=obj)def_genfunctions(self,name,funcobj):module=self.getparent(Module).objclscol=self.getparent(Class)cls=clscolandclscol.objorNonemetafunc=Metafunc(funcobj,config=self.config,cls=cls,module=module)gentesthook=self.config.hook.pytest_generate_testsextra=[module]ifclsisnotNone:extra.append(cls())plugins=self.getplugins()+extragentesthook.pcall(plugins,metafunc=metafunc)Function=self._getcustomclass("Function")ifnotmetafunc._calls:returnFunction(name,parent=self)l=[]forcallspecinmetafunc._calls:subname="%s[%s]"%(name,callspec.id)function=Function(name=subname,parent=self,callspec=callspec,callobj=funcobj,keywords={callspec.id:True})l.append(function)returnlclassModule(pytest.File,PyCollectorMixin):def_getobj(self):returnself._memoizedcall('_obj',self._importtestmodule)def_importtestmodule(self):# we assume we are only called once per modulefrom_pytestimportassertionassertion.before_module_import(self)try:try:mod=self.fspath.pyimport(ensuresyspath=True)finally:assertion.after_module_import(self)exceptSyntaxError:excinfo=py.code.ExceptionInfo()raiseself.CollectError(excinfo.getrepr(style="short"))exceptself.fspath.ImportMismatchError:e=sys.exc_info()[1]raiseself.CollectError("import file mismatch:\n""imported module %r has this __file__ attribute:\n"" %s\n""which is not the same as the test file we want to collect:\n"" %s\n""HINT: use a unique basename for your test file modules"%e.args)#print "imported test module", modself.config.pluginmanager.consider_module(mod)returnmoddefsetup(self):ifhasattr(self.obj,'setup_module'):#XXX: nose compat hack, move to nose plugin# if it takes a positional arg, its probably a pytest style one# so we pass the current module objectifinspect.getargspec(self.obj.setup_module)[0]:self.obj.setup_module(self.obj)else:self.obj.setup_module()defteardown(self):ifhasattr(self.obj,'teardown_module'):#XXX: nose compat hack, move to nose plugin# if it takes a positional arg, its probably a py.test style one# so we pass the current module objectifinspect.getargspec(self.obj.teardown_module)[0]:self.obj.teardown_module(self.obj)else:self.obj.teardown_module()classClass(PyCollectorMixin,pytest.Collector):defcollect(self):return[self._getcustomclass("Instance")(name="()",parent=self)]defsetup(self):setup_class=getattr(self.obj,'setup_class',None)ifsetup_classisnotNone:setup_class=getattr(setup_class,'im_func',setup_class)setup_class(self.obj)defteardown(self):teardown_class=getattr(self.obj,'teardown_class',None)ifteardown_classisnotNone:teardown_class=getattr(teardown_class,'im_func',teardown_class)teardown_class(self.obj)classInstance(PyCollectorMixin,pytest.Collector):def_getobj(self):returnself.parent.obj()defnewinstance(self):self.obj=self._getobj()returnself.objclassFunctionMixin(PyobjMixin):""" mixin for the code common to Function and Generator. """defsetup(self):""" perform setup for this test function. """ifhasattr(self,'_preservedparent'):obj=self._preservedparentelifisinstance(self.parent,Instance):obj=self.parent.newinstance()self.obj=self._getobj()else:obj=self.parent.objifinspect.ismethod(self.obj):name='setup_method'else:name='setup_function'setup_func_or_method=getattr(obj,name,None)ifsetup_func_or_methodisnotNone:setup_func_or_method(self.obj)defteardown(self):""" perform teardown for this test function. """ifinspect.ismethod(self.obj):name='teardown_method'else:name='teardown_function'obj=self.parent.objteardown_func_or_meth=getattr(obj,name,None)ifteardown_func_or_methisnotNone:teardown_func_or_meth(self.obj)def_prunetraceback(self,excinfo):ifhasattr(self,'_obj')andnotself.config.option.fulltrace:code=py.code.Code(self.obj)path,firstlineno=code.path,code.firstlinenotraceback=excinfo.tracebackntraceback=traceback.cut(path=path,firstlineno=firstlineno)ifntraceback==traceback:ntraceback=ntraceback.cut(path=path)ifntraceback==traceback:ntraceback=ntraceback.cut(excludepath=cutdir)excinfo.traceback=ntraceback.filter()def_repr_failure_py(self,excinfo,style="long"):ifexcinfo.errisinstance(FuncargRequest.LookupError):fspath,lineno,msg=self.reportinfo()lines,_=inspect.getsourcelines(self.obj)fori,lineinenumerate(lines):ifline.strip().startswith('def'):returnFuncargLookupErrorRepr(fspath,lineno,lines[:i+1],str(excinfo.value))ifexcinfo.errisinstance(pytest.fail.Exception):ifnotexcinfo.value.pytrace:returnstr(excinfo.value)returnsuper(FunctionMixin,self)._repr_failure_py(excinfo,style=style)defrepr_failure(self,excinfo,outerr=None):assertouterrisNone,"XXX outerr usage is deprecated"returnself._repr_failure_py(excinfo,style=self.config.option.tbstyle)classFuncargLookupErrorRepr(TerminalRepr):def__init__(self,filename,firstlineno,deflines,errorstring):self.deflines=deflinesself.errorstring=errorstringself.filename=filenameself.firstlineno=firstlinenodeftoterminal(self,tw):tw.line()forlineinself.deflines:tw.line(" "+line.strip())forlineinself.errorstring.split("\n"):tw.line(" "+line.strip(),red=True)tw.line()tw.line("%s:%d"%(self.filename,self.firstlineno+1))classGenerator(FunctionMixin,PyCollectorMixin,pytest.Collector):defcollect(self):# test generators are seen as collectors but they also# invoke setup/teardown on popular request# (induced by the common "test_*" naming shared with normal tests)self.session._setupstate.prepare(self)# see FunctionMixin.setup and test_setupstate_is_preserved_134self._preservedparent=self.parent.objl=[]seen={}fori,xinenumerate(self.obj()):name,call,args=self.getcallargs(x)ifnotpy.builtin.callable(call):raiseTypeError("%r yielded non callable test %r"%(self.obj,call,))ifnameisNone:name="[%d]"%ielse:name="['%s']"%nameifnameinseen:raiseValueError("%r generated tests with non-unique name %r"%(self,name))seen[name]=Truel.append(self.Function(name,self,args=args,callobj=call))returnldefgetcallargs(self,obj):ifnotisinstance(obj,(tuple,list)):obj=(obj,)# explict namingifisinstance(obj[0],py.builtin._basestring):name=obj[0]obj=obj[1:]else:name=Nonecall,args=obj[0],obj[1:]returnname,call,args## Test Items#_dummy=object()classFunction(FunctionMixin,pytest.Item):""" a Function Item is responsible for setting up and executing a Python callable test object. """_genid=Nonedef__init__(self,name,parent=None,args=None,config=None,callspec=None,callobj=_dummy,keywords=None,session=None):super(Function,self).__init__(name,parent,config=config,session=session)self._args=argsifself._isyieldedfunction():assertnotcallspec,("yielded functions (deprecated) cannot have funcargs")else:ifcallspecisnotNone:self.funcargs=callspec.funcargsor{}self._genid=callspec.idifhasattr(callspec,"param"):self._requestparam=callspec.paramelse:self.funcargs={}ifcallobjisnot_dummy:self._obj=callobjself.function=getattr(self.obj,'im_func',self.obj)self.keywords.update(py.builtin._getfuncdict(self.obj)or{})ifkeywords:self.keywords.update(keywords)def_getobj(self):name=self.namei=name.find("[")# parametrizationifi!=-1:name=name[:i]returngetattr(self.parent.obj,name)def_isyieldedfunction(self):returnself._argsisnotNonedefruntest(self):""" execute the underlying test function. """self.ihook.pytest_pyfunc_call(pyfuncitem=self)defsetup(self):super(Function,self).setup()ifhasattr(self,'funcargs'):fillfuncargs(self)def__eq__(self,other):try:return(self.name==other.nameandself._args==other._argsandself.parent==other.parentandself.obj==other.objandgetattr(self,'_genid',None)==getattr(other,'_genid',None))exceptAttributeError:passreturnFalsedef__ne__(self,other):returnnotself==otherdef__hash__(self):returnhash((self.parent,self.name))defhasinit(obj):init=getattr(obj,'__init__',None)ifinit:ifinit!=object.__init__:returnTruedefgetfuncargnames(function,startindex=None):# XXX merge with main.py's varnamesargnames=py.std.inspect.getargs(py.code.getrawcode(function))[0]ifstartindexisNone:startindex=py.std.inspect.ismethod(function)and1or0defaults=getattr(function,'func_defaults',getattr(function,'__defaults__',None))or()numdefaults=len(defaults)ifnumdefaults:returnargnames[startindex:-numdefaults]returnargnames[startindex:]deffillfuncargs(function):""" fill missing funcargs. """request=FuncargRequest(pyfuncitem=function)request._fillfuncargs()_notexists=object()classCallSpec:def__init__(self,funcargs,id,param):self.funcargs=funcargsself.id=idifparamisnot_notexists:self.param=paramdef__repr__(self):return"<CallSpec id=%r param=%r funcargs=%r>"%(self.id,getattr(self,'param','?'),self.funcargs)classMetafunc:def__init__(self,function,config=None,cls=None,module=None):self.config=configself.module=moduleself.function=functionself.funcargnames=getfuncargnames(function,startindex=int(clsisnotNone))self.cls=clsself.module=moduleself._calls=[]self._ids=py.builtin.set()defaddcall(self,funcargs=None,id=_notexists,param=_notexists):""" add a new call to the underlying test function during the collection phase of a test run. Note that request.addcall() is called during the test collection phase prior and independently to actual test execution. Therefore you should perform setup of resources in a funcarg factory which can be instrumented with the ``param``. :arg funcargs: argument keyword dictionary used when invoking the test function. :arg id: used for reporting and identification purposes. If you don't supply an `id` the length of the currently list of calls to the test function will be used. :arg param: will be exposed to a later funcarg factory invocation through the ``request.param`` attribute. It allows to defer test fixture setup activities to when an actual test is run. """assertfuncargsisNoneorisinstance(funcargs,dict)iffuncargsisnotNone:fornameinfuncargs:ifnamenotinself.funcargnames:pytest.fail("funcarg %r not used in this function."%name)ifidisNone:raiseValueError("id=None not allowed")ifidis_notexists:id=len(self._calls)id=str(id)ifidinself._ids:raiseValueError("duplicate id %r"%id)self._ids.add(id)self._calls.append(CallSpec(funcargs,id,param))classFuncargRequest:""" A request for function arguments from a test function. 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=NoneclassLookupError(LookupError):""" error on performing funcarg request. """def__init__(self,pyfuncitem):self._pyfuncitem=pyfuncitemifhasattr(pyfuncitem,'_requestparam'):self.param=pyfuncitem._requestparamextra=[objforobjin(self.module,self.instance)ifobj]self._plugins=pyfuncitem.getplugins()+extraself._funcargs=self._pyfuncitem.funcargs.copy()self._name2factory={}self._currentarg=None@propertydeffunction(self):""" function object of the test invocation. """returnself._pyfuncitem.obj@propertydefkeywords(self):""" keywords of the test function item. .. versionadded:: 2.0 """returnself._pyfuncitem.keywords@propertydefmodule(self):""" module where the test function was collected. """returnself._pyfuncitem.getparent(pytest.Module).obj@propertydefcls(self):""" class (can be None) where the test function was collected. """clscol=self._pyfuncitem.getparent(pytest.Class)ifclscol:returnclscol.obj@propertydefinstance(self):""" instance (can be None) on which test function was collected. """returnpy.builtin._getimself(self.function)@propertydefconfig(self):""" the pytest config object associated with this request. """returnself._pyfuncitem.config@propertydeffspath(self):""" the file system path of the test module which collected this test. """returnself._pyfuncitem.fspathdef_fillfuncargs(self):argnames=getfuncargnames(self.function)ifargnames:assertnotgetattr(self._pyfuncitem,'_args',None),("yielded functions cannot have funcargs")forargnameinargnames:ifargnamenotinself._pyfuncitem.funcargs:self._pyfuncitem.funcargs[argname]=self.getfuncargvalue(argname)defapplymarker(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(...)``. """ifnotisinstance(marker,py.test.mark.XYZ.__class__):raiseValueError("%r is not a py.test.mark.* object")self._pyfuncitem.keywords[marker.markname]=markerdefcached_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). """ifnothasattr(self.config,'_setupcache'):self.config._setupcache={}# XXX weakref?cachekey=(self._currentarg,self._getscopeitem(scope),extrakey)cache=self.config._setupcachetry:val=cache[cachekey]exceptKeyError:val=setup()cache[cachekey]=valifteardownisnotNone:deffinalizer():delcache[cachekey]teardown(val)self._addfinalizer(finalizer,scope=scope)returnvaldefgetfuncargvalue(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:returnself._funcargs[argname]exceptKeyError:passifargnamenotinself._name2factory:self._name2factory[argname]=self.config.pluginmanager.listattr(plugins=self._plugins,attrname=self._argprefix+str(argname))#else: we are called recursivelyifnotself._name2factory[argname]:self._raiselookupfailed(argname)funcargfactory=self._name2factory[argname].pop()oldarg=self._currentargself._currentarg=argnametry:self._funcargs[argname]=res=funcargfactory(request=self)finally:self._currentarg=oldargreturnresdef_getscopeitem(self,scope):ifscope=="function":returnself._pyfuncitemelifscope=="session":returnNoneelifscope=="class":x=self._pyfuncitem.getparent(pytest.Class)ifxisnotNone:returnxscope="module"ifscope=="module":returnself._pyfuncitem.getparent(pytest.Module)raiseValueError("unknown finalization scope %r"%(scope,))defaddfinalizer(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"<FuncargRequest for %r>"%(self._pyfuncitem)def_raiselookupfailed(self,argname):available=[]forplugininself._plugins:fornameinvars(plugin):ifname.startswith(self._argprefix):name=name[len(self._argprefix):]ifnamenotinavailable: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."raiseself.LookupError(msg)defshowfuncargs(config):from_pytest.mainimportwrap_sessionreturnwrap_session(config,_showfuncargs_main)def_showfuncargs_main(config,session):session.perform_collect()ifsession.items:plugins=session.items[0].getplugins()else:plugins=session.getplugins()curdir=py.path.local()tw=py.io.TerminalWriter()verbose=config.getvalue("verbose")forplugininplugins:available=[]forname,factoryinvars(plugin).items():ifname.startswith(FuncargRequest._argprefix):name=name[len(FuncargRequest._argprefix):]ifnamenotinavailable:available.append([name,factory])ifavailable:pluginname=plugin.__name__forname,factoryinavailable:loc=getlocation(factory,curdir)ifverbose:funcargspec="%s -- %s"%(name,loc,)else:funcargspec=nametw.line(funcargspec,green=True)doc=factory.__doc__or""ifdoc:forlineindoc.split("\n"):tw.line(" "+line.strip())else:tw.line(" %s: no docstring available"%(loc,),red=True)defgetlocation(function,curdir):importinspectfn=py.path.local(inspect.getfile(function))lineno=py.builtin._getcode(function).co_firstlinenoiffn.relto(curdir):fn=fn.relto(curdir)return"%s:%d"%(fn,lineno+1)# builtin pytest.raises helperdefraises(ExpectedException,*args,**kwargs):""" assert that a code block/function call raises @ExpectedException and raise a failure exception otherwise. If using Python 2.5 or above, you may use this function as a context manager:: >>> with raises(ZeroDivisionError): ... 1/0 Or you can specify a callable by passing a to-be-called lambda:: >>> raises(ZeroDivisionError, lambda: 1/0) <ExceptionInfo ...> or you can specify an arbitrary callable with arguments:: >>> def f(x): return 1/x ... >>> raises(ZeroDivisionError, f, 0) <ExceptionInfo ...> >>> raises(ZeroDivisionError, f, x=0) <ExceptionInfo ...> A third possibility is to use a string which which will be executed:: >>> raises(ZeroDivisionError, "f(0)") <ExceptionInfo ...> """__tracebackhide__=Trueifnotargs:returnRaisesContext(ExpectedException)elifisinstance(args[0],str):code,=argsassertisinstance(code,str)frame=sys._getframe(1)loc=frame.f_locals.copy()loc.update(kwargs)#print "raises frame scope: %r" % frame.f_localstry:code=py.code.Source(code).compile()py.builtin.exec_(code,frame.f_globals,loc)# XXX didn'T mean f_globals == f_locals something special?# this is destroyed here ...exceptExpectedException:returnpy.code.ExceptionInfo()else:func=args[0]try:func(*args[1:],**kwargs)exceptExpectedException:returnpy.code.ExceptionInfo()k=", ".join(["%s=%r"%xforxinkwargs.items()])ifk:k=', '+kexpr='%s(%r%s)'%(getattr(func,'__name__',func),args,k)pytest.fail("DID NOT RAISE")classRaisesContext(object):def__init__(self,ExpectedException):self.ExpectedException=ExpectedExceptionself.excinfo=Nonedef__enter__(self):self.excinfo=object.__new__(py.code.ExceptionInfo)returnself.excinfodef__exit__(self,*tp):__tracebackhide__=Trueiftp[0]isNone:pytest.fail("DID NOT RAISE")self.excinfo.__init__(tp)returnissubclass(self.excinfo.type,self.ExpectedException)