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