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