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