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