# HG changeset patch # User Vadim Gelfer # Date 1152223450 25200 # Node ID 78c2903fcabee7e7b30a8c02e0e1469e0f43e5c8 # Parent b13a98bd078ed591578d6cf374c7285369ae0b1c# Parent 82e3b2966862b0ad28b0d49ece7b2a6312242ee6 merge with mpm. diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -2649,7 +2649,7 @@ def serve(ui, repo, **opts): if opts['pid_file']: fp = open(opts['pid_file'], 'w') - fp.write(str(os.getpid())) + fp.write(str(os.getpid()) + '\n') fp.close() if opts['daemon_pipefds']: @@ -3371,12 +3371,12 @@ external = {} def findext(name): '''return module with given extension name''' try: - return external[name] + return sys.modules[external[name]] except KeyError: dotname = '.' + name for k, v in external.iteritems(): - if k.endswith('.' + name) or v.__name__ == name: - return v + if k.endswith('.' + name) or v == name: + return sys.modules[v] raise KeyError(name) def dispatch(args): @@ -3390,14 +3390,14 @@ def dispatch(args): sys.stderr.write(_("abort: %s\n") % inst) return -1 - for x in u.extensions(): + for ext_name, load_from_name in u.extensions(): try: - if x[1]: + if load_from_name: # the module will be loaded in sys.modules # choose an unique name so that it doesn't # conflicts with other modules - module_name = "hgext_%s" % x[0].replace('.', '_') - mod = imp.load_source(module_name, x[1]) + module_name = "hgext_%s" % ext_name.replace('.', '_') + mod = imp.load_source(module_name, load_from_name) else: def importh(name): mod = __import__(name) @@ -3406,12 +3406,10 @@ def dispatch(args): mod = getattr(mod, comp) return mod try: - name = 'hgext.' + x[0] - mod = importh(name) + mod = importh("hgext.%s" % ext_name) except ImportError: - name = x[0] - mod = importh(name) - external[name] = mod + mod = importh(ext_name) + external[ext_name] = mod.__name__ except (util.SignalInterrupt, KeyboardInterrupt): raise except Exception, inst: @@ -3419,14 +3417,15 @@ def dispatch(args): if u.print_exc(): return 1 - for x in external.itervalues(): - uisetup = getattr(x, 'uisetup', None) + for name in external.itervalues(): + mod = sys.modules[name] + uisetup = getattr(mod, 'uisetup', None) if uisetup: uisetup(u) - cmdtable = getattr(x, 'cmdtable', {}) + cmdtable = getattr(mod, 'cmdtable', {}) for t in cmdtable: if t in table: - u.warn(_("module %s overrides %s\n") % (x.__name__, t)) + u.warn(_("module %s overrides %s\n") % (name, t)) table.update(cmdtable) try: @@ -3475,9 +3474,10 @@ def dispatch(args): if not repo: repo = hg.repository(u, path=path) u = repo.ui - for x in external.itervalues(): - if hasattr(x, 'reposetup'): - x.reposetup(u, repo) + for name in external.itervalues(): + mod = sys.modules[name] + if hasattr(mod, 'reposetup'): + mod.reposetup(u, repo) except hg.RepoError: if cmd not in optionalrepo.split(): raise diff --git a/mercurial/httprepo.py b/mercurial/httprepo.py --- a/mercurial/httprepo.py +++ b/mercurial/httprepo.py @@ -87,25 +87,31 @@ class httpconnection(keepalive.HTTPConne for chunk in util.filechunkiter(data): keepalive.HTTPConnection.send(self, chunk) -class httphandler(keepalive.HTTPHandler): +class basehttphandler(keepalive.HTTPHandler): def http_open(self, req): return self.do_open(httpconnection, req) -class httpsconnection(httplib.HTTPSConnection): - # must be able to send big bundle as stream. +has_https = hasattr(urllib2, 'HTTPSHandler') +if has_https: + class httpsconnection(httplib.HTTPSConnection): + response_class = keepalive.HTTPResponse + # must be able to send big bundle as stream. - def send(self, data): - if isinstance(data, str): - httplib.HTTPSConnection.send(self, data) - else: - # if auth required, some data sent twice, so rewind here - data.seek(0) - for chunk in util.filechunkiter(data): - httplib.HTTPSConnection.send(self, chunk) + def send(self, data): + if isinstance(data, str): + httplib.HTTPSConnection.send(self, data) + else: + # if auth required, some data sent twice, so rewind here + data.seek(0) + for chunk in util.filechunkiter(data): + httplib.HTTPSConnection.send(self, chunk) -class httpshandler(urllib2.HTTPSHandler): - def https_open(self, req): - return self.do_open(httpsconnection, req) + class httphandler(basehttphandler, urllib2.HTTPSHandler): + def https_open(self, req): + return self.do_open(httpsconnection, req) +else: + class httphandler(basehttphandler): + pass class httprepository(remoterepository): def __init__(self, ui, path): @@ -176,7 +182,6 @@ class httprepository(remoterepository): opener = urllib2.build_opener( handler, - httpshandler(), urllib2.HTTPBasicAuthHandler(passmgr), urllib2.HTTPDigestAuthHandler(passmgr)) @@ -322,4 +327,8 @@ class httprepository(remoterepository): os.unlink(tempname) class httpsrepository(httprepository): - pass + def __init__(self, ui, path): + if not has_https: + raise util.Abort(_('Python support for SSL and HTTPS ' + 'is not installed')) + httprepository.__init__(self, ui, path) diff --git a/tests/run-tests.py b/tests/run-tests.py --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -7,23 +7,32 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os, sys, shutil, re +import difflib +import errno +import optparse +import os +import popen2 +import re +import shutil +import signal +import sys import tempfile -import difflib -import popen2 -from optparse import OptionParser +import time required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] -parser = OptionParser("%prog [options] [tests]") +parser = optparse.OptionParser("%prog [options] [tests]") parser.add_option("-v", "--verbose", action="store_true", help="output verbose messages") +parser.add_option("-t", "--timeout", type="int", + help="output verbose messages") parser.add_option("-c", "--cover", action="store_true", help="print a test coverage report") parser.add_option("-s", "--cover_stdlib", action="store_true", help="print a test coverage report inc. standard libraries") parser.add_option("-C", "--annotate", action="store_true", help="output files annotated with coverage") +parser.set_defaults(timeout=30) (options, args) = parser.parse_args() verbose = options.verbose coverage = options.cover or options.cover_stdlib or options.annotate @@ -79,6 +88,23 @@ def cleanup_exit(): print "# Cleaning up HGTMP", HGTMP shutil.rmtree(HGTMP, True) +def use_correct_python(): + # some tests run python interpreter. they must use same + # interpreter we use or bad things will happen. + exedir, exename = os.path.split(sys.executable) + if exename == 'python': + path = find_program('python') + if os.path.dirname(path) == exedir: + return + vlog('# Making python executable in test path use correct Python') + my_python = os.path.join(BINDIR, 'python') + try: + os.symlink(sys.executable, my_python) + except AttributeError: + # windows fallback + shutil.copyfile(sys.executable, my_python) + shutil.copymode(sys.executable, my_python) + def install_hg(): vlog("# Performing temporary installation of HG") installerrs = os.path.join("tests", "install.err") @@ -102,6 +128,8 @@ def install_hg(): os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"]) os.environ["PYTHONPATH"] = PYTHONDIR + use_correct_python() + if coverage: vlog("# Installing coverage wrapper") os.environ['COVERAGE_FILE'] = COVERAGE_FILE @@ -140,6 +168,12 @@ def output_coverage(): vlog("# Running: "+cmd) os.system(cmd) +class Timeout(Exception): + pass + +def alarmed(signum, frame): + raise Timeout + def run(cmd): """Run command in a sub-process, capturing the output (stdout and stderr). Return the exist code, and output.""" @@ -153,9 +187,17 @@ def run(cmd): ret = 0 else: proc = popen2.Popen4(cmd) - proc.tochild.close() - output = proc.fromchild.read() - ret = proc.wait() + try: + output = '' + proc.tochild.close() + output = proc.fromchild.read() + ret = proc.wait() + except Timeout: + vlog('# Process %d timed out - killing it' % proc.pid) + os.kill(proc.pid, signal.SIGTERM) + ret = proc.wait() + if ret == 0: + ret = signal.SIGTERM << 8 return ret, splitnewlines(output) def run_one(test): @@ -185,10 +227,16 @@ def run_one(test): if os.name == 'nt' and test.endswith(".bat"): cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test)) + if options.timeout > 0: + signal.alarm(options.timeout) + vlog("# Running", cmd) ret, out = run(cmd) vlog("# Ret was:", ret) + if options.timeout > 0: + signal.alarm(0) + diffret = 0 # If reference output file exists, check test output against it if os.path.exists(ref): @@ -212,6 +260,30 @@ def run_one(test): f.write(line) f.close() + # Kill off any leftover daemon processes + try: + fp = file(DAEMON_PIDS) + for line in fp: + try: + pid = int(line) + except ValueError: + continue + try: + 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) + except OSError, err: + if err.errno != errno.ESRCH: + raise + fp.close() + os.unlink(DAEMON_PIDS) + except IOError: + pass + os.chdir(TESTDIR) shutil.rmtree(tmpd, True) return ret == 0 @@ -233,6 +305,8 @@ os.environ["HGRCPATH"] = "" TESTDIR = os.environ["TESTDIR"] = os.getcwd() HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.") +DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') + vlog("# Using TESTDIR", TESTDIR) vlog("# Using HGTMP", HGTMP) @@ -245,6 +319,15 @@ try: try: install_hg() + if options.timeout > 0: + try: + signal.signal(signal.SIGALRM, alarmed) + vlog('# Running tests with %d-second timeout' % + options.timeout) + except AttributeError: + print 'WARNING: cannot run tests with timeouts' + options.timeout = 0 + tests = 0 failed = 0 diff --git a/tests/test-archive b/tests/test-archive --- a/tests/test-archive +++ b/tests/test-archive @@ -17,6 +17,7 @@ echo "[web]" >> .hg/hgrc echo "name = test-archive" >> .hg/hgrc echo "allow_archive = gz bz2, zip" >> .hg/hgrc hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS TIP=`hg id -v | cut -f1 -d' '` QTIP=`hg id -q` @@ -32,9 +33,6 @@ http_proxy= python getarchive.py "$TIP" http_proxy= python getarchive.py "$TIP" zip > archive.zip unzip -t archive.zip | sed "s/$QTIP/TIP/" -kill `cat hg.pid` -sleep 1 # wait for server to scream and die - hg archive -t tar test.tar tar tf test.tar diff --git a/tests/test-bad-pull b/tests/test-bad-pull --- a/tests/test-bad-pull +++ b/tests/test-bad-pull @@ -18,6 +18,7 @@ run() EOF python dumb.py 2>/dev/null & +echo $! >> $DAEMON_PIDS http_proxy= hg clone http://localhost:20059/foo copy2 2>&1 | \ sed -e 's/404.*/404/' -e 's/Date:.*/Date:/' diff --git a/tests/test-http-proxy b/tests/test-http-proxy --- a/tests/test-http-proxy +++ b/tests/test-http-proxy @@ -5,10 +5,12 @@ cd a echo a > a hg ci -Ama -d '1123456789 0' hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS cd .. -("$TESTDIR/tinyproxy.py" 20060 localhost >/dev/null 2>&1 proxy.log 2>&1 proxy.pid) +cat proxy.pid >> $DAEMON_PIDS sleep 2 echo %% url for proxy @@ -26,5 +28,4 @@ http_proxy=http://user:passwd@localhost: echo %% bad host:port for proxy http_proxy=localhost:20061 hg clone --config http_proxy.always=True http://localhost:20059/ f -kill `cat proxy.pid a/hg.pid` exit 0 diff --git a/tests/test-incoming-outgoing b/tests/test-incoming-outgoing --- a/tests/test-incoming-outgoing +++ b/tests/test-incoming-outgoing @@ -9,6 +9,7 @@ for i in 0 1 2 3 4 5 6 7 8; do done hg verify hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS cd .. hg init new @@ -45,5 +46,3 @@ cd .. hg -R test-dev outgoing test http_proxy= hg -R test-dev outgoing http://localhost:20059/ http_proxy= hg -R test-dev outgoing -r 11 http://localhost:20059/ - -kill `cat test/hg.pid` diff --git a/tests/test-pull b/tests/test-pull --- a/tests/test-pull +++ b/tests/test-pull @@ -8,6 +8,7 @@ hg addremove hg commit -m 1 hg verify hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS cd .. http_proxy= hg clone http://localhost:20059/ copy @@ -17,5 +18,3 @@ hg co cat foo hg manifest hg pull - -kill `cat ../test/hg.pid` diff --git a/tests/test-push-http b/tests/test-push-http --- a/tests/test-push-http +++ b/tests/test-push-http @@ -15,6 +15,7 @@ cd ../test echo % expect ssl error hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS hg --cwd ../test2 push http://localhost:20059/ kill `cat hg.pid` @@ -22,18 +23,21 @@ echo % expect authorization error echo '[web]' > .hg/hgrc echo 'push_ssl = false' >> .hg/hgrc hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS hg --cwd ../test2 push http://localhost:20059/ kill `cat hg.pid` echo % expect authorization error: must have authorized user echo 'allow_push = unperson' >> .hg/hgrc hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS hg --cwd ../test2 push http://localhost:20059/ kill `cat hg.pid` echo % expect success echo 'allow_push = *' >> .hg/hgrc hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS hg --cwd ../test2 push http://localhost:20059/ kill `cat hg.pid` hg rollback @@ -41,11 +45,13 @@ hg rollback echo % expect authorization error: all users denied echo 'deny_push = *' >> .hg/hgrc hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS hg --cwd ../test2 push http://localhost:20059/ kill `cat hg.pid` echo % expect authorization error: some users denied, users must be authenticated echo 'deny_push = unperson' >> .hg/hgrc hg serve -p 20059 -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS hg --cwd ../test2 push http://localhost:20059/ kill `cat hg.pid` diff --git a/tests/test-static-http b/tests/test-static-http --- a/tests/test-static-http +++ b/tests/test-static-http @@ -20,6 +20,7 @@ run() EOF python dumb.py 2>/dev/null & +echo $! >> $DAEMON_PIDS mkdir remote cd remote diff --git a/tests/test-webraw b/tests/test-webraw --- a/tests/test-webraw +++ b/tests/test-webraw @@ -11,6 +11,7 @@ ENDSOME hg add sometext.txt hg commit -d "1 0" -m "Just some text" hg serve -p 20059 -A access.log -E error.log -d --pid-file=hg.pid +cat hg.pid >> $DAEMON_PIDS ("$TESTDIR/get-with-headers.py" localhost:20059 '/?f=f165dc289438;file=sometext.txt;style=raw' content-type content-length content-disposition) >getoutput.txt & sleep 5