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