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