#!/usr/bin/env python## run-tests.py - Run a set of tests on Mercurial## Copyright 2006 Matt Mackall <mpm@selenic.com>## This software may be used and distributed according to the terms of the# GNU General Public License version 2 or any later version.# Modifying this script is tricky because it has many modes:# - serial (default) vs parallel (-jN, N > 1)# - no coverage (default) vs coverage (-c, -C, -s)# - temp install (default) vs specific hg script (--with-hg, --local)# - tests are a mix of shell scripts and Python scripts## If you change this script, it is recommended that you ensure you# haven't broken it by running it in various modes with a representative# sample of test scripts. For example:## 1) serial, no coverage, temp install:# ./run-tests.py test-s*# 2) serial, no coverage, local hg:# ./run-tests.py --local test-s*# 3) serial, coverage, temp install:# ./run-tests.py -c test-s*# 4) serial, coverage, local hg:# ./run-tests.py -c --local test-s* # unsupported# 5) parallel, no coverage, temp install:# ./run-tests.py -j2 test-s*# 6) parallel, no coverage, local hg:# ./run-tests.py -j2 --local test-s*# 7) parallel, coverage, temp install:# ./run-tests.py -j2 -c test-s* # currently broken# 8) parallel, coverage, local install:# ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)# 9) parallel, custom tmp dir:# ./run-tests.py -j2 --tmpdir /tmp/myhgtests## (You could use any subset of the tests: test-s* happens to match# enough that it's worth doing parallel runs, few enough that it# completes fairly quickly, includes both shell and Python scripts, and# includes some scripts that run daemon processes.)fromdistutilsimportversionimportdifflibimporterrnoimportoptparseimportosimportshutilimportsubprocessimportsignalimportsysimporttempfileimporttimeimportreclosefds=os.name=='posix'defPopen4(cmd,bufsize=-1):p=subprocess.Popen(cmd,shell=True,bufsize=bufsize,close_fds=closefds,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)p.fromchild=p.stdoutp.tochild=p.stdinp.childerr=p.stderrreturnp# reserved exit code to skip test (used by hghave)SKIPPED_STATUS=80SKIPPED_PREFIX='skipped: 'FAILED_PREFIX='hghave check failed: 'PYTHON=sys.executableIMPL_PATH='PYTHONPATH'if'java'insys.platform:IMPL_PATH='JYTHONPATH'requiredtools=["python","diff","grep","unzip","gunzip","bunzip2","sed"]defaults={'jobs':('HGTEST_JOBS',1),'timeout':('HGTEST_TIMEOUT',180),'port':('HGTEST_PORT',20059),}defparseargs():parser=optparse.OptionParser("%prog [options] [tests]")# keep these sortedparser.add_option("--blacklist",action="append",help="skip tests listed in the specified blacklist file")parser.add_option("-C","--annotate",action="store_true",help="output files annotated with coverage")parser.add_option("--child",type="int",help="run as child process, summary to given fd")parser.add_option("-c","--cover",action="store_true",help="print a test coverage report")parser.add_option("-d","--debug",action="store_true",help="debug mode: write output of test scripts to console"" rather than capturing and diff'ing it (disables timeout)")parser.add_option("-f","--first",action="store_true",help="exit on the first test failure")parser.add_option("--inotify",action="store_true",help="enable inotify extension when running tests")parser.add_option("-i","--interactive",action="store_true",help="prompt to accept changed output")parser.add_option("-j","--jobs",type="int",help="number of jobs to run in parallel"" (default: $%s or %d)"%defaults['jobs'])parser.add_option("--keep-tmpdir",action="store_true",help="keep temporary directory after running tests")parser.add_option("-k","--keywords",help="run tests matching keywords")parser.add_option("-l","--local",action="store_true",help="shortcut for --with-hg=<testdir>/../hg")parser.add_option("-n","--nodiff",action="store_true",help="skip showing test changes")parser.add_option("-p","--port",type="int",help="port on which servers should listen"" (default: $%s or %d)"%defaults['port'])parser.add_option("--pure",action="store_true",help="use pure Python code instead of C extensions")parser.add_option("-R","--restart",action="store_true",help="restart at last error")parser.add_option("-r","--retest",action="store_true",help="retest failed tests")parser.add_option("-S","--noskips",action="store_true",help="don't report skip tests verbosely")parser.add_option("-t","--timeout",type="int",help="kill errant tests after TIMEOUT seconds"" (default: $%s or %d)"%defaults['timeout'])parser.add_option("--tmpdir",type="string",help="run tests in the given temporary directory"" (implies --keep-tmpdir)")parser.add_option("-v","--verbose",action="store_true",help="output verbose messages")parser.add_option("--view",type="string",help="external diff viewer")parser.add_option("--with-hg",type="string",metavar="HG",help="test using specified hg script rather than a ""temporary installation")parser.add_option("-3","--py3k-warnings",action="store_true",help="enable Py3k warnings on Python 2.6+")foroption,defaultindefaults.items():defaults[option]=int(os.environ.get(*default))parser.set_defaults(**defaults)(options,args)=parser.parse_args()# jython is always pureif'java'insys.platformor'__pypy__'insys.modules:options.pure=Trueifoptions.with_hg:ifnot(os.path.isfile(options.with_hg)andos.access(options.with_hg,os.X_OK)):parser.error('--with-hg must specify an executable hg script')ifnotos.path.basename(options.with_hg)=='hg':sys.stderr.write('warning: --with-hg should specify an hg script')ifoptions.local:testdir=os.path.dirname(os.path.realpath(sys.argv[0]))hgbin=os.path.join(os.path.dirname(testdir),'hg')ifnotos.access(hgbin,os.X_OK):parser.error('--local specified, but %r not found or not executable'%hgbin)options.with_hg=hgbinoptions.anycoverage=options.coveroroptions.annotateifoptions.anycoverage:try:importcoveragecovver=version.StrictVersion(coverage.__version__).versionifcovver<(3,3):parser.error('coverage options require coverage 3.3 or later')exceptImportError:parser.error('coverage options now require the coverage package')ifoptions.anycoverageandoptions.local:# this needs some path mangling somewhere, I guessparser.error("sorry, coverage options do not work when --local ""is specified")globalvlogifoptions.verbose:ifoptions.jobs>1oroptions.childisnotNone:pid="[%d]"%os.getpid()else:pid=Nonedefvlog(*msg):ifpid:printpid,forminmsg:printm,printsys.stdout.flush()else:vlog=lambda*msg:Noneifoptions.tmpdir:options.tmpdir=os.path.expanduser(options.tmpdir)ifoptions.jobs<1:parser.error('--jobs must be positive')ifoptions.interactiveandoptions.jobs>1:print'(--interactive overrides --jobs)'options.jobs=1ifoptions.interactiveandoptions.debug:parser.error("-i/--interactive and -d/--debug are incompatible")ifoptions.debug:ifoptions.timeout!=defaults['timeout']:sys.stderr.write('warning: --timeout option ignored with --debug\n')options.timeout=0ifoptions.py3k_warnings:ifsys.version_info[:2]<(2,6)orsys.version_info[:2]>=(3,0):parser.error('--py3k-warnings can only be used on Python 2.6+')ifoptions.blacklist:blacklist=dict()forfilenameinoptions.blacklist:try:path=os.path.expanduser(os.path.expandvars(filename))f=open(path,"r")exceptIOError,err:iferr.errno!=errno.ENOENT:raiseprint"warning: no such blacklist file: %s"%filenamecontinueforlineinf.readlines():line=line.strip()iflineandnotline.startswith('#'):blacklist[line]=filenameoptions.blacklist=blacklistreturn(options,args)defrename(src,dst):"""Like os.rename(), trade atomicity and opened files friendliness for existing destination support. """shutil.copy(src,dst)os.remove(src)defsplitnewlines(text):'''like str.splitlines, but only split on newlines. keep line endings.'''i=0lines=[]whileTrue:n=text.find('\n',i)ifn==-1:last=text[i:]iflast:lines.append(last)returnlineslines.append(text[i:n+1])i=n+1defparsehghaveoutput(lines):'''Parse hghave log lines. Return tuple of lists (missing, failed): * the missing/unknown features * the features for which existence check failed'''missing=[]failed=[]forlineinlines:ifline.startswith(SKIPPED_PREFIX):line=line.splitlines()[0]missing.append(line[len(SKIPPED_PREFIX):])elifline.startswith(FAILED_PREFIX):line=line.splitlines()[0]failed.append(line[len(FAILED_PREFIX):])returnmissing,faileddefshowdiff(expected,output,ref,err):forlineindifflib.unified_diff(expected,output,ref,err):sys.stdout.write(line)deffindprogram(program):"""Search PATH for a executable program"""forpinos.environ.get('PATH',os.defpath).split(os.pathsep):name=os.path.join(p,program)ifos.access(name,os.X_OK):returnnamereturnNonedefchecktools():# Before we go any further, check for pre-requisite tools# stuff from coreutils (cat, rm, etc) are not testedforpinrequiredtools:ifos.name=='nt':p+='.exe'found=findprogram(p)iffound:vlog("# Found prerequisite",p,"at",found)else:print"WARNING: Did not find prerequisite tool: "+pdefkilldaemons():# Kill off any leftover daemon processestry:fp=open(DAEMON_PIDS)forlineinfp:try:pid=int(line)exceptValueError:continuetry:os.kill(pid,0)vlog('# Killing daemon process %d'%pid)os.kill(pid,signal.SIGTERM)time.sleep(0.25)os.kill(pid,0)vlog('# Daemon process %d is stuck - really killing it'%pid)os.kill(pid,signal.SIGKILL)exceptOSError,err:iferr.errno!=errno.ESRCH:raisefp.close()os.unlink(DAEMON_PIDS)exceptIOError:passdefcleanup(options):ifnotoptions.keep_tmpdir:vlog("# Cleaning up HGTMP",HGTMP)shutil.rmtree(HGTMP,True)defusecorrectpython():# some tests run python interpreter. they must use same# interpreter we use or bad things will happen.exedir,exename=os.path.split(sys.executable)ifexename=='python':path=findprogram('python')ifos.path.dirname(path)==exedir:returnvlog('# Making python executable in test path use correct Python')mypython=os.path.join(BINDIR,'python')try:os.symlink(sys.executable,mypython)exceptAttributeError:# windows fallbackshutil.copyfile(sys.executable,mypython)shutil.copymode(sys.executable,mypython)definstallhg(options):vlog("# Performing temporary installation of HG")installerrs=os.path.join("tests","install.err")pure=options.pureand"--pure"or""# Run installer in hg rootscript=os.path.realpath(sys.argv[0])hgroot=os.path.dirname(os.path.dirname(script))os.chdir(hgroot)nohome='--home=""'ifos.name=='nt':# The --home="" trick works only on OS where os.sep == '/'# because of a distutils convert_path() fast-path. Avoid it at# least on Windows for now, deal with .pydistutils.cfg bugs# when they happen.nohome=''cmd=('%s setup.py %s clean --all'' install --force --prefix="%s" --install-lib="%s"'' --install-scripts="%s" %s >%s 2>&1'%(sys.executable,pure,INST,PYTHONDIR,BINDIR,nohome,installerrs))vlog("# Running",cmd)ifos.system(cmd)==0:ifnotoptions.verbose:os.remove(installerrs)else:f=open(installerrs)forlineinf:printline,f.close()sys.exit(1)os.chdir(TESTDIR)usecorrectpython()vlog("# Installing dummy diffstat")f=open(os.path.join(BINDIR,'diffstat'),'w')f.write('#!'+sys.executable+'\n''import sys\n''files = 0\n''for line in sys.stdin:\n'' if line.startswith("diff "):\n'' files += 1\n''sys.stdout.write("files patched: %d\\n" % files)\n')f.close()os.chmod(os.path.join(BINDIR,'diffstat'),0700)ifoptions.py3k_warningsandnotoptions.anycoverage:vlog("# Updating hg command to enable Py3k Warnings switch")f=open(os.path.join(BINDIR,'hg'),'r')lines=[line.rstrip()forlineinf]lines[0]+=' -3'f.close()f=open(os.path.join(BINDIR,'hg'),'w')forlineinlines:f.write(line+'\n')f.close()ifoptions.anycoverage:custom=os.path.join(TESTDIR,'sitecustomize.py')target=os.path.join(PYTHONDIR,'sitecustomize.py')vlog('# Installing coverage trigger to %s'%target)shutil.copyfile(custom,target)rc=os.path.join(TESTDIR,'.coveragerc')vlog('# Installing coverage rc to %s'%rc)os.environ['COVERAGE_PROCESS_START']=rcfn=os.path.join(INST,'..','.coverage')os.environ['COVERAGE_FILE']=fndefoutputcoverage(options):vlog('# Producing coverage report')os.chdir(PYTHONDIR)defcovrun(*args):cmd='coverage %s'%' '.join(args)vlog('# Running: %s'%cmd)os.system(cmd)ifoptions.child:returncovrun('-c')omit=','.join([BINDIR,TESTDIR])covrun('-i','-r','"--omit=%s"'%omit)# reportifoptions.annotate:adir=os.path.join(TESTDIR,'annotated')ifnotos.path.isdir(adir):os.mkdir(adir)covrun('-i','-a','"--directory=%s"'%adir,'"--omit=%s"'%omit)classTimeout(Exception):passdefalarmed(signum,frame):raiseTimeoutdefpytest(test,options):py3kswitch=options.py3k_warningsand' -3'or''cmd='%s%s "%s"'%(PYTHON,py3kswitch,test)vlog("# Running",cmd)returnrun(cmd,options)defshtest(test,options):cmd='"%s"'%testvlog("# Running",cmd)returnrun(cmd,options)defbattest(test,options):# To reliably get the error code from batch files on WinXP,# the "cmd /c call" prefix is needed. Grrrcmd='cmd /c call "%s"'%testpathvlog("# Running",cmd)returnrun(cmd,options)deftsttest(test,options):t=open(test)out=[]script=[]salt="SALT"+str(time.time())pos=prepos=-1after={}expected={}forn,linenumerate(t):ifl.startswith(' $ '):# commandsafter.setdefault(pos,[]).append(l)prepos=pospos=nscript.append('echo %s%s $?\n'%(salt,n))script.append(l[4:])elifl.startswith(' > '):# continuationsafter.setdefault(prepos,[]).append(l)script.append(l[4:])elifl.startswith(' '):# results# queue up a list of expected resultsexpected.setdefault(pos,[]).append(l[2:])else:# non-command/result - queue up for merged outputafter.setdefault(pos,[]).append(l)script.append('echo %s%s $?\n'%(salt,n+1))fd,name=tempfile.mkstemp(suffix='hg-tst')try:forlinscript:os.write(fd,l)os.close(fd)cmd='/bin/sh "%s"'%namevlog("# Running",cmd)exitcode,output=run(cmd,options)finally:os.remove(name)defrematch(el,l):try:returnre.match(el,l)exceptre.error:# el is an invalid regexreturnFalsepos=-1postout=[]ret=0forn,linenumerate(output):ifl.startswith(salt):# add on last return coderet=int(l.split()[2])ifret!=0:postout.append(" [%s]\n"%ret)ifposinafter:postout+=after.pop(pos)pos=int(l.split()[1])else:el=Noneifposinexpectedandexpected[pos]:el=expected[pos].pop(0)ifel==l:# perfect match (fast)postout.append(" "+l)elifelandrematch(el,l):# fallback regex matchpostout.append(" "+el)else:# mismatch - let diff deal with itpostout.append(" "+l)ifposinafter:postout+=after.pop(pos)returnexitcode,postoutdefrun(cmd,options):"""Run command in a sub-process, capturing the output (stdout and stderr). Return a tuple (exitcode, output). output is None in debug mode."""# TODO: Use subprocess.Popen if we're running on Python 2.4ifoptions.debug:proc=subprocess.Popen(cmd,shell=True)ret=proc.wait()return(ret,None)ifos.name=='nt'orsys.platform.startswith('java'):tochild,fromchild=os.popen4(cmd)tochild.close()output=fromchild.read()ret=fromchild.close()ifret==None:ret=0else:proc=Popen4(cmd)defcleanup():os.kill(proc.pid,signal.SIGTERM)ret=proc.wait()ifret==0:ret=signal.SIGTERM<<8killdaemons()returnrettry:output=''proc.tochild.close()output=proc.fromchild.read()ret=proc.wait()ifos.WIFEXITED(ret):ret=os.WEXITSTATUS(ret)exceptTimeout:vlog('# Process %d timed out - killing it'%proc.pid)ret=cleanup()output+=("\n### Abort: timeout after %d seconds.\n"%options.timeout)exceptKeyboardInterrupt:vlog('# Handling keyboard interrupt')cleanup()raisereturnret,splitnewlines(output)defrunone(options,test,skips,fails):'''tristate output: None -> skipped True -> passed False -> failed'''defskip(msg):ifnotoptions.verbose:skips.append((test,msg))else:print"\nSkipping %s: %s"%(testpath,msg)returnNonedeffail(msg):fails.append((test,msg))ifnotoptions.nodiff:print"\nERROR: %s%s"%(testpath,msg)returnNonevlog("# Test",test)# create a fresh hgrchgrc=open(HGRCPATH,'w+')hgrc.write('[ui]\n')hgrc.write('slash = True\n')hgrc.write('[defaults]\n')hgrc.write('backout = -d "0 0"\n')hgrc.write('commit = -d "0 0"\n')hgrc.write('tag = -d "0 0"\n')ifoptions.inotify:hgrc.write('[extensions]\n')hgrc.write('inotify=\n')hgrc.write('[inotify]\n')hgrc.write('pidfile=%s\n'%DAEMON_PIDS)hgrc.write('appendpid=True\n')hgrc.close()testpath=os.path.join(TESTDIR,test)ref=os.path.join(TESTDIR,test+".out")err=os.path.join(TESTDIR,test+".err")ifos.path.exists(err):os.remove(err)# Remove any previous output filestry:tf=open(testpath)firstline=tf.readline().rstrip()tf.close()except:firstline=''lctest=test.lower()iflctest.endswith('.py')orfirstline=='#!/usr/bin/env python':runner=pytesteliflctest.endswith('.bat'):# do not run batch scripts on non-windowsifos.name!='nt':returnskip("batch script")runner=battesteliflctest.endswith('.t'):runner=tsttestref=testpathelse:# do not run shell scripts on windowsifos.name=='nt':returnskip("shell script")# do not try to run non-executable programsifnotos.path.exists(testpath):returnfail("does not exist")elifnotos.access(testpath,os.X_OK):returnskip("not executable")runner=shtest# Make a tmp subdirectory to work intmpd=os.path.join(HGTMP,test)os.mkdir(tmpd)os.chdir(tmpd)ifoptions.timeout>0:signal.alarm(options.timeout)ret,out=runner(testpath,options)vlog("# Ret was:",ret)ifoptions.timeout>0:signal.alarm(0)mark='.'skipped=(ret==SKIPPED_STATUS)# If we're not in --debug mode and reference output file exists,# check test output against it.ifoptions.debug:refout=None# to match out == Noneelifos.path.exists(ref):f=open(ref,"r")refout=splitnewlines(f.read())f.close()else:refout=[]if(ret!=0orout!=refout)andnotskippedandnotoptions.debug:# Save errors to a file for diagnosisf=open(err,"wb")forlineinout:f.write(line)f.close()ifskipped:mark='s'ifoutisNone:# debug mode: nothing to parsemissing=['unknown']failed=Noneelse:missing,failed=parsehghaveoutput(out)ifnotmissing:missing=['irrelevant']iffailed:fail("hghave failed checking for %s"%failed[-1])skipped=Falseelse:skip(missing[-1])elifout!=refout:mark='!'ifret:fail("output changed and returned error code %d"%ret)else:fail("output changed")ifnotoptions.nodiff:ifoptions.view:os.system("%s%s%s"%(options.view,ref,err))else:showdiff(refout,out,ref,err)ret=1elifret:mark='!'fail("returned error code %d"%ret)ifnotoptions.verbose:sys.stdout.write(mark)sys.stdout.flush()killdaemons()os.chdir(TESTDIR)ifnotoptions.keep_tmpdir:shutil.rmtree(tmpd,True)ifskipped:returnNonereturnret==0_hgpath=Nonedef_gethgpath():"""Return the path to the mercurial package that is actually found by the current Python interpreter."""global_hgpathif_hgpathisnotNone:return_hgpathcmd='%s -c "import mercurial; print mercurial.__path__[0]"'pipe=os.popen(cmd%PYTHON)try:_hgpath=pipe.read().strip()finally:pipe.close()return_hgpathdef_checkhglib(verb):"""Ensure that the 'mercurial' package imported by python is the one we expect it to be. If not, print a warning to stderr."""expecthg=os.path.join(PYTHONDIR,'mercurial')actualhg=_gethgpath()ifactualhg!=expecthg:sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'' (expected %s)\n'%(verb,actualhg,expecthg))defrunchildren(options,tests):ifINST:installhg(options)_checkhglib("Testing")optcopy=dict(options.__dict__)optcopy['jobs']=1deloptcopy['blacklist']ifoptcopy['with_hg']isNone:optcopy['with_hg']=os.path.join(BINDIR,"hg")optcopy.pop('anycoverage',None)opts=[]foropt,valueinoptcopy.iteritems():name='--'+opt.replace('_','-')ifvalueisTrue:opts.append(name)elifvalueisnotNone:opts.append(name+'='+str(value))tests.reverse()jobs=[[]forjinxrange(options.jobs)]whiletests:forjobinjobs:ifnottests:breakjob.append(tests.pop())fps={}forj,jobinenumerate(jobs):ifnotjob:continuerfd,wfd=os.pipe()childopts=['--child=%d'%wfd,'--port=%d'%(options.port+j*3)]childtmp=os.path.join(HGTMP,'child%d'%j)childopts+=['--tmpdir',childtmp]cmdline=[PYTHON,sys.argv[0]]+opts+childopts+jobvlog(' '.join(cmdline))fps[os.spawnvp(os.P_NOWAIT,cmdline[0],cmdline)]=os.fdopen(rfd,'r')os.close(wfd)signal.signal(signal.SIGINT,signal.SIG_IGN)failures=0tested,skipped,failed=0,0,0skips=[]fails=[]whilefps:pid,status=os.wait()fp=fps.pop(pid)l=fp.read().splitlines()try:test,skip,fail=map(int,l[:3])exceptValueError:test,skip,fail=0,0,0split=-failorlen(l)forsinl[3:split]:skips.append(s.split(" ",1))forsinl[split:]:fails.append(s.split(" ",1))tested+=testskipped+=skipfailed+=failvlog('pid %d exited, status %d'%(pid,status))failures|=statusprintifnotoptions.noskips:forsinskips:print"Skipped %s: %s"%(s[0],s[1])forsinfails:print"Failed %s: %s"%(s[0],s[1])_checkhglib("Tested")print"# Ran %d tests, %d skipped, %d failed."%(tested,skipped,failed)ifoptions.anycoverage:outputcoverage(options)sys.exit(failures!=0)defruntests(options,tests):globalDAEMON_PIDS,HGRCPATHDAEMON_PIDS=os.environ["DAEMON_PIDS"]=os.path.join(HGTMP,'daemon.pids')HGRCPATH=os.environ["HGRCPATH"]=os.path.join(HGTMP,'.hgrc')try:ifINST:installhg(options)_checkhglib("Testing")ifoptions.timeout>0:try:signal.signal(signal.SIGALRM,alarmed)vlog('# Running each test with %d second timeout'%options.timeout)exceptAttributeError:print'WARNING: cannot run tests with timeouts'options.timeout=0tested=0failed=0skipped=0ifoptions.restart:orig=list(tests)whiletests:ifos.path.exists(tests[0]+".err"):breaktests.pop(0)ifnottests:print"running all tests"tests=origskips=[]fails=[]fortestintests:ifoptions.blacklist:filename=options.blacklist.get(test)iffilenameisnotNone:skips.append((test,"blacklisted (%s)"%filename))skipped+=1continueifoptions.retestandnotos.path.exists(test+".err"):skipped+=1continueifoptions.keywords:t=open(test).read().lower()+test.lower()forkinoptions.keywords.lower().split():ifkint:breakelse:skipped+=1continueret=runone(options,test,skips,fails)ifretisNone:skipped+=1elifnotret:ifoptions.interactive:print"Accept this change? [n] ",answer=sys.stdin.readline().strip()ifanswer.lower()in"y yes".split():iftest.endswith(".t"):rename(test+".err",test)else:rename(test+".err",test+".out")tested+=1fails.pop()continuefailed+=1ifoptions.first:breaktested+=1ifoptions.child:fp=os.fdopen(options.child,'w')fp.write('%d\n%d\n%d\n'%(tested,skipped,failed))forsinskips:fp.write("%s%s\n"%s)forsinfails:fp.write("%s%s\n"%s)fp.close()else:printforsinskips:print"Skipped %s: %s"%sforsinfails:print"Failed %s: %s"%s_checkhglib("Tested")print"# Ran %d tests, %d skipped, %d failed."%(tested,skipped,failed)ifoptions.anycoverage:outputcoverage(options)exceptKeyboardInterrupt:failed=Trueprint"\ninterrupted!"iffailed:sys.exit(1)defmain():(options,args)=parseargs()ifnotoptions.child:os.umask(022)checktools()# Reset some environment variables to well-known values so that# the tests produce repeatable output.os.environ['LANG']=os.environ['LC_ALL']=os.environ['LANGUAGE']='C'os.environ['TZ']='GMT'os.environ["EMAIL"]="Foo Bar <foo.bar@example.com>"os.environ['CDPATH']=''os.environ['COLUMNS']='80'os.environ['GREP_OPTIONS']=''os.environ['http_proxy']=''# unset env related to hooksforkinos.environ.keys():ifk.startswith('HG_'):# can't remove on solarisos.environ[k]=''delos.environ[k]globalTESTDIR,HGTMP,INST,BINDIR,PYTHONDIR,COVERAGE_FILETESTDIR=os.environ["TESTDIR"]=os.getcwd()ifoptions.tmpdir:options.keep_tmpdir=Truetmpdir=options.tmpdirifos.path.exists(tmpdir):# Meaning of tmpdir has changed since 1.3: we used to create# HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if# tmpdir already exists.sys.exit("error: temp dir %r already exists"%tmpdir)# Automatically removing tmpdir sounds convenient, but could# really annoy anyone in the habit of using "--tmpdir=/tmp"# or "--tmpdir=$HOME".#vlog("# Removing temp dir", tmpdir)#shutil.rmtree(tmpdir)os.makedirs(tmpdir)else:tmpdir=tempfile.mkdtemp('','hgtests.')HGTMP=os.environ['HGTMP']=os.path.realpath(tmpdir)DAEMON_PIDS=NoneHGRCPATH=Noneos.environ["HGEDITOR"]=sys.executable+' -c "import sys; sys.exit(0)"'os.environ["HGMERGE"]="internal:merge"os.environ["HGUSER"]="test"os.environ["HGENCODING"]="ascii"os.environ["HGENCODINGMODE"]="strict"os.environ["HGPORT"]=str(options.port)os.environ["HGPORT1"]=str(options.port+1)os.environ["HGPORT2"]=str(options.port+2)ifoptions.with_hg:INST=NoneBINDIR=os.path.dirname(os.path.realpath(options.with_hg))# This looks redundant with how Python initializes sys.path from# the location of the script being executed. Needed because the# "hg" specified by --with-hg is not the only Python script# executed in the test suite that needs to import 'mercurial'# ... which means it's not really redundant at all.PYTHONDIR=BINDIRelse:INST=os.path.join(HGTMP,"install")BINDIR=os.environ["BINDIR"]=os.path.join(INST,"bin")PYTHONDIR=os.path.join(INST,"lib","python")os.environ["BINDIR"]=BINDIRos.environ["PYTHON"]=PYTHONifnotoptions.child:path=[BINDIR]+os.environ["PATH"].split(os.pathsep)os.environ["PATH"]=os.pathsep.join(path)# Include TESTDIR in PYTHONPATH so that out-of-tree extensions# can run .../tests/run-tests.py test-foo where test-foo# adds an extension to HGRCpypath=[PYTHONDIR,TESTDIR]# We have to augment PYTHONPATH, rather than simply replacing# it, in case external libraries are only available via current# PYTHONPATH. (In particular, the Subversion bindings on OS X# are in /opt/subversion.)oldpypath=os.environ.get(IMPL_PATH)ifoldpypath:pypath.append(oldpypath)os.environ[IMPL_PATH]=os.pathsep.join(pypath)COVERAGE_FILE=os.path.join(TESTDIR,".coverage")iflen(args)==0:args=os.listdir(".")args.sort()tests=[]fortestinargs:if(test.startswith("test-")and'~'notintestand('.'notintestortest.endswith('.py')ortest.endswith('.bat')ortest.endswith('.t'))):tests.append(test)ifnottests:print"# Ran 0 tests, 0 skipped, 0 failed."returnvlog("# Using TESTDIR",TESTDIR)vlog("# Using HGTMP",HGTMP)vlog("# Using PATH",os.environ["PATH"])vlog("# Using",IMPL_PATH,os.environ[IMPL_PATH])try:iflen(tests)>1andoptions.jobs>1:runchildren(options,tests)else:runtests(options,tests)finally:time.sleep(1)cleanup(options)main()