merge with mpm.
--- 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
--- 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)
--- 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
--- 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
--- 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:/'
--- 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 </dev/null &
+("$TESTDIR/tinyproxy.py" 20060 localhost >proxy.log 2>&1 </dev/null &
echo $! > 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
--- 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`
--- 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`
--- 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`
--- 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
--- 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