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