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