##// END OF EJS Templates
run-tests: pull out line matching function
Matt Mackall -
r15415:8c90b3df default
parent child Browse files
Show More
@@ -1,1249 +1,1253 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
90 PYTHON = sys.executable
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([BINDIR, TESTDIR])
496 omit = ','.join([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"' % test
511 cmd = '"%s"' % 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 ?. Escaping is
533 # The only supported special characters are * and ?. Escaping is
534 # supported.
534 # 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 else:
547 else:
548 res += re.escape(c)
548 res += re.escape(c)
549 return rematch(res, l)
549 return rematch(res, l)
550
550
551 def linematch(el, l):
552 if el == l: # perfect match (fast)
553 return True
554 if (el and
555 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
556 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l) or
557 el.endswith(" (esc)\n") and el.decode('string-escape') == l)):
558 return True
559 return False
560
551 def tsttest(test, wd, options, replacements):
561 def tsttest(test, wd, options, replacements):
552 t = open(test)
562 t = open(test)
553 out = []
563 out = []
554 script = []
564 script = []
555
565
556 # We generate a shell script which outputs unique markers to line
566 # We generate a shell script which outputs unique markers to line
557 # up script results with our source. These markers include input
567 # up script results with our source. These markers include input
558 # line number and the last return code
568 # line number and the last return code
559 salt = "SALT" + str(time.time())
569 salt = "SALT" + str(time.time())
560 def addsalt(line):
570 def addsalt(line):
561 script.append('echo %s %s $?\n' % (salt, line))
571 script.append('echo %s %s $?\n' % (salt, line))
562
572
563 # After we run the shell script, we re-unify the script output
573 # After we run the shell script, we re-unify the script output
564 # with non-active parts of the source, with synchronization by our
574 # with non-active parts of the source, with synchronization by our
565 # SALT line number markers. The after table contains the
575 # SALT line number markers. The after table contains the
566 # non-active components, ordered by line number
576 # non-active components, ordered by line number
567 after = {}
577 after = {}
568 pos = prepos = -1
578 pos = prepos = -1
569
579
570 # Expected shellscript output
580 # Expected shellscript output
571 expected = {}
581 expected = {}
572
582
573 # We keep track of whether or not we're in a Python block so we
583 # We keep track of whether or not we're in a Python block so we
574 # can generate the surrounding doctest magic
584 # can generate the surrounding doctest magic
575 inpython = False
585 inpython = False
576
586
577 for n, l in enumerate(t):
587 for n, l in enumerate(t):
578 if not l.endswith('\n'):
588 if not l.endswith('\n'):
579 l += '\n'
589 l += '\n'
580 if l.startswith(' >>> '): # python inlines
590 if l.startswith(' >>> '): # python inlines
581 if not inpython:
591 if not inpython:
582 # we've just entered a Python block, add the header
592 # we've just entered a Python block, add the header
583 inpython = True
593 inpython = True
584 addsalt(n)
594 addsalt(n)
585 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
595 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
586 prepos = pos
596 prepos = pos
587 pos = n
597 pos = n
588 after.setdefault(prepos, []).append(l)
598 after.setdefault(prepos, []).append(l)
589 script.append(l[2:])
599 script.append(l[2:])
590 elif l.startswith(' $ '): # commands
600 elif l.startswith(' $ '): # commands
591 if inpython:
601 if inpython:
592 script.append("EOF\n")
602 script.append("EOF\n")
593 inpython = False
603 inpython = False
594 after.setdefault(pos, []).append(l)
604 after.setdefault(pos, []).append(l)
595 prepos = pos
605 prepos = pos
596 pos = n
606 pos = n
597 addsalt(n)
607 addsalt(n)
598 script.append(l[4:])
608 script.append(l[4:])
599 elif l.startswith(' > '): # continuations
609 elif l.startswith(' > '): # continuations
600 after.setdefault(prepos, []).append(l)
610 after.setdefault(prepos, []).append(l)
601 script.append(l[4:])
611 script.append(l[4:])
602 elif l.startswith(' '): # results
612 elif l.startswith(' '): # results
603 if inpython:
613 if inpython:
604 script.append(l[2:])
614 script.append(l[2:])
605 after.setdefault(prepos, []).append(l)
615 after.setdefault(prepos, []).append(l)
606 else:
616 else:
607 # queue up a list of expected results
617 # queue up a list of expected results
608 expected.setdefault(pos, []).append(l[2:])
618 expected.setdefault(pos, []).append(l[2:])
609 else:
619 else:
610 if inpython:
620 if inpython:
611 script.append("EOF\n")
621 script.append("EOF\n")
612 inpython = False
622 inpython = False
613 # non-command/result - queue up for merged output
623 # non-command/result - queue up for merged output
614 after.setdefault(pos, []).append(l)
624 after.setdefault(pos, []).append(l)
615
625
616 t.close()
626 t.close()
617
627
618 if inpython:
628 if inpython:
619 script.append("EOF\n")
629 script.append("EOF\n")
620 addsalt(n + 1)
630 addsalt(n + 1)
621
631
622 fd, name = tempfile.mkstemp(suffix='hg-tst')
632 fd, name = tempfile.mkstemp(suffix='hg-tst')
623 try:
633 try:
624 for l in script:
634 for l in script:
625 os.write(fd, l)
635 os.write(fd, l)
626 os.close(fd)
636 os.close(fd)
627
637
628 cmd = '"%s" "%s"' % (options.shell, name)
638 cmd = '"%s" "%s"' % (options.shell, name)
629 vlog("# Running", cmd)
639 vlog("# Running", cmd)
630 exitcode, output = run(cmd, wd, options, replacements)
640 exitcode, output = run(cmd, wd, options, replacements)
631 # do not merge output if skipped, return hghave message instead
641 # do not merge output if skipped, return hghave message instead
632 # similarly, with --debug, output is None
642 # similarly, with --debug, output is None
633 if exitcode == SKIPPED_STATUS or output is None:
643 if exitcode == SKIPPED_STATUS or output is None:
634 return exitcode, output
644 return exitcode, output
635 finally:
645 finally:
636 os.remove(name)
646 os.remove(name)
637
647
638 # Merge the script output back into a unified test
648 # Merge the script output back into a unified test
639
649
640 pos = -1
650 pos = -1
641 postout = []
651 postout = []
642 ret = 0
652 ret = 0
643 for n, l in enumerate(output):
653 for n, l in enumerate(output):
644 lout, lcmd = l, None
654 lout, lcmd = l, None
645 if salt in l:
655 if salt in l:
646 lout, lcmd = l.split(salt, 1)
656 lout, lcmd = l.split(salt, 1)
647
657
648 if lout:
658 if lout:
649 if lcmd:
659 if lcmd:
650 # output block had no trailing newline, clean up
660 # output block had no trailing newline, clean up
651 lout += ' (no-eol)\n'
661 lout += ' (no-eol)\n'
652
662
653 # find the expected output at the current position
663 # find the expected output at the current position
654 el = None
664 el = None
655 if pos in expected and expected[pos]:
665 if pos in expected and expected[pos]:
656 el = expected[pos].pop(0)
666 el = expected[pos].pop(0)
657
667
658 if el == lout: # perfect match (fast)
668 if linematch(el, lout):
659 postout.append(" " + lout)
669 postout.append(" " + el)
660 elif (el and
661 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
662 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
663 or el.endswith(" (esc)\n") and
664 el.decode('string-escape') == l)):
665 postout.append(" " + el) # fallback regex/glob/esc match
666 else:
670 else:
667 if needescape(lout):
671 if needescape(lout):
668 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
672 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
669 postout.append(" " + lout) # let diff deal with it
673 postout.append(" " + lout) # let diff deal with it
670
674
671 if lcmd:
675 if lcmd:
672 # add on last return code
676 # add on last return code
673 ret = int(lcmd.split()[1])
677 ret = int(lcmd.split()[1])
674 if ret != 0:
678 if ret != 0:
675 postout.append(" [%s]\n" % ret)
679 postout.append(" [%s]\n" % ret)
676 if pos in after:
680 if pos in after:
677 # merge in non-active test bits
681 # merge in non-active test bits
678 postout += after.pop(pos)
682 postout += after.pop(pos)
679 pos = int(lcmd.split()[0])
683 pos = int(lcmd.split()[0])
680
684
681 if pos in after:
685 if pos in after:
682 postout += after.pop(pos)
686 postout += after.pop(pos)
683
687
684 return exitcode, postout
688 return exitcode, postout
685
689
686 wifexited = getattr(os, "WIFEXITED", lambda x: False)
690 wifexited = getattr(os, "WIFEXITED", lambda x: False)
687 def run(cmd, wd, options, replacements):
691 def run(cmd, wd, options, replacements):
688 """Run command in a sub-process, capturing the output (stdout and stderr).
692 """Run command in a sub-process, capturing the output (stdout and stderr).
689 Return a tuple (exitcode, output). output is None in debug mode."""
693 Return a tuple (exitcode, output). output is None in debug mode."""
690 # TODO: Use subprocess.Popen if we're running on Python 2.4
694 # TODO: Use subprocess.Popen if we're running on Python 2.4
691 if options.debug:
695 if options.debug:
692 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
696 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
693 ret = proc.wait()
697 ret = proc.wait()
694 return (ret, None)
698 return (ret, None)
695
699
696 proc = Popen4(cmd, wd, options.timeout)
700 proc = Popen4(cmd, wd, options.timeout)
697 def cleanup():
701 def cleanup():
698 terminate(proc)
702 terminate(proc)
699 ret = proc.wait()
703 ret = proc.wait()
700 if ret == 0:
704 if ret == 0:
701 ret = signal.SIGTERM << 8
705 ret = signal.SIGTERM << 8
702 killdaemons()
706 killdaemons()
703 return ret
707 return ret
704
708
705 output = ''
709 output = ''
706 proc.tochild.close()
710 proc.tochild.close()
707
711
708 try:
712 try:
709 output = proc.fromchild.read()
713 output = proc.fromchild.read()
710 except KeyboardInterrupt:
714 except KeyboardInterrupt:
711 vlog('# Handling keyboard interrupt')
715 vlog('# Handling keyboard interrupt')
712 cleanup()
716 cleanup()
713 raise
717 raise
714
718
715 ret = proc.wait()
719 ret = proc.wait()
716 if wifexited(ret):
720 if wifexited(ret):
717 ret = os.WEXITSTATUS(ret)
721 ret = os.WEXITSTATUS(ret)
718
722
719 if proc.timeout:
723 if proc.timeout:
720 ret = 'timeout'
724 ret = 'timeout'
721
725
722 if ret:
726 if ret:
723 killdaemons()
727 killdaemons()
724
728
725 for s, r in replacements:
729 for s, r in replacements:
726 output = re.sub(s, r, output)
730 output = re.sub(s, r, output)
727 return ret, splitnewlines(output)
731 return ret, splitnewlines(output)
728
732
729 def runone(options, test):
733 def runone(options, test):
730 '''tristate output:
734 '''tristate output:
731 None -> skipped
735 None -> skipped
732 True -> passed
736 True -> passed
733 False -> failed'''
737 False -> failed'''
734
738
735 global results, resultslock, iolock
739 global results, resultslock, iolock
736
740
737 testpath = os.path.join(TESTDIR, test)
741 testpath = os.path.join(TESTDIR, test)
738
742
739 def result(l, e):
743 def result(l, e):
740 resultslock.acquire()
744 resultslock.acquire()
741 results[l].append(e)
745 results[l].append(e)
742 resultslock.release()
746 resultslock.release()
743
747
744 def skip(msg):
748 def skip(msg):
745 if not options.verbose:
749 if not options.verbose:
746 result('s', (test, msg))
750 result('s', (test, msg))
747 else:
751 else:
748 iolock.acquire()
752 iolock.acquire()
749 print "\nSkipping %s: %s" % (testpath, msg)
753 print "\nSkipping %s: %s" % (testpath, msg)
750 iolock.release()
754 iolock.release()
751 return None
755 return None
752
756
753 def fail(msg, ret):
757 def fail(msg, ret):
754 if not options.nodiff:
758 if not options.nodiff:
755 iolock.acquire()
759 iolock.acquire()
756 print "\nERROR: %s %s" % (testpath, msg)
760 print "\nERROR: %s %s" % (testpath, msg)
757 iolock.release()
761 iolock.release()
758 if (not ret and options.interactive
762 if (not ret and options.interactive
759 and os.path.exists(testpath + ".err")):
763 and os.path.exists(testpath + ".err")):
760 iolock.acquire()
764 iolock.acquire()
761 print "Accept this change? [n] ",
765 print "Accept this change? [n] ",
762 answer = sys.stdin.readline().strip()
766 answer = sys.stdin.readline().strip()
763 iolock.release()
767 iolock.release()
764 if answer.lower() in "y yes".split():
768 if answer.lower() in "y yes".split():
765 if test.endswith(".t"):
769 if test.endswith(".t"):
766 rename(testpath + ".err", testpath)
770 rename(testpath + ".err", testpath)
767 else:
771 else:
768 rename(testpath + ".err", testpath + ".out")
772 rename(testpath + ".err", testpath + ".out")
769 result('p', test)
773 result('p', test)
770 return
774 return
771 result('f', (test, msg))
775 result('f', (test, msg))
772
776
773 def success():
777 def success():
774 result('p', test)
778 result('p', test)
775
779
776 def ignore(msg):
780 def ignore(msg):
777 result('i', (test, msg))
781 result('i', (test, msg))
778
782
779 if (os.path.basename(test).startswith("test-") and '~' not in test and
783 if (os.path.basename(test).startswith("test-") and '~' not in test and
780 ('.' not in test or test.endswith('.py') or
784 ('.' not in test or test.endswith('.py') or
781 test.endswith('.bat') or test.endswith('.t'))):
785 test.endswith('.bat') or test.endswith('.t'))):
782 if not os.path.exists(test):
786 if not os.path.exists(test):
783 skip("doesn't exist")
787 skip("doesn't exist")
784 return None
788 return None
785 else:
789 else:
786 vlog('# Test file', test, 'not supported, ignoring')
790 vlog('# Test file', test, 'not supported, ignoring')
787 return None # not a supported test, don't record
791 return None # not a supported test, don't record
788
792
789 if not (options.whitelisted and test in options.whitelisted):
793 if not (options.whitelisted and test in options.whitelisted):
790 if options.blacklist and test in options.blacklist:
794 if options.blacklist and test in options.blacklist:
791 skip("blacklisted")
795 skip("blacklisted")
792 return None
796 return None
793
797
794 if options.retest and not os.path.exists(test + ".err"):
798 if options.retest and not os.path.exists(test + ".err"):
795 ignore("not retesting")
799 ignore("not retesting")
796 return None
800 return None
797
801
798 if options.keywords:
802 if options.keywords:
799 fp = open(test)
803 fp = open(test)
800 t = fp.read().lower() + test.lower()
804 t = fp.read().lower() + test.lower()
801 fp.close()
805 fp.close()
802 for k in options.keywords.lower().split():
806 for k in options.keywords.lower().split():
803 if k in t:
807 if k in t:
804 break
808 break
805 else:
809 else:
806 ignore("doesn't match keyword")
810 ignore("doesn't match keyword")
807 return None
811 return None
808
812
809 vlog("# Test", test)
813 vlog("# Test", test)
810
814
811 # create a fresh hgrc
815 # create a fresh hgrc
812 hgrc = open(HGRCPATH, 'w+')
816 hgrc = open(HGRCPATH, 'w+')
813 hgrc.write('[ui]\n')
817 hgrc.write('[ui]\n')
814 hgrc.write('slash = True\n')
818 hgrc.write('slash = True\n')
815 hgrc.write('[defaults]\n')
819 hgrc.write('[defaults]\n')
816 hgrc.write('backout = -d "0 0"\n')
820 hgrc.write('backout = -d "0 0"\n')
817 hgrc.write('commit = -d "0 0"\n')
821 hgrc.write('commit = -d "0 0"\n')
818 hgrc.write('tag = -d "0 0"\n')
822 hgrc.write('tag = -d "0 0"\n')
819 if options.inotify:
823 if options.inotify:
820 hgrc.write('[extensions]\n')
824 hgrc.write('[extensions]\n')
821 hgrc.write('inotify=\n')
825 hgrc.write('inotify=\n')
822 hgrc.write('[inotify]\n')
826 hgrc.write('[inotify]\n')
823 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
827 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
824 hgrc.write('appendpid=True\n')
828 hgrc.write('appendpid=True\n')
825 if options.extra_config_opt:
829 if options.extra_config_opt:
826 for opt in options.extra_config_opt:
830 for opt in options.extra_config_opt:
827 section, key = opt.split('.', 1)
831 section, key = opt.split('.', 1)
828 assert '=' in key, ('extra config opt %s must '
832 assert '=' in key, ('extra config opt %s must '
829 'have an = for assignment' % opt)
833 'have an = for assignment' % opt)
830 hgrc.write('[%s]\n%s\n' % (section, key))
834 hgrc.write('[%s]\n%s\n' % (section, key))
831 hgrc.close()
835 hgrc.close()
832
836
833 ref = os.path.join(TESTDIR, test+".out")
837 ref = os.path.join(TESTDIR, test+".out")
834 err = os.path.join(TESTDIR, test+".err")
838 err = os.path.join(TESTDIR, test+".err")
835 if os.path.exists(err):
839 if os.path.exists(err):
836 os.remove(err) # Remove any previous output files
840 os.remove(err) # Remove any previous output files
837 try:
841 try:
838 tf = open(testpath)
842 tf = open(testpath)
839 firstline = tf.readline().rstrip()
843 firstline = tf.readline().rstrip()
840 tf.close()
844 tf.close()
841 except:
845 except:
842 firstline = ''
846 firstline = ''
843 lctest = test.lower()
847 lctest = test.lower()
844
848
845 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
849 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
846 runner = pytest
850 runner = pytest
847 elif lctest.endswith('.t'):
851 elif lctest.endswith('.t'):
848 runner = tsttest
852 runner = tsttest
849 ref = testpath
853 ref = testpath
850 else:
854 else:
851 # do not try to run non-executable programs
855 # do not try to run non-executable programs
852 if not os.access(testpath, os.X_OK):
856 if not os.access(testpath, os.X_OK):
853 return skip("not executable")
857 return skip("not executable")
854 runner = shtest
858 runner = shtest
855
859
856 # Make a tmp subdirectory to work in
860 # Make a tmp subdirectory to work in
857 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
861 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
858 os.path.join(HGTMP, os.path.basename(test))
862 os.path.join(HGTMP, os.path.basename(test))
859
863
860 os.mkdir(testtmp)
864 os.mkdir(testtmp)
861 ret, out = runner(testpath, testtmp, options, [
865 ret, out = runner(testpath, testtmp, options, [
862 (re.escape(testtmp), '$TESTTMP'),
866 (re.escape(testtmp), '$TESTTMP'),
863 (r':%s\b' % options.port, ':$HGPORT'),
867 (r':%s\b' % options.port, ':$HGPORT'),
864 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
868 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
865 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
869 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
866 ])
870 ])
867 vlog("# Ret was:", ret)
871 vlog("# Ret was:", ret)
868
872
869 mark = '.'
873 mark = '.'
870
874
871 skipped = (ret == SKIPPED_STATUS)
875 skipped = (ret == SKIPPED_STATUS)
872
876
873 # If we're not in --debug mode and reference output file exists,
877 # If we're not in --debug mode and reference output file exists,
874 # check test output against it.
878 # check test output against it.
875 if options.debug:
879 if options.debug:
876 refout = None # to match "out is None"
880 refout = None # to match "out is None"
877 elif os.path.exists(ref):
881 elif os.path.exists(ref):
878 f = open(ref, "r")
882 f = open(ref, "r")
879 refout = list(splitnewlines(f.read()))
883 refout = list(splitnewlines(f.read()))
880 f.close()
884 f.close()
881 else:
885 else:
882 refout = []
886 refout = []
883
887
884 if (ret != 0 or out != refout) and not skipped and not options.debug:
888 if (ret != 0 or out != refout) and not skipped and not options.debug:
885 # Save errors to a file for diagnosis
889 # Save errors to a file for diagnosis
886 f = open(err, "wb")
890 f = open(err, "wb")
887 for line in out:
891 for line in out:
888 f.write(line)
892 f.write(line)
889 f.close()
893 f.close()
890
894
891 if skipped:
895 if skipped:
892 mark = 's'
896 mark = 's'
893 if out is None: # debug mode: nothing to parse
897 if out is None: # debug mode: nothing to parse
894 missing = ['unknown']
898 missing = ['unknown']
895 failed = None
899 failed = None
896 else:
900 else:
897 missing, failed = parsehghaveoutput(out)
901 missing, failed = parsehghaveoutput(out)
898 if not missing:
902 if not missing:
899 missing = ['irrelevant']
903 missing = ['irrelevant']
900 if failed:
904 if failed:
901 fail("hghave failed checking for %s" % failed[-1], ret)
905 fail("hghave failed checking for %s" % failed[-1], ret)
902 skipped = False
906 skipped = False
903 else:
907 else:
904 skip(missing[-1])
908 skip(missing[-1])
905 elif ret == 'timeout':
909 elif ret == 'timeout':
906 mark = 't'
910 mark = 't'
907 fail("timed out", ret)
911 fail("timed out", ret)
908 elif out != refout:
912 elif out != refout:
909 mark = '!'
913 mark = '!'
910 if not options.nodiff:
914 if not options.nodiff:
911 iolock.acquire()
915 iolock.acquire()
912 if options.view:
916 if options.view:
913 os.system("%s %s %s" % (options.view, ref, err))
917 os.system("%s %s %s" % (options.view, ref, err))
914 else:
918 else:
915 showdiff(refout, out, ref, err)
919 showdiff(refout, out, ref, err)
916 iolock.release()
920 iolock.release()
917 if ret:
921 if ret:
918 fail("output changed and returned error code %d" % ret, ret)
922 fail("output changed and returned error code %d" % ret, ret)
919 else:
923 else:
920 fail("output changed", ret)
924 fail("output changed", ret)
921 ret = 1
925 ret = 1
922 elif ret:
926 elif ret:
923 mark = '!'
927 mark = '!'
924 fail("returned error code %d" % ret, ret)
928 fail("returned error code %d" % ret, ret)
925 else:
929 else:
926 success()
930 success()
927
931
928 if not options.verbose:
932 if not options.verbose:
929 iolock.acquire()
933 iolock.acquire()
930 sys.stdout.write(mark)
934 sys.stdout.write(mark)
931 sys.stdout.flush()
935 sys.stdout.flush()
932 iolock.release()
936 iolock.release()
933
937
934 killdaemons()
938 killdaemons()
935
939
936 if not options.keep_tmpdir:
940 if not options.keep_tmpdir:
937 shutil.rmtree(testtmp, True)
941 shutil.rmtree(testtmp, True)
938 if skipped:
942 if skipped:
939 return None
943 return None
940 return ret == 0
944 return ret == 0
941
945
942 _hgpath = None
946 _hgpath = None
943
947
944 def _gethgpath():
948 def _gethgpath():
945 """Return the path to the mercurial package that is actually found by
949 """Return the path to the mercurial package that is actually found by
946 the current Python interpreter."""
950 the current Python interpreter."""
947 global _hgpath
951 global _hgpath
948 if _hgpath is not None:
952 if _hgpath is not None:
949 return _hgpath
953 return _hgpath
950
954
951 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
955 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
952 pipe = os.popen(cmd % PYTHON)
956 pipe = os.popen(cmd % PYTHON)
953 try:
957 try:
954 _hgpath = pipe.read().strip()
958 _hgpath = pipe.read().strip()
955 finally:
959 finally:
956 pipe.close()
960 pipe.close()
957 return _hgpath
961 return _hgpath
958
962
959 def _checkhglib(verb):
963 def _checkhglib(verb):
960 """Ensure that the 'mercurial' package imported by python is
964 """Ensure that the 'mercurial' package imported by python is
961 the one we expect it to be. If not, print a warning to stderr."""
965 the one we expect it to be. If not, print a warning to stderr."""
962 expecthg = os.path.join(PYTHONDIR, 'mercurial')
966 expecthg = os.path.join(PYTHONDIR, 'mercurial')
963 actualhg = _gethgpath()
967 actualhg = _gethgpath()
964 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
968 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
965 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
969 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
966 ' (expected %s)\n'
970 ' (expected %s)\n'
967 % (verb, actualhg, expecthg))
971 % (verb, actualhg, expecthg))
968
972
969 def runchildren(options, tests):
973 def runchildren(options, tests):
970 if INST:
974 if INST:
971 installhg(options)
975 installhg(options)
972 _checkhglib("Testing")
976 _checkhglib("Testing")
973
977
974 optcopy = dict(options.__dict__)
978 optcopy = dict(options.__dict__)
975 optcopy['jobs'] = 1
979 optcopy['jobs'] = 1
976
980
977 # Because whitelist has to override keyword matches, we have to
981 # Because whitelist has to override keyword matches, we have to
978 # actually load the whitelist in the children as well, so we allow
982 # actually load the whitelist in the children as well, so we allow
979 # the list of whitelist files to pass through and be parsed in the
983 # the list of whitelist files to pass through and be parsed in the
980 # children, but not the dict of whitelisted tests resulting from
984 # children, but not the dict of whitelisted tests resulting from
981 # the parse, used here to override blacklisted tests.
985 # the parse, used here to override blacklisted tests.
982 whitelist = optcopy['whitelisted'] or []
986 whitelist = optcopy['whitelisted'] or []
983 del optcopy['whitelisted']
987 del optcopy['whitelisted']
984
988
985 blacklist = optcopy['blacklist'] or []
989 blacklist = optcopy['blacklist'] or []
986 del optcopy['blacklist']
990 del optcopy['blacklist']
987 blacklisted = []
991 blacklisted = []
988
992
989 if optcopy['with_hg'] is None:
993 if optcopy['with_hg'] is None:
990 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
994 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
991 optcopy.pop('anycoverage', None)
995 optcopy.pop('anycoverage', None)
992
996
993 opts = []
997 opts = []
994 for opt, value in optcopy.iteritems():
998 for opt, value in optcopy.iteritems():
995 name = '--' + opt.replace('_', '-')
999 name = '--' + opt.replace('_', '-')
996 if value is True:
1000 if value is True:
997 opts.append(name)
1001 opts.append(name)
998 elif isinstance(value, list):
1002 elif isinstance(value, list):
999 for v in value:
1003 for v in value:
1000 opts.append(name + '=' + str(v))
1004 opts.append(name + '=' + str(v))
1001 elif value is not None:
1005 elif value is not None:
1002 opts.append(name + '=' + str(value))
1006 opts.append(name + '=' + str(value))
1003
1007
1004 tests.reverse()
1008 tests.reverse()
1005 jobs = [[] for j in xrange(options.jobs)]
1009 jobs = [[] for j in xrange(options.jobs)]
1006 while tests:
1010 while tests:
1007 for job in jobs:
1011 for job in jobs:
1008 if not tests:
1012 if not tests:
1009 break
1013 break
1010 test = tests.pop()
1014 test = tests.pop()
1011 if test not in whitelist and test in blacklist:
1015 if test not in whitelist and test in blacklist:
1012 blacklisted.append(test)
1016 blacklisted.append(test)
1013 else:
1017 else:
1014 job.append(test)
1018 job.append(test)
1015 fps = {}
1019 fps = {}
1016
1020
1017 for j, job in enumerate(jobs):
1021 for j, job in enumerate(jobs):
1018 if not job:
1022 if not job:
1019 continue
1023 continue
1020 rfd, wfd = os.pipe()
1024 rfd, wfd = os.pipe()
1021 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1025 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1022 childtmp = os.path.join(HGTMP, 'child%d' % j)
1026 childtmp = os.path.join(HGTMP, 'child%d' % j)
1023 childopts += ['--tmpdir', childtmp]
1027 childopts += ['--tmpdir', childtmp]
1024 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1028 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1025 vlog(' '.join(cmdline))
1029 vlog(' '.join(cmdline))
1026 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1030 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1027 os.close(wfd)
1031 os.close(wfd)
1028 signal.signal(signal.SIGINT, signal.SIG_IGN)
1032 signal.signal(signal.SIGINT, signal.SIG_IGN)
1029 failures = 0
1033 failures = 0
1030 tested, skipped, failed = 0, 0, 0
1034 tested, skipped, failed = 0, 0, 0
1031 skips = []
1035 skips = []
1032 fails = []
1036 fails = []
1033 while fps:
1037 while fps:
1034 pid, status = os.wait()
1038 pid, status = os.wait()
1035 fp = fps.pop(pid)
1039 fp = fps.pop(pid)
1036 l = fp.read().splitlines()
1040 l = fp.read().splitlines()
1037 try:
1041 try:
1038 test, skip, fail = map(int, l[:3])
1042 test, skip, fail = map(int, l[:3])
1039 except ValueError:
1043 except ValueError:
1040 test, skip, fail = 0, 0, 0
1044 test, skip, fail = 0, 0, 0
1041 split = -fail or len(l)
1045 split = -fail or len(l)
1042 for s in l[3:split]:
1046 for s in l[3:split]:
1043 skips.append(s.split(" ", 1))
1047 skips.append(s.split(" ", 1))
1044 for s in l[split:]:
1048 for s in l[split:]:
1045 fails.append(s.split(" ", 1))
1049 fails.append(s.split(" ", 1))
1046 tested += test
1050 tested += test
1047 skipped += skip
1051 skipped += skip
1048 failed += fail
1052 failed += fail
1049 vlog('pid %d exited, status %d' % (pid, status))
1053 vlog('pid %d exited, status %d' % (pid, status))
1050 failures |= status
1054 failures |= status
1051 print
1055 print
1052 skipped += len(blacklisted)
1056 skipped += len(blacklisted)
1053 if not options.noskips:
1057 if not options.noskips:
1054 for s in skips:
1058 for s in skips:
1055 print "Skipped %s: %s" % (s[0], s[1])
1059 print "Skipped %s: %s" % (s[0], s[1])
1056 for s in blacklisted:
1060 for s in blacklisted:
1057 print "Skipped %s: blacklisted" % s
1061 print "Skipped %s: blacklisted" % s
1058 for s in fails:
1062 for s in fails:
1059 print "Failed %s: %s" % (s[0], s[1])
1063 print "Failed %s: %s" % (s[0], s[1])
1060
1064
1061 _checkhglib("Tested")
1065 _checkhglib("Tested")
1062 print "# Ran %d tests, %d skipped, %d failed." % (
1066 print "# Ran %d tests, %d skipped, %d failed." % (
1063 tested, skipped, failed)
1067 tested, skipped, failed)
1064
1068
1065 if options.anycoverage:
1069 if options.anycoverage:
1066 outputcoverage(options)
1070 outputcoverage(options)
1067 sys.exit(failures != 0)
1071 sys.exit(failures != 0)
1068
1072
1069 results = dict(p=[], f=[], s=[], i=[])
1073 results = dict(p=[], f=[], s=[], i=[])
1070 resultslock = threading.Lock()
1074 resultslock = threading.Lock()
1071 iolock = threading.Lock()
1075 iolock = threading.Lock()
1072
1076
1073 def runqueue(options, tests, results):
1077 def runqueue(options, tests, results):
1074 for test in tests:
1078 for test in tests:
1075 ret = runone(options, test)
1079 ret = runone(options, test)
1076 if options.first and ret is not None and not ret:
1080 if options.first and ret is not None and not ret:
1077 break
1081 break
1078
1082
1079 def runtests(options, tests):
1083 def runtests(options, tests):
1080 global DAEMON_PIDS, HGRCPATH
1084 global DAEMON_PIDS, HGRCPATH
1081 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1085 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1082 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1086 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1083
1087
1084 try:
1088 try:
1085 if INST:
1089 if INST:
1086 installhg(options)
1090 installhg(options)
1087 _checkhglib("Testing")
1091 _checkhglib("Testing")
1088
1092
1089 if options.restart:
1093 if options.restart:
1090 orig = list(tests)
1094 orig = list(tests)
1091 while tests:
1095 while tests:
1092 if os.path.exists(tests[0] + ".err"):
1096 if os.path.exists(tests[0] + ".err"):
1093 break
1097 break
1094 tests.pop(0)
1098 tests.pop(0)
1095 if not tests:
1099 if not tests:
1096 print "running all tests"
1100 print "running all tests"
1097 tests = orig
1101 tests = orig
1098
1102
1099 runqueue(options, tests, results)
1103 runqueue(options, tests, results)
1100
1104
1101 failed = len(results['f'])
1105 failed = len(results['f'])
1102 tested = len(results['p']) + failed
1106 tested = len(results['p']) + failed
1103 skipped = len(results['s'])
1107 skipped = len(results['s'])
1104 ignored = len(results['i'])
1108 ignored = len(results['i'])
1105
1109
1106 if options.child:
1110 if options.child:
1107 fp = os.fdopen(options.child, 'w')
1111 fp = os.fdopen(options.child, 'w')
1108 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1112 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1109 for s in results['s']:
1113 for s in results['s']:
1110 fp.write("%s %s\n" % s)
1114 fp.write("%s %s\n" % s)
1111 for s in results['f']:
1115 for s in results['f']:
1112 fp.write("%s %s\n" % s)
1116 fp.write("%s %s\n" % s)
1113 fp.close()
1117 fp.close()
1114 else:
1118 else:
1115 print
1119 print
1116 for s in results['s']:
1120 for s in results['s']:
1117 print "Skipped %s: %s" % s
1121 print "Skipped %s: %s" % s
1118 for s in results['f']:
1122 for s in results['f']:
1119 print "Failed %s: %s" % s
1123 print "Failed %s: %s" % s
1120 _checkhglib("Tested")
1124 _checkhglib("Tested")
1121 print "# Ran %d tests, %d skipped, %d failed." % (
1125 print "# Ran %d tests, %d skipped, %d failed." % (
1122 tested, skipped + ignored, failed)
1126 tested, skipped + ignored, failed)
1123
1127
1124 if options.anycoverage:
1128 if options.anycoverage:
1125 outputcoverage(options)
1129 outputcoverage(options)
1126 except KeyboardInterrupt:
1130 except KeyboardInterrupt:
1127 failed = True
1131 failed = True
1128 print "\ninterrupted!"
1132 print "\ninterrupted!"
1129
1133
1130 if failed:
1134 if failed:
1131 sys.exit(1)
1135 sys.exit(1)
1132
1136
1133 def main():
1137 def main():
1134 (options, args) = parseargs()
1138 (options, args) = parseargs()
1135 if not options.child:
1139 if not options.child:
1136 os.umask(022)
1140 os.umask(022)
1137
1141
1138 checktools()
1142 checktools()
1139
1143
1140 if len(args) == 0:
1144 if len(args) == 0:
1141 args = os.listdir(".")
1145 args = os.listdir(".")
1142 args.sort()
1146 args.sort()
1143
1147
1144 tests = args
1148 tests = args
1145
1149
1146 # Reset some environment variables to well-known values so that
1150 # Reset some environment variables to well-known values so that
1147 # the tests produce repeatable output.
1151 # the tests produce repeatable output.
1148 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1152 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1149 os.environ['TZ'] = 'GMT'
1153 os.environ['TZ'] = 'GMT'
1150 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1154 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1151 os.environ['CDPATH'] = ''
1155 os.environ['CDPATH'] = ''
1152 os.environ['COLUMNS'] = '80'
1156 os.environ['COLUMNS'] = '80'
1153 os.environ['GREP_OPTIONS'] = ''
1157 os.environ['GREP_OPTIONS'] = ''
1154 os.environ['http_proxy'] = ''
1158 os.environ['http_proxy'] = ''
1155 os.environ['no_proxy'] = ''
1159 os.environ['no_proxy'] = ''
1156 os.environ['NO_PROXY'] = ''
1160 os.environ['NO_PROXY'] = ''
1157
1161
1158 # unset env related to hooks
1162 # unset env related to hooks
1159 for k in os.environ.keys():
1163 for k in os.environ.keys():
1160 if k.startswith('HG_'):
1164 if k.startswith('HG_'):
1161 # can't remove on solaris
1165 # can't remove on solaris
1162 os.environ[k] = ''
1166 os.environ[k] = ''
1163 del os.environ[k]
1167 del os.environ[k]
1164
1168
1165 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1169 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1166 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1170 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1167 if options.tmpdir:
1171 if options.tmpdir:
1168 options.keep_tmpdir = True
1172 options.keep_tmpdir = True
1169 tmpdir = options.tmpdir
1173 tmpdir = options.tmpdir
1170 if os.path.exists(tmpdir):
1174 if os.path.exists(tmpdir):
1171 # Meaning of tmpdir has changed since 1.3: we used to create
1175 # Meaning of tmpdir has changed since 1.3: we used to create
1172 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1176 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1173 # tmpdir already exists.
1177 # tmpdir already exists.
1174 sys.exit("error: temp dir %r already exists" % tmpdir)
1178 sys.exit("error: temp dir %r already exists" % tmpdir)
1175
1179
1176 # Automatically removing tmpdir sounds convenient, but could
1180 # Automatically removing tmpdir sounds convenient, but could
1177 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1181 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1178 # or "--tmpdir=$HOME".
1182 # or "--tmpdir=$HOME".
1179 #vlog("# Removing temp dir", tmpdir)
1183 #vlog("# Removing temp dir", tmpdir)
1180 #shutil.rmtree(tmpdir)
1184 #shutil.rmtree(tmpdir)
1181 os.makedirs(tmpdir)
1185 os.makedirs(tmpdir)
1182 else:
1186 else:
1183 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1187 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1184 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1188 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1185 DAEMON_PIDS = None
1189 DAEMON_PIDS = None
1186 HGRCPATH = None
1190 HGRCPATH = None
1187
1191
1188 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1192 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1189 os.environ["HGMERGE"] = "internal:merge"
1193 os.environ["HGMERGE"] = "internal:merge"
1190 os.environ["HGUSER"] = "test"
1194 os.environ["HGUSER"] = "test"
1191 os.environ["HGENCODING"] = "ascii"
1195 os.environ["HGENCODING"] = "ascii"
1192 os.environ["HGENCODINGMODE"] = "strict"
1196 os.environ["HGENCODINGMODE"] = "strict"
1193 os.environ["HGPORT"] = str(options.port)
1197 os.environ["HGPORT"] = str(options.port)
1194 os.environ["HGPORT1"] = str(options.port + 1)
1198 os.environ["HGPORT1"] = str(options.port + 1)
1195 os.environ["HGPORT2"] = str(options.port + 2)
1199 os.environ["HGPORT2"] = str(options.port + 2)
1196
1200
1197 if options.with_hg:
1201 if options.with_hg:
1198 INST = None
1202 INST = None
1199 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1203 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1200
1204
1201 # This looks redundant with how Python initializes sys.path from
1205 # This looks redundant with how Python initializes sys.path from
1202 # the location of the script being executed. Needed because the
1206 # the location of the script being executed. Needed because the
1203 # "hg" specified by --with-hg is not the only Python script
1207 # "hg" specified by --with-hg is not the only Python script
1204 # executed in the test suite that needs to import 'mercurial'
1208 # executed in the test suite that needs to import 'mercurial'
1205 # ... which means it's not really redundant at all.
1209 # ... which means it's not really redundant at all.
1206 PYTHONDIR = BINDIR
1210 PYTHONDIR = BINDIR
1207 else:
1211 else:
1208 INST = os.path.join(HGTMP, "install")
1212 INST = os.path.join(HGTMP, "install")
1209 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1213 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1210 PYTHONDIR = os.path.join(INST, "lib", "python")
1214 PYTHONDIR = os.path.join(INST, "lib", "python")
1211
1215
1212 os.environ["BINDIR"] = BINDIR
1216 os.environ["BINDIR"] = BINDIR
1213 os.environ["PYTHON"] = PYTHON
1217 os.environ["PYTHON"] = PYTHON
1214
1218
1215 if not options.child:
1219 if not options.child:
1216 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1220 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1217 os.environ["PATH"] = os.pathsep.join(path)
1221 os.environ["PATH"] = os.pathsep.join(path)
1218
1222
1219 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1223 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1220 # can run .../tests/run-tests.py test-foo where test-foo
1224 # can run .../tests/run-tests.py test-foo where test-foo
1221 # adds an extension to HGRC
1225 # adds an extension to HGRC
1222 pypath = [PYTHONDIR, TESTDIR]
1226 pypath = [PYTHONDIR, TESTDIR]
1223 # We have to augment PYTHONPATH, rather than simply replacing
1227 # We have to augment PYTHONPATH, rather than simply replacing
1224 # it, in case external libraries are only available via current
1228 # it, in case external libraries are only available via current
1225 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1229 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1226 # are in /opt/subversion.)
1230 # are in /opt/subversion.)
1227 oldpypath = os.environ.get(IMPL_PATH)
1231 oldpypath = os.environ.get(IMPL_PATH)
1228 if oldpypath:
1232 if oldpypath:
1229 pypath.append(oldpypath)
1233 pypath.append(oldpypath)
1230 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1234 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1231
1235
1232 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1236 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1233
1237
1234 vlog("# Using TESTDIR", TESTDIR)
1238 vlog("# Using TESTDIR", TESTDIR)
1235 vlog("# Using HGTMP", HGTMP)
1239 vlog("# Using HGTMP", HGTMP)
1236 vlog("# Using PATH", os.environ["PATH"])
1240 vlog("# Using PATH", os.environ["PATH"])
1237 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1241 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1238
1242
1239 try:
1243 try:
1240 if len(tests) > 1 and options.jobs > 1:
1244 if len(tests) > 1 and options.jobs > 1:
1241 runchildren(options, tests)
1245 runchildren(options, tests)
1242 else:
1246 else:
1243 runtests(options, tests)
1247 runtests(options, tests)
1244 finally:
1248 finally:
1245 time.sleep(1)
1249 time.sleep(1)
1246 cleanup(options)
1250 cleanup(options)
1247
1251
1248 if __name__ == '__main__':
1252 if __name__ == '__main__':
1249 main()
1253 main()
General Comments 0
You need to be logged in to leave comments. Login now