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