##// END OF EJS Templates
tests: add support for inline doctests in test files...
Idan Kamara -
r15236:4fae5df4 default
parent child Browse files
Show More
@@ -1,1204 +1,1224 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 transformtst(lines):
525 inblock = False
526 for l in lines:
527 if inblock:
528 if l.startswith(' $ '):
529 inblock = False
530 yield ' > EOF\n'
531 yield l
532 else:
533 yield ' > ' + l[2:]
534 else:
535 if l.startswith(' >>> '):
536 inblock = True
537 yield ' $ %s -m heredoctest <<EOF\n' % PYTHON
538 yield ' > ' + l[2:]
539 else:
540 yield l
541 if inblock:
542 yield ' > EOF\n'
543
524 def tsttest(test, wd, options, replacements):
544 def tsttest(test, wd, options, replacements):
525 t = open(test)
545 t = open(test)
526 out = []
546 out = []
527 script = []
547 script = []
528 salt = "SALT" + str(time.time())
548 salt = "SALT" + str(time.time())
529
549
530 pos = prepos = -1
550 pos = prepos = -1
531 after = {}
551 after = {}
532 expected = {}
552 expected = {}
533 for n, l in enumerate(t):
553 for n, l in enumerate(transformtst(t)):
534 if not l.endswith('\n'):
554 if not l.endswith('\n'):
535 l += '\n'
555 l += '\n'
536 if l.startswith(' $ '): # commands
556 if l.startswith(' $ '): # commands
537 after.setdefault(pos, []).append(l)
557 after.setdefault(pos, []).append(l)
538 prepos = pos
558 prepos = pos
539 pos = n
559 pos = n
540 script.append('echo %s %s $?\n' % (salt, n))
560 script.append('echo %s %s $?\n' % (salt, n))
541 script.append(l[4:])
561 script.append(l[4:])
542 elif l.startswith(' > '): # continuations
562 elif l.startswith(' > '): # continuations
543 after.setdefault(prepos, []).append(l)
563 after.setdefault(prepos, []).append(l)
544 script.append(l[4:])
564 script.append(l[4:])
545 elif l.startswith(' '): # results
565 elif l.startswith(' '): # results
546 # queue up a list of expected results
566 # queue up a list of expected results
547 expected.setdefault(pos, []).append(l[2:])
567 expected.setdefault(pos, []).append(l[2:])
548 else:
568 else:
549 # non-command/result - queue up for merged output
569 # non-command/result - queue up for merged output
550 after.setdefault(pos, []).append(l)
570 after.setdefault(pos, []).append(l)
551
571
552 t.close()
572 t.close()
553
573
554 script.append('echo %s %s $?\n' % (salt, n + 1))
574 script.append('echo %s %s $?\n' % (salt, n + 1))
555
575
556 fd, name = tempfile.mkstemp(suffix='hg-tst')
576 fd, name = tempfile.mkstemp(suffix='hg-tst')
557
577
558 try:
578 try:
559 for l in script:
579 for l in script:
560 os.write(fd, l)
580 os.write(fd, l)
561 os.close(fd)
581 os.close(fd)
562
582
563 cmd = '"%s" "%s"' % (options.shell, name)
583 cmd = '"%s" "%s"' % (options.shell, name)
564 vlog("# Running", cmd)
584 vlog("# Running", cmd)
565 exitcode, output = run(cmd, wd, options, replacements)
585 exitcode, output = run(cmd, wd, options, replacements)
566 # do not merge output if skipped, return hghave message instead
586 # do not merge output if skipped, return hghave message instead
567 # similarly, with --debug, output is None
587 # similarly, with --debug, output is None
568 if exitcode == SKIPPED_STATUS or output is None:
588 if exitcode == SKIPPED_STATUS or output is None:
569 return exitcode, output
589 return exitcode, output
570 finally:
590 finally:
571 os.remove(name)
591 os.remove(name)
572
592
573 def rematch(el, l):
593 def rematch(el, l):
574 try:
594 try:
575 # ensure that the regex matches to the end of the string
595 # ensure that the regex matches to the end of the string
576 return re.match(el + r'\Z', l)
596 return re.match(el + r'\Z', l)
577 except re.error:
597 except re.error:
578 # el is an invalid regex
598 # el is an invalid regex
579 return False
599 return False
580
600
581 def globmatch(el, l):
601 def globmatch(el, l):
582 # The only supported special characters are * and ?. Escaping is
602 # The only supported special characters are * and ?. Escaping is
583 # supported.
603 # supported.
584 i, n = 0, len(el)
604 i, n = 0, len(el)
585 res = ''
605 res = ''
586 while i < n:
606 while i < n:
587 c = el[i]
607 c = el[i]
588 i += 1
608 i += 1
589 if c == '\\' and el[i] in '*?\\':
609 if c == '\\' and el[i] in '*?\\':
590 res += el[i - 1:i + 1]
610 res += el[i - 1:i + 1]
591 i += 1
611 i += 1
592 elif c == '*':
612 elif c == '*':
593 res += '.*'
613 res += '.*'
594 elif c == '?':
614 elif c == '?':
595 res += '.'
615 res += '.'
596 else:
616 else:
597 res += re.escape(c)
617 res += re.escape(c)
598 return rematch(res, l)
618 return rematch(res, l)
599
619
600 pos = -1
620 pos = -1
601 postout = []
621 postout = []
602 ret = 0
622 ret = 0
603 for n, l in enumerate(output):
623 for n, l in enumerate(output):
604 lout, lcmd = l, None
624 lout, lcmd = l, None
605 if salt in l:
625 if salt in l:
606 lout, lcmd = l.split(salt, 1)
626 lout, lcmd = l.split(salt, 1)
607
627
608 if lout:
628 if lout:
609 if lcmd:
629 if lcmd:
610 lout += ' (no-eol)\n'
630 lout += ' (no-eol)\n'
611
631
612 el = None
632 el = None
613 if pos in expected and expected[pos]:
633 if pos in expected and expected[pos]:
614 el = expected[pos].pop(0)
634 el = expected[pos].pop(0)
615
635
616 if el == lout: # perfect match (fast)
636 if el == lout: # perfect match (fast)
617 postout.append(" " + lout)
637 postout.append(" " + lout)
618 elif (el and
638 elif (el and
619 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
639 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
620 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
640 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
621 or el.endswith(" (esc)\n") and
641 or el.endswith(" (esc)\n") and
622 el.decode('string-escape') == l)):
642 el.decode('string-escape') == l)):
623 postout.append(" " + el) # fallback regex/glob/esc match
643 postout.append(" " + el) # fallback regex/glob/esc match
624 else:
644 else:
625 if needescape(lout):
645 if needescape(lout):
626 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
646 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
627 postout.append(" " + lout) # let diff deal with it
647 postout.append(" " + lout) # let diff deal with it
628
648
629 if lcmd:
649 if lcmd:
630 # add on last return code
650 # add on last return code
631 ret = int(lcmd.split()[1])
651 ret = int(lcmd.split()[1])
632 if ret != 0:
652 if ret != 0:
633 postout.append(" [%s]\n" % ret)
653 postout.append(" [%s]\n" % ret)
634 if pos in after:
654 if pos in after:
635 postout += after.pop(pos)
655 postout += after.pop(pos)
636 pos = int(lcmd.split()[0])
656 pos = int(lcmd.split()[0])
637
657
638 if pos in after:
658 if pos in after:
639 postout += after.pop(pos)
659 postout += after.pop(pos)
640
660
641 return exitcode, postout
661 return exitcode, postout
642
662
643 wifexited = getattr(os, "WIFEXITED", lambda x: False)
663 wifexited = getattr(os, "WIFEXITED", lambda x: False)
644 def run(cmd, wd, options, replacements):
664 def run(cmd, wd, options, replacements):
645 """Run command in a sub-process, capturing the output (stdout and stderr).
665 """Run command in a sub-process, capturing the output (stdout and stderr).
646 Return a tuple (exitcode, output). output is None in debug mode."""
666 Return a tuple (exitcode, output). output is None in debug mode."""
647 # TODO: Use subprocess.Popen if we're running on Python 2.4
667 # TODO: Use subprocess.Popen if we're running on Python 2.4
648 if options.debug:
668 if options.debug:
649 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
669 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
650 ret = proc.wait()
670 ret = proc.wait()
651 return (ret, None)
671 return (ret, None)
652
672
653 proc = Popen4(cmd, wd, options.timeout)
673 proc = Popen4(cmd, wd, options.timeout)
654 def cleanup():
674 def cleanup():
655 terminate(proc)
675 terminate(proc)
656 ret = proc.wait()
676 ret = proc.wait()
657 if ret == 0:
677 if ret == 0:
658 ret = signal.SIGTERM << 8
678 ret = signal.SIGTERM << 8
659 killdaemons()
679 killdaemons()
660 return ret
680 return ret
661
681
662 output = ''
682 output = ''
663 proc.tochild.close()
683 proc.tochild.close()
664
684
665 try:
685 try:
666 output = proc.fromchild.read()
686 output = proc.fromchild.read()
667 except KeyboardInterrupt:
687 except KeyboardInterrupt:
668 vlog('# Handling keyboard interrupt')
688 vlog('# Handling keyboard interrupt')
669 cleanup()
689 cleanup()
670 raise
690 raise
671
691
672 ret = proc.wait()
692 ret = proc.wait()
673 if wifexited(ret):
693 if wifexited(ret):
674 ret = os.WEXITSTATUS(ret)
694 ret = os.WEXITSTATUS(ret)
675
695
676 if proc.timeout:
696 if proc.timeout:
677 ret = 'timeout'
697 ret = 'timeout'
678
698
679 if ret:
699 if ret:
680 killdaemons()
700 killdaemons()
681
701
682 for s, r in replacements:
702 for s, r in replacements:
683 output = re.sub(s, r, output)
703 output = re.sub(s, r, output)
684 return ret, splitnewlines(output)
704 return ret, splitnewlines(output)
685
705
686 def runone(options, test):
706 def runone(options, test):
687 '''tristate output:
707 '''tristate output:
688 None -> skipped
708 None -> skipped
689 True -> passed
709 True -> passed
690 False -> failed'''
710 False -> failed'''
691
711
692 global results, resultslock, iolock
712 global results, resultslock, iolock
693
713
694 testpath = os.path.join(TESTDIR, test)
714 testpath = os.path.join(TESTDIR, test)
695
715
696 def result(l, e):
716 def result(l, e):
697 resultslock.acquire()
717 resultslock.acquire()
698 results[l].append(e)
718 results[l].append(e)
699 resultslock.release()
719 resultslock.release()
700
720
701 def skip(msg):
721 def skip(msg):
702 if not options.verbose:
722 if not options.verbose:
703 result('s', (test, msg))
723 result('s', (test, msg))
704 else:
724 else:
705 iolock.acquire()
725 iolock.acquire()
706 print "\nSkipping %s: %s" % (testpath, msg)
726 print "\nSkipping %s: %s" % (testpath, msg)
707 iolock.release()
727 iolock.release()
708 return None
728 return None
709
729
710 def fail(msg, ret):
730 def fail(msg, ret):
711 if not options.nodiff:
731 if not options.nodiff:
712 iolock.acquire()
732 iolock.acquire()
713 print "\nERROR: %s %s" % (testpath, msg)
733 print "\nERROR: %s %s" % (testpath, msg)
714 iolock.release()
734 iolock.release()
715 if (not ret and options.interactive
735 if (not ret and options.interactive
716 and os.path.exists(testpath + ".err")):
736 and os.path.exists(testpath + ".err")):
717 iolock.acquire()
737 iolock.acquire()
718 print "Accept this change? [n] ",
738 print "Accept this change? [n] ",
719 answer = sys.stdin.readline().strip()
739 answer = sys.stdin.readline().strip()
720 iolock.release()
740 iolock.release()
721 if answer.lower() in "y yes".split():
741 if answer.lower() in "y yes".split():
722 if test.endswith(".t"):
742 if test.endswith(".t"):
723 rename(testpath + ".err", testpath)
743 rename(testpath + ".err", testpath)
724 else:
744 else:
725 rename(testpath + ".err", testpath + ".out")
745 rename(testpath + ".err", testpath + ".out")
726 result('p', test)
746 result('p', test)
727 return
747 return
728 result('f', (test, msg))
748 result('f', (test, msg))
729
749
730 def success():
750 def success():
731 result('p', test)
751 result('p', test)
732
752
733 def ignore(msg):
753 def ignore(msg):
734 result('i', (test, msg))
754 result('i', (test, msg))
735
755
736 if (os.path.basename(test).startswith("test-") and '~' not in test and
756 if (os.path.basename(test).startswith("test-") and '~' not in test and
737 ('.' not in test or test.endswith('.py') or
757 ('.' not in test or test.endswith('.py') or
738 test.endswith('.bat') or test.endswith('.t'))):
758 test.endswith('.bat') or test.endswith('.t'))):
739 if not os.path.exists(test):
759 if not os.path.exists(test):
740 skip("doesn't exist")
760 skip("doesn't exist")
741 return None
761 return None
742 else:
762 else:
743 vlog('# Test file', test, 'not supported, ignoring')
763 vlog('# Test file', test, 'not supported, ignoring')
744 return None # not a supported test, don't record
764 return None # not a supported test, don't record
745
765
746 if not (options.whitelisted and test in options.whitelisted):
766 if not (options.whitelisted and test in options.whitelisted):
747 if options.blacklist and test in options.blacklist:
767 if options.blacklist and test in options.blacklist:
748 skip("blacklisted")
768 skip("blacklisted")
749 return None
769 return None
750
770
751 if options.retest and not os.path.exists(test + ".err"):
771 if options.retest and not os.path.exists(test + ".err"):
752 ignore("not retesting")
772 ignore("not retesting")
753 return None
773 return None
754
774
755 if options.keywords:
775 if options.keywords:
756 fp = open(test)
776 fp = open(test)
757 t = fp.read().lower() + test.lower()
777 t = fp.read().lower() + test.lower()
758 fp.close()
778 fp.close()
759 for k in options.keywords.lower().split():
779 for k in options.keywords.lower().split():
760 if k in t:
780 if k in t:
761 break
781 break
762 else:
782 else:
763 ignore("doesn't match keyword")
783 ignore("doesn't match keyword")
764 return None
784 return None
765
785
766 vlog("# Test", test)
786 vlog("# Test", test)
767
787
768 # create a fresh hgrc
788 # create a fresh hgrc
769 hgrc = open(HGRCPATH, 'w+')
789 hgrc = open(HGRCPATH, 'w+')
770 hgrc.write('[ui]\n')
790 hgrc.write('[ui]\n')
771 hgrc.write('slash = True\n')
791 hgrc.write('slash = True\n')
772 hgrc.write('[defaults]\n')
792 hgrc.write('[defaults]\n')
773 hgrc.write('backout = -d "0 0"\n')
793 hgrc.write('backout = -d "0 0"\n')
774 hgrc.write('commit = -d "0 0"\n')
794 hgrc.write('commit = -d "0 0"\n')
775 hgrc.write('tag = -d "0 0"\n')
795 hgrc.write('tag = -d "0 0"\n')
776 if options.inotify:
796 if options.inotify:
777 hgrc.write('[extensions]\n')
797 hgrc.write('[extensions]\n')
778 hgrc.write('inotify=\n')
798 hgrc.write('inotify=\n')
779 hgrc.write('[inotify]\n')
799 hgrc.write('[inotify]\n')
780 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
800 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
781 hgrc.write('appendpid=True\n')
801 hgrc.write('appendpid=True\n')
782 if options.extra_config_opt:
802 if options.extra_config_opt:
783 for opt in options.extra_config_opt:
803 for opt in options.extra_config_opt:
784 section, key = opt.split('.', 1)
804 section, key = opt.split('.', 1)
785 assert '=' in key, ('extra config opt %s must '
805 assert '=' in key, ('extra config opt %s must '
786 'have an = for assignment' % opt)
806 'have an = for assignment' % opt)
787 hgrc.write('[%s]\n%s\n' % (section, key))
807 hgrc.write('[%s]\n%s\n' % (section, key))
788 hgrc.close()
808 hgrc.close()
789
809
790 ref = os.path.join(TESTDIR, test+".out")
810 ref = os.path.join(TESTDIR, test+".out")
791 err = os.path.join(TESTDIR, test+".err")
811 err = os.path.join(TESTDIR, test+".err")
792 if os.path.exists(err):
812 if os.path.exists(err):
793 os.remove(err) # Remove any previous output files
813 os.remove(err) # Remove any previous output files
794 try:
814 try:
795 tf = open(testpath)
815 tf = open(testpath)
796 firstline = tf.readline().rstrip()
816 firstline = tf.readline().rstrip()
797 tf.close()
817 tf.close()
798 except:
818 except:
799 firstline = ''
819 firstline = ''
800 lctest = test.lower()
820 lctest = test.lower()
801
821
802 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
822 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
803 runner = pytest
823 runner = pytest
804 elif lctest.endswith('.t'):
824 elif lctest.endswith('.t'):
805 runner = tsttest
825 runner = tsttest
806 ref = testpath
826 ref = testpath
807 else:
827 else:
808 # do not try to run non-executable programs
828 # do not try to run non-executable programs
809 if not os.access(testpath, os.X_OK):
829 if not os.access(testpath, os.X_OK):
810 return skip("not executable")
830 return skip("not executable")
811 runner = shtest
831 runner = shtest
812
832
813 # Make a tmp subdirectory to work in
833 # Make a tmp subdirectory to work in
814 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
834 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
815 os.path.join(HGTMP, os.path.basename(test))
835 os.path.join(HGTMP, os.path.basename(test))
816
836
817 os.mkdir(testtmp)
837 os.mkdir(testtmp)
818 ret, out = runner(testpath, testtmp, options, [
838 ret, out = runner(testpath, testtmp, options, [
819 (re.escape(testtmp), '$TESTTMP'),
839 (re.escape(testtmp), '$TESTTMP'),
820 (r':%s\b' % options.port, ':$HGPORT'),
840 (r':%s\b' % options.port, ':$HGPORT'),
821 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
841 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
822 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
842 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
823 ])
843 ])
824 vlog("# Ret was:", ret)
844 vlog("# Ret was:", ret)
825
845
826 mark = '.'
846 mark = '.'
827
847
828 skipped = (ret == SKIPPED_STATUS)
848 skipped = (ret == SKIPPED_STATUS)
829
849
830 # If we're not in --debug mode and reference output file exists,
850 # If we're not in --debug mode and reference output file exists,
831 # check test output against it.
851 # check test output against it.
832 if options.debug:
852 if options.debug:
833 refout = None # to match "out is None"
853 refout = None # to match "out is None"
834 elif os.path.exists(ref):
854 elif os.path.exists(ref):
835 f = open(ref, "r")
855 f = open(ref, "r")
836 refout = splitnewlines(f.read())
856 refout = list(transformtst(splitnewlines(f.read())))
837 f.close()
857 f.close()
838 else:
858 else:
839 refout = []
859 refout = []
840
860
841 if (ret != 0 or out != refout) and not skipped and not options.debug:
861 if (ret != 0 or out != refout) and not skipped and not options.debug:
842 # Save errors to a file for diagnosis
862 # Save errors to a file for diagnosis
843 f = open(err, "wb")
863 f = open(err, "wb")
844 for line in out:
864 for line in out:
845 f.write(line)
865 f.write(line)
846 f.close()
866 f.close()
847
867
848 if skipped:
868 if skipped:
849 mark = 's'
869 mark = 's'
850 if out is None: # debug mode: nothing to parse
870 if out is None: # debug mode: nothing to parse
851 missing = ['unknown']
871 missing = ['unknown']
852 failed = None
872 failed = None
853 else:
873 else:
854 missing, failed = parsehghaveoutput(out)
874 missing, failed = parsehghaveoutput(out)
855 if not missing:
875 if not missing:
856 missing = ['irrelevant']
876 missing = ['irrelevant']
857 if failed:
877 if failed:
858 fail("hghave failed checking for %s" % failed[-1], ret)
878 fail("hghave failed checking for %s" % failed[-1], ret)
859 skipped = False
879 skipped = False
860 else:
880 else:
861 skip(missing[-1])
881 skip(missing[-1])
862 elif ret == 'timeout':
882 elif ret == 'timeout':
863 mark = 't'
883 mark = 't'
864 fail("timed out", ret)
884 fail("timed out", ret)
865 elif out != refout:
885 elif out != refout:
866 mark = '!'
886 mark = '!'
867 if not options.nodiff:
887 if not options.nodiff:
868 iolock.acquire()
888 iolock.acquire()
869 if options.view:
889 if options.view:
870 os.system("%s %s %s" % (options.view, ref, err))
890 os.system("%s %s %s" % (options.view, ref, err))
871 else:
891 else:
872 showdiff(refout, out, ref, err)
892 showdiff(refout, out, ref, err)
873 iolock.release()
893 iolock.release()
874 if ret:
894 if ret:
875 fail("output changed and returned error code %d" % ret, ret)
895 fail("output changed and returned error code %d" % ret, ret)
876 else:
896 else:
877 fail("output changed", ret)
897 fail("output changed", ret)
878 ret = 1
898 ret = 1
879 elif ret:
899 elif ret:
880 mark = '!'
900 mark = '!'
881 fail("returned error code %d" % ret, ret)
901 fail("returned error code %d" % ret, ret)
882 else:
902 else:
883 success()
903 success()
884
904
885 if not options.verbose:
905 if not options.verbose:
886 iolock.acquire()
906 iolock.acquire()
887 sys.stdout.write(mark)
907 sys.stdout.write(mark)
888 sys.stdout.flush()
908 sys.stdout.flush()
889 iolock.release()
909 iolock.release()
890
910
891 killdaemons()
911 killdaemons()
892
912
893 if not options.keep_tmpdir:
913 if not options.keep_tmpdir:
894 shutil.rmtree(testtmp, True)
914 shutil.rmtree(testtmp, True)
895 if skipped:
915 if skipped:
896 return None
916 return None
897 return ret == 0
917 return ret == 0
898
918
899 _hgpath = None
919 _hgpath = None
900
920
901 def _gethgpath():
921 def _gethgpath():
902 """Return the path to the mercurial package that is actually found by
922 """Return the path to the mercurial package that is actually found by
903 the current Python interpreter."""
923 the current Python interpreter."""
904 global _hgpath
924 global _hgpath
905 if _hgpath is not None:
925 if _hgpath is not None:
906 return _hgpath
926 return _hgpath
907
927
908 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
928 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
909 pipe = os.popen(cmd % PYTHON)
929 pipe = os.popen(cmd % PYTHON)
910 try:
930 try:
911 _hgpath = pipe.read().strip()
931 _hgpath = pipe.read().strip()
912 finally:
932 finally:
913 pipe.close()
933 pipe.close()
914 return _hgpath
934 return _hgpath
915
935
916 def _checkhglib(verb):
936 def _checkhglib(verb):
917 """Ensure that the 'mercurial' package imported by python is
937 """Ensure that the 'mercurial' package imported by python is
918 the one we expect it to be. If not, print a warning to stderr."""
938 the one we expect it to be. If not, print a warning to stderr."""
919 expecthg = os.path.join(PYTHONDIR, 'mercurial')
939 expecthg = os.path.join(PYTHONDIR, 'mercurial')
920 actualhg = _gethgpath()
940 actualhg = _gethgpath()
921 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
941 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
922 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
942 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
923 ' (expected %s)\n'
943 ' (expected %s)\n'
924 % (verb, actualhg, expecthg))
944 % (verb, actualhg, expecthg))
925
945
926 def runchildren(options, tests):
946 def runchildren(options, tests):
927 if INST:
947 if INST:
928 installhg(options)
948 installhg(options)
929 _checkhglib("Testing")
949 _checkhglib("Testing")
930
950
931 optcopy = dict(options.__dict__)
951 optcopy = dict(options.__dict__)
932 optcopy['jobs'] = 1
952 optcopy['jobs'] = 1
933
953
934 # Because whitelist has to override keyword matches, we have to
954 # Because whitelist has to override keyword matches, we have to
935 # actually load the whitelist in the children as well, so we allow
955 # actually load the whitelist in the children as well, so we allow
936 # the list of whitelist files to pass through and be parsed in the
956 # the list of whitelist files to pass through and be parsed in the
937 # children, but not the dict of whitelisted tests resulting from
957 # children, but not the dict of whitelisted tests resulting from
938 # the parse, used here to override blacklisted tests.
958 # the parse, used here to override blacklisted tests.
939 whitelist = optcopy['whitelisted'] or []
959 whitelist = optcopy['whitelisted'] or []
940 del optcopy['whitelisted']
960 del optcopy['whitelisted']
941
961
942 blacklist = optcopy['blacklist'] or []
962 blacklist = optcopy['blacklist'] or []
943 del optcopy['blacklist']
963 del optcopy['blacklist']
944 blacklisted = []
964 blacklisted = []
945
965
946 if optcopy['with_hg'] is None:
966 if optcopy['with_hg'] is None:
947 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
967 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
948 optcopy.pop('anycoverage', None)
968 optcopy.pop('anycoverage', None)
949
969
950 opts = []
970 opts = []
951 for opt, value in optcopy.iteritems():
971 for opt, value in optcopy.iteritems():
952 name = '--' + opt.replace('_', '-')
972 name = '--' + opt.replace('_', '-')
953 if value is True:
973 if value is True:
954 opts.append(name)
974 opts.append(name)
955 elif isinstance(value, list):
975 elif isinstance(value, list):
956 for v in value:
976 for v in value:
957 opts.append(name + '=' + str(v))
977 opts.append(name + '=' + str(v))
958 elif value is not None:
978 elif value is not None:
959 opts.append(name + '=' + str(value))
979 opts.append(name + '=' + str(value))
960
980
961 tests.reverse()
981 tests.reverse()
962 jobs = [[] for j in xrange(options.jobs)]
982 jobs = [[] for j in xrange(options.jobs)]
963 while tests:
983 while tests:
964 for job in jobs:
984 for job in jobs:
965 if not tests:
985 if not tests:
966 break
986 break
967 test = tests.pop()
987 test = tests.pop()
968 if test not in whitelist and test in blacklist:
988 if test not in whitelist and test in blacklist:
969 blacklisted.append(test)
989 blacklisted.append(test)
970 else:
990 else:
971 job.append(test)
991 job.append(test)
972 fps = {}
992 fps = {}
973
993
974 for j, job in enumerate(jobs):
994 for j, job in enumerate(jobs):
975 if not job:
995 if not job:
976 continue
996 continue
977 rfd, wfd = os.pipe()
997 rfd, wfd = os.pipe()
978 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
998 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
979 childtmp = os.path.join(HGTMP, 'child%d' % j)
999 childtmp = os.path.join(HGTMP, 'child%d' % j)
980 childopts += ['--tmpdir', childtmp]
1000 childopts += ['--tmpdir', childtmp]
981 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1001 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
982 vlog(' '.join(cmdline))
1002 vlog(' '.join(cmdline))
983 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1003 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
984 os.close(wfd)
1004 os.close(wfd)
985 signal.signal(signal.SIGINT, signal.SIG_IGN)
1005 signal.signal(signal.SIGINT, signal.SIG_IGN)
986 failures = 0
1006 failures = 0
987 tested, skipped, failed = 0, 0, 0
1007 tested, skipped, failed = 0, 0, 0
988 skips = []
1008 skips = []
989 fails = []
1009 fails = []
990 while fps:
1010 while fps:
991 pid, status = os.wait()
1011 pid, status = os.wait()
992 fp = fps.pop(pid)
1012 fp = fps.pop(pid)
993 l = fp.read().splitlines()
1013 l = fp.read().splitlines()
994 try:
1014 try:
995 test, skip, fail = map(int, l[:3])
1015 test, skip, fail = map(int, l[:3])
996 except ValueError:
1016 except ValueError:
997 test, skip, fail = 0, 0, 0
1017 test, skip, fail = 0, 0, 0
998 split = -fail or len(l)
1018 split = -fail or len(l)
999 for s in l[3:split]:
1019 for s in l[3:split]:
1000 skips.append(s.split(" ", 1))
1020 skips.append(s.split(" ", 1))
1001 for s in l[split:]:
1021 for s in l[split:]:
1002 fails.append(s.split(" ", 1))
1022 fails.append(s.split(" ", 1))
1003 tested += test
1023 tested += test
1004 skipped += skip
1024 skipped += skip
1005 failed += fail
1025 failed += fail
1006 vlog('pid %d exited, status %d' % (pid, status))
1026 vlog('pid %d exited, status %d' % (pid, status))
1007 failures |= status
1027 failures |= status
1008 print
1028 print
1009 skipped += len(blacklisted)
1029 skipped += len(blacklisted)
1010 if not options.noskips:
1030 if not options.noskips:
1011 for s in skips:
1031 for s in skips:
1012 print "Skipped %s: %s" % (s[0], s[1])
1032 print "Skipped %s: %s" % (s[0], s[1])
1013 for s in blacklisted:
1033 for s in blacklisted:
1014 print "Skipped %s: blacklisted" % s
1034 print "Skipped %s: blacklisted" % s
1015 for s in fails:
1035 for s in fails:
1016 print "Failed %s: %s" % (s[0], s[1])
1036 print "Failed %s: %s" % (s[0], s[1])
1017
1037
1018 _checkhglib("Tested")
1038 _checkhglib("Tested")
1019 print "# Ran %d tests, %d skipped, %d failed." % (
1039 print "# Ran %d tests, %d skipped, %d failed." % (
1020 tested, skipped, failed)
1040 tested, skipped, failed)
1021
1041
1022 if options.anycoverage:
1042 if options.anycoverage:
1023 outputcoverage(options)
1043 outputcoverage(options)
1024 sys.exit(failures != 0)
1044 sys.exit(failures != 0)
1025
1045
1026 results = dict(p=[], f=[], s=[], i=[])
1046 results = dict(p=[], f=[], s=[], i=[])
1027 resultslock = threading.Lock()
1047 resultslock = threading.Lock()
1028 iolock = threading.Lock()
1048 iolock = threading.Lock()
1029
1049
1030 def runqueue(options, tests, results):
1050 def runqueue(options, tests, results):
1031 for test in tests:
1051 for test in tests:
1032 ret = runone(options, test)
1052 ret = runone(options, test)
1033 if options.first and ret is not None and not ret:
1053 if options.first and ret is not None and not ret:
1034 break
1054 break
1035
1055
1036 def runtests(options, tests):
1056 def runtests(options, tests):
1037 global DAEMON_PIDS, HGRCPATH
1057 global DAEMON_PIDS, HGRCPATH
1038 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1058 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1039 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1059 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1040
1060
1041 try:
1061 try:
1042 if INST:
1062 if INST:
1043 installhg(options)
1063 installhg(options)
1044 _checkhglib("Testing")
1064 _checkhglib("Testing")
1045
1065
1046 if options.restart:
1066 if options.restart:
1047 orig = list(tests)
1067 orig = list(tests)
1048 while tests:
1068 while tests:
1049 if os.path.exists(tests[0] + ".err"):
1069 if os.path.exists(tests[0] + ".err"):
1050 break
1070 break
1051 tests.pop(0)
1071 tests.pop(0)
1052 if not tests:
1072 if not tests:
1053 print "running all tests"
1073 print "running all tests"
1054 tests = orig
1074 tests = orig
1055
1075
1056 runqueue(options, tests, results)
1076 runqueue(options, tests, results)
1057
1077
1058 failed = len(results['f'])
1078 failed = len(results['f'])
1059 tested = len(results['p']) + failed
1079 tested = len(results['p']) + failed
1060 skipped = len(results['s'])
1080 skipped = len(results['s'])
1061 ignored = len(results['i'])
1081 ignored = len(results['i'])
1062
1082
1063 if options.child:
1083 if options.child:
1064 fp = os.fdopen(options.child, 'w')
1084 fp = os.fdopen(options.child, 'w')
1065 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1085 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1066 for s in results['s']:
1086 for s in results['s']:
1067 fp.write("%s %s\n" % s)
1087 fp.write("%s %s\n" % s)
1068 for s in results['f']:
1088 for s in results['f']:
1069 fp.write("%s %s\n" % s)
1089 fp.write("%s %s\n" % s)
1070 fp.close()
1090 fp.close()
1071 else:
1091 else:
1072 print
1092 print
1073 for s in results['s']:
1093 for s in results['s']:
1074 print "Skipped %s: %s" % s
1094 print "Skipped %s: %s" % s
1075 for s in results['f']:
1095 for s in results['f']:
1076 print "Failed %s: %s" % s
1096 print "Failed %s: %s" % s
1077 _checkhglib("Tested")
1097 _checkhglib("Tested")
1078 print "# Ran %d tests, %d skipped, %d failed." % (
1098 print "# Ran %d tests, %d skipped, %d failed." % (
1079 tested, skipped + ignored, failed)
1099 tested, skipped + ignored, failed)
1080
1100
1081 if options.anycoverage:
1101 if options.anycoverage:
1082 outputcoverage(options)
1102 outputcoverage(options)
1083 except KeyboardInterrupt:
1103 except KeyboardInterrupt:
1084 failed = True
1104 failed = True
1085 print "\ninterrupted!"
1105 print "\ninterrupted!"
1086
1106
1087 if failed:
1107 if failed:
1088 sys.exit(1)
1108 sys.exit(1)
1089
1109
1090 def main():
1110 def main():
1091 (options, args) = parseargs()
1111 (options, args) = parseargs()
1092 if not options.child:
1112 if not options.child:
1093 os.umask(022)
1113 os.umask(022)
1094
1114
1095 checktools()
1115 checktools()
1096
1116
1097 if len(args) == 0:
1117 if len(args) == 0:
1098 args = os.listdir(".")
1118 args = os.listdir(".")
1099 args.sort()
1119 args.sort()
1100
1120
1101 tests = args
1121 tests = args
1102
1122
1103 # Reset some environment variables to well-known values so that
1123 # Reset some environment variables to well-known values so that
1104 # the tests produce repeatable output.
1124 # the tests produce repeatable output.
1105 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1125 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1106 os.environ['TZ'] = 'GMT'
1126 os.environ['TZ'] = 'GMT'
1107 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1127 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1108 os.environ['CDPATH'] = ''
1128 os.environ['CDPATH'] = ''
1109 os.environ['COLUMNS'] = '80'
1129 os.environ['COLUMNS'] = '80'
1110 os.environ['GREP_OPTIONS'] = ''
1130 os.environ['GREP_OPTIONS'] = ''
1111 os.environ['http_proxy'] = ''
1131 os.environ['http_proxy'] = ''
1112
1132
1113 # unset env related to hooks
1133 # unset env related to hooks
1114 for k in os.environ.keys():
1134 for k in os.environ.keys():
1115 if k.startswith('HG_'):
1135 if k.startswith('HG_'):
1116 # can't remove on solaris
1136 # can't remove on solaris
1117 os.environ[k] = ''
1137 os.environ[k] = ''
1118 del os.environ[k]
1138 del os.environ[k]
1119
1139
1120 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1140 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1121 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1141 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1122 if options.tmpdir:
1142 if options.tmpdir:
1123 options.keep_tmpdir = True
1143 options.keep_tmpdir = True
1124 tmpdir = options.tmpdir
1144 tmpdir = options.tmpdir
1125 if os.path.exists(tmpdir):
1145 if os.path.exists(tmpdir):
1126 # Meaning of tmpdir has changed since 1.3: we used to create
1146 # Meaning of tmpdir has changed since 1.3: we used to create
1127 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1147 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1128 # tmpdir already exists.
1148 # tmpdir already exists.
1129 sys.exit("error: temp dir %r already exists" % tmpdir)
1149 sys.exit("error: temp dir %r already exists" % tmpdir)
1130
1150
1131 # Automatically removing tmpdir sounds convenient, but could
1151 # Automatically removing tmpdir sounds convenient, but could
1132 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1152 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1133 # or "--tmpdir=$HOME".
1153 # or "--tmpdir=$HOME".
1134 #vlog("# Removing temp dir", tmpdir)
1154 #vlog("# Removing temp dir", tmpdir)
1135 #shutil.rmtree(tmpdir)
1155 #shutil.rmtree(tmpdir)
1136 os.makedirs(tmpdir)
1156 os.makedirs(tmpdir)
1137 else:
1157 else:
1138 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1158 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1139 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1159 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1140 DAEMON_PIDS = None
1160 DAEMON_PIDS = None
1141 HGRCPATH = None
1161 HGRCPATH = None
1142
1162
1143 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1163 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1144 os.environ["HGMERGE"] = "internal:merge"
1164 os.environ["HGMERGE"] = "internal:merge"
1145 os.environ["HGUSER"] = "test"
1165 os.environ["HGUSER"] = "test"
1146 os.environ["HGENCODING"] = "ascii"
1166 os.environ["HGENCODING"] = "ascii"
1147 os.environ["HGENCODINGMODE"] = "strict"
1167 os.environ["HGENCODINGMODE"] = "strict"
1148 os.environ["HGPORT"] = str(options.port)
1168 os.environ["HGPORT"] = str(options.port)
1149 os.environ["HGPORT1"] = str(options.port + 1)
1169 os.environ["HGPORT1"] = str(options.port + 1)
1150 os.environ["HGPORT2"] = str(options.port + 2)
1170 os.environ["HGPORT2"] = str(options.port + 2)
1151
1171
1152 if options.with_hg:
1172 if options.with_hg:
1153 INST = None
1173 INST = None
1154 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1174 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1155
1175
1156 # This looks redundant with how Python initializes sys.path from
1176 # This looks redundant with how Python initializes sys.path from
1157 # the location of the script being executed. Needed because the
1177 # the location of the script being executed. Needed because the
1158 # "hg" specified by --with-hg is not the only Python script
1178 # "hg" specified by --with-hg is not the only Python script
1159 # executed in the test suite that needs to import 'mercurial'
1179 # executed in the test suite that needs to import 'mercurial'
1160 # ... which means it's not really redundant at all.
1180 # ... which means it's not really redundant at all.
1161 PYTHONDIR = BINDIR
1181 PYTHONDIR = BINDIR
1162 else:
1182 else:
1163 INST = os.path.join(HGTMP, "install")
1183 INST = os.path.join(HGTMP, "install")
1164 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1184 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1165 PYTHONDIR = os.path.join(INST, "lib", "python")
1185 PYTHONDIR = os.path.join(INST, "lib", "python")
1166
1186
1167 os.environ["BINDIR"] = BINDIR
1187 os.environ["BINDIR"] = BINDIR
1168 os.environ["PYTHON"] = PYTHON
1188 os.environ["PYTHON"] = PYTHON
1169
1189
1170 if not options.child:
1190 if not options.child:
1171 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1191 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1172 os.environ["PATH"] = os.pathsep.join(path)
1192 os.environ["PATH"] = os.pathsep.join(path)
1173
1193
1174 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1194 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1175 # can run .../tests/run-tests.py test-foo where test-foo
1195 # can run .../tests/run-tests.py test-foo where test-foo
1176 # adds an extension to HGRC
1196 # adds an extension to HGRC
1177 pypath = [PYTHONDIR, TESTDIR]
1197 pypath = [PYTHONDIR, TESTDIR]
1178 # We have to augment PYTHONPATH, rather than simply replacing
1198 # We have to augment PYTHONPATH, rather than simply replacing
1179 # it, in case external libraries are only available via current
1199 # it, in case external libraries are only available via current
1180 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1200 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1181 # are in /opt/subversion.)
1201 # are in /opt/subversion.)
1182 oldpypath = os.environ.get(IMPL_PATH)
1202 oldpypath = os.environ.get(IMPL_PATH)
1183 if oldpypath:
1203 if oldpypath:
1184 pypath.append(oldpypath)
1204 pypath.append(oldpypath)
1185 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1205 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1186
1206
1187 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1207 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1188
1208
1189 vlog("# Using TESTDIR", TESTDIR)
1209 vlog("# Using TESTDIR", TESTDIR)
1190 vlog("# Using HGTMP", HGTMP)
1210 vlog("# Using HGTMP", HGTMP)
1191 vlog("# Using PATH", os.environ["PATH"])
1211 vlog("# Using PATH", os.environ["PATH"])
1192 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1212 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1193
1213
1194 try:
1214 try:
1195 if len(tests) > 1 and options.jobs > 1:
1215 if len(tests) > 1 and options.jobs > 1:
1196 runchildren(options, tests)
1216 runchildren(options, tests)
1197 else:
1217 else:
1198 runtests(options, tests)
1218 runtests(options, tests)
1199 finally:
1219 finally:
1200 time.sleep(1)
1220 time.sleep(1)
1201 cleanup(options)
1221 cleanup(options)
1202
1222
1203 if __name__ == '__main__':
1223 if __name__ == '__main__':
1204 main()
1224 main()
General Comments 0
You need to be logged in to leave comments. Login now