""" (disabled by default) support for testing py.test and py.test plugins. """importpy,pytestimportsys,osimportreimportinspectimporttimefromfnmatchimportfnmatchfrom_pytest.mainimportSessionfrompy.builtinimportprint_from_pytest.coreimportHookRelaydefpytest_addoption(parser):group=parser.getgroup("pylib")group.addoption('--no-tools-on-path',action="store_true",dest="notoolsonpath",default=False,help=("discover tools on PATH instead of going through py.cmdline."))defpytest_configure(config):# This might be called multiple times. Only take the first.global_pytest_fullpathimportpytesttry:_pytest_fullpathexceptNameError:_pytest_fullpath=os.path.abspath(pytest.__file__.rstrip("oc"))defpytest_funcarg___pytest(request):returnPytestArg(request)classPytestArg:def__init__(self,request):self.request=requestdefgethookrecorder(self,hook):hookrecorder=HookRecorder(hook._pm)hookrecorder.start_recording(hook._hookspecs)self.request.addfinalizer(hookrecorder.finish_recording)returnhookrecorderclassParsedCall:def__init__(self,name,locals):assert'_name'notinlocalsself.__dict__.update(locals)self.__dict__.pop('self')self._name=namedef__repr__(self):d=self.__dict__.copy()deld['_name']return"<ParsedCall %r(**%r)>"%(self._name,d)classHookRecorder:def__init__(self,pluginmanager):self._pluginmanager=pluginmanagerself.calls=[]self._recorders={}defstart_recording(self,hookspecs):ifnotisinstance(hookspecs,(list,tuple)):hookspecs=[hookspecs]forhookspecinhookspecs:asserthookspecnotinself._recordersclassRecordCalls:_recorder=selfforname,methodinvars(hookspec).items():ifname[0]!="_":setattr(RecordCalls,name,self._makecallparser(method))recorder=RecordCalls()self._recorders[hookspec]=recorderself._pluginmanager.register(recorder)self.hook=HookRelay(hookspecs,pm=self._pluginmanager,prefix="pytest_")deffinish_recording(self):forrecorderinself._recorders.values():self._pluginmanager.unregister(recorder)self._recorders.clear()def_makecallparser(self,method):name=method.__name__args,varargs,varkw,default=py.std.inspect.getargspec(method)ifnotargsorargs[0]!="self":args.insert(0,'self')fspec=py.std.inspect.formatargspec(args,varargs,varkw,default)# we use exec because we want to have early type# errors on wrong input arguments, using# *args/**kwargs delays this and gives errors# elsewhereexec(py.code.compile(""" def %(name)s%(fspec)s: self._recorder.calls.append( ParsedCall(%(name)r, locals())) """%locals()))returnlocals()[name]defgetcalls(self,names):ifisinstance(names,str):names=names.split()fornameinnames:forclsinself._recorders:ifnameinvars(cls):breakelse:raiseValueError("callname %r not found in %r"%(name,self._recorders.keys()))l=[]forcallinself.calls:ifcall._nameinnames:l.append(call)returnldefcontains(self,entries):__tracebackhide__=Truefrompy.builtinimportprint_i=0entries=list(entries)backlocals=py.std.sys._getframe(1).f_localswhileentries:name,check=entries.pop(0)forind,callinenumerate(self.calls[i:]):ifcall._name==name:print_("NAMEMATCH",name,call)ifeval(check,backlocals,call.__dict__):print_("CHECKERMATCH",repr(check),"->",call)else:print_("NOCHECKERMATCH",repr(check),"-",call)continuei+=ind+1breakprint_("NONAMEMATCH",name,"with",call)else:py.test.fail("could not find %r check %r"%(name,check))defpopcall(self,name):__tracebackhide__=Truefori,callinenumerate(self.calls):ifcall._name==name:delself.calls[i]returncalllines=["could not find call %r, in:"%(name,)]lines.extend([" %s"%str(x)forxinself.calls])py.test.fail("\n".join(lines))defgetcall(self,name):l=self.getcalls(name)assertlen(l)==1,(name,l)returnl[0]defpytest_funcarg__linecomp(request):returnLineComp()defpytest_funcarg__LineMatcher(request):returnLineMatcherdefpytest_funcarg__testdir(request):tmptestdir=TmpTestdir(request)returntmptestdirrex_outcome=re.compile("(\d+) (\w+)")classRunResult:def__init__(self,ret,outlines,errlines,duration):self.ret=retself.outlines=outlinesself.errlines=errlinesself.stdout=LineMatcher(outlines)self.stderr=LineMatcher(errlines)self.duration=durationdefparseoutcomes(self):forlineinreversed(self.outlines):if'seconds'inline:outcomes=rex_outcome.findall(line)ifoutcomes:d={}fornum,catinoutcomes:d[cat]=int(num)returndclassTmpTestdir:def__init__(self,request):self.request=requestself.Config=request.config.__class__self._pytest=request.getfuncargvalue("_pytest")# XXX remove duplication with tmpdir pluginbasetmp=request.config._tmpdirhandler.ensuretemp("testdir")name=request.function.__name__foriinrange(100):try:tmpdir=basetmp.mkdir(name+str(i))exceptpy.error.EEXIST:continuebreak# we need to create another subdir# because Directory.collect() currently loads# conftest.py from sibling directoriesself.tmpdir=tmpdir.mkdir(name)self.plugins=[]self._syspathremove=[]self.chdir()# always chdirself.request.addfinalizer(self.finalize)def__repr__(self):return"<TmpTestdir %r>"%(self.tmpdir,)deffinalize(self):forpinself._syspathremove:py.std.sys.path.remove(p)ifhasattr(self,'_olddir'):self._olddir.chdir()# delete modules that have been loaded from tmpdirforname,modinlist(sys.modules.items()):ifmod:fn=getattr(mod,'__file__',None)iffnandfn.startswith(str(self.tmpdir)):delsys.modules[name]defgetreportrecorder(self,obj):ifhasattr(obj,'config'):obj=obj.configifhasattr(obj,'hook'):obj=obj.hookasserthasattr(obj,'_hookspecs'),objreprec=ReportRecorder(obj)reprec.hookrecorder=self._pytest.gethookrecorder(obj)reprec.hook=reprec.hookrecorder.hookreturnreprecdefchdir(self):old=self.tmpdir.chdir()ifnothasattr(self,'_olddir'):self._olddir=olddef_makefile(self,ext,args,kwargs):items=list(kwargs.items())ifargs:source="\n".join(map(str,args))+"\n"basename=self.request.function.__name__items.insert(0,(basename,source))ret=Noneforname,valueinitems:p=self.tmpdir.join(name).new(ext=ext)source=str(py.code.Source(value)).lstrip()p.write(source.encode("utf-8"),"wb")ifretisNone:ret=preturnretdefmakefile(self,ext,*args,**kwargs):returnself._makefile(ext,args,kwargs)defmakeini(self,source):returnself.makefile('cfg',setup=source)defmakeconftest(self,source):returnself.makepyfile(conftest=source)defmakeini(self,source):returnself.makefile('.ini',tox=source)defgetinicfg(self,source):p=self.makeini(source)returnpy.iniconfig.IniConfig(p)['pytest']defmakepyfile(self,*args,**kwargs):returnself._makefile('.py',args,kwargs)defmaketxtfile(self,*args,**kwargs):returnself._makefile('.txt',args,kwargs)defsyspathinsert(self,path=None):ifpathisNone:path=self.tmpdirpy.std.sys.path.insert(0,str(path))self._syspathremove.append(str(path))defmkdir(self,name):returnself.tmpdir.mkdir(name)defmkpydir(self,name):p=self.mkdir(name)p.ensure("__init__.py")returnpSession=Sessiondefgetnode(self,config,arg):session=Session(config)assert'::'notinstr(arg)p=py.path.local(arg)x=session.fspath.bestrelpath(p)returnsession.perform_collect([x],genitems=False)[0]defgetpathnode(self,path):config=self.parseconfig(path)session=Session(config)x=session.fspath.bestrelpath(path)returnsession.perform_collect([x],genitems=False)[0]defgenitems(self,colitems):session=colitems[0].sessionresult=[]forcolitemincolitems:result.extend(session.genitems(colitem))returnresultdefinline_genitems(self,*args):#config = self.parseconfig(*args)config=self.parseconfigure(*args)rec=self.getreportrecorder(config)session=Session(config)session.perform_collect()returnsession.items,recdefrunitem(self,source):# used from runner functional testsitem=self.getitem(source)# the test class where we are called from wants to provide the runnertestclassinstance=py.builtin._getimself(self.request.function)runner=testclassinstance.getrunner()returnrunner(item)definline_runsource(self,source,*cmdlineargs):p=self.makepyfile(source)l=list(cmdlineargs)+[p]returnself.inline_run(*l)definline_runsource1(self,*args):args=list(args)source=args.pop()p=self.makepyfile(source)l=list(args)+[p]reprec=self.inline_run(*l)reports=reprec.getreports("pytest_runtest_logreport")assertlen(reports)==1,reportsreturnreports[0]definline_run(self,*args):args=("-s",)+args# otherwise FD leakageconfig=self.parseconfig(*args)reprec=self.getreportrecorder(config)#config.pluginmanager.do_configure(config)config.hook.pytest_cmdline_main(config=config)#config.pluginmanager.do_unconfigure(config)returnreprecdefconfig_preparse(self):config=self.Config()forplugininself.plugins:ifisinstance(plugin,str):config.pluginmanager.import_plugin(plugin)else:ifisinstance(plugin,dict):plugin=PseudoPlugin(plugin)ifnotconfig.pluginmanager.isregistered(plugin):config.pluginmanager.register(plugin)returnconfigdefparseconfig(self,*args):ifnotargs:args=(self.tmpdir,)config=self.config_preparse()args=list(args)forxinargs:ifstr(x).startswith('--basetemp'):breakelse:args.append("--basetemp=%s"%self.tmpdir.dirpath('basetemp'))config.parse(args)returnconfigdefreparseconfig(self,args=None):""" this is used from tests that want to re-invoke parse(). """ifnotargs:args=[self.tmpdir]oldconfig=getattr(py.test,'config',None)try:c=py.test.config=self.Config()c.basetemp=py.path.local.make_numbered_dir(prefix="reparse",keep=0,rootdir=self.tmpdir,lock_timeout=None)c.parse(args)returncfinally:py.test.config=oldconfigdefparseconfigure(self,*args):config=self.parseconfig(*args)config.pluginmanager.do_configure(config)self.request.addfinalizer(lambda:config.pluginmanager.do_unconfigure(config))returnconfigdefgetitem(self,source,funcname="test_func"):foriteminself.getitems(source):ifitem.name==funcname:returnitemassert0,"%r item not found in module:\n%s"%(funcname,source)defgetitems(self,source):modcol=self.getmodulecol(source)returnself.genitems([modcol])defgetmodulecol(self,source,configargs=(),withinit=False):kw={self.request.function.__name__:py.code.Source(source).strip()}path=self.makepyfile(**kw)ifwithinit:self.makepyfile(__init__="#")self.config=config=self.parseconfigure(path,*configargs)node=self.getnode(config,path)#config.pluginmanager.do_unconfigure(config)returnnodedefcollect_by_name(self,modcol,name):forcoliteminmodcol._memocollect():ifcolitem.name==name:returncolitemdefpopen(self,cmdargs,stdout,stderr,**kw):env=os.environ.copy()env['PYTHONPATH']=os.pathsep.join(filter(None,[str(os.getcwd()),env.get('PYTHONPATH','')]))kw['env']=env#print "env", envreturnpy.std.subprocess.Popen(cmdargs,stdout=stdout,stderr=stderr,**kw)defpytestmain(self,*args,**kwargs):ret=pytest.main(*args,**kwargs)ifret==2:raiseKeyboardInterrupt()defrun(self,*cmdargs):returnself._run(*cmdargs)def_run(self,*cmdargs):cmdargs=[str(x)forxincmdargs]p1=self.tmpdir.join("stdout")p2=self.tmpdir.join("stderr")print_("running",cmdargs,"curdir=",py.path.local())f1=p1.open("wb")f2=p2.open("wb")now=time.time()popen=self.popen(cmdargs,stdout=f1,stderr=f2,close_fds=(sys.platform!="win32"))ret=popen.wait()f1.close()f2.close()out=p1.read("rb")out=getdecoded(out).splitlines()err=p2.read("rb")err=getdecoded(err).splitlines()defdump_lines(lines,fp):try:forlineinlines:py.builtin.print_(line,file=fp)exceptUnicodeEncodeError:print("couldn't print to %s because of encoding"%(fp,))dump_lines(out,sys.stdout)dump_lines(err,sys.stderr)returnRunResult(ret,out,err,time.time()-now)defrunpybin(self,scriptname,*args):fullargs=self._getpybinargs(scriptname)+argsreturnself.run(*fullargs)def_getpybinargs(self,scriptname):ifnotself.request.config.getvalue("notoolsonpath"):# XXX we rely on script refering to the correct environment# we cannot use "(py.std.sys.executable,script)"# becaue on windows the script is e.g. a py.test.exereturn(py.std.sys.executable,_pytest_fullpath,)else:py.test.skip("cannot run %r with --no-tools-on-path"%scriptname)defrunpython(self,script,prepend=True):ifprepend:s=self._getsysprepend()ifs:script.write(s+"\n"+script.read())returnself.run(sys.executable,script)def_getsysprepend(self):ifself.request.config.getvalue("notoolsonpath"):s="import sys;sys.path.insert(0,%r);"%str(py._pydir.dirpath())else:s=""returnsdefrunpython_c(self,command):command=self._getsysprepend()+commandreturnself.run(py.std.sys.executable,"-c",command)defrunpytest(self,*args):p=py.path.local.make_numbered_dir(prefix="runpytest-",keep=None,rootdir=self.tmpdir)args=('--basetemp=%s'%p,)+args#for x in args:# if '--confcutdir' in str(x):# break#else:# pass# args = ('--confcutdir=.',) + argsplugins=[xforxinself.pluginsifisinstance(x,str)]ifplugins:args=('-p',plugins[0])+argsreturnself.runpybin("py.test",*args)defspawn_pytest(self,string,expect_timeout=10.0):ifself.request.config.getvalue("notoolsonpath"):py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")basetemp=self.tmpdir.mkdir("pexpect")invoke=" ".join(map(str,self._getpybinargs("py.test")))cmd="%s --basetemp=%s%s"%(invoke,basetemp,string)returnself.spawn(cmd,expect_timeout=expect_timeout)defspawn(self,cmd,expect_timeout=10.0):pexpect=py.test.importorskip("pexpect","2.4")ifhasattr(sys,'pypy_version_info')and'64'inpy.std.platform.machine():pytest.skip("pypy-64 bit not supported")logfile=self.tmpdir.join("spawn.out")child=pexpect.spawn(cmd,logfile=logfile.open("w"))child.timeout=expect_timeoutreturnchilddefgetdecoded(out):try:returnout.decode("utf-8")exceptUnicodeDecodeError:return"INTERNAL not-utf8-decodeable, truncated string:\n%s"%(py.io.saferepr(out),)classPseudoPlugin:def__init__(self,vars):self.__dict__.update(vars)classReportRecorder(object):def__init__(self,hook):self.hook=hookself.pluginmanager=hook._pmself.pluginmanager.register(self)defgetcall(self,name):returnself.hookrecorder.getcall(name)defpopcall(self,name):returnself.hookrecorder.popcall(name)defgetcalls(self,names):""" return list of ParsedCall instances matching the given eventname. """returnself.hookrecorder.getcalls(names)# functionality for test reportsdefgetreports(self,names="pytest_runtest_logreport pytest_collectreport"):return[x.reportforxinself.getcalls(names)]defmatchreport(self,inamepart="",names="pytest_runtest_logreport pytest_collectreport",when=None):""" return a testreport whose dotted import path matches """l=[]forrepinself.getreports(names=names):ifwhenandgetattr(rep,'when',None)!=when:continueifnotinamepartorinamepartinrep.nodeid.split("::"):l.append(rep)ifnotl:raiseValueError("could not find test report matching %r: no test reports at all!"%(inamepart,))iflen(l)>1:raiseValueError("found more than one testreport matching %r: %s"%(inamepart,l))returnl[0]defgetfailures(self,names='pytest_runtest_logreport pytest_collectreport'):return[repforrepinself.getreports(names)ifrep.failed]defgetfailedcollections(self):returnself.getfailures('pytest_collectreport')deflistoutcomes(self):passed=[]skipped=[]failed=[]forrepinself.getreports("pytest_runtest_logreport"):ifrep.passed:ifrep.when=="call":passed.append(rep)elifrep.skipped:skipped.append(rep)elifrep.failed:failed.append(rep)returnpassed,skipped,faileddefcountoutcomes(self):return[len(x)forxinself.listoutcomes()]defassertoutcome(self,passed=0,skipped=0,failed=0):realpassed,realskipped,realfailed=self.listoutcomes()assertpassed==len(realpassed)assertskipped==len(realskipped)assertfailed==len(realfailed)defclear(self):self.hookrecorder.calls[:]=[]defunregister(self):self.pluginmanager.unregister(self)self.hookrecorder.finish_recording()classLineComp:def__init__(self):self.stringio=py.io.TextIO()defassert_contains_lines(self,lines2):""" assert that lines2 are contained (linearly) in lines1. return a list of extralines found. """__tracebackhide__=Trueval=self.stringio.getvalue()self.stringio.truncate(0)self.stringio.seek(0)lines1=val.split("\n")returnLineMatcher(lines1).fnmatch_lines(lines2)classLineMatcher:def__init__(self,lines):self.lines=linesdefstr(self):return"\n".join(self.lines)def_getlines(self,lines2):ifisinstance(lines2,str):lines2=py.code.Source(lines2)ifisinstance(lines2,py.code.Source):lines2=lines2.strip().linesreturnlines2deffnmatch_lines_random(self,lines2):lines2=self._getlines(lines2)forlineinlines2:forxinself.lines:ifline==xorfnmatch(x,line):print_("matched: ",repr(line))breakelse:raiseValueError("line %r not found in output"%line)deffnmatch_lines(self,lines2):defshow(arg1,arg2):py.builtin.print_(arg1,arg2,file=py.std.sys.stderr)lines2=self._getlines(lines2)lines1=self.lines[:]nextline=Noneextralines=[]__tracebackhide__=Trueforlineinlines2:nomatchprinted=Falsewhilelines1:nextline=lines1.pop(0)ifline==nextline:show("exact match:",repr(line))breakeliffnmatch(nextline,line):show("fnmatch:",repr(line))show(" with:",repr(nextline))breakelse:ifnotnomatchprinted:show("nomatch:",repr(line))nomatchprinted=Trueshow(" and:",repr(nextline))extralines.append(nextline)else:py.test.fail("remains unmatched: %r, see stderr"%(line,))