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