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