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