##// END OF EJS Templates
run-tests: simplify interrupted message
Matt Mackall -
r19304:59d5281b default
parent child Browse files
Show More
@@ -1,1258 +1,1257 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from distutils import version
44 from distutils import version
45 import difflib
45 import difflib
46 import errno
46 import errno
47 import optparse
47 import optparse
48 import os
48 import os
49 import shutil
49 import shutil
50 import subprocess
50 import subprocess
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55 import random
55 import random
56 import re
56 import re
57 import threading
57 import threading
58 import killdaemons as killmod
58 import killdaemons as killmod
59 import cPickle as pickle
59 import cPickle as pickle
60 import Queue as queue
60 import Queue as queue
61
61
62 processlock = threading.Lock()
62 processlock = threading.Lock()
63
63
64 closefds = os.name == 'posix'
64 closefds = os.name == 'posix'
65 def Popen4(cmd, wd, timeout, env=None):
65 def Popen4(cmd, wd, timeout, env=None):
66 processlock.acquire()
66 processlock.acquire()
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.STDOUT)
70 stderr=subprocess.STDOUT)
71 processlock.release()
71 processlock.release()
72
72
73 p.fromchild = p.stdout
73 p.fromchild = p.stdout
74 p.tochild = p.stdin
74 p.tochild = p.stdin
75 p.childerr = p.stderr
75 p.childerr = p.stderr
76
76
77 p.timeout = False
77 p.timeout = False
78 if timeout:
78 if timeout:
79 def t():
79 def t():
80 start = time.time()
80 start = time.time()
81 while time.time() - start < timeout and p.returncode is None:
81 while time.time() - start < timeout and p.returncode is None:
82 time.sleep(.1)
82 time.sleep(.1)
83 p.timeout = True
83 p.timeout = True
84 if p.returncode is None:
84 if p.returncode is None:
85 terminate(p)
85 terminate(p)
86 threading.Thread(target=t).start()
86 threading.Thread(target=t).start()
87
87
88 return p
88 return p
89
89
90 # reserved exit code to skip test (used by hghave)
90 # reserved exit code to skip test (used by hghave)
91 SKIPPED_STATUS = 80
91 SKIPPED_STATUS = 80
92 SKIPPED_PREFIX = 'skipped: '
92 SKIPPED_PREFIX = 'skipped: '
93 FAILED_PREFIX = 'hghave check failed: '
93 FAILED_PREFIX = 'hghave check failed: '
94 PYTHON = sys.executable.replace('\\', '/')
94 PYTHON = sys.executable.replace('\\', '/')
95 IMPL_PATH = 'PYTHONPATH'
95 IMPL_PATH = 'PYTHONPATH'
96 if 'java' in sys.platform:
96 if 'java' in sys.platform:
97 IMPL_PATH = 'JYTHONPATH'
97 IMPL_PATH = 'JYTHONPATH'
98
98
99 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
99 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
100 "gunzip", "bunzip2", "sed"]
100 "gunzip", "bunzip2", "sed"]
101
101
102 defaults = {
102 defaults = {
103 'jobs': ('HGTEST_JOBS', 1),
103 'jobs': ('HGTEST_JOBS', 1),
104 'timeout': ('HGTEST_TIMEOUT', 180),
104 'timeout': ('HGTEST_TIMEOUT', 180),
105 'port': ('HGTEST_PORT', 20059),
105 'port': ('HGTEST_PORT', 20059),
106 'shell': ('HGTEST_SHELL', 'sh'),
106 'shell': ('HGTEST_SHELL', 'sh'),
107 }
107 }
108
108
109 def parselistfiles(files, listtype, warn=True):
109 def parselistfiles(files, listtype, warn=True):
110 entries = dict()
110 entries = dict()
111 for filename in files:
111 for filename in files:
112 try:
112 try:
113 path = os.path.expanduser(os.path.expandvars(filename))
113 path = os.path.expanduser(os.path.expandvars(filename))
114 f = open(path, "r")
114 f = open(path, "r")
115 except IOError, err:
115 except IOError, err:
116 if err.errno != errno.ENOENT:
116 if err.errno != errno.ENOENT:
117 raise
117 raise
118 if warn:
118 if warn:
119 print "warning: no such %s file: %s" % (listtype, filename)
119 print "warning: no such %s file: %s" % (listtype, filename)
120 continue
120 continue
121
121
122 for line in f.readlines():
122 for line in f.readlines():
123 line = line.split('#', 1)[0].strip()
123 line = line.split('#', 1)[0].strip()
124 if line:
124 if line:
125 entries[line] = filename
125 entries[line] = filename
126
126
127 f.close()
127 f.close()
128 return entries
128 return entries
129
129
130 def parseargs():
130 def parseargs():
131 parser = optparse.OptionParser("%prog [options] [tests]")
131 parser = optparse.OptionParser("%prog [options] [tests]")
132
132
133 # keep these sorted
133 # keep these sorted
134 parser.add_option("--blacklist", action="append",
134 parser.add_option("--blacklist", action="append",
135 help="skip tests listed in the specified blacklist file")
135 help="skip tests listed in the specified blacklist file")
136 parser.add_option("--whitelist", action="append",
136 parser.add_option("--whitelist", action="append",
137 help="always run tests listed in the specified whitelist file")
137 help="always run tests listed in the specified whitelist file")
138 parser.add_option("-C", "--annotate", action="store_true",
138 parser.add_option("-C", "--annotate", action="store_true",
139 help="output files annotated with coverage")
139 help="output files annotated with coverage")
140 parser.add_option("-c", "--cover", action="store_true",
140 parser.add_option("-c", "--cover", action="store_true",
141 help="print a test coverage report")
141 help="print a test coverage report")
142 parser.add_option("-d", "--debug", action="store_true",
142 parser.add_option("-d", "--debug", action="store_true",
143 help="debug mode: write output of test scripts to console"
143 help="debug mode: write output of test scripts to console"
144 " rather than capturing and diff'ing it (disables timeout)")
144 " rather than capturing and diff'ing it (disables timeout)")
145 parser.add_option("-f", "--first", action="store_true",
145 parser.add_option("-f", "--first", action="store_true",
146 help="exit on the first test failure")
146 help="exit on the first test failure")
147 parser.add_option("-H", "--htmlcov", action="store_true",
147 parser.add_option("-H", "--htmlcov", action="store_true",
148 help="create an HTML report of the coverage of the files")
148 help="create an HTML report of the coverage of the files")
149 parser.add_option("--inotify", action="store_true",
149 parser.add_option("--inotify", action="store_true",
150 help="enable inotify extension when running tests")
150 help="enable inotify extension when running tests")
151 parser.add_option("-i", "--interactive", action="store_true",
151 parser.add_option("-i", "--interactive", action="store_true",
152 help="prompt to accept changed output")
152 help="prompt to accept changed output")
153 parser.add_option("-j", "--jobs", type="int",
153 parser.add_option("-j", "--jobs", type="int",
154 help="number of jobs to run in parallel"
154 help="number of jobs to run in parallel"
155 " (default: $%s or %d)" % defaults['jobs'])
155 " (default: $%s or %d)" % defaults['jobs'])
156 parser.add_option("--keep-tmpdir", action="store_true",
156 parser.add_option("--keep-tmpdir", action="store_true",
157 help="keep temporary directory after running tests")
157 help="keep temporary directory after running tests")
158 parser.add_option("-k", "--keywords",
158 parser.add_option("-k", "--keywords",
159 help="run tests matching keywords")
159 help="run tests matching keywords")
160 parser.add_option("-l", "--local", action="store_true",
160 parser.add_option("-l", "--local", action="store_true",
161 help="shortcut for --with-hg=<testdir>/../hg")
161 help="shortcut for --with-hg=<testdir>/../hg")
162 parser.add_option("--loop", action="store_true",
162 parser.add_option("--loop", action="store_true",
163 help="loop tests repeatedly")
163 help="loop tests repeatedly")
164 parser.add_option("-n", "--nodiff", action="store_true",
164 parser.add_option("-n", "--nodiff", action="store_true",
165 help="skip showing test changes")
165 help="skip showing test changes")
166 parser.add_option("-p", "--port", type="int",
166 parser.add_option("-p", "--port", type="int",
167 help="port on which servers should listen"
167 help="port on which servers should listen"
168 " (default: $%s or %d)" % defaults['port'])
168 " (default: $%s or %d)" % defaults['port'])
169 parser.add_option("--compiler", type="string",
169 parser.add_option("--compiler", type="string",
170 help="compiler to build with")
170 help="compiler to build with")
171 parser.add_option("--pure", action="store_true",
171 parser.add_option("--pure", action="store_true",
172 help="use pure Python code instead of C extensions")
172 help="use pure Python code instead of C extensions")
173 parser.add_option("-R", "--restart", action="store_true",
173 parser.add_option("-R", "--restart", action="store_true",
174 help="restart at last error")
174 help="restart at last error")
175 parser.add_option("-r", "--retest", action="store_true",
175 parser.add_option("-r", "--retest", action="store_true",
176 help="retest failed tests")
176 help="retest failed tests")
177 parser.add_option("-S", "--noskips", action="store_true",
177 parser.add_option("-S", "--noskips", action="store_true",
178 help="don't report skip tests verbosely")
178 help="don't report skip tests verbosely")
179 parser.add_option("--shell", type="string",
179 parser.add_option("--shell", type="string",
180 help="shell to use (default: $%s or %s)" % defaults['shell'])
180 help="shell to use (default: $%s or %s)" % defaults['shell'])
181 parser.add_option("-t", "--timeout", type="int",
181 parser.add_option("-t", "--timeout", type="int",
182 help="kill errant tests after TIMEOUT seconds"
182 help="kill errant tests after TIMEOUT seconds"
183 " (default: $%s or %d)" % defaults['timeout'])
183 " (default: $%s or %d)" % defaults['timeout'])
184 parser.add_option("--time", action="store_true",
184 parser.add_option("--time", action="store_true",
185 help="time how long each test takes")
185 help="time how long each test takes")
186 parser.add_option("--tmpdir", type="string",
186 parser.add_option("--tmpdir", type="string",
187 help="run tests in the given temporary directory"
187 help="run tests in the given temporary directory"
188 " (implies --keep-tmpdir)")
188 " (implies --keep-tmpdir)")
189 parser.add_option("-v", "--verbose", action="store_true",
189 parser.add_option("-v", "--verbose", action="store_true",
190 help="output verbose messages")
190 help="output verbose messages")
191 parser.add_option("--view", type="string",
191 parser.add_option("--view", type="string",
192 help="external diff viewer")
192 help="external diff viewer")
193 parser.add_option("--with-hg", type="string",
193 parser.add_option("--with-hg", type="string",
194 metavar="HG",
194 metavar="HG",
195 help="test using specified hg script rather than a "
195 help="test using specified hg script rather than a "
196 "temporary installation")
196 "temporary installation")
197 parser.add_option("-3", "--py3k-warnings", action="store_true",
197 parser.add_option("-3", "--py3k-warnings", action="store_true",
198 help="enable Py3k warnings on Python 2.6+")
198 help="enable Py3k warnings on Python 2.6+")
199 parser.add_option('--extra-config-opt', action="append",
199 parser.add_option('--extra-config-opt', action="append",
200 help='set the given config opt in the test hgrc')
200 help='set the given config opt in the test hgrc')
201 parser.add_option('--random', action="store_true",
201 parser.add_option('--random', action="store_true",
202 help='run tests in random order')
202 help='run tests in random order')
203
203
204 for option, (envvar, default) in defaults.items():
204 for option, (envvar, default) in defaults.items():
205 defaults[option] = type(default)(os.environ.get(envvar, default))
205 defaults[option] = type(default)(os.environ.get(envvar, default))
206 parser.set_defaults(**defaults)
206 parser.set_defaults(**defaults)
207 (options, args) = parser.parse_args()
207 (options, args) = parser.parse_args()
208
208
209 # jython is always pure
209 # jython is always pure
210 if 'java' in sys.platform or '__pypy__' in sys.modules:
210 if 'java' in sys.platform or '__pypy__' in sys.modules:
211 options.pure = True
211 options.pure = True
212
212
213 if options.with_hg:
213 if options.with_hg:
214 options.with_hg = os.path.expanduser(options.with_hg)
214 options.with_hg = os.path.expanduser(options.with_hg)
215 if not (os.path.isfile(options.with_hg) and
215 if not (os.path.isfile(options.with_hg) and
216 os.access(options.with_hg, os.X_OK)):
216 os.access(options.with_hg, os.X_OK)):
217 parser.error('--with-hg must specify an executable hg script')
217 parser.error('--with-hg must specify an executable hg script')
218 if not os.path.basename(options.with_hg) == 'hg':
218 if not os.path.basename(options.with_hg) == 'hg':
219 sys.stderr.write('warning: --with-hg should specify an hg script\n')
219 sys.stderr.write('warning: --with-hg should specify an hg script\n')
220 if options.local:
220 if options.local:
221 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
221 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
222 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
222 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
223 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
223 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
224 parser.error('--local specified, but %r not found or not executable'
224 parser.error('--local specified, but %r not found or not executable'
225 % hgbin)
225 % hgbin)
226 options.with_hg = hgbin
226 options.with_hg = hgbin
227
227
228 options.anycoverage = options.cover or options.annotate or options.htmlcov
228 options.anycoverage = options.cover or options.annotate or options.htmlcov
229 if options.anycoverage:
229 if options.anycoverage:
230 try:
230 try:
231 import coverage
231 import coverage
232 covver = version.StrictVersion(coverage.__version__).version
232 covver = version.StrictVersion(coverage.__version__).version
233 if covver < (3, 3):
233 if covver < (3, 3):
234 parser.error('coverage options require coverage 3.3 or later')
234 parser.error('coverage options require coverage 3.3 or later')
235 except ImportError:
235 except ImportError:
236 parser.error('coverage options now require the coverage package')
236 parser.error('coverage options now require the coverage package')
237
237
238 if options.anycoverage and options.local:
238 if options.anycoverage and options.local:
239 # this needs some path mangling somewhere, I guess
239 # this needs some path mangling somewhere, I guess
240 parser.error("sorry, coverage options do not work when --local "
240 parser.error("sorry, coverage options do not work when --local "
241 "is specified")
241 "is specified")
242
242
243 global verbose
243 global verbose
244 if options.verbose:
244 if options.verbose:
245 verbose = ''
245 verbose = ''
246
246
247 if options.tmpdir:
247 if options.tmpdir:
248 options.tmpdir = os.path.expanduser(options.tmpdir)
248 options.tmpdir = os.path.expanduser(options.tmpdir)
249
249
250 if options.jobs < 1:
250 if options.jobs < 1:
251 parser.error('--jobs must be positive')
251 parser.error('--jobs must be positive')
252 if options.interactive and options.debug:
252 if options.interactive and options.debug:
253 parser.error("-i/--interactive and -d/--debug are incompatible")
253 parser.error("-i/--interactive and -d/--debug are incompatible")
254 if options.debug:
254 if options.debug:
255 if options.timeout != defaults['timeout']:
255 if options.timeout != defaults['timeout']:
256 sys.stderr.write(
256 sys.stderr.write(
257 'warning: --timeout option ignored with --debug\n')
257 'warning: --timeout option ignored with --debug\n')
258 options.timeout = 0
258 options.timeout = 0
259 if options.py3k_warnings:
259 if options.py3k_warnings:
260 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
260 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
261 parser.error('--py3k-warnings can only be used on Python 2.6+')
261 parser.error('--py3k-warnings can only be used on Python 2.6+')
262 if options.blacklist:
262 if options.blacklist:
263 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
263 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
264 if options.whitelist:
264 if options.whitelist:
265 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
265 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
266 else:
266 else:
267 options.whitelisted = {}
267 options.whitelisted = {}
268
268
269 return (options, args)
269 return (options, args)
270
270
271 def rename(src, dst):
271 def rename(src, dst):
272 """Like os.rename(), trade atomicity and opened files friendliness
272 """Like os.rename(), trade atomicity and opened files friendliness
273 for existing destination support.
273 for existing destination support.
274 """
274 """
275 shutil.copy(src, dst)
275 shutil.copy(src, dst)
276 os.remove(src)
276 os.remove(src)
277
277
278 def parsehghaveoutput(lines):
278 def parsehghaveoutput(lines):
279 '''Parse hghave log lines.
279 '''Parse hghave log lines.
280 Return tuple of lists (missing, failed):
280 Return tuple of lists (missing, failed):
281 * the missing/unknown features
281 * the missing/unknown features
282 * the features for which existence check failed'''
282 * the features for which existence check failed'''
283 missing = []
283 missing = []
284 failed = []
284 failed = []
285 for line in lines:
285 for line in lines:
286 if line.startswith(SKIPPED_PREFIX):
286 if line.startswith(SKIPPED_PREFIX):
287 line = line.splitlines()[0]
287 line = line.splitlines()[0]
288 missing.append(line[len(SKIPPED_PREFIX):])
288 missing.append(line[len(SKIPPED_PREFIX):])
289 elif line.startswith(FAILED_PREFIX):
289 elif line.startswith(FAILED_PREFIX):
290 line = line.splitlines()[0]
290 line = line.splitlines()[0]
291 failed.append(line[len(FAILED_PREFIX):])
291 failed.append(line[len(FAILED_PREFIX):])
292
292
293 return missing, failed
293 return missing, failed
294
294
295 def showdiff(expected, output, ref, err):
295 def showdiff(expected, output, ref, err):
296 print
296 print
297 for line in difflib.unified_diff(expected, output, ref, err):
297 for line in difflib.unified_diff(expected, output, ref, err):
298 sys.stdout.write(line)
298 sys.stdout.write(line)
299
299
300 verbose = False
300 verbose = False
301 def vlog(*msg):
301 def vlog(*msg):
302 if verbose is not False:
302 if verbose is not False:
303 iolock.acquire()
303 iolock.acquire()
304 if verbose:
304 if verbose:
305 print verbose,
305 print verbose,
306 for m in msg:
306 for m in msg:
307 print m,
307 print m,
308 print
308 print
309 sys.stdout.flush()
309 sys.stdout.flush()
310 iolock.release()
310 iolock.release()
311
311
312 def log(*msg):
312 def log(*msg):
313 iolock.acquire()
313 iolock.acquire()
314 if verbose:
314 if verbose:
315 print verbose,
315 print verbose,
316 for m in msg:
316 for m in msg:
317 print m,
317 print m,
318 print
318 print
319 sys.stdout.flush()
319 sys.stdout.flush()
320 iolock.release()
320 iolock.release()
321
321
322 def findprogram(program):
322 def findprogram(program):
323 """Search PATH for a executable program"""
323 """Search PATH for a executable program"""
324 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
324 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
325 name = os.path.join(p, program)
325 name = os.path.join(p, program)
326 if os.name == 'nt' or os.access(name, os.X_OK):
326 if os.name == 'nt' or os.access(name, os.X_OK):
327 return name
327 return name
328 return None
328 return None
329
329
330 def createhgrc(path, options):
330 def createhgrc(path, options):
331 # create a fresh hgrc
331 # create a fresh hgrc
332 hgrc = open(path, 'w')
332 hgrc = open(path, 'w')
333 hgrc.write('[ui]\n')
333 hgrc.write('[ui]\n')
334 hgrc.write('slash = True\n')
334 hgrc.write('slash = True\n')
335 hgrc.write('interactive = False\n')
335 hgrc.write('interactive = False\n')
336 hgrc.write('[defaults]\n')
336 hgrc.write('[defaults]\n')
337 hgrc.write('backout = -d "0 0"\n')
337 hgrc.write('backout = -d "0 0"\n')
338 hgrc.write('commit = -d "0 0"\n')
338 hgrc.write('commit = -d "0 0"\n')
339 hgrc.write('tag = -d "0 0"\n')
339 hgrc.write('tag = -d "0 0"\n')
340 if options.inotify:
340 if options.inotify:
341 hgrc.write('[extensions]\n')
341 hgrc.write('[extensions]\n')
342 hgrc.write('inotify=\n')
342 hgrc.write('inotify=\n')
343 hgrc.write('[inotify]\n')
343 hgrc.write('[inotify]\n')
344 hgrc.write('pidfile=daemon.pids')
344 hgrc.write('pidfile=daemon.pids')
345 hgrc.write('appendpid=True\n')
345 hgrc.write('appendpid=True\n')
346 if options.extra_config_opt:
346 if options.extra_config_opt:
347 for opt in options.extra_config_opt:
347 for opt in options.extra_config_opt:
348 section, key = opt.split('.', 1)
348 section, key = opt.split('.', 1)
349 assert '=' in key, ('extra config opt %s must '
349 assert '=' in key, ('extra config opt %s must '
350 'have an = for assignment' % opt)
350 'have an = for assignment' % opt)
351 hgrc.write('[%s]\n%s\n' % (section, key))
351 hgrc.write('[%s]\n%s\n' % (section, key))
352 hgrc.close()
352 hgrc.close()
353
353
354 def createenv(options, testtmp, threadtmp, port):
354 def createenv(options, testtmp, threadtmp, port):
355 env = os.environ.copy()
355 env = os.environ.copy()
356 env['TESTTMP'] = testtmp
356 env['TESTTMP'] = testtmp
357 env['HOME'] = testtmp
357 env['HOME'] = testtmp
358 env["HGPORT"] = str(port)
358 env["HGPORT"] = str(port)
359 env["HGPORT1"] = str(port + 1)
359 env["HGPORT1"] = str(port + 1)
360 env["HGPORT2"] = str(port + 2)
360 env["HGPORT2"] = str(port + 2)
361 env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc')
361 env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc')
362 env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids')
362 env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids')
363 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
363 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
364 env["HGMERGE"] = "internal:merge"
364 env["HGMERGE"] = "internal:merge"
365 env["HGUSER"] = "test"
365 env["HGUSER"] = "test"
366 env["HGENCODING"] = "ascii"
366 env["HGENCODING"] = "ascii"
367 env["HGENCODINGMODE"] = "strict"
367 env["HGENCODINGMODE"] = "strict"
368
368
369 # Reset some environment variables to well-known values so that
369 # Reset some environment variables to well-known values so that
370 # the tests produce repeatable output.
370 # the tests produce repeatable output.
371 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
371 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
372 env['TZ'] = 'GMT'
372 env['TZ'] = 'GMT'
373 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
373 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
374 env['COLUMNS'] = '80'
374 env['COLUMNS'] = '80'
375 env['TERM'] = 'xterm'
375 env['TERM'] = 'xterm'
376
376
377 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
377 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
378 'NO_PROXY').split():
378 'NO_PROXY').split():
379 if k in env:
379 if k in env:
380 del env[k]
380 del env[k]
381
381
382 # unset env related to hooks
382 # unset env related to hooks
383 for k in env.keys():
383 for k in env.keys():
384 if k.startswith('HG_'):
384 if k.startswith('HG_'):
385 del env[k]
385 del env[k]
386
386
387 return env
387 return env
388
388
389 def checktools():
389 def checktools():
390 # Before we go any further, check for pre-requisite tools
390 # Before we go any further, check for pre-requisite tools
391 # stuff from coreutils (cat, rm, etc) are not tested
391 # stuff from coreutils (cat, rm, etc) are not tested
392 for p in requiredtools:
392 for p in requiredtools:
393 if os.name == 'nt' and not p.endswith('.exe'):
393 if os.name == 'nt' and not p.endswith('.exe'):
394 p += '.exe'
394 p += '.exe'
395 found = findprogram(p)
395 found = findprogram(p)
396 if found:
396 if found:
397 vlog("# Found prerequisite", p, "at", found)
397 vlog("# Found prerequisite", p, "at", found)
398 else:
398 else:
399 print "WARNING: Did not find prerequisite tool: "+p
399 print "WARNING: Did not find prerequisite tool: "+p
400
400
401 def terminate(proc):
401 def terminate(proc):
402 """Terminate subprocess (with fallback for Python versions < 2.6)"""
402 """Terminate subprocess (with fallback for Python versions < 2.6)"""
403 vlog('# Terminating process %d' % proc.pid)
403 vlog('# Terminating process %d' % proc.pid)
404 try:
404 try:
405 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
405 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
406 except OSError:
406 except OSError:
407 pass
407 pass
408
408
409 def killdaemons(pidfile):
409 def killdaemons(pidfile):
410 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
410 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
411 logfn=vlog)
411 logfn=vlog)
412
412
413 def cleanup(options):
413 def cleanup(options):
414 if not options.keep_tmpdir:
414 if not options.keep_tmpdir:
415 vlog("# Cleaning up HGTMP", HGTMP)
415 vlog("# Cleaning up HGTMP", HGTMP)
416 shutil.rmtree(HGTMP, True)
416 shutil.rmtree(HGTMP, True)
417
417
418 def usecorrectpython():
418 def usecorrectpython():
419 # some tests run python interpreter. they must use same
419 # some tests run python interpreter. they must use same
420 # interpreter we use or bad things will happen.
420 # interpreter we use or bad things will happen.
421 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
421 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
422 if getattr(os, 'symlink', None):
422 if getattr(os, 'symlink', None):
423 vlog("# Making python executable in test path a symlink to '%s'" %
423 vlog("# Making python executable in test path a symlink to '%s'" %
424 sys.executable)
424 sys.executable)
425 mypython = os.path.join(BINDIR, pyexename)
425 mypython = os.path.join(BINDIR, pyexename)
426 try:
426 try:
427 if os.readlink(mypython) == sys.executable:
427 if os.readlink(mypython) == sys.executable:
428 return
428 return
429 os.unlink(mypython)
429 os.unlink(mypython)
430 except OSError, err:
430 except OSError, err:
431 if err.errno != errno.ENOENT:
431 if err.errno != errno.ENOENT:
432 raise
432 raise
433 if findprogram(pyexename) != sys.executable:
433 if findprogram(pyexename) != sys.executable:
434 try:
434 try:
435 os.symlink(sys.executable, mypython)
435 os.symlink(sys.executable, mypython)
436 except OSError, err:
436 except OSError, err:
437 # child processes may race, which is harmless
437 # child processes may race, which is harmless
438 if err.errno != errno.EEXIST:
438 if err.errno != errno.EEXIST:
439 raise
439 raise
440 else:
440 else:
441 exedir, exename = os.path.split(sys.executable)
441 exedir, exename = os.path.split(sys.executable)
442 vlog("# Modifying search path to find %s as %s in '%s'" %
442 vlog("# Modifying search path to find %s as %s in '%s'" %
443 (exename, pyexename, exedir))
443 (exename, pyexename, exedir))
444 path = os.environ['PATH'].split(os.pathsep)
444 path = os.environ['PATH'].split(os.pathsep)
445 while exedir in path:
445 while exedir in path:
446 path.remove(exedir)
446 path.remove(exedir)
447 os.environ['PATH'] = os.pathsep.join([exedir] + path)
447 os.environ['PATH'] = os.pathsep.join([exedir] + path)
448 if not findprogram(pyexename):
448 if not findprogram(pyexename):
449 print "WARNING: Cannot find %s in search path" % pyexename
449 print "WARNING: Cannot find %s in search path" % pyexename
450
450
451 def installhg(options):
451 def installhg(options):
452 vlog("# Performing temporary installation of HG")
452 vlog("# Performing temporary installation of HG")
453 installerrs = os.path.join("tests", "install.err")
453 installerrs = os.path.join("tests", "install.err")
454 compiler = ''
454 compiler = ''
455 if options.compiler:
455 if options.compiler:
456 compiler = '--compiler ' + options.compiler
456 compiler = '--compiler ' + options.compiler
457 pure = options.pure and "--pure" or ""
457 pure = options.pure and "--pure" or ""
458
458
459 # Run installer in hg root
459 # Run installer in hg root
460 script = os.path.realpath(sys.argv[0])
460 script = os.path.realpath(sys.argv[0])
461 hgroot = os.path.dirname(os.path.dirname(script))
461 hgroot = os.path.dirname(os.path.dirname(script))
462 os.chdir(hgroot)
462 os.chdir(hgroot)
463 nohome = '--home=""'
463 nohome = '--home=""'
464 if os.name == 'nt':
464 if os.name == 'nt':
465 # The --home="" trick works only on OS where os.sep == '/'
465 # The --home="" trick works only on OS where os.sep == '/'
466 # because of a distutils convert_path() fast-path. Avoid it at
466 # because of a distutils convert_path() fast-path. Avoid it at
467 # least on Windows for now, deal with .pydistutils.cfg bugs
467 # least on Windows for now, deal with .pydistutils.cfg bugs
468 # when they happen.
468 # when they happen.
469 nohome = ''
469 nohome = ''
470 cmd = ('%(exe)s setup.py %(pure)s clean --all'
470 cmd = ('%(exe)s setup.py %(pure)s clean --all'
471 ' build %(compiler)s --build-base="%(base)s"'
471 ' build %(compiler)s --build-base="%(base)s"'
472 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
472 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
473 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
473 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
474 % dict(exe=sys.executable, pure=pure, compiler=compiler,
474 % dict(exe=sys.executable, pure=pure, compiler=compiler,
475 base=os.path.join(HGTMP, "build"),
475 base=os.path.join(HGTMP, "build"),
476 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
476 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
477 nohome=nohome, logfile=installerrs))
477 nohome=nohome, logfile=installerrs))
478 vlog("# Running", cmd)
478 vlog("# Running", cmd)
479 if os.system(cmd) == 0:
479 if os.system(cmd) == 0:
480 if not options.verbose:
480 if not options.verbose:
481 os.remove(installerrs)
481 os.remove(installerrs)
482 else:
482 else:
483 f = open(installerrs)
483 f = open(installerrs)
484 for line in f:
484 for line in f:
485 print line,
485 print line,
486 f.close()
486 f.close()
487 sys.exit(1)
487 sys.exit(1)
488 os.chdir(TESTDIR)
488 os.chdir(TESTDIR)
489
489
490 usecorrectpython()
490 usecorrectpython()
491
491
492 vlog("# Installing dummy diffstat")
492 vlog("# Installing dummy diffstat")
493 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
493 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
494 f.write('#!' + sys.executable + '\n'
494 f.write('#!' + sys.executable + '\n'
495 'import sys\n'
495 'import sys\n'
496 'files = 0\n'
496 'files = 0\n'
497 'for line in sys.stdin:\n'
497 'for line in sys.stdin:\n'
498 ' if line.startswith("diff "):\n'
498 ' if line.startswith("diff "):\n'
499 ' files += 1\n'
499 ' files += 1\n'
500 'sys.stdout.write("files patched: %d\\n" % files)\n')
500 'sys.stdout.write("files patched: %d\\n" % files)\n')
501 f.close()
501 f.close()
502 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
502 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
503
503
504 if options.py3k_warnings and not options.anycoverage:
504 if options.py3k_warnings and not options.anycoverage:
505 vlog("# Updating hg command to enable Py3k Warnings switch")
505 vlog("# Updating hg command to enable Py3k Warnings switch")
506 f = open(os.path.join(BINDIR, 'hg'), 'r')
506 f = open(os.path.join(BINDIR, 'hg'), 'r')
507 lines = [line.rstrip() for line in f]
507 lines = [line.rstrip() for line in f]
508 lines[0] += ' -3'
508 lines[0] += ' -3'
509 f.close()
509 f.close()
510 f = open(os.path.join(BINDIR, 'hg'), 'w')
510 f = open(os.path.join(BINDIR, 'hg'), 'w')
511 for line in lines:
511 for line in lines:
512 f.write(line + '\n')
512 f.write(line + '\n')
513 f.close()
513 f.close()
514
514
515 hgbat = os.path.join(BINDIR, 'hg.bat')
515 hgbat = os.path.join(BINDIR, 'hg.bat')
516 if os.path.isfile(hgbat):
516 if os.path.isfile(hgbat):
517 # hg.bat expects to be put in bin/scripts while run-tests.py
517 # hg.bat expects to be put in bin/scripts while run-tests.py
518 # installation layout put it in bin/ directly. Fix it
518 # installation layout put it in bin/ directly. Fix it
519 f = open(hgbat, 'rb')
519 f = open(hgbat, 'rb')
520 data = f.read()
520 data = f.read()
521 f.close()
521 f.close()
522 if '"%~dp0..\python" "%~dp0hg" %*' in data:
522 if '"%~dp0..\python" "%~dp0hg" %*' in data:
523 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
523 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
524 '"%~dp0python" "%~dp0hg" %*')
524 '"%~dp0python" "%~dp0hg" %*')
525 f = open(hgbat, 'wb')
525 f = open(hgbat, 'wb')
526 f.write(data)
526 f.write(data)
527 f.close()
527 f.close()
528 else:
528 else:
529 print 'WARNING: cannot fix hg.bat reference to python.exe'
529 print 'WARNING: cannot fix hg.bat reference to python.exe'
530
530
531 if options.anycoverage:
531 if options.anycoverage:
532 custom = os.path.join(TESTDIR, 'sitecustomize.py')
532 custom = os.path.join(TESTDIR, 'sitecustomize.py')
533 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
533 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
534 vlog('# Installing coverage trigger to %s' % target)
534 vlog('# Installing coverage trigger to %s' % target)
535 shutil.copyfile(custom, target)
535 shutil.copyfile(custom, target)
536 rc = os.path.join(TESTDIR, '.coveragerc')
536 rc = os.path.join(TESTDIR, '.coveragerc')
537 vlog('# Installing coverage rc to %s' % rc)
537 vlog('# Installing coverage rc to %s' % rc)
538 os.environ['COVERAGE_PROCESS_START'] = rc
538 os.environ['COVERAGE_PROCESS_START'] = rc
539 fn = os.path.join(INST, '..', '.coverage')
539 fn = os.path.join(INST, '..', '.coverage')
540 os.environ['COVERAGE_FILE'] = fn
540 os.environ['COVERAGE_FILE'] = fn
541
541
542 def outputtimes(options):
542 def outputtimes(options):
543 vlog('# Producing time report')
543 vlog('# Producing time report')
544 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
544 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
545 cols = '%7.3f %s'
545 cols = '%7.3f %s'
546 print '\n%-7s %s' % ('Time', 'Test')
546 print '\n%-7s %s' % ('Time', 'Test')
547 for test, timetaken in times:
547 for test, timetaken in times:
548 print cols % (timetaken, test)
548 print cols % (timetaken, test)
549
549
550 def outputcoverage(options):
550 def outputcoverage(options):
551
551
552 vlog('# Producing coverage report')
552 vlog('# Producing coverage report')
553 os.chdir(PYTHONDIR)
553 os.chdir(PYTHONDIR)
554
554
555 def covrun(*args):
555 def covrun(*args):
556 cmd = 'coverage %s' % ' '.join(args)
556 cmd = 'coverage %s' % ' '.join(args)
557 vlog('# Running: %s' % cmd)
557 vlog('# Running: %s' % cmd)
558 os.system(cmd)
558 os.system(cmd)
559
559
560 covrun('-c')
560 covrun('-c')
561 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
561 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
562 covrun('-i', '-r', '"--omit=%s"' % omit) # report
562 covrun('-i', '-r', '"--omit=%s"' % omit) # report
563 if options.htmlcov:
563 if options.htmlcov:
564 htmldir = os.path.join(TESTDIR, 'htmlcov')
564 htmldir = os.path.join(TESTDIR, 'htmlcov')
565 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
565 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
566 if options.annotate:
566 if options.annotate:
567 adir = os.path.join(TESTDIR, 'annotated')
567 adir = os.path.join(TESTDIR, 'annotated')
568 if not os.path.isdir(adir):
568 if not os.path.isdir(adir):
569 os.mkdir(adir)
569 os.mkdir(adir)
570 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
570 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
571
571
572 def pytest(test, wd, options, replacements, env):
572 def pytest(test, wd, options, replacements, env):
573 py3kswitch = options.py3k_warnings and ' -3' or ''
573 py3kswitch = options.py3k_warnings and ' -3' or ''
574 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
574 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
575 vlog("# Running", cmd)
575 vlog("# Running", cmd)
576 if os.name == 'nt':
576 if os.name == 'nt':
577 replacements.append((r'\r\n', '\n'))
577 replacements.append((r'\r\n', '\n'))
578 return run(cmd, wd, options, replacements, env)
578 return run(cmd, wd, options, replacements, env)
579
579
580 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
580 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
581 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
581 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
582 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
582 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
583 escapemap.update({'\\': '\\\\', '\r': r'\r'})
583 escapemap.update({'\\': '\\\\', '\r': r'\r'})
584 def escapef(m):
584 def escapef(m):
585 return escapemap[m.group(0)]
585 return escapemap[m.group(0)]
586 def stringescape(s):
586 def stringescape(s):
587 return escapesub(escapef, s)
587 return escapesub(escapef, s)
588
588
589 def rematch(el, l):
589 def rematch(el, l):
590 try:
590 try:
591 # use \Z to ensure that the regex matches to the end of the string
591 # use \Z to ensure that the regex matches to the end of the string
592 if os.name == 'nt':
592 if os.name == 'nt':
593 return re.match(el + r'\r?\n\Z', l)
593 return re.match(el + r'\r?\n\Z', l)
594 return re.match(el + r'\n\Z', l)
594 return re.match(el + r'\n\Z', l)
595 except re.error:
595 except re.error:
596 # el is an invalid regex
596 # el is an invalid regex
597 return False
597 return False
598
598
599 def globmatch(el, l):
599 def globmatch(el, l):
600 # The only supported special characters are * and ? plus / which also
600 # The only supported special characters are * and ? plus / which also
601 # matches \ on windows. Escaping of these caracters is supported.
601 # matches \ on windows. Escaping of these caracters is supported.
602 if el + '\n' == l:
602 if el + '\n' == l:
603 if os.name == 'nt':
603 if os.name == 'nt':
604 # matching on "/" is not needed for this line
604 # matching on "/" is not needed for this line
605 log("\nInfo, unnecessary glob: %s (glob)" % el)
605 log("\nInfo, unnecessary glob: %s (glob)" % el)
606 return True
606 return True
607 i, n = 0, len(el)
607 i, n = 0, len(el)
608 res = ''
608 res = ''
609 while i < n:
609 while i < n:
610 c = el[i]
610 c = el[i]
611 i += 1
611 i += 1
612 if c == '\\' and el[i] in '*?\\/':
612 if c == '\\' and el[i] in '*?\\/':
613 res += el[i - 1:i + 1]
613 res += el[i - 1:i + 1]
614 i += 1
614 i += 1
615 elif c == '*':
615 elif c == '*':
616 res += '.*'
616 res += '.*'
617 elif c == '?':
617 elif c == '?':
618 res += '.'
618 res += '.'
619 elif c == '/' and os.name == 'nt':
619 elif c == '/' and os.name == 'nt':
620 res += '[/\\\\]'
620 res += '[/\\\\]'
621 else:
621 else:
622 res += re.escape(c)
622 res += re.escape(c)
623 return rematch(res, l)
623 return rematch(res, l)
624
624
625 def linematch(el, l):
625 def linematch(el, l):
626 if el == l: # perfect match (fast)
626 if el == l: # perfect match (fast)
627 return True
627 return True
628 if el:
628 if el:
629 if el.endswith(" (esc)\n"):
629 if el.endswith(" (esc)\n"):
630 el = el[:-7].decode('string-escape') + '\n'
630 el = el[:-7].decode('string-escape') + '\n'
631 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
631 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
632 return True
632 return True
633 if (el.endswith(" (re)\n") and rematch(el[:-6], l) or
633 if (el.endswith(" (re)\n") and rematch(el[:-6], l) or
634 el.endswith(" (glob)\n") and globmatch(el[:-8], l)):
634 el.endswith(" (glob)\n") and globmatch(el[:-8], l)):
635 return True
635 return True
636 return False
636 return False
637
637
638 def tsttest(test, wd, options, replacements, env):
638 def tsttest(test, wd, options, replacements, env):
639 # We generate a shell script which outputs unique markers to line
639 # We generate a shell script which outputs unique markers to line
640 # up script results with our source. These markers include input
640 # up script results with our source. These markers include input
641 # line number and the last return code
641 # line number and the last return code
642 salt = "SALT" + str(time.time())
642 salt = "SALT" + str(time.time())
643 def addsalt(line, inpython):
643 def addsalt(line, inpython):
644 if inpython:
644 if inpython:
645 script.append('%s %d 0\n' % (salt, line))
645 script.append('%s %d 0\n' % (salt, line))
646 else:
646 else:
647 script.append('echo %s %s $?\n' % (salt, line))
647 script.append('echo %s %s $?\n' % (salt, line))
648
648
649 # After we run the shell script, we re-unify the script output
649 # After we run the shell script, we re-unify the script output
650 # with non-active parts of the source, with synchronization by our
650 # with non-active parts of the source, with synchronization by our
651 # SALT line number markers. The after table contains the
651 # SALT line number markers. The after table contains the
652 # non-active components, ordered by line number
652 # non-active components, ordered by line number
653 after = {}
653 after = {}
654 pos = prepos = -1
654 pos = prepos = -1
655
655
656 # Expected shellscript output
656 # Expected shellscript output
657 expected = {}
657 expected = {}
658
658
659 # We keep track of whether or not we're in a Python block so we
659 # We keep track of whether or not we're in a Python block so we
660 # can generate the surrounding doctest magic
660 # can generate the surrounding doctest magic
661 inpython = False
661 inpython = False
662
662
663 # True or False when in a true or false conditional section
663 # True or False when in a true or false conditional section
664 skipping = None
664 skipping = None
665
665
666 def hghave(reqs):
666 def hghave(reqs):
667 # TODO: do something smarter when all other uses of hghave is gone
667 # TODO: do something smarter when all other uses of hghave is gone
668 tdir = TESTDIR.replace('\\', '/')
668 tdir = TESTDIR.replace('\\', '/')
669 proc = Popen4('%s -c "%s/hghave %s"' %
669 proc = Popen4('%s -c "%s/hghave %s"' %
670 (options.shell, tdir, ' '.join(reqs)), wd, 0)
670 (options.shell, tdir, ' '.join(reqs)), wd, 0)
671 stdout, stderr = proc.communicate()
671 stdout, stderr = proc.communicate()
672 ret = proc.wait()
672 ret = proc.wait()
673 if wifexited(ret):
673 if wifexited(ret):
674 ret = os.WEXITSTATUS(ret)
674 ret = os.WEXITSTATUS(ret)
675 if ret == 2:
675 if ret == 2:
676 print stdout
676 print stdout
677 sys.exit(1)
677 sys.exit(1)
678 return ret == 0
678 return ret == 0
679
679
680 f = open(test)
680 f = open(test)
681 t = f.readlines()
681 t = f.readlines()
682 f.close()
682 f.close()
683
683
684 script = []
684 script = []
685 if options.debug:
685 if options.debug:
686 script.append('set -x\n')
686 script.append('set -x\n')
687 if os.getenv('MSYSTEM'):
687 if os.getenv('MSYSTEM'):
688 script.append('alias pwd="pwd -W"\n')
688 script.append('alias pwd="pwd -W"\n')
689 n = 0
689 n = 0
690 for n, l in enumerate(t):
690 for n, l in enumerate(t):
691 if not l.endswith('\n'):
691 if not l.endswith('\n'):
692 l += '\n'
692 l += '\n'
693 if l.startswith('#if'):
693 if l.startswith('#if'):
694 if skipping is not None:
694 if skipping is not None:
695 after.setdefault(pos, []).append(' !!! nested #if\n')
695 after.setdefault(pos, []).append(' !!! nested #if\n')
696 skipping = not hghave(l.split()[1:])
696 skipping = not hghave(l.split()[1:])
697 after.setdefault(pos, []).append(l)
697 after.setdefault(pos, []).append(l)
698 elif l.startswith('#else'):
698 elif l.startswith('#else'):
699 if skipping is None:
699 if skipping is None:
700 after.setdefault(pos, []).append(' !!! missing #if\n')
700 after.setdefault(pos, []).append(' !!! missing #if\n')
701 skipping = not skipping
701 skipping = not skipping
702 after.setdefault(pos, []).append(l)
702 after.setdefault(pos, []).append(l)
703 elif l.startswith('#endif'):
703 elif l.startswith('#endif'):
704 if skipping is None:
704 if skipping is None:
705 after.setdefault(pos, []).append(' !!! missing #if\n')
705 after.setdefault(pos, []).append(' !!! missing #if\n')
706 skipping = None
706 skipping = None
707 after.setdefault(pos, []).append(l)
707 after.setdefault(pos, []).append(l)
708 elif skipping:
708 elif skipping:
709 after.setdefault(pos, []).append(l)
709 after.setdefault(pos, []).append(l)
710 elif l.startswith(' >>> '): # python inlines
710 elif l.startswith(' >>> '): # python inlines
711 after.setdefault(pos, []).append(l)
711 after.setdefault(pos, []).append(l)
712 prepos = pos
712 prepos = pos
713 pos = n
713 pos = n
714 if not inpython:
714 if not inpython:
715 # we've just entered a Python block, add the header
715 # we've just entered a Python block, add the header
716 inpython = True
716 inpython = True
717 addsalt(prepos, False) # make sure we report the exit code
717 addsalt(prepos, False) # make sure we report the exit code
718 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
718 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
719 addsalt(n, True)
719 addsalt(n, True)
720 script.append(l[2:])
720 script.append(l[2:])
721 elif l.startswith(' ... '): # python inlines
721 elif l.startswith(' ... '): # python inlines
722 after.setdefault(prepos, []).append(l)
722 after.setdefault(prepos, []).append(l)
723 script.append(l[2:])
723 script.append(l[2:])
724 elif l.startswith(' $ '): # commands
724 elif l.startswith(' $ '): # commands
725 if inpython:
725 if inpython:
726 script.append("EOF\n")
726 script.append("EOF\n")
727 inpython = False
727 inpython = False
728 after.setdefault(pos, []).append(l)
728 after.setdefault(pos, []).append(l)
729 prepos = pos
729 prepos = pos
730 pos = n
730 pos = n
731 addsalt(n, False)
731 addsalt(n, False)
732 cmd = l[4:].split()
732 cmd = l[4:].split()
733 if len(cmd) == 2 and cmd[0] == 'cd':
733 if len(cmd) == 2 and cmd[0] == 'cd':
734 l = ' $ cd %s || exit 1\n' % cmd[1]
734 l = ' $ cd %s || exit 1\n' % cmd[1]
735 script.append(l[4:])
735 script.append(l[4:])
736 elif l.startswith(' > '): # continuations
736 elif l.startswith(' > '): # continuations
737 after.setdefault(prepos, []).append(l)
737 after.setdefault(prepos, []).append(l)
738 script.append(l[4:])
738 script.append(l[4:])
739 elif l.startswith(' '): # results
739 elif l.startswith(' '): # results
740 # queue up a list of expected results
740 # queue up a list of expected results
741 expected.setdefault(pos, []).append(l[2:])
741 expected.setdefault(pos, []).append(l[2:])
742 else:
742 else:
743 if inpython:
743 if inpython:
744 script.append("EOF\n")
744 script.append("EOF\n")
745 inpython = False
745 inpython = False
746 # non-command/result - queue up for merged output
746 # non-command/result - queue up for merged output
747 after.setdefault(pos, []).append(l)
747 after.setdefault(pos, []).append(l)
748
748
749 if inpython:
749 if inpython:
750 script.append("EOF\n")
750 script.append("EOF\n")
751 if skipping is not None:
751 if skipping is not None:
752 after.setdefault(pos, []).append(' !!! missing #endif\n')
752 after.setdefault(pos, []).append(' !!! missing #endif\n')
753 addsalt(n + 1, False)
753 addsalt(n + 1, False)
754
754
755 # Write out the script and execute it
755 # Write out the script and execute it
756 fd, name = tempfile.mkstemp(suffix='hg-tst')
756 fd, name = tempfile.mkstemp(suffix='hg-tst')
757 try:
757 try:
758 for l in script:
758 for l in script:
759 os.write(fd, l)
759 os.write(fd, l)
760 os.close(fd)
760 os.close(fd)
761
761
762 cmd = '%s "%s"' % (options.shell, name)
762 cmd = '%s "%s"' % (options.shell, name)
763 vlog("# Running", cmd)
763 vlog("# Running", cmd)
764 exitcode, output = run(cmd, wd, options, replacements, env)
764 exitcode, output = run(cmd, wd, options, replacements, env)
765 # do not merge output if skipped, return hghave message instead
765 # do not merge output if skipped, return hghave message instead
766 # similarly, with --debug, output is None
766 # similarly, with --debug, output is None
767 if exitcode == SKIPPED_STATUS or output is None:
767 if exitcode == SKIPPED_STATUS or output is None:
768 return exitcode, output
768 return exitcode, output
769 finally:
769 finally:
770 os.remove(name)
770 os.remove(name)
771
771
772 # Merge the script output back into a unified test
772 # Merge the script output back into a unified test
773
773
774 pos = -1
774 pos = -1
775 postout = []
775 postout = []
776 ret = 0
776 ret = 0
777 for l in output:
777 for l in output:
778 lout, lcmd = l, None
778 lout, lcmd = l, None
779 if salt in l:
779 if salt in l:
780 lout, lcmd = l.split(salt, 1)
780 lout, lcmd = l.split(salt, 1)
781
781
782 if lout:
782 if lout:
783 if not lout.endswith('\n'):
783 if not lout.endswith('\n'):
784 lout += ' (no-eol)\n'
784 lout += ' (no-eol)\n'
785
785
786 # find the expected output at the current position
786 # find the expected output at the current position
787 el = None
787 el = None
788 if pos in expected and expected[pos]:
788 if pos in expected and expected[pos]:
789 el = expected[pos].pop(0)
789 el = expected[pos].pop(0)
790
790
791 if linematch(el, lout):
791 if linematch(el, lout):
792 postout.append(" " + el)
792 postout.append(" " + el)
793 else:
793 else:
794 if needescape(lout):
794 if needescape(lout):
795 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
795 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
796 postout.append(" " + lout) # let diff deal with it
796 postout.append(" " + lout) # let diff deal with it
797
797
798 if lcmd:
798 if lcmd:
799 # add on last return code
799 # add on last return code
800 ret = int(lcmd.split()[1])
800 ret = int(lcmd.split()[1])
801 if ret != 0:
801 if ret != 0:
802 postout.append(" [%s]\n" % ret)
802 postout.append(" [%s]\n" % ret)
803 if pos in after:
803 if pos in after:
804 # merge in non-active test bits
804 # merge in non-active test bits
805 postout += after.pop(pos)
805 postout += after.pop(pos)
806 pos = int(lcmd.split()[0])
806 pos = int(lcmd.split()[0])
807
807
808 if pos in after:
808 if pos in after:
809 postout += after.pop(pos)
809 postout += after.pop(pos)
810
810
811 return exitcode, postout
811 return exitcode, postout
812
812
813 wifexited = getattr(os, "WIFEXITED", lambda x: False)
813 wifexited = getattr(os, "WIFEXITED", lambda x: False)
814 def run(cmd, wd, options, replacements, env):
814 def run(cmd, wd, options, replacements, env):
815 """Run command in a sub-process, capturing the output (stdout and stderr).
815 """Run command in a sub-process, capturing the output (stdout and stderr).
816 Return a tuple (exitcode, output). output is None in debug mode."""
816 Return a tuple (exitcode, output). output is None in debug mode."""
817 # TODO: Use subprocess.Popen if we're running on Python 2.4
817 # TODO: Use subprocess.Popen if we're running on Python 2.4
818 if options.debug:
818 if options.debug:
819 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
819 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
820 ret = proc.wait()
820 ret = proc.wait()
821 return (ret, None)
821 return (ret, None)
822
822
823 proc = Popen4(cmd, wd, options.timeout, env)
823 proc = Popen4(cmd, wd, options.timeout, env)
824 def cleanup():
824 def cleanup():
825 terminate(proc)
825 terminate(proc)
826 ret = proc.wait()
826 ret = proc.wait()
827 if ret == 0:
827 if ret == 0:
828 ret = signal.SIGTERM << 8
828 ret = signal.SIGTERM << 8
829 killdaemons(env['DAEMON_PIDS'])
829 killdaemons(env['DAEMON_PIDS'])
830 return ret
830 return ret
831
831
832 output = ''
832 output = ''
833 proc.tochild.close()
833 proc.tochild.close()
834
834
835 try:
835 try:
836 output = proc.fromchild.read()
836 output = proc.fromchild.read()
837 except KeyboardInterrupt:
837 except KeyboardInterrupt:
838 vlog('# Handling keyboard interrupt')
838 vlog('# Handling keyboard interrupt')
839 cleanup()
839 cleanup()
840 raise
840 raise
841
841
842 ret = proc.wait()
842 ret = proc.wait()
843 if wifexited(ret):
843 if wifexited(ret):
844 ret = os.WEXITSTATUS(ret)
844 ret = os.WEXITSTATUS(ret)
845
845
846 if proc.timeout:
846 if proc.timeout:
847 ret = 'timeout'
847 ret = 'timeout'
848
848
849 if ret:
849 if ret:
850 killdaemons(env['DAEMON_PIDS'])
850 killdaemons(env['DAEMON_PIDS'])
851
851
852 if abort:
852 if abort:
853 raise KeyboardInterrupt()
853 raise KeyboardInterrupt()
854
854
855 for s, r in replacements:
855 for s, r in replacements:
856 output = re.sub(s, r, output)
856 output = re.sub(s, r, output)
857 return ret, output.splitlines(True)
857 return ret, output.splitlines(True)
858
858
859 def runone(options, test, count):
859 def runone(options, test, count):
860 '''returns a result element: (code, test, msg)'''
860 '''returns a result element: (code, test, msg)'''
861
861
862 def skip(msg):
862 def skip(msg):
863 if options.verbose:
863 if options.verbose:
864 log("\nSkipping %s: %s" % (testpath, msg))
864 log("\nSkipping %s: %s" % (testpath, msg))
865 return 's', test, msg
865 return 's', test, msg
866
866
867 def fail(msg, ret):
867 def fail(msg, ret):
868 if not options.nodiff:
868 if not options.nodiff:
869 log("\nERROR: %s %s" % (testpath, msg))
869 log("\nERROR: %s %s" % (testpath, msg))
870 if (not ret and options.interactive
870 if (not ret and options.interactive
871 and os.path.exists(testpath + ".err")):
871 and os.path.exists(testpath + ".err")):
872 iolock.acquire()
872 iolock.acquire()
873 print "Accept this change? [n] ",
873 print "Accept this change? [n] ",
874 answer = sys.stdin.readline().strip()
874 answer = sys.stdin.readline().strip()
875 iolock.release()
875 iolock.release()
876 if answer.lower() in "y yes".split():
876 if answer.lower() in "y yes".split():
877 if test.endswith(".t"):
877 if test.endswith(".t"):
878 rename(testpath + ".err", testpath)
878 rename(testpath + ".err", testpath)
879 else:
879 else:
880 rename(testpath + ".err", testpath + ".out")
880 rename(testpath + ".err", testpath + ".out")
881 return '.', test, ''
881 return '.', test, ''
882 return '!', test, msg
882 return '!', test, msg
883
883
884 def success():
884 def success():
885 return '.', test, ''
885 return '.', test, ''
886
886
887 def ignore(msg):
887 def ignore(msg):
888 return 'i', test, msg
888 return 'i', test, msg
889
889
890 def describe(ret):
890 def describe(ret):
891 if ret < 0:
891 if ret < 0:
892 return 'killed by signal %d' % -ret
892 return 'killed by signal %d' % -ret
893 return 'returned error code %d' % ret
893 return 'returned error code %d' % ret
894
894
895 testpath = os.path.join(TESTDIR, test)
895 testpath = os.path.join(TESTDIR, test)
896 err = os.path.join(TESTDIR, test + ".err")
896 err = os.path.join(TESTDIR, test + ".err")
897 lctest = test.lower()
897 lctest = test.lower()
898
898
899 if not os.path.exists(testpath):
899 if not os.path.exists(testpath):
900 return skip("doesn't exist")
900 return skip("doesn't exist")
901
901
902 if not (options.whitelisted and test in options.whitelisted):
902 if not (options.whitelisted and test in options.whitelisted):
903 if options.blacklist and test in options.blacklist:
903 if options.blacklist and test in options.blacklist:
904 return skip("blacklisted")
904 return skip("blacklisted")
905
905
906 if options.retest and not os.path.exists(test + ".err"):
906 if options.retest and not os.path.exists(test + ".err"):
907 ignore("not retesting")
907 ignore("not retesting")
908 return None
908 return None
909
909
910 if options.keywords:
910 if options.keywords:
911 fp = open(test)
911 fp = open(test)
912 t = fp.read().lower() + test.lower()
912 t = fp.read().lower() + test.lower()
913 fp.close()
913 fp.close()
914 for k in options.keywords.lower().split():
914 for k in options.keywords.lower().split():
915 if k in t:
915 if k in t:
916 break
916 break
917 else:
917 else:
918 ignore("doesn't match keyword")
918 ignore("doesn't match keyword")
919 return None
919 return None
920
920
921 for ext, func, out in testtypes:
921 for ext, func, out in testtypes:
922 if lctest.startswith("test-") and lctest.endswith(ext):
922 if lctest.startswith("test-") and lctest.endswith(ext):
923 runner = func
923 runner = func
924 ref = os.path.join(TESTDIR, test + out)
924 ref = os.path.join(TESTDIR, test + out)
925 break
925 break
926 else:
926 else:
927 return skip("unknown test type")
927 return skip("unknown test type")
928
928
929 vlog("# Test", test)
929 vlog("# Test", test)
930
930
931 if os.path.exists(err):
931 if os.path.exists(err):
932 os.remove(err) # Remove any previous output files
932 os.remove(err) # Remove any previous output files
933
933
934 # Make a tmp subdirectory to work in
934 # Make a tmp subdirectory to work in
935 threadtmp = os.path.join(HGTMP, "child%d" % count)
935 threadtmp = os.path.join(HGTMP, "child%d" % count)
936 testtmp = os.path.join(threadtmp, os.path.basename(test))
936 testtmp = os.path.join(threadtmp, os.path.basename(test))
937 os.mkdir(threadtmp)
937 os.mkdir(threadtmp)
938 os.mkdir(testtmp)
938 os.mkdir(testtmp)
939
939
940 port = options.port + count * 3
940 port = options.port + count * 3
941 replacements = [
941 replacements = [
942 (r':%s\b' % port, ':$HGPORT'),
942 (r':%s\b' % port, ':$HGPORT'),
943 (r':%s\b' % (port + 1), ':$HGPORT1'),
943 (r':%s\b' % (port + 1), ':$HGPORT1'),
944 (r':%s\b' % (port + 2), ':$HGPORT2'),
944 (r':%s\b' % (port + 2), ':$HGPORT2'),
945 ]
945 ]
946 if os.name == 'nt':
946 if os.name == 'nt':
947 replacements.append(
947 replacements.append(
948 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
948 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
949 c in '/\\' and r'[/\\]' or
949 c in '/\\' and r'[/\\]' or
950 c.isdigit() and c or
950 c.isdigit() and c or
951 '\\' + c
951 '\\' + c
952 for c in testtmp), '$TESTTMP'))
952 for c in testtmp), '$TESTTMP'))
953 else:
953 else:
954 replacements.append((re.escape(testtmp), '$TESTTMP'))
954 replacements.append((re.escape(testtmp), '$TESTTMP'))
955
955
956 env = createenv(options, testtmp, threadtmp, port)
956 env = createenv(options, testtmp, threadtmp, port)
957 createhgrc(env['HGRCPATH'], options)
957 createhgrc(env['HGRCPATH'], options)
958
958
959 starttime = time.time()
959 starttime = time.time()
960 try:
960 try:
961 ret, out = runner(testpath, testtmp, options, replacements, env)
961 ret, out = runner(testpath, testtmp, options, replacements, env)
962 except KeyboardInterrupt:
962 except KeyboardInterrupt:
963 endtime = time.time()
963 endtime = time.time()
964 msg = '(after %d seconds)' % (endtime - starttime)
964 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
965 log('INTERRUPTED:', test, msg)
966 raise
965 raise
967 endtime = time.time()
966 endtime = time.time()
968 times.append((test, endtime - starttime))
967 times.append((test, endtime - starttime))
969 vlog("# Ret was:", ret)
968 vlog("# Ret was:", ret)
970
969
971 killdaemons(env['DAEMON_PIDS'])
970 killdaemons(env['DAEMON_PIDS'])
972
971
973 skipped = (ret == SKIPPED_STATUS)
972 skipped = (ret == SKIPPED_STATUS)
974
973
975 # If we're not in --debug mode and reference output file exists,
974 # If we're not in --debug mode and reference output file exists,
976 # check test output against it.
975 # check test output against it.
977 if options.debug:
976 if options.debug:
978 refout = None # to match "out is None"
977 refout = None # to match "out is None"
979 elif os.path.exists(ref):
978 elif os.path.exists(ref):
980 f = open(ref, "r")
979 f = open(ref, "r")
981 refout = f.read().splitlines(True)
980 refout = f.read().splitlines(True)
982 f.close()
981 f.close()
983 else:
982 else:
984 refout = []
983 refout = []
985
984
986 if (ret != 0 or out != refout) and not skipped and not options.debug:
985 if (ret != 0 or out != refout) and not skipped and not options.debug:
987 # Save errors to a file for diagnosis
986 # Save errors to a file for diagnosis
988 f = open(err, "wb")
987 f = open(err, "wb")
989 for line in out:
988 for line in out:
990 f.write(line)
989 f.write(line)
991 f.close()
990 f.close()
992
991
993 if skipped:
992 if skipped:
994 if out is None: # debug mode: nothing to parse
993 if out is None: # debug mode: nothing to parse
995 missing = ['unknown']
994 missing = ['unknown']
996 failed = None
995 failed = None
997 else:
996 else:
998 missing, failed = parsehghaveoutput(out)
997 missing, failed = parsehghaveoutput(out)
999 if not missing:
998 if not missing:
1000 missing = ['irrelevant']
999 missing = ['irrelevant']
1001 if failed:
1000 if failed:
1002 result = fail("hghave failed checking for %s" % failed[-1], ret)
1001 result = fail("hghave failed checking for %s" % failed[-1], ret)
1003 skipped = False
1002 skipped = False
1004 else:
1003 else:
1005 result = skip(missing[-1])
1004 result = skip(missing[-1])
1006 elif ret == 'timeout':
1005 elif ret == 'timeout':
1007 result = fail("timed out", ret)
1006 result = fail("timed out", ret)
1008 elif out != refout:
1007 elif out != refout:
1009 if not options.nodiff:
1008 if not options.nodiff:
1010 iolock.acquire()
1009 iolock.acquire()
1011 if options.view:
1010 if options.view:
1012 os.system("%s %s %s" % (options.view, ref, err))
1011 os.system("%s %s %s" % (options.view, ref, err))
1013 else:
1012 else:
1014 showdiff(refout, out, ref, err)
1013 showdiff(refout, out, ref, err)
1015 iolock.release()
1014 iolock.release()
1016 if ret:
1015 if ret:
1017 result = fail("output changed and " + describe(ret), ret)
1016 result = fail("output changed and " + describe(ret), ret)
1018 else:
1017 else:
1019 result = fail("output changed", ret)
1018 result = fail("output changed", ret)
1020 elif ret:
1019 elif ret:
1021 result = fail(describe(ret), ret)
1020 result = fail(describe(ret), ret)
1022 else:
1021 else:
1023 result = success()
1022 result = success()
1024
1023
1025 if not options.verbose:
1024 if not options.verbose:
1026 iolock.acquire()
1025 iolock.acquire()
1027 sys.stdout.write(result[0])
1026 sys.stdout.write(result[0])
1028 sys.stdout.flush()
1027 sys.stdout.flush()
1029 iolock.release()
1028 iolock.release()
1030
1029
1031 if not options.keep_tmpdir:
1030 if not options.keep_tmpdir:
1032 shutil.rmtree(threadtmp, True)
1031 shutil.rmtree(threadtmp, True)
1033 return result
1032 return result
1034
1033
1035 _hgpath = None
1034 _hgpath = None
1036
1035
1037 def _gethgpath():
1036 def _gethgpath():
1038 """Return the path to the mercurial package that is actually found by
1037 """Return the path to the mercurial package that is actually found by
1039 the current Python interpreter."""
1038 the current Python interpreter."""
1040 global _hgpath
1039 global _hgpath
1041 if _hgpath is not None:
1040 if _hgpath is not None:
1042 return _hgpath
1041 return _hgpath
1043
1042
1044 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1043 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1045 pipe = os.popen(cmd % PYTHON)
1044 pipe = os.popen(cmd % PYTHON)
1046 try:
1045 try:
1047 _hgpath = pipe.read().strip()
1046 _hgpath = pipe.read().strip()
1048 finally:
1047 finally:
1049 pipe.close()
1048 pipe.close()
1050 return _hgpath
1049 return _hgpath
1051
1050
1052 def _checkhglib(verb):
1051 def _checkhglib(verb):
1053 """Ensure that the 'mercurial' package imported by python is
1052 """Ensure that the 'mercurial' package imported by python is
1054 the one we expect it to be. If not, print a warning to stderr."""
1053 the one we expect it to be. If not, print a warning to stderr."""
1055 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1054 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1056 actualhg = _gethgpath()
1055 actualhg = _gethgpath()
1057 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1056 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1058 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1057 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1059 ' (expected %s)\n'
1058 ' (expected %s)\n'
1060 % (verb, actualhg, expecthg))
1059 % (verb, actualhg, expecthg))
1061
1060
1062 results = {'.':[], '!':[], 's':[], 'i':[]}
1061 results = {'.':[], '!':[], 's':[], 'i':[]}
1063 times = []
1062 times = []
1064 iolock = threading.Lock()
1063 iolock = threading.Lock()
1065 abort = False
1064 abort = False
1066
1065
1067 def scheduletests(options, tests):
1066 def scheduletests(options, tests):
1068 jobs = options.jobs
1067 jobs = options.jobs
1069 done = queue.Queue()
1068 done = queue.Queue()
1070 running = 0
1069 running = 0
1071 count = 0
1070 count = 0
1072 global abort
1071 global abort
1073
1072
1074 def job(test, count):
1073 def job(test, count):
1075 try:
1074 try:
1076 done.put(runone(options, test, count))
1075 done.put(runone(options, test, count))
1077 except KeyboardInterrupt:
1076 except KeyboardInterrupt:
1078 pass
1077 pass
1079
1078
1080 try:
1079 try:
1081 while tests or running:
1080 while tests or running:
1082 if not done.empty() or running == jobs or not tests:
1081 if not done.empty() or running == jobs or not tests:
1083 try:
1082 try:
1084 code, test, msg = done.get(True, 1)
1083 code, test, msg = done.get(True, 1)
1085 results[code].append((test, msg))
1084 results[code].append((test, msg))
1086 if options.first and code not in '.si':
1085 if options.first and code not in '.si':
1087 break
1086 break
1088 except queue.Empty:
1087 except queue.Empty:
1089 continue
1088 continue
1090 running -= 1
1089 running -= 1
1091 if tests and not running == jobs:
1090 if tests and not running == jobs:
1092 test = tests.pop(0)
1091 test = tests.pop(0)
1093 if options.loop:
1092 if options.loop:
1094 tests.append(test)
1093 tests.append(test)
1095 t = threading.Thread(None, job, args=(test, count))
1094 t = threading.Thread(None, job, args=(test, count))
1096 t.start()
1095 t.start()
1097 running += 1
1096 running += 1
1098 count += 1
1097 count += 1
1099 except KeyboardInterrupt:
1098 except KeyboardInterrupt:
1100 abort = True
1099 abort = True
1101
1100
1102 def runtests(options, tests):
1101 def runtests(options, tests):
1103 try:
1102 try:
1104 if INST:
1103 if INST:
1105 installhg(options)
1104 installhg(options)
1106 _checkhglib("Testing")
1105 _checkhglib("Testing")
1107 else:
1106 else:
1108 usecorrectpython()
1107 usecorrectpython()
1109
1108
1110 if options.restart:
1109 if options.restart:
1111 orig = list(tests)
1110 orig = list(tests)
1112 while tests:
1111 while tests:
1113 if os.path.exists(tests[0] + ".err"):
1112 if os.path.exists(tests[0] + ".err"):
1114 break
1113 break
1115 tests.pop(0)
1114 tests.pop(0)
1116 if not tests:
1115 if not tests:
1117 print "running all tests"
1116 print "running all tests"
1118 tests = orig
1117 tests = orig
1119
1118
1120 scheduletests(options, tests)
1119 scheduletests(options, tests)
1121
1120
1122 failed = len(results['!'])
1121 failed = len(results['!'])
1123 tested = len(results['.']) + failed
1122 tested = len(results['.']) + failed
1124 skipped = len(results['s'])
1123 skipped = len(results['s'])
1125 ignored = len(results['i'])
1124 ignored = len(results['i'])
1126
1125
1127 print
1126 print
1128 if not options.noskips:
1127 if not options.noskips:
1129 for s in results['s']:
1128 for s in results['s']:
1130 print "Skipped %s: %s" % s
1129 print "Skipped %s: %s" % s
1131 for s in results['!']:
1130 for s in results['!']:
1132 print "Failed %s: %s" % s
1131 print "Failed %s: %s" % s
1133 _checkhglib("Tested")
1132 _checkhglib("Tested")
1134 print "# Ran %d tests, %d skipped, %d failed." % (
1133 print "# Ran %d tests, %d skipped, %d failed." % (
1135 tested, skipped + ignored, failed)
1134 tested, skipped + ignored, failed)
1136 if options.time:
1135 if options.time:
1137 outputtimes(options)
1136 outputtimes(options)
1138
1137
1139 if options.anycoverage:
1138 if options.anycoverage:
1140 outputcoverage(options)
1139 outputcoverage(options)
1141 except KeyboardInterrupt:
1140 except KeyboardInterrupt:
1142 failed = True
1141 failed = True
1143 print "\ninterrupted!"
1142 print "\ninterrupted!"
1144
1143
1145 if failed:
1144 if failed:
1146 sys.exit(1)
1145 sys.exit(1)
1147
1146
1148 testtypes = [('.py', pytest, '.out'),
1147 testtypes = [('.py', pytest, '.out'),
1149 ('.t', tsttest, '')]
1148 ('.t', tsttest, '')]
1150
1149
1151 def main():
1150 def main():
1152 (options, args) = parseargs()
1151 (options, args) = parseargs()
1153 os.umask(022)
1152 os.umask(022)
1154
1153
1155 checktools()
1154 checktools()
1156
1155
1157 if len(args) == 0:
1156 if len(args) == 0:
1158 args = [t for t in os.listdir(".")
1157 args = [t for t in os.listdir(".")
1159 if t.startswith("test-")
1158 if t.startswith("test-")
1160 and (t.endswith(".py") or t.endswith(".t"))]
1159 and (t.endswith(".py") or t.endswith(".t"))]
1161
1160
1162 tests = args
1161 tests = args
1163
1162
1164 if options.random:
1163 if options.random:
1165 random.shuffle(tests)
1164 random.shuffle(tests)
1166 else:
1165 else:
1167 # keywords for slow tests
1166 # keywords for slow tests
1168 slow = 'svn gendoc check-code-hg'.split()
1167 slow = 'svn gendoc check-code-hg'.split()
1169 def sortkey(f):
1168 def sortkey(f):
1170 # run largest tests first, as they tend to take the longest
1169 # run largest tests first, as they tend to take the longest
1171 val = -os.stat(f).st_size
1170 val = -os.stat(f).st_size
1172 for kw in slow:
1171 for kw in slow:
1173 if kw in f:
1172 if kw in f:
1174 val *= 10
1173 val *= 10
1175 return val
1174 return val
1176 tests.sort(key=sortkey)
1175 tests.sort(key=sortkey)
1177
1176
1178 if 'PYTHONHASHSEED' not in os.environ:
1177 if 'PYTHONHASHSEED' not in os.environ:
1179 # use a random python hash seed all the time
1178 # use a random python hash seed all the time
1180 # we do the randomness ourself to know what seed is used
1179 # we do the randomness ourself to know what seed is used
1181 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1180 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1182 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1181 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1183
1182
1184 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1183 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1185 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1184 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1186 if options.tmpdir:
1185 if options.tmpdir:
1187 options.keep_tmpdir = True
1186 options.keep_tmpdir = True
1188 tmpdir = options.tmpdir
1187 tmpdir = options.tmpdir
1189 if os.path.exists(tmpdir):
1188 if os.path.exists(tmpdir):
1190 # Meaning of tmpdir has changed since 1.3: we used to create
1189 # Meaning of tmpdir has changed since 1.3: we used to create
1191 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1190 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1192 # tmpdir already exists.
1191 # tmpdir already exists.
1193 sys.exit("error: temp dir %r already exists" % tmpdir)
1192 sys.exit("error: temp dir %r already exists" % tmpdir)
1194
1193
1195 # Automatically removing tmpdir sounds convenient, but could
1194 # Automatically removing tmpdir sounds convenient, but could
1196 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1195 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1197 # or "--tmpdir=$HOME".
1196 # or "--tmpdir=$HOME".
1198 #vlog("# Removing temp dir", tmpdir)
1197 #vlog("# Removing temp dir", tmpdir)
1199 #shutil.rmtree(tmpdir)
1198 #shutil.rmtree(tmpdir)
1200 os.makedirs(tmpdir)
1199 os.makedirs(tmpdir)
1201 else:
1200 else:
1202 d = None
1201 d = None
1203 if os.name == 'nt':
1202 if os.name == 'nt':
1204 # without this, we get the default temp dir location, but
1203 # without this, we get the default temp dir location, but
1205 # in all lowercase, which causes troubles with paths (issue3490)
1204 # in all lowercase, which causes troubles with paths (issue3490)
1206 d = os.getenv('TMP')
1205 d = os.getenv('TMP')
1207 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1206 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1208 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1207 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1209
1208
1210 if options.with_hg:
1209 if options.with_hg:
1211 INST = None
1210 INST = None
1212 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1211 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1213
1212
1214 # This looks redundant with how Python initializes sys.path from
1213 # This looks redundant with how Python initializes sys.path from
1215 # the location of the script being executed. Needed because the
1214 # the location of the script being executed. Needed because the
1216 # "hg" specified by --with-hg is not the only Python script
1215 # "hg" specified by --with-hg is not the only Python script
1217 # executed in the test suite that needs to import 'mercurial'
1216 # executed in the test suite that needs to import 'mercurial'
1218 # ... which means it's not really redundant at all.
1217 # ... which means it's not really redundant at all.
1219 PYTHONDIR = BINDIR
1218 PYTHONDIR = BINDIR
1220 else:
1219 else:
1221 INST = os.path.join(HGTMP, "install")
1220 INST = os.path.join(HGTMP, "install")
1222 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1221 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1223 PYTHONDIR = os.path.join(INST, "lib", "python")
1222 PYTHONDIR = os.path.join(INST, "lib", "python")
1224
1223
1225 os.environ["BINDIR"] = BINDIR
1224 os.environ["BINDIR"] = BINDIR
1226 os.environ["PYTHON"] = PYTHON
1225 os.environ["PYTHON"] = PYTHON
1227
1226
1228 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1227 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1229 os.environ["PATH"] = os.pathsep.join(path)
1228 os.environ["PATH"] = os.pathsep.join(path)
1230
1229
1231 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1230 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1232 # can run .../tests/run-tests.py test-foo where test-foo
1231 # can run .../tests/run-tests.py test-foo where test-foo
1233 # adds an extension to HGRC
1232 # adds an extension to HGRC
1234 pypath = [PYTHONDIR, TESTDIR]
1233 pypath = [PYTHONDIR, TESTDIR]
1235 # We have to augment PYTHONPATH, rather than simply replacing
1234 # We have to augment PYTHONPATH, rather than simply replacing
1236 # it, in case external libraries are only available via current
1235 # it, in case external libraries are only available via current
1237 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1236 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1238 # are in /opt/subversion.)
1237 # are in /opt/subversion.)
1239 oldpypath = os.environ.get(IMPL_PATH)
1238 oldpypath = os.environ.get(IMPL_PATH)
1240 if oldpypath:
1239 if oldpypath:
1241 pypath.append(oldpypath)
1240 pypath.append(oldpypath)
1242 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1241 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1243
1242
1244 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1243 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1245
1244
1246 vlog("# Using TESTDIR", TESTDIR)
1245 vlog("# Using TESTDIR", TESTDIR)
1247 vlog("# Using HGTMP", HGTMP)
1246 vlog("# Using HGTMP", HGTMP)
1248 vlog("# Using PATH", os.environ["PATH"])
1247 vlog("# Using PATH", os.environ["PATH"])
1249 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1248 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1250
1249
1251 try:
1250 try:
1252 runtests(options, tests)
1251 runtests(options, tests)
1253 finally:
1252 finally:
1254 time.sleep(.1)
1253 time.sleep(.1)
1255 cleanup(options)
1254 cleanup(options)
1256
1255
1257 if __name__ == '__main__':
1256 if __name__ == '__main__':
1258 main()
1257 main()
General Comments 0
You need to be logged in to leave comments. Login now