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