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