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