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