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