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