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