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