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