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