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