""" core implementation of testing process: init, session, runtest loop. """importpyimportpytest,_pytestimportos,sys,imptracebackcutdir=py.path.local(_pytest.__file__).dirpath()# exitcodes for the command lineEXIT_OK=0EXIT_TESTSFAILED=1EXIT_INTERRUPTED=2EXIT_INTERNALERROR=3name_re=py.std.re.compile("^[a-zA-Z_]\w*$")defpytest_addoption(parser):parser.addini("norecursedirs","directory patterns to avoid for recursion",type="args",default=('.*','CVS','_darcs','{arch}'))#parser.addini("dirpatterns",# "patterns specifying possible locations of test files",# type="linelist", default=["**/test_*.txt",# "**/test_*.py", "**/*_test.py"]#)group=parser.getgroup("general","running and selection options")group._addoption('-x','--exitfirst',action="store_true",default=False,dest="exitfirst",help="exit instantly on first error or failed test."),group._addoption('--maxfail',metavar="num",action="store",type="int",dest="maxfail",default=0,help="exit after first num failures or errors.")group._addoption('--strict',action="store_true",help="run pytest in strict mode, warnings become errors.")group=parser.getgroup("collect","collection")group.addoption('--collectonly',action="store_true",dest="collectonly",help="only collect tests, don't execute them."),group.addoption('--pyargs',action="store_true",help="try to interpret all arguments as python packages.")group.addoption("--ignore",action="append",metavar="path",help="ignore path during collection (multi-allowed).")group.addoption('--confcutdir',dest="confcutdir",default=None,metavar="dir",help="only load conftest.py's relative to specified dir.")group=parser.getgroup("debugconfig","test session debugging and configuration")group.addoption('--basetemp',dest="basetemp",default=None,metavar="dir",help="base temporary directory for this test run.")defpytest_namespace():collect=dict(Item=Item,Collector=Collector,File=File,Session=Session)returndict(collect=collect)defpytest_configure(config):py.test.config=config# compatibiltiyifconfig.option.exitfirst:config.option.maxfail=1defwrap_session(config,doit):"""Skeleton command line program"""session=Session(config)session.exitstatus=EXIT_OKinitstate=0try:config.pluginmanager.do_configure(config)initstate=1config.hook.pytest_sessionstart(session=session)initstate=2doit(config,session)exceptpytest.UsageError:raiseexceptKeyboardInterrupt:excinfo=py.code.ExceptionInfo()config.hook.pytest_keyboard_interrupt(excinfo=excinfo)session.exitstatus=EXIT_INTERRUPTEDexcept:excinfo=py.code.ExceptionInfo()config.pluginmanager.notify_exception(excinfo,config.option)session.exitstatus=EXIT_INTERNALERRORifexcinfo.errisinstance(SystemExit):sys.stderr.write("mainloop: caught Spurious SystemExit!\n")ifinitstate>=2:config.hook.pytest_sessionfinish(session=session,exitstatus=session.exitstatusor(session._testsfailedand1))ifnotsession.exitstatusandsession._testsfailed:session.exitstatus=EXIT_TESTSFAILEDifinitstate>=1:config.pluginmanager.do_unconfigure(config)returnsession.exitstatusdefpytest_cmdline_main(config):returnwrap_session(config,_main)def_main(config,session):""" default command line protocol for initialization, session, running tests and reporting. """config.hook.pytest_collection(session=session)config.hook.pytest_runtestloop(session=session)defpytest_collection(session):returnsession.perform_collect()defpytest_runtestloop(session):ifsession.config.option.collectonly:returnTruefori,iteminenumerate(session.items):try:nextitem=session.items[i+1]exceptIndexError:nextitem=Noneitem.config.hook.pytest_runtest_protocol(item=item,nextitem=nextitem)ifsession.shouldstop:raisesession.Interrupted(session.shouldstop)returnTruedefpytest_ignore_collect(path,config):p=path.dirpath()ignore_paths=config._getconftest_pathlist("collect_ignore",path=p)ignore_paths=ignore_pathsor[]excludeopt=config.getvalue("ignore")ifexcludeopt:ignore_paths.extend([py.path.local(x)forxinexcludeopt])returnpathinignore_pathsclassHookProxy:def__init__(self,fspath,config):self.fspath=fspathself.config=configdef__getattr__(self,name):hookmethod=getattr(self.config.hook,name)defcall_matching_hooks(**kwargs):plugins=self.config._getmatchingplugins(self.fspath)returnhookmethod.pcall(plugins,**kwargs)returncall_matching_hooksdefcompatproperty(name):deffget(self):returngetattr(pytest,name)returnproperty(fget,None,None,"deprecated attribute %r, use pytest.%s"%(name,name))classNode(object):""" base class for all Nodes in the collection tree. Collector subclasses have children, Items are terminal nodes."""def__init__(self,name,parent=None,config=None,session=None):#: a unique name with the scope of the parentself.name=name#: the parent collector node.self.parent=parent#: the test config objectself.config=configorparent.config#: the collection this node is part ofself.session=sessionorparent.session#: filesystem path where this node was collected fromself.fspath=getattr(parent,'fspath',None)self.ihook=self.session.gethookproxy(self.fspath)self.keywords={self.name:True}Module=compatproperty("Module")Class=compatproperty("Class")Instance=compatproperty("Instance")Function=compatproperty("Function")File=compatproperty("File")Item=compatproperty("Item")def_getcustomclass(self,name):cls=getattr(self,name)ifcls!=getattr(pytest,name):py.log._apiwarn("2.0","use of node.%s is deprecated, ""use pytest_pycollect_makeitem(...) to create custom ""collection nodes"%name)returnclsdef__repr__(self):return"<%s%r>"%(self.__class__.__name__,getattr(self,'name',None))# methods for ordering nodes@propertydefnodeid(self):try:returnself._nodeidexceptAttributeError:self._nodeid=x=self._makeid()returnxdef_makeid(self):returnself.parent.nodeid+"::"+self.namedef__eq__(self,other):ifnotisinstance(other,Node):returnFalsereturnself.__class__==other.__class__and \
self.name==other.nameandself.parent==other.parentdef__ne__(self,other):returnnotself==otherdef__hash__(self):returnhash((self.name,self.parent))defsetup(self):passdefteardown(self):passdef_memoizedcall(self,attrname,function):exattrname="_ex_"+attrnamefailure=getattr(self,exattrname,None)iffailureisnotNone:py.builtin._reraise(failure[0],failure[1],failure[2])ifhasattr(self,attrname):returngetattr(self,attrname)try:res=function()exceptpy.builtin._sysex:raiseexcept:failure=py.std.sys.exc_info()setattr(self,exattrname,failure)raisesetattr(self,attrname,res)returnresdeflistchain(self):""" return list of all parent collectors up to self, starting from root of collection tree. """chain=[]item=selfwhileitemisnotNone:chain.append(item)item=item.parentchain.reverse()returnchaindeflistnames(self):return[x.nameforxinself.listchain()]defgetplugins(self):returnself.config._getmatchingplugins(self.fspath)defgetparent(self,cls):current=selfwhilecurrentandnotisinstance(current,cls):current=current.parentreturncurrentdef_prunetraceback(self,excinfo):passdef_repr_failure_py(self,excinfo,style=None):ifself.config.option.fulltrace:style="long"else:self._prunetraceback(excinfo)# XXX should excinfo.getrepr record all data and toterminal()# process it?ifstyleisNone:ifself.config.option.tbstyle=="short":style="short"else:style="long"returnexcinfo.getrepr(funcargs=True,showlocals=self.config.option.showlocals,style=style)repr_failure=_repr_failure_pyclassCollector(Node):""" Collector instances create children through collect() and thus iteratively build a tree. """classCollectError(Exception):""" an error during collection, contains a custom message. """defcollect(self):""" returns a list of children (items and collectors) for this collection node. """raiseNotImplementedError("abstract")defrepr_failure(self,excinfo):""" represent a collection failure. """ifexcinfo.errisinstance(self.CollectError):exc=excinfo.valuereturnstr(exc.args[0])returnself._repr_failure_py(excinfo,style="short")def_memocollect(self):""" internal helper method to cache results of calling collect(). """returnself._memoizedcall('_collected',lambda:list(self.collect()))def_prunetraceback(self,excinfo):ifhasattr(self,'fspath'):path=self.fspathtraceback=excinfo.tracebackntraceback=traceback.cut(path=self.fspath)ifntraceback==traceback:ntraceback=ntraceback.cut(excludepath=tracebackcutdir)excinfo.traceback=ntraceback.filter()classFSCollector(Collector):def__init__(self,fspath,parent=None,config=None,session=None):fspath=py.path.local(fspath)# xxx only for test_resultlog.py?name=fspath.basenameifparentisnotNone:rel=fspath.relto(parent.fspath)ifrel:name=relname=name.replace(os.sep,"/")super(FSCollector,self).__init__(name,parent,config,session)self.fspath=fspathdef_makeid(self):ifself==self.session:return"."relpath=self.session.fspath.bestrelpath(self.fspath)ifos.sep!="/":relpath=relpath.replace(os.sep,"/")returnrelpathclassFile(FSCollector):""" base class for collecting tests from a file. """classItem(Node):""" a basic test invocation item. Note that for a single function there might be multiple test invocation items. """nextitem=Nonedefreportinfo(self):returnself.fspath,None,""@propertydeflocation(self):try:returnself._locationexceptAttributeError:location=self.reportinfo()# bestrelpath is a quite slow functioncache=self.config.__dict__.setdefault("_bestrelpathcache",{})try:fspath=cache[location[0]]exceptKeyError:fspath=self.session.fspath.bestrelpath(location[0])cache[location[0]]=fspathlocation=(fspath,location[1],str(location[2]))self._location=locationreturnlocationclassNoMatch(Exception):""" raised if matching cannot locate a matching names. """classSession(FSCollector):classInterrupted(KeyboardInterrupt):""" signals an interrupted test run. """__module__='builtins'# for py3def__init__(self,config):super(Session,self).__init__(py.path.local(),parent=None,config=config,session=self)assertself.config.pluginmanager.register(self,name="session",prepend=True)self._testsfailed=0self.shouldstop=Falseself.trace=config.trace.root.get("collection")self._norecursepatterns=config.getini("norecursedirs")defpytest_collectstart(self):ifself.shouldstop:raiseself.Interrupted(self.shouldstop)defpytest_runtest_logreport(self,report):ifreport.failedand'xfail'notingetattr(report,'keywords',[]):self._testsfailed+=1maxfail=self.config.getvalue("maxfail")ifmaxfailandself._testsfailed>=maxfail:self.shouldstop="stopping after %d failures"%(self._testsfailed)pytest_collectreport=pytest_runtest_logreportdefisinitpath(self,path):returnpathinself._initialpathsdefgethookproxy(self,fspath):returnHookProxy(fspath,self.config)defperform_collect(self,args=None,genitems=True):hook=self.config.hooktry:items=self._perform_collect(args,genitems)hook.pytest_collection_modifyitems(session=self,config=self.config,items=items)finally:hook.pytest_collection_finish(session=self)returnitemsdef_perform_collect(self,args,genitems):ifargsisNone:args=self.config.argsself.trace("perform_collect",self,args)self.trace.root.indent+=1self._notfound=[]self._initialpaths=set()self._initialparts=[]self.items=items=[]forarginargs:parts=self._parsearg(arg)self._initialparts.append(parts)self._initialpaths.add(parts[0])self.ihook.pytest_collectstart(collector=self)rep=self.ihook.pytest_make_collect_report(collector=self)self.ihook.pytest_collectreport(report=rep)self.trace.root.indent-=1ifself._notfound:forarg,excinself._notfound:line="(no name %r in any of %r)"%(arg,exc.args[0])raisepytest.UsageError("not found: %s\n%s"%(arg,line))ifnotgenitems:returnrep.resultelse:ifrep.passed:fornodeinrep.result:self.items.extend(self.genitems(node))returnitemsdefcollect(self):forpartsinself._initialparts:arg="::".join(map(str,parts))self.trace("processing argument",arg)self.trace.root.indent+=1try:forxinself._collect(arg):yieldxexceptNoMatch:# we are inside a make_report hook so# we cannot directly pass through the exceptionself._notfound.append((arg,sys.exc_info()[1]))self.trace.root.indent-=1breakself.trace.root.indent-=1def_collect(self,arg):names=self._parsearg(arg)path=names.pop(0)ifpath.check(dir=1):assertnotnames,"invalid arg %r"%(arg,)forpathinpath.visit(fil=lambdax:x.check(file=1),rec=self._recurse,bf=True,sort=True):forxinself._collectfile(path):yieldxelse:assertpath.check(file=1)forxinself.matchnodes(self._collectfile(path),names):yieldxdef_collectfile(self,path):ihook=self.gethookproxy(path)ifnotself.isinitpath(path):ifihook.pytest_ignore_collect(path=path,config=self.config):return()returnihook.pytest_collect_file(path=path,parent=self)def_recurse(self,path):ihook=self.gethookproxy(path.dirpath())ifihook.pytest_ignore_collect(path=path,config=self.config):returnforpatinself._norecursepatterns:ifpath.check(fnmatch=pat):returnFalseihook=self.gethookproxy(path)ihook.pytest_collect_directory(path=path,parent=self)returnTruedef_tryconvertpyarg(self,x):mod=Nonepath=[os.path.abspath('.')]+sys.pathfornameinx.split('.'):# ignore anything that's not a proper name here# else something like --pyargs will mess up '.'# since imp.find_module will actually sometimes work for it# but it's supposed to be considered a filesystem path# not a packageifname_re.match(name)isNone:returnxtry:fd,mod,type_=imp.find_module(name,path)exceptImportError:returnxelse:iffdisnotNone:fd.close()iftype_[2]!=imp.PKG_DIRECTORY:path=[os.path.dirname(mod)]else:path=[mod]returnmoddef_parsearg(self,arg):""" return (fspath, names) tuple after checking the file exists. """arg=str(arg)ifself.config.option.pyargs:arg=self._tryconvertpyarg(arg)parts=str(arg).split("::")relpath=parts[0].replace("/",os.sep)path=self.fspath.join(relpath,abs=True)ifnotpath.check():ifself.config.option.pyargs:msg="file or package not found: "else:msg="file not found: "raisepytest.UsageError(msg+arg)parts[0]=pathreturnpartsdefmatchnodes(self,matching,names):self.trace("matchnodes",matching,names)self.trace.root.indent+=1nodes=self._matchnodes(matching,names)num=len(nodes)self.trace("matchnodes finished -> ",num,"nodes")self.trace.root.indent-=1ifnum==0:raiseNoMatch(matching,names[:1])returnnodesdef_matchnodes(self,matching,names):ifnotmatchingornotnames:returnmatchingname=names[0]assertnamenextnames=names[1:]resultnodes=[]fornodeinmatching:ifisinstance(node,pytest.Item):ifnotnames:resultnodes.append(node)continueassertisinstance(node,pytest.Collector)node.ihook.pytest_collectstart(collector=node)rep=node.ihook.pytest_make_collect_report(collector=node)ifrep.passed:has_matched=Falseforxinrep.result:ifx.name==name:resultnodes.extend(self.matchnodes([x],nextnames))has_matched=True# XXX accept IDs that don't have "()" for class instancesifnothas_matchedandlen(rep.result)==1andx.name=="()":nextnames.insert(0,name)resultnodes.extend(self.matchnodes([x],nextnames))node.ihook.pytest_collectreport(report=rep)returnresultnodesdefgenitems(self,node):self.trace("genitems",node)ifisinstance(node,pytest.Item):node.ihook.pytest_itemcollected(item=node)yieldnodeelse:assertisinstance(node,pytest.Collector)node.ihook.pytest_collectstart(collector=node)rep=node.ihook.pytest_make_collect_report(collector=node)ifrep.passed:forsubnodeinrep.result:forxinself.genitems(subnode):yieldxnode.ihook.pytest_collectreport(report=rep)