##// END OF EJS Templates
run-tests: record warnings by raising WarnTest...
Gregory Szorc -
r21444:2b7d3646 default
parent child Browse files
Show More
@@ -1,1636 +1,1643 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 random
55 import random
56 import re
56 import re
57 import threading
57 import threading
58 import killdaemons as killmod
58 import killdaemons as killmod
59 import Queue as queue
59 import Queue as queue
60 import unittest
60 import unittest
61
61
62 processlock = threading.Lock()
62 processlock = threading.Lock()
63
63
64 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
65 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
66 # zombies but it's pretty harmless even if we do.
66 # zombies but it's pretty harmless even if we do.
67 if sys.version_info < (2, 5):
67 if sys.version_info < (2, 5):
68 subprocess._cleanup = lambda: None
68 subprocess._cleanup = lambda: None
69
69
70 closefds = os.name == 'posix'
70 closefds = os.name == 'posix'
71 def Popen4(cmd, wd, timeout, env=None):
71 def Popen4(cmd, wd, timeout, env=None):
72 processlock.acquire()
72 processlock.acquire()
73 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
74 close_fds=closefds,
74 close_fds=closefds,
75 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
76 stderr=subprocess.STDOUT)
76 stderr=subprocess.STDOUT)
77 processlock.release()
77 processlock.release()
78
78
79 p.fromchild = p.stdout
79 p.fromchild = p.stdout
80 p.tochild = p.stdin
80 p.tochild = p.stdin
81 p.childerr = p.stderr
81 p.childerr = p.stderr
82
82
83 p.timeout = False
83 p.timeout = False
84 if timeout:
84 if timeout:
85 def t():
85 def t():
86 start = time.time()
86 start = time.time()
87 while time.time() - start < timeout and p.returncode is None:
87 while time.time() - start < timeout and p.returncode is None:
88 time.sleep(.1)
88 time.sleep(.1)
89 p.timeout = True
89 p.timeout = True
90 if p.returncode is None:
90 if p.returncode is None:
91 terminate(p)
91 terminate(p)
92 threading.Thread(target=t).start()
92 threading.Thread(target=t).start()
93
93
94 return p
94 return p
95
95
96 PYTHON = sys.executable.replace('\\', '/')
96 PYTHON = sys.executable.replace('\\', '/')
97 IMPL_PATH = 'PYTHONPATH'
97 IMPL_PATH = 'PYTHONPATH'
98 if 'java' in sys.platform:
98 if 'java' in sys.platform:
99 IMPL_PATH = 'JYTHONPATH'
99 IMPL_PATH = 'JYTHONPATH'
100
100
101 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
101 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
102
102
103 defaults = {
103 defaults = {
104 'jobs': ('HGTEST_JOBS', 1),
104 'jobs': ('HGTEST_JOBS', 1),
105 'timeout': ('HGTEST_TIMEOUT', 180),
105 'timeout': ('HGTEST_TIMEOUT', 180),
106 'port': ('HGTEST_PORT', 20059),
106 'port': ('HGTEST_PORT', 20059),
107 'shell': ('HGTEST_SHELL', 'sh'),
107 'shell': ('HGTEST_SHELL', 'sh'),
108 }
108 }
109
109
110 def parselistfiles(files, listtype, warn=True):
110 def parselistfiles(files, listtype, warn=True):
111 entries = dict()
111 entries = dict()
112 for filename in files:
112 for filename in files:
113 try:
113 try:
114 path = os.path.expanduser(os.path.expandvars(filename))
114 path = os.path.expanduser(os.path.expandvars(filename))
115 f = open(path, "r")
115 f = open(path, "r")
116 except IOError, err:
116 except IOError, err:
117 if err.errno != errno.ENOENT:
117 if err.errno != errno.ENOENT:
118 raise
118 raise
119 if warn:
119 if warn:
120 print "warning: no such %s file: %s" % (listtype, filename)
120 print "warning: no such %s file: %s" % (listtype, filename)
121 continue
121 continue
122
122
123 for line in f.readlines():
123 for line in f.readlines():
124 line = line.split('#', 1)[0].strip()
124 line = line.split('#', 1)[0].strip()
125 if line:
125 if line:
126 entries[line] = filename
126 entries[line] = filename
127
127
128 f.close()
128 f.close()
129 return entries
129 return entries
130
130
131 def getparser():
131 def getparser():
132 """Obtain the OptionParser used by the CLI."""
132 """Obtain the OptionParser used by the CLI."""
133 parser = optparse.OptionParser("%prog [options] [tests]")
133 parser = optparse.OptionParser("%prog [options] [tests]")
134
134
135 # keep these sorted
135 # keep these sorted
136 parser.add_option("--blacklist", action="append",
136 parser.add_option("--blacklist", action="append",
137 help="skip tests listed in the specified blacklist file")
137 help="skip tests listed in the specified blacklist file")
138 parser.add_option("--whitelist", action="append",
138 parser.add_option("--whitelist", action="append",
139 help="always run tests listed in the specified whitelist file")
139 help="always run tests listed in the specified whitelist file")
140 parser.add_option("--changed", type="string",
140 parser.add_option("--changed", type="string",
141 help="run tests that are changed in parent rev or working directory")
141 help="run tests that are changed in parent rev or working directory")
142 parser.add_option("-C", "--annotate", action="store_true",
142 parser.add_option("-C", "--annotate", action="store_true",
143 help="output files annotated with coverage")
143 help="output files annotated with coverage")
144 parser.add_option("-c", "--cover", action="store_true",
144 parser.add_option("-c", "--cover", action="store_true",
145 help="print a test coverage report")
145 help="print a test coverage report")
146 parser.add_option("-d", "--debug", action="store_true",
146 parser.add_option("-d", "--debug", action="store_true",
147 help="debug mode: write output of test scripts to console"
147 help="debug mode: write output of test scripts to console"
148 " rather than capturing and diffing it (disables timeout)")
148 " rather than capturing and diffing it (disables timeout)")
149 parser.add_option("-f", "--first", action="store_true",
149 parser.add_option("-f", "--first", action="store_true",
150 help="exit on the first test failure")
150 help="exit on the first test failure")
151 parser.add_option("-H", "--htmlcov", action="store_true",
151 parser.add_option("-H", "--htmlcov", action="store_true",
152 help="create an HTML report of the coverage of the files")
152 help="create an HTML report of the coverage of the files")
153 parser.add_option("-i", "--interactive", action="store_true",
153 parser.add_option("-i", "--interactive", action="store_true",
154 help="prompt to accept changed output")
154 help="prompt to accept changed output")
155 parser.add_option("-j", "--jobs", type="int",
155 parser.add_option("-j", "--jobs", type="int",
156 help="number of jobs to run in parallel"
156 help="number of jobs to run in parallel"
157 " (default: $%s or %d)" % defaults['jobs'])
157 " (default: $%s or %d)" % defaults['jobs'])
158 parser.add_option("--keep-tmpdir", action="store_true",
158 parser.add_option("--keep-tmpdir", action="store_true",
159 help="keep temporary directory after running tests")
159 help="keep temporary directory after running tests")
160 parser.add_option("-k", "--keywords",
160 parser.add_option("-k", "--keywords",
161 help="run tests matching keywords")
161 help="run tests matching keywords")
162 parser.add_option("-l", "--local", action="store_true",
162 parser.add_option("-l", "--local", action="store_true",
163 help="shortcut for --with-hg=<testdir>/../hg")
163 help="shortcut for --with-hg=<testdir>/../hg")
164 parser.add_option("--loop", action="store_true",
164 parser.add_option("--loop", action="store_true",
165 help="loop tests repeatedly")
165 help="loop tests repeatedly")
166 parser.add_option("-n", "--nodiff", action="store_true",
166 parser.add_option("-n", "--nodiff", action="store_true",
167 help="skip showing test changes")
167 help="skip showing test changes")
168 parser.add_option("-p", "--port", type="int",
168 parser.add_option("-p", "--port", type="int",
169 help="port on which servers should listen"
169 help="port on which servers should listen"
170 " (default: $%s or %d)" % defaults['port'])
170 " (default: $%s or %d)" % defaults['port'])
171 parser.add_option("--compiler", type="string",
171 parser.add_option("--compiler", type="string",
172 help="compiler to build with")
172 help="compiler to build with")
173 parser.add_option("--pure", action="store_true",
173 parser.add_option("--pure", action="store_true",
174 help="use pure Python code instead of C extensions")
174 help="use pure Python code instead of C extensions")
175 parser.add_option("-R", "--restart", action="store_true",
175 parser.add_option("-R", "--restart", action="store_true",
176 help="restart at last error")
176 help="restart at last error")
177 parser.add_option("-r", "--retest", action="store_true",
177 parser.add_option("-r", "--retest", action="store_true",
178 help="retest failed tests")
178 help="retest failed tests")
179 parser.add_option("-S", "--noskips", action="store_true",
179 parser.add_option("-S", "--noskips", action="store_true",
180 help="don't report skip tests verbosely")
180 help="don't report skip tests verbosely")
181 parser.add_option("--shell", type="string",
181 parser.add_option("--shell", type="string",
182 help="shell to use (default: $%s or %s)" % defaults['shell'])
182 help="shell to use (default: $%s or %s)" % defaults['shell'])
183 parser.add_option("-t", "--timeout", type="int",
183 parser.add_option("-t", "--timeout", type="int",
184 help="kill errant tests after TIMEOUT seconds"
184 help="kill errant tests after TIMEOUT seconds"
185 " (default: $%s or %d)" % defaults['timeout'])
185 " (default: $%s or %d)" % defaults['timeout'])
186 parser.add_option("--time", action="store_true",
186 parser.add_option("--time", action="store_true",
187 help="time how long each test takes")
187 help="time how long each test takes")
188 parser.add_option("--tmpdir", type="string",
188 parser.add_option("--tmpdir", type="string",
189 help="run tests in the given temporary directory"
189 help="run tests in the given temporary directory"
190 " (implies --keep-tmpdir)")
190 " (implies --keep-tmpdir)")
191 parser.add_option("--unittest", action="store_true",
191 parser.add_option("--unittest", action="store_true",
192 help="run tests with Python's unittest package"
192 help="run tests with Python's unittest package"
193 " (this is an experimental feature)")
193 " (this is an experimental feature)")
194 parser.add_option("-v", "--verbose", action="store_true",
194 parser.add_option("-v", "--verbose", action="store_true",
195 help="output verbose messages")
195 help="output verbose messages")
196 parser.add_option("--view", type="string",
196 parser.add_option("--view", type="string",
197 help="external diff viewer")
197 help="external diff viewer")
198 parser.add_option("--with-hg", type="string",
198 parser.add_option("--with-hg", type="string",
199 metavar="HG",
199 metavar="HG",
200 help="test using specified hg script rather than a "
200 help="test using specified hg script rather than a "
201 "temporary installation")
201 "temporary installation")
202 parser.add_option("-3", "--py3k-warnings", action="store_true",
202 parser.add_option("-3", "--py3k-warnings", action="store_true",
203 help="enable Py3k warnings on Python 2.6+")
203 help="enable Py3k warnings on Python 2.6+")
204 parser.add_option('--extra-config-opt', action="append",
204 parser.add_option('--extra-config-opt', action="append",
205 help='set the given config opt in the test hgrc')
205 help='set the given config opt in the test hgrc')
206 parser.add_option('--random', action="store_true",
206 parser.add_option('--random', action="store_true",
207 help='run tests in random order')
207 help='run tests in random order')
208
208
209 for option, (envvar, default) in defaults.items():
209 for option, (envvar, default) in defaults.items():
210 defaults[option] = type(default)(os.environ.get(envvar, default))
210 defaults[option] = type(default)(os.environ.get(envvar, default))
211 parser.set_defaults(**defaults)
211 parser.set_defaults(**defaults)
212
212
213 return parser
213 return parser
214
214
215 def parseargs(args, parser):
215 def parseargs(args, parser):
216 """Parse arguments with our OptionParser and validate results."""
216 """Parse arguments with our OptionParser and validate results."""
217 (options, args) = parser.parse_args(args)
217 (options, args) = parser.parse_args(args)
218
218
219 # jython is always pure
219 # jython is always pure
220 if 'java' in sys.platform or '__pypy__' in sys.modules:
220 if 'java' in sys.platform or '__pypy__' in sys.modules:
221 options.pure = True
221 options.pure = True
222
222
223 if options.with_hg:
223 if options.with_hg:
224 options.with_hg = os.path.expanduser(options.with_hg)
224 options.with_hg = os.path.expanduser(options.with_hg)
225 if not (os.path.isfile(options.with_hg) and
225 if not (os.path.isfile(options.with_hg) and
226 os.access(options.with_hg, os.X_OK)):
226 os.access(options.with_hg, os.X_OK)):
227 parser.error('--with-hg must specify an executable hg script')
227 parser.error('--with-hg must specify an executable hg script')
228 if not os.path.basename(options.with_hg) == 'hg':
228 if not os.path.basename(options.with_hg) == 'hg':
229 sys.stderr.write('warning: --with-hg should specify an hg script\n')
229 sys.stderr.write('warning: --with-hg should specify an hg script\n')
230 if options.local:
230 if options.local:
231 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
231 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
232 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
232 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
233 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
233 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
234 parser.error('--local specified, but %r not found or not executable'
234 parser.error('--local specified, but %r not found or not executable'
235 % hgbin)
235 % hgbin)
236 options.with_hg = hgbin
236 options.with_hg = hgbin
237
237
238 options.anycoverage = options.cover or options.annotate or options.htmlcov
238 options.anycoverage = options.cover or options.annotate or options.htmlcov
239 if options.anycoverage:
239 if options.anycoverage:
240 try:
240 try:
241 import coverage
241 import coverage
242 covver = version.StrictVersion(coverage.__version__).version
242 covver = version.StrictVersion(coverage.__version__).version
243 if covver < (3, 3):
243 if covver < (3, 3):
244 parser.error('coverage options require coverage 3.3 or later')
244 parser.error('coverage options require coverage 3.3 or later')
245 except ImportError:
245 except ImportError:
246 parser.error('coverage options now require the coverage package')
246 parser.error('coverage options now require the coverage package')
247
247
248 if options.anycoverage and options.local:
248 if options.anycoverage and options.local:
249 # this needs some path mangling somewhere, I guess
249 # this needs some path mangling somewhere, I guess
250 parser.error("sorry, coverage options do not work when --local "
250 parser.error("sorry, coverage options do not work when --local "
251 "is specified")
251 "is specified")
252
252
253 global verbose
253 global verbose
254 if options.verbose:
254 if options.verbose:
255 verbose = ''
255 verbose = ''
256
256
257 if options.tmpdir:
257 if options.tmpdir:
258 options.tmpdir = os.path.expanduser(options.tmpdir)
258 options.tmpdir = os.path.expanduser(options.tmpdir)
259
259
260 if options.jobs < 1:
260 if options.jobs < 1:
261 parser.error('--jobs must be positive')
261 parser.error('--jobs must be positive')
262 if options.interactive and options.debug:
262 if options.interactive and options.debug:
263 parser.error("-i/--interactive and -d/--debug are incompatible")
263 parser.error("-i/--interactive and -d/--debug are incompatible")
264 if options.debug:
264 if options.debug:
265 if options.timeout != defaults['timeout']:
265 if options.timeout != defaults['timeout']:
266 sys.stderr.write(
266 sys.stderr.write(
267 'warning: --timeout option ignored with --debug\n')
267 'warning: --timeout option ignored with --debug\n')
268 options.timeout = 0
268 options.timeout = 0
269 if options.py3k_warnings:
269 if options.py3k_warnings:
270 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
270 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
271 parser.error('--py3k-warnings can only be used on Python 2.6+')
271 parser.error('--py3k-warnings can only be used on Python 2.6+')
272 if options.blacklist:
272 if options.blacklist:
273 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
273 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
274 if options.whitelist:
274 if options.whitelist:
275 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
275 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
276 else:
276 else:
277 options.whitelisted = {}
277 options.whitelisted = {}
278
278
279 return (options, args)
279 return (options, args)
280
280
281 def rename(src, dst):
281 def rename(src, dst):
282 """Like os.rename(), trade atomicity and opened files friendliness
282 """Like os.rename(), trade atomicity and opened files friendliness
283 for existing destination support.
283 for existing destination support.
284 """
284 """
285 shutil.copy(src, dst)
285 shutil.copy(src, dst)
286 os.remove(src)
286 os.remove(src)
287
287
288 def showdiff(expected, output, ref, err):
288 def showdiff(expected, output, ref, err):
289 print
289 print
290 servefail = False
290 servefail = False
291 for line in difflib.unified_diff(expected, output, ref, err):
291 for line in difflib.unified_diff(expected, output, ref, err):
292 sys.stdout.write(line)
292 sys.stdout.write(line)
293 if not servefail and line.startswith(
293 if not servefail and line.startswith(
294 '+ abort: child process failed to start'):
294 '+ abort: child process failed to start'):
295 servefail = True
295 servefail = True
296 return {'servefail': servefail}
296 return {'servefail': servefail}
297
297
298
298
299 verbose = False
299 verbose = False
300 def vlog(*msg):
300 def vlog(*msg):
301 if verbose is not False:
301 if verbose is not False:
302 iolock.acquire()
302 iolock.acquire()
303 if verbose:
303 if verbose:
304 print verbose,
304 print verbose,
305 for m in msg:
305 for m in msg:
306 print m,
306 print m,
307 print
307 print
308 sys.stdout.flush()
308 sys.stdout.flush()
309 iolock.release()
309 iolock.release()
310
310
311 def log(*msg):
311 def log(*msg):
312 iolock.acquire()
312 iolock.acquire()
313 if verbose:
313 if verbose:
314 print verbose,
314 print verbose,
315 for m in msg:
315 for m in msg:
316 print m,
316 print m,
317 print
317 print
318 sys.stdout.flush()
318 sys.stdout.flush()
319 iolock.release()
319 iolock.release()
320
320
321 def terminate(proc):
321 def terminate(proc):
322 """Terminate subprocess (with fallback for Python versions < 2.6)"""
322 """Terminate subprocess (with fallback for Python versions < 2.6)"""
323 vlog('# Terminating process %d' % proc.pid)
323 vlog('# Terminating process %d' % proc.pid)
324 try:
324 try:
325 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
325 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
326 except OSError:
326 except OSError:
327 pass
327 pass
328
328
329 def killdaemons(pidfile):
329 def killdaemons(pidfile):
330 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
330 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
331 logfn=vlog)
331 logfn=vlog)
332
332
333 class Test(object):
333 class Test(object):
334 """Encapsulates a single, runnable test.
334 """Encapsulates a single, runnable test.
335
335
336 Test instances can be run multiple times via run(). However, multiple
336 Test instances can be run multiple times via run(). However, multiple
337 runs cannot be run concurrently.
337 runs cannot be run concurrently.
338 """
338 """
339
339
340 # Status code reserved for skipped tests (used by hghave).
340 # Status code reserved for skipped tests (used by hghave).
341 SKIPPED_STATUS = 80
341 SKIPPED_STATUS = 80
342
342
343 def __init__(self, runner, test, count, refpath, unittest=False):
343 def __init__(self, runner, test, count, refpath, unittest=False):
344 path = os.path.join(runner.testdir, test)
344 path = os.path.join(runner.testdir, test)
345 errpath = os.path.join(runner.testdir, '%s.err' % test)
345 errpath = os.path.join(runner.testdir, '%s.err' % test)
346
346
347 self.name = test
347 self.name = test
348
348
349 self._runner = runner
349 self._runner = runner
350 self._testdir = runner.testdir
350 self._testdir = runner.testdir
351 self._path = path
351 self._path = path
352 self._options = runner.options
352 self._options = runner.options
353 self._count = count
353 self._count = count
354 self._daemonpids = []
354 self._daemonpids = []
355 self._refpath = refpath
355 self._refpath = refpath
356 self._errpath = errpath
356 self._errpath = errpath
357 self._unittest = unittest
357 self._unittest = unittest
358
358
359 # If we're not in --debug mode and reference output file exists,
359 # If we're not in --debug mode and reference output file exists,
360 # check test output against it.
360 # check test output against it.
361 if runner.options.debug:
361 if runner.options.debug:
362 self._refout = None # to match "out is None"
362 self._refout = None # to match "out is None"
363 elif os.path.exists(refpath):
363 elif os.path.exists(refpath):
364 f = open(refpath, 'r')
364 f = open(refpath, 'r')
365 self._refout = f.read().splitlines(True)
365 self._refout = f.read().splitlines(True)
366 f.close()
366 f.close()
367 else:
367 else:
368 self._refout = []
368 self._refout = []
369
369
370 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
370 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
371 os.mkdir(self._threadtmp)
371 os.mkdir(self._threadtmp)
372
372
373 def cleanup(self):
373 def cleanup(self):
374 for entry in self._daemonpids:
374 for entry in self._daemonpids:
375 killdaemons(entry)
375 killdaemons(entry)
376
376
377 if self._threadtmp and not self._options.keep_tmpdir:
377 if self._threadtmp and not self._options.keep_tmpdir:
378 shutil.rmtree(self._threadtmp, True)
378 shutil.rmtree(self._threadtmp, True)
379
379
380 def run(self):
380 def run(self):
381 """Run this test instance.
381 """Run this test instance.
382
382
383 This will return a tuple describing the result of the test.
383 This will return a tuple describing the result of the test.
384 """
384 """
385 if not os.path.exists(self._path):
385 if not os.path.exists(self._path):
386 return self.skip("Doesn't exist")
386 return self.skip("Doesn't exist")
387
387
388 options = self._options
388 options = self._options
389 if not (options.whitelisted and self.name in options.whitelisted):
389 if not (options.whitelisted and self.name in options.whitelisted):
390 if options.blacklist and self.name in options.blacklist:
390 if options.blacklist and self.name in options.blacklist:
391 return self.skip('blacklisted')
391 return self.skip('blacklisted')
392
392
393 if options.retest and not os.path.exists('%s.err' % self.name):
393 if options.retest and not os.path.exists('%s.err' % self.name):
394 return self.ignore('not retesting')
394 return self.ignore('not retesting')
395
395
396 if options.keywords:
396 if options.keywords:
397 f = open(self.name)
397 f = open(self.name)
398 t = f.read().lower() + self.name.lower()
398 t = f.read().lower() + self.name.lower()
399 f.close()
399 f.close()
400 for k in options.keywords.lower().split():
400 for k in options.keywords.lower().split():
401 if k in t:
401 if k in t:
402 break
402 break
403 else:
403 else:
404 return self.ignore("doesn't match keyword")
404 return self.ignore("doesn't match keyword")
405
405
406 if not os.path.basename(self.name.lower()).startswith('test-'):
406 if not os.path.basename(self.name.lower()).startswith('test-'):
407 return self.skip('not a test file')
407 return self.skip('not a test file')
408
408
409 # Remove any previous output files.
409 # Remove any previous output files.
410 if os.path.exists(self._errpath):
410 if os.path.exists(self._errpath):
411 os.remove(self._errpath)
411 os.remove(self._errpath)
412
412
413 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
413 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
414 os.mkdir(testtmp)
414 os.mkdir(testtmp)
415 replacements, port = self._getreplacements(testtmp)
415 replacements, port = self._getreplacements(testtmp)
416 env = self._getenv(testtmp, port)
416 env = self._getenv(testtmp, port)
417 self._daemonpids.append(env['DAEMON_PIDS'])
417 self._daemonpids.append(env['DAEMON_PIDS'])
418 self._createhgrc(env['HGRCPATH'])
418 self._createhgrc(env['HGRCPATH'])
419
419
420 vlog('# Test', self.name)
420 vlog('# Test', self.name)
421
421
422 starttime = time.time()
422 starttime = time.time()
423 try:
423 try:
424 ret, out = self._run(testtmp, replacements, env)
424 ret, out = self._run(testtmp, replacements, env)
425 duration = time.time() - starttime
425 duration = time.time() - starttime
426 except KeyboardInterrupt:
426 except KeyboardInterrupt:
427 duration = time.time() - starttime
427 duration = time.time() - starttime
428 log('INTERRUPTED: %s (after %d seconds)' % (self.name, duration))
428 log('INTERRUPTED: %s (after %d seconds)' % (self.name, duration))
429 raise
429 raise
430 except Exception, e:
430 except Exception, e:
431 return self.fail('Exception during execution: %s' % e, 255)
431 return self.fail('Exception during execution: %s' % e, 255)
432
432
433 killdaemons(env['DAEMON_PIDS'])
433 killdaemons(env['DAEMON_PIDS'])
434
434
435 if not options.keep_tmpdir:
435 if not options.keep_tmpdir:
436 shutil.rmtree(testtmp)
436 shutil.rmtree(testtmp)
437
437
438 def describe(ret):
438 def describe(ret):
439 if ret < 0:
439 if ret < 0:
440 return 'killed by signal: %d' % -ret
440 return 'killed by signal: %d' % -ret
441 return 'returned error code %d' % ret
441 return 'returned error code %d' % ret
442
442
443 skipped = False
443 skipped = False
444
444
445 if ret == self.SKIPPED_STATUS:
445 if ret == self.SKIPPED_STATUS:
446 if out is None: # Debug mode, nothing to parse.
446 if out is None: # Debug mode, nothing to parse.
447 missing = ['unknown']
447 missing = ['unknown']
448 failed = None
448 failed = None
449 else:
449 else:
450 missing, failed = TTest.parsehghaveoutput(out)
450 missing, failed = TTest.parsehghaveoutput(out)
451
451
452 if not missing:
452 if not missing:
453 missing = ['irrelevant']
453 missing = ['irrelevant']
454
454
455 if failed:
455 if failed:
456 res = self.fail('hg have failed checking for %s' % failed[-1],
456 res = self.fail('hg have failed checking for %s' % failed[-1],
457 ret)
457 ret)
458 else:
458 else:
459 skipped = True
459 skipped = True
460 res = self.skip(missing[-1])
460 res = self.skip(missing[-1])
461 elif ret == 'timeout':
461 elif ret == 'timeout':
462 res = self.fail('timed out', ret)
462 res = self.fail('timed out', ret)
463 elif out != self._refout:
463 elif out != self._refout:
464 info = {}
464 info = {}
465 if not options.nodiff:
465 if not options.nodiff:
466 iolock.acquire()
466 iolock.acquire()
467 if options.view:
467 if options.view:
468 os.system("%s %s %s" % (options.view, self._refpath,
468 os.system("%s %s %s" % (options.view, self._refpath,
469 self._errpath))
469 self._errpath))
470 else:
470 else:
471 info = showdiff(self._refout, out, self._refpath,
471 info = showdiff(self._refout, out, self._refpath,
472 self._errpath)
472 self._errpath)
473 iolock.release()
473 iolock.release()
474 msg = ''
474 msg = ''
475 if info.get('servefail'):
475 if info.get('servefail'):
476 msg += 'serve failed and '
476 msg += 'serve failed and '
477 if ret:
477 if ret:
478 msg += 'output changed and ' + describe(ret)
478 msg += 'output changed and ' + describe(ret)
479 else:
479 else:
480 msg += 'output changed'
480 msg += 'output changed'
481
481
482 if (ret != 0 or out != self._refout) and not skipped \
482 if (ret != 0 or out != self._refout) and not skipped \
483 and not options.debug:
483 and not options.debug:
484 f = open(self._errpath, 'wb')
484 f = open(self._errpath, 'wb')
485 for line in out:
485 for line in out:
486 f.write(line)
486 f.write(line)
487 f.close()
487 f.close()
488 res = self.fail(msg, ret)
488 res = self.fail(msg, ret)
489 elif ret:
489 elif ret:
490 res = self.fail(describe(ret), ret)
490 res = self.fail(describe(ret), ret)
491 else:
491 else:
492 res = self.success()
492 res = self.success()
493
493
494
494
495 vlog("# Ret was:", ret)
495 vlog("# Ret was:", ret)
496
496
497 # Don't print progress in unittest mode because that is handled
497 # Don't print progress in unittest mode because that is handled
498 # by TestResult.
498 # by TestResult.
499 if not options.verbose and not self._unittest:
499 if not options.verbose and not self._unittest:
500 iolock.acquire()
500 iolock.acquire()
501 sys.stdout.write(res[0])
501 sys.stdout.write(res[0])
502 sys.stdout.flush()
502 sys.stdout.flush()
503 iolock.release()
503 iolock.release()
504
504
505 self._runner.times.append((self.name, duration))
505 self._runner.times.append((self.name, duration))
506
506
507 return res
507 return res
508
508
509 def _run(self, testtmp, replacements, env):
509 def _run(self, testtmp, replacements, env):
510 # This should be implemented in child classes to run tests.
510 # This should be implemented in child classes to run tests.
511 return self._skip('unknown test type')
511 return self._skip('unknown test type')
512
512
513 def _getreplacements(self, testtmp):
513 def _getreplacements(self, testtmp):
514 port = self._options.port + self._count * 3
514 port = self._options.port + self._count * 3
515 r = [
515 r = [
516 (r':%s\b' % port, ':$HGPORT'),
516 (r':%s\b' % port, ':$HGPORT'),
517 (r':%s\b' % (port + 1), ':$HGPORT1'),
517 (r':%s\b' % (port + 1), ':$HGPORT1'),
518 (r':%s\b' % (port + 2), ':$HGPORT2'),
518 (r':%s\b' % (port + 2), ':$HGPORT2'),
519 ]
519 ]
520
520
521 if os.name == 'nt':
521 if os.name == 'nt':
522 r.append(
522 r.append(
523 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
523 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
524 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
524 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
525 for c in testtmp), '$TESTTMP'))
525 for c in testtmp), '$TESTTMP'))
526 else:
526 else:
527 r.append((re.escape(testtmp), '$TESTTMP'))
527 r.append((re.escape(testtmp), '$TESTTMP'))
528
528
529 return r, port
529 return r, port
530
530
531 def _getenv(self, testtmp, port):
531 def _getenv(self, testtmp, port):
532 env = os.environ.copy()
532 env = os.environ.copy()
533 env['TESTTMP'] = testtmp
533 env['TESTTMP'] = testtmp
534 env['HOME'] = testtmp
534 env['HOME'] = testtmp
535 env["HGPORT"] = str(port)
535 env["HGPORT"] = str(port)
536 env["HGPORT1"] = str(port + 1)
536 env["HGPORT1"] = str(port + 1)
537 env["HGPORT2"] = str(port + 2)
537 env["HGPORT2"] = str(port + 2)
538 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
538 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
539 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
539 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
540 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
540 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
541 env["HGMERGE"] = "internal:merge"
541 env["HGMERGE"] = "internal:merge"
542 env["HGUSER"] = "test"
542 env["HGUSER"] = "test"
543 env["HGENCODING"] = "ascii"
543 env["HGENCODING"] = "ascii"
544 env["HGENCODINGMODE"] = "strict"
544 env["HGENCODINGMODE"] = "strict"
545
545
546 # Reset some environment variables to well-known values so that
546 # Reset some environment variables to well-known values so that
547 # the tests produce repeatable output.
547 # the tests produce repeatable output.
548 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
548 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
549 env['TZ'] = 'GMT'
549 env['TZ'] = 'GMT'
550 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
550 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
551 env['COLUMNS'] = '80'
551 env['COLUMNS'] = '80'
552 env['TERM'] = 'xterm'
552 env['TERM'] = 'xterm'
553
553
554 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
554 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
555 'NO_PROXY').split():
555 'NO_PROXY').split():
556 if k in env:
556 if k in env:
557 del env[k]
557 del env[k]
558
558
559 # unset env related to hooks
559 # unset env related to hooks
560 for k in env.keys():
560 for k in env.keys():
561 if k.startswith('HG_'):
561 if k.startswith('HG_'):
562 del env[k]
562 del env[k]
563
563
564 return env
564 return env
565
565
566 def _createhgrc(self, path):
566 def _createhgrc(self, path):
567 # create a fresh hgrc
567 # create a fresh hgrc
568 hgrc = open(path, 'w')
568 hgrc = open(path, 'w')
569 hgrc.write('[ui]\n')
569 hgrc.write('[ui]\n')
570 hgrc.write('slash = True\n')
570 hgrc.write('slash = True\n')
571 hgrc.write('interactive = False\n')
571 hgrc.write('interactive = False\n')
572 hgrc.write('[defaults]\n')
572 hgrc.write('[defaults]\n')
573 hgrc.write('backout = -d "0 0"\n')
573 hgrc.write('backout = -d "0 0"\n')
574 hgrc.write('commit = -d "0 0"\n')
574 hgrc.write('commit = -d "0 0"\n')
575 hgrc.write('shelve = --date "0 0"\n')
575 hgrc.write('shelve = --date "0 0"\n')
576 hgrc.write('tag = -d "0 0"\n')
576 hgrc.write('tag = -d "0 0"\n')
577 if self._options.extra_config_opt:
577 if self._options.extra_config_opt:
578 for opt in self._options.extra_config_opt:
578 for opt in self._options.extra_config_opt:
579 section, key = opt.split('.', 1)
579 section, key = opt.split('.', 1)
580 assert '=' in key, ('extra config opt %s must '
580 assert '=' in key, ('extra config opt %s must '
581 'have an = for assignment' % opt)
581 'have an = for assignment' % opt)
582 hgrc.write('[%s]\n%s\n' % (section, key))
582 hgrc.write('[%s]\n%s\n' % (section, key))
583 hgrc.close()
583 hgrc.close()
584
584
585 def success(self):
585 def success(self):
586 return '.', self.name, ''
586 return '.', self.name, ''
587
587
588 def fail(self, msg, ret):
588 def fail(self, msg, ret):
589 warned = ret is False
589 warned = ret is False
590 if not self._options.nodiff:
590 if not self._options.nodiff:
591 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
591 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
592 msg))
592 msg))
593 if (not ret and self._options.interactive and
593 if (not ret and self._options.interactive and
594 os.path.exists(self._errpath)):
594 os.path.exists(self._errpath)):
595 iolock.acquire()
595 iolock.acquire()
596 print 'Accept this change? [n] ',
596 print 'Accept this change? [n] ',
597 answer = sys.stdin.readline().strip()
597 answer = sys.stdin.readline().strip()
598 iolock.release()
598 iolock.release()
599 if answer.lower() in ('y', 'yes'):
599 if answer.lower() in ('y', 'yes'):
600 if self.name.endswith('.t'):
600 if self.name.endswith('.t'):
601 rename(self._errpath, self._path)
601 rename(self._errpath, self._path)
602 else:
602 else:
603 rename(self._errpath, '%s.out' % self._path)
603 rename(self._errpath, '%s.out' % self._path)
604
604
605 return '.', self.name, ''
605 return '.', self.name, ''
606
606
607 if self._unittest:
608 if warned:
609 raise WarnTest(msg)
610
607 return warned and '~' or '!', self.name, msg
611 return warned and '~' or '!', self.name, msg
608
612
609 def skip(self, msg):
613 def skip(self, msg):
610 if self._unittest:
614 if self._unittest:
611 raise SkipTest(msg)
615 raise SkipTest(msg)
612
616
613 if self._options.verbose:
617 if self._options.verbose:
614 log("\nSkipping %s: %s" % (self._path, msg))
618 log("\nSkipping %s: %s" % (self._path, msg))
615
619
616 return 's', self.name, msg
620 return 's', self.name, msg
617
621
618 def ignore(self, msg):
622 def ignore(self, msg):
619 if self._unittest:
623 if self._unittest:
620 raise IgnoreTest(msg)
624 raise IgnoreTest(msg)
621
625
622 return 'i', self.name, msg
626 return 'i', self.name, msg
623
627
624 class PythonTest(Test):
628 class PythonTest(Test):
625 """A Python-based test."""
629 """A Python-based test."""
626 def _run(self, testtmp, replacements, env):
630 def _run(self, testtmp, replacements, env):
627 py3kswitch = self._options.py3k_warnings and ' -3' or ''
631 py3kswitch = self._options.py3k_warnings and ' -3' or ''
628 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
632 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
629 vlog("# Running", cmd)
633 vlog("# Running", cmd)
630 if os.name == 'nt':
634 if os.name == 'nt':
631 replacements.append((r'\r\n', '\n'))
635 replacements.append((r'\r\n', '\n'))
632 return run(cmd, testtmp, self._options, replacements, env,
636 return run(cmd, testtmp, self._options, replacements, env,
633 self._runner.abort)
637 self._runner.abort)
634
638
635 class TTest(Test):
639 class TTest(Test):
636 """A "t test" is a test backed by a .t file."""
640 """A "t test" is a test backed by a .t file."""
637
641
638 SKIPPED_PREFIX = 'skipped: '
642 SKIPPED_PREFIX = 'skipped: '
639 FAILED_PREFIX = 'hghave check failed: '
643 FAILED_PREFIX = 'hghave check failed: '
640 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
644 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
641
645
642 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
646 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
643 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
647 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
644 {'\\': '\\\\', '\r': r'\r'})
648 {'\\': '\\\\', '\r': r'\r'})
645
649
646 def _run(self, testtmp, replacements, env):
650 def _run(self, testtmp, replacements, env):
647 f = open(self._path)
651 f = open(self._path)
648 lines = f.readlines()
652 lines = f.readlines()
649 f.close()
653 f.close()
650
654
651 salt, script, after, expected = self._parsetest(lines, testtmp)
655 salt, script, after, expected = self._parsetest(lines, testtmp)
652
656
653 # Write out the generated script.
657 # Write out the generated script.
654 fname = '%s.sh' % testtmp
658 fname = '%s.sh' % testtmp
655 f = open(fname, 'w')
659 f = open(fname, 'w')
656 for l in script:
660 for l in script:
657 f.write(l)
661 f.write(l)
658 f.close()
662 f.close()
659
663
660 cmd = '%s "%s"' % (self._options.shell, fname)
664 cmd = '%s "%s"' % (self._options.shell, fname)
661 vlog("# Running", cmd)
665 vlog("# Running", cmd)
662
666
663 exitcode, output = run(cmd, testtmp, self._options, replacements, env,
667 exitcode, output = run(cmd, testtmp, self._options, replacements, env,
664 self._runner.abort)
668 self._runner.abort)
665 # Do not merge output if skipped. Return hghave message instead.
669 # Do not merge output if skipped. Return hghave message instead.
666 # Similarly, with --debug, output is None.
670 # Similarly, with --debug, output is None.
667 if exitcode == self.SKIPPED_STATUS or output is None:
671 if exitcode == self.SKIPPED_STATUS or output is None:
668 return exitcode, output
672 return exitcode, output
669
673
670 return self._processoutput(exitcode, output, salt, after, expected)
674 return self._processoutput(exitcode, output, salt, after, expected)
671
675
672 def _hghave(self, reqs, testtmp):
676 def _hghave(self, reqs, testtmp):
673 # TODO do something smarter when all other uses of hghave are gone.
677 # TODO do something smarter when all other uses of hghave are gone.
674 tdir = self._testdir.replace('\\', '/')
678 tdir = self._testdir.replace('\\', '/')
675 proc = Popen4('%s -c "%s/hghave %s"' %
679 proc = Popen4('%s -c "%s/hghave %s"' %
676 (self._options.shell, tdir, ' '.join(reqs)),
680 (self._options.shell, tdir, ' '.join(reqs)),
677 testtmp, 0)
681 testtmp, 0)
678 stdout, stderr = proc.communicate()
682 stdout, stderr = proc.communicate()
679 ret = proc.wait()
683 ret = proc.wait()
680 if wifexited(ret):
684 if wifexited(ret):
681 ret = os.WEXITSTATUS(ret)
685 ret = os.WEXITSTATUS(ret)
682 if ret == 2:
686 if ret == 2:
683 print stdout
687 print stdout
684 sys.exit(1)
688 sys.exit(1)
685
689
686 return ret == 0
690 return ret == 0
687
691
688 def _parsetest(self, lines, testtmp):
692 def _parsetest(self, lines, testtmp):
689 # We generate a shell script which outputs unique markers to line
693 # We generate a shell script which outputs unique markers to line
690 # up script results with our source. These markers include input
694 # up script results with our source. These markers include input
691 # line number and the last return code.
695 # line number and the last return code.
692 salt = "SALT" + str(time.time())
696 salt = "SALT" + str(time.time())
693 def addsalt(line, inpython):
697 def addsalt(line, inpython):
694 if inpython:
698 if inpython:
695 script.append('%s %d 0\n' % (salt, line))
699 script.append('%s %d 0\n' % (salt, line))
696 else:
700 else:
697 script.append('echo %s %s $?\n' % (salt, line))
701 script.append('echo %s %s $?\n' % (salt, line))
698
702
699 script = []
703 script = []
700
704
701 # After we run the shell script, we re-unify the script output
705 # After we run the shell script, we re-unify the script output
702 # with non-active parts of the source, with synchronization by our
706 # with non-active parts of the source, with synchronization by our
703 # SALT line number markers. The after table contains the non-active
707 # SALT line number markers. The after table contains the non-active
704 # components, ordered by line number.
708 # components, ordered by line number.
705 after = {}
709 after = {}
706
710
707 # Expected shell script output.
711 # Expected shell script output.
708 expected = {}
712 expected = {}
709
713
710 pos = prepos = -1
714 pos = prepos = -1
711
715
712 # True or False when in a true or false conditional section
716 # True or False when in a true or false conditional section
713 skipping = None
717 skipping = None
714
718
715 # We keep track of whether or not we're in a Python block so we
719 # We keep track of whether or not we're in a Python block so we
716 # can generate the surrounding doctest magic.
720 # can generate the surrounding doctest magic.
717 inpython = False
721 inpython = False
718
722
719 if self._options.debug:
723 if self._options.debug:
720 script.append('set -x\n')
724 script.append('set -x\n')
721 if os.getenv('MSYSTEM'):
725 if os.getenv('MSYSTEM'):
722 script.append('alias pwd="pwd -W"\n')
726 script.append('alias pwd="pwd -W"\n')
723
727
724 for n, l in enumerate(lines):
728 for n, l in enumerate(lines):
725 if not l.endswith('\n'):
729 if not l.endswith('\n'):
726 l += '\n'
730 l += '\n'
727 if l.startswith('#if'):
731 if l.startswith('#if'):
728 lsplit = l.split()
732 lsplit = l.split()
729 if len(lsplit) < 2 or lsplit[0] != '#if':
733 if len(lsplit) < 2 or lsplit[0] != '#if':
730 after.setdefault(pos, []).append(' !!! invalid #if\n')
734 after.setdefault(pos, []).append(' !!! invalid #if\n')
731 if skipping is not None:
735 if skipping is not None:
732 after.setdefault(pos, []).append(' !!! nested #if\n')
736 after.setdefault(pos, []).append(' !!! nested #if\n')
733 skipping = not self._hghave(lsplit[1:], testtmp)
737 skipping = not self._hghave(lsplit[1:], testtmp)
734 after.setdefault(pos, []).append(l)
738 after.setdefault(pos, []).append(l)
735 elif l.startswith('#else'):
739 elif l.startswith('#else'):
736 if skipping is None:
740 if skipping is None:
737 after.setdefault(pos, []).append(' !!! missing #if\n')
741 after.setdefault(pos, []).append(' !!! missing #if\n')
738 skipping = not skipping
742 skipping = not skipping
739 after.setdefault(pos, []).append(l)
743 after.setdefault(pos, []).append(l)
740 elif l.startswith('#endif'):
744 elif l.startswith('#endif'):
741 if skipping is None:
745 if skipping is None:
742 after.setdefault(pos, []).append(' !!! missing #if\n')
746 after.setdefault(pos, []).append(' !!! missing #if\n')
743 skipping = None
747 skipping = None
744 after.setdefault(pos, []).append(l)
748 after.setdefault(pos, []).append(l)
745 elif skipping:
749 elif skipping:
746 after.setdefault(pos, []).append(l)
750 after.setdefault(pos, []).append(l)
747 elif l.startswith(' >>> '): # python inlines
751 elif l.startswith(' >>> '): # python inlines
748 after.setdefault(pos, []).append(l)
752 after.setdefault(pos, []).append(l)
749 prepos = pos
753 prepos = pos
750 pos = n
754 pos = n
751 if not inpython:
755 if not inpython:
752 # We've just entered a Python block. Add the header.
756 # We've just entered a Python block. Add the header.
753 inpython = True
757 inpython = True
754 addsalt(prepos, False) # Make sure we report the exit code.
758 addsalt(prepos, False) # Make sure we report the exit code.
755 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
759 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
756 addsalt(n, True)
760 addsalt(n, True)
757 script.append(l[2:])
761 script.append(l[2:])
758 elif l.startswith(' ... '): # python inlines
762 elif l.startswith(' ... '): # python inlines
759 after.setdefault(prepos, []).append(l)
763 after.setdefault(prepos, []).append(l)
760 script.append(l[2:])
764 script.append(l[2:])
761 elif l.startswith(' $ '): # commands
765 elif l.startswith(' $ '): # commands
762 if inpython:
766 if inpython:
763 script.append('EOF\n')
767 script.append('EOF\n')
764 inpython = False
768 inpython = False
765 after.setdefault(pos, []).append(l)
769 after.setdefault(pos, []).append(l)
766 prepos = pos
770 prepos = pos
767 pos = n
771 pos = n
768 addsalt(n, False)
772 addsalt(n, False)
769 cmd = l[4:].split()
773 cmd = l[4:].split()
770 if len(cmd) == 2 and cmd[0] == 'cd':
774 if len(cmd) == 2 and cmd[0] == 'cd':
771 l = ' $ cd %s || exit 1\n' % cmd[1]
775 l = ' $ cd %s || exit 1\n' % cmd[1]
772 script.append(l[4:])
776 script.append(l[4:])
773 elif l.startswith(' > '): # continuations
777 elif l.startswith(' > '): # continuations
774 after.setdefault(prepos, []).append(l)
778 after.setdefault(prepos, []).append(l)
775 script.append(l[4:])
779 script.append(l[4:])
776 elif l.startswith(' '): # results
780 elif l.startswith(' '): # results
777 # Queue up a list of expected results.
781 # Queue up a list of expected results.
778 expected.setdefault(pos, []).append(l[2:])
782 expected.setdefault(pos, []).append(l[2:])
779 else:
783 else:
780 if inpython:
784 if inpython:
781 script.append('EOF\n')
785 script.append('EOF\n')
782 inpython = False
786 inpython = False
783 # Non-command/result. Queue up for merged output.
787 # Non-command/result. Queue up for merged output.
784 after.setdefault(pos, []).append(l)
788 after.setdefault(pos, []).append(l)
785
789
786 if inpython:
790 if inpython:
787 script.append('EOF\n')
791 script.append('EOF\n')
788 if skipping is not None:
792 if skipping is not None:
789 after.setdefault(pos, []).append(' !!! missing #endif\n')
793 after.setdefault(pos, []).append(' !!! missing #endif\n')
790 addsalt(n + 1, False)
794 addsalt(n + 1, False)
791
795
792 return salt, script, after, expected
796 return salt, script, after, expected
793
797
794 def _processoutput(self, exitcode, output, salt, after, expected):
798 def _processoutput(self, exitcode, output, salt, after, expected):
795 # Merge the script output back into a unified test.
799 # Merge the script output back into a unified test.
796 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
800 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
797 if exitcode != 0:
801 if exitcode != 0:
798 warnonly = 3
802 warnonly = 3
799
803
800 pos = -1
804 pos = -1
801 postout = []
805 postout = []
802 for l in output:
806 for l in output:
803 lout, lcmd = l, None
807 lout, lcmd = l, None
804 if salt in l:
808 if salt in l:
805 lout, lcmd = l.split(salt, 1)
809 lout, lcmd = l.split(salt, 1)
806
810
807 if lout:
811 if lout:
808 if not lout.endswith('\n'):
812 if not lout.endswith('\n'):
809 lout += ' (no-eol)\n'
813 lout += ' (no-eol)\n'
810
814
811 # Find the expected output at the current position.
815 # Find the expected output at the current position.
812 el = None
816 el = None
813 if expected.get(pos, None):
817 if expected.get(pos, None):
814 el = expected[pos].pop(0)
818 el = expected[pos].pop(0)
815
819
816 r = TTest.linematch(el, lout)
820 r = TTest.linematch(el, lout)
817 if isinstance(r, str):
821 if isinstance(r, str):
818 if r == '+glob':
822 if r == '+glob':
819 lout = el[:-1] + ' (glob)\n'
823 lout = el[:-1] + ' (glob)\n'
820 r = '' # Warn only this line.
824 r = '' # Warn only this line.
821 elif r == '-glob':
825 elif r == '-glob':
822 lout = ''.join(el.rsplit(' (glob)', 1))
826 lout = ''.join(el.rsplit(' (glob)', 1))
823 r = '' # Warn only this line.
827 r = '' # Warn only this line.
824 else:
828 else:
825 log('\ninfo, unknown linematch result: %r\n' % r)
829 log('\ninfo, unknown linematch result: %r\n' % r)
826 r = False
830 r = False
827 if r:
831 if r:
828 postout.append(' ' + el)
832 postout.append(' ' + el)
829 else:
833 else:
830 if self.NEEDESCAPE(lout):
834 if self.NEEDESCAPE(lout):
831 lout = TTest.stringescape('%s (esc)\n' %
835 lout = TTest.stringescape('%s (esc)\n' %
832 lout.rstrip('\n'))
836 lout.rstrip('\n'))
833 postout.append(' ' + lout) # Let diff deal with it.
837 postout.append(' ' + lout) # Let diff deal with it.
834 if r != '': # If line failed.
838 if r != '': # If line failed.
835 warnonly = 3 # for sure not
839 warnonly = 3 # for sure not
836 elif warnonly == 1: # Is "not yet" and line is warn only.
840 elif warnonly == 1: # Is "not yet" and line is warn only.
837 warnonly = 2 # Yes do warn.
841 warnonly = 2 # Yes do warn.
838
842
839 if lcmd:
843 if lcmd:
840 # Add on last return code.
844 # Add on last return code.
841 ret = int(lcmd.split()[1])
845 ret = int(lcmd.split()[1])
842 if ret != 0:
846 if ret != 0:
843 postout.append(' [%s]\n' % ret)
847 postout.append(' [%s]\n' % ret)
844 if pos in after:
848 if pos in after:
845 # Merge in non-active test bits.
849 # Merge in non-active test bits.
846 postout += after.pop(pos)
850 postout += after.pop(pos)
847 pos = int(lcmd.split()[0])
851 pos = int(lcmd.split()[0])
848
852
849 if pos in after:
853 if pos in after:
850 postout += after.pop(pos)
854 postout += after.pop(pos)
851
855
852 if warnonly == 2:
856 if warnonly == 2:
853 exitcode = False # Set exitcode to warned.
857 exitcode = False # Set exitcode to warned.
854
858
855 return exitcode, postout
859 return exitcode, postout
856
860
857 @staticmethod
861 @staticmethod
858 def rematch(el, l):
862 def rematch(el, l):
859 try:
863 try:
860 # use \Z to ensure that the regex matches to the end of the string
864 # use \Z to ensure that the regex matches to the end of the string
861 if os.name == 'nt':
865 if os.name == 'nt':
862 return re.match(el + r'\r?\n\Z', l)
866 return re.match(el + r'\r?\n\Z', l)
863 return re.match(el + r'\n\Z', l)
867 return re.match(el + r'\n\Z', l)
864 except re.error:
868 except re.error:
865 # el is an invalid regex
869 # el is an invalid regex
866 return False
870 return False
867
871
868 @staticmethod
872 @staticmethod
869 def globmatch(el, l):
873 def globmatch(el, l):
870 # The only supported special characters are * and ? plus / which also
874 # The only supported special characters are * and ? plus / which also
871 # matches \ on windows. Escaping of these characters is supported.
875 # matches \ on windows. Escaping of these characters is supported.
872 if el + '\n' == l:
876 if el + '\n' == l:
873 if os.altsep:
877 if os.altsep:
874 # matching on "/" is not needed for this line
878 # matching on "/" is not needed for this line
875 return '-glob'
879 return '-glob'
876 return True
880 return True
877 i, n = 0, len(el)
881 i, n = 0, len(el)
878 res = ''
882 res = ''
879 while i < n:
883 while i < n:
880 c = el[i]
884 c = el[i]
881 i += 1
885 i += 1
882 if c == '\\' and el[i] in '*?\\/':
886 if c == '\\' and el[i] in '*?\\/':
883 res += el[i - 1:i + 1]
887 res += el[i - 1:i + 1]
884 i += 1
888 i += 1
885 elif c == '*':
889 elif c == '*':
886 res += '.*'
890 res += '.*'
887 elif c == '?':
891 elif c == '?':
888 res += '.'
892 res += '.'
889 elif c == '/' and os.altsep:
893 elif c == '/' and os.altsep:
890 res += '[/\\\\]'
894 res += '[/\\\\]'
891 else:
895 else:
892 res += re.escape(c)
896 res += re.escape(c)
893 return TTest.rematch(res, l)
897 return TTest.rematch(res, l)
894
898
895 @staticmethod
899 @staticmethod
896 def linematch(el, l):
900 def linematch(el, l):
897 if el == l: # perfect match (fast)
901 if el == l: # perfect match (fast)
898 return True
902 return True
899 if el:
903 if el:
900 if el.endswith(" (esc)\n"):
904 if el.endswith(" (esc)\n"):
901 el = el[:-7].decode('string-escape') + '\n'
905 el = el[:-7].decode('string-escape') + '\n'
902 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
906 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
903 return True
907 return True
904 if el.endswith(" (re)\n"):
908 if el.endswith(" (re)\n"):
905 return TTest.rematch(el[:-6], l)
909 return TTest.rematch(el[:-6], l)
906 if el.endswith(" (glob)\n"):
910 if el.endswith(" (glob)\n"):
907 return TTest.globmatch(el[:-8], l)
911 return TTest.globmatch(el[:-8], l)
908 if os.altsep and l.replace('\\', '/') == el:
912 if os.altsep and l.replace('\\', '/') == el:
909 return '+glob'
913 return '+glob'
910 return False
914 return False
911
915
912 @staticmethod
916 @staticmethod
913 def parsehghaveoutput(lines):
917 def parsehghaveoutput(lines):
914 '''Parse hghave log lines.
918 '''Parse hghave log lines.
915
919
916 Return tuple of lists (missing, failed):
920 Return tuple of lists (missing, failed):
917 * the missing/unknown features
921 * the missing/unknown features
918 * the features for which existence check failed'''
922 * the features for which existence check failed'''
919 missing = []
923 missing = []
920 failed = []
924 failed = []
921 for line in lines:
925 for line in lines:
922 if line.startswith(TTest.SKIPPED_PREFIX):
926 if line.startswith(TTest.SKIPPED_PREFIX):
923 line = line.splitlines()[0]
927 line = line.splitlines()[0]
924 missing.append(line[len(TTest.SKIPPED_PREFIX):])
928 missing.append(line[len(TTest.SKIPPED_PREFIX):])
925 elif line.startswith(TTest.FAILED_PREFIX):
929 elif line.startswith(TTest.FAILED_PREFIX):
926 line = line.splitlines()[0]
930 line = line.splitlines()[0]
927 failed.append(line[len(TTest.FAILED_PREFIX):])
931 failed.append(line[len(TTest.FAILED_PREFIX):])
928
932
929 return missing, failed
933 return missing, failed
930
934
931 @staticmethod
935 @staticmethod
932 def _escapef(m):
936 def _escapef(m):
933 return TTest.ESCAPEMAP[m.group(0)]
937 return TTest.ESCAPEMAP[m.group(0)]
934
938
935 @staticmethod
939 @staticmethod
936 def _stringescape(s):
940 def _stringescape(s):
937 return TTest.ESCAPESUB(TTest._escapef, s)
941 return TTest.ESCAPESUB(TTest._escapef, s)
938
942
939
943
940 wifexited = getattr(os, "WIFEXITED", lambda x: False)
944 wifexited = getattr(os, "WIFEXITED", lambda x: False)
941 def run(cmd, wd, options, replacements, env, abort):
945 def run(cmd, wd, options, replacements, env, abort):
942 """Run command in a sub-process, capturing the output (stdout and stderr).
946 """Run command in a sub-process, capturing the output (stdout and stderr).
943 Return a tuple (exitcode, output). output is None in debug mode."""
947 Return a tuple (exitcode, output). output is None in debug mode."""
944 # TODO: Use subprocess.Popen if we're running on Python 2.4
948 # TODO: Use subprocess.Popen if we're running on Python 2.4
945 if options.debug:
949 if options.debug:
946 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
950 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
947 ret = proc.wait()
951 ret = proc.wait()
948 return (ret, None)
952 return (ret, None)
949
953
950 proc = Popen4(cmd, wd, options.timeout, env)
954 proc = Popen4(cmd, wd, options.timeout, env)
951 def cleanup():
955 def cleanup():
952 terminate(proc)
956 terminate(proc)
953 ret = proc.wait()
957 ret = proc.wait()
954 if ret == 0:
958 if ret == 0:
955 ret = signal.SIGTERM << 8
959 ret = signal.SIGTERM << 8
956 killdaemons(env['DAEMON_PIDS'])
960 killdaemons(env['DAEMON_PIDS'])
957 return ret
961 return ret
958
962
959 output = ''
963 output = ''
960 proc.tochild.close()
964 proc.tochild.close()
961
965
962 try:
966 try:
963 output = proc.fromchild.read()
967 output = proc.fromchild.read()
964 except KeyboardInterrupt:
968 except KeyboardInterrupt:
965 vlog('# Handling keyboard interrupt')
969 vlog('# Handling keyboard interrupt')
966 cleanup()
970 cleanup()
967 raise
971 raise
968
972
969 ret = proc.wait()
973 ret = proc.wait()
970 if wifexited(ret):
974 if wifexited(ret):
971 ret = os.WEXITSTATUS(ret)
975 ret = os.WEXITSTATUS(ret)
972
976
973 if proc.timeout:
977 if proc.timeout:
974 ret = 'timeout'
978 ret = 'timeout'
975
979
976 if ret:
980 if ret:
977 killdaemons(env['DAEMON_PIDS'])
981 killdaemons(env['DAEMON_PIDS'])
978
982
979 if abort[0]:
983 if abort[0]:
980 raise KeyboardInterrupt()
984 raise KeyboardInterrupt()
981
985
982 for s, r in replacements:
986 for s, r in replacements:
983 output = re.sub(s, r, output)
987 output = re.sub(s, r, output)
984 return ret, output.splitlines(True)
988 return ret, output.splitlines(True)
985
989
986 iolock = threading.Lock()
990 iolock = threading.Lock()
987
991
988 class SkipTest(Exception):
992 class SkipTest(Exception):
989 """Raised to indicate that a test is to be skipped."""
993 """Raised to indicate that a test is to be skipped."""
990
994
991 class IgnoreTest(Exception):
995 class IgnoreTest(Exception):
992 """Raised to indicate that a test is to be ignored."""
996 """Raised to indicate that a test is to be ignored."""
993
997
998 class WarnTest(Exception):
999 """Raised to indicate that a test warned."""
1000
994 class TestResult(unittest._TextTestResult):
1001 class TestResult(unittest._TextTestResult):
995 """Holds results when executing via unittest."""
1002 """Holds results when executing via unittest."""
996 # Don't worry too much about accessing the non-public _TextTestResult.
1003 # Don't worry too much about accessing the non-public _TextTestResult.
997 # It is relatively common in Python testing tools.
1004 # It is relatively common in Python testing tools.
998 def __init__(self, *args, **kwargs):
1005 def __init__(self, *args, **kwargs):
999 super(TestResult, self).__init__(*args, **kwargs)
1006 super(TestResult, self).__init__(*args, **kwargs)
1000
1007
1001 # unittest.TestResult didn't have skipped until 2.7. We need to
1008 # unittest.TestResult didn't have skipped until 2.7. We need to
1002 # polyfill it.
1009 # polyfill it.
1003 self.skipped = []
1010 self.skipped = []
1004
1011
1005 # We have a custom "ignored" result that isn't present in any Python
1012 # We have a custom "ignored" result that isn't present in any Python
1006 # unittest implementation. It is very similar to skipped. It may make
1013 # unittest implementation. It is very similar to skipped. It may make
1007 # sense to map it into skip some day.
1014 # sense to map it into skip some day.
1008 self.ignored = []
1015 self.ignored = []
1009
1016
1010 # We have a custom "warned" result that isn't present in any Python
1017 # We have a custom "warned" result that isn't present in any Python
1011 # unittest implementation. It is very similar to failed. It may make
1018 # unittest implementation. It is very similar to failed. It may make
1012 # sense to map it into fail some day.
1019 # sense to map it into fail some day.
1013 self.warned = []
1020 self.warned = []
1014
1021
1015 # Polyfill.
1022 # Polyfill.
1016 def addSkip(self, test, reason):
1023 def addSkip(self, test, reason):
1017 self.skipped.append((test, reason))
1024 self.skipped.append((test, reason))
1018
1025
1019 if self.showAll:
1026 if self.showAll:
1020 self.stream.writeln('skipped %s' % reason)
1027 self.stream.writeln('skipped %s' % reason)
1021 else:
1028 else:
1022 self.stream.write('s')
1029 self.stream.write('s')
1023 self.stream.flush()
1030 self.stream.flush()
1024
1031
1025 def addIgnore(self, test, reason):
1032 def addIgnore(self, test, reason):
1026 self.ignored.append((test, reason))
1033 self.ignored.append((test, reason))
1027
1034
1028 if self.showAll:
1035 if self.showAll:
1029 self.stream.writeln('ignored %s' % reason)
1036 self.stream.writeln('ignored %s' % reason)
1030 else:
1037 else:
1031 self.stream.write('i')
1038 self.stream.write('i')
1032 self.stream.flush()
1039 self.stream.flush()
1033
1040
1034 def addWarn(self, test, reason):
1041 def addWarn(self, test, reason):
1035 self.warned.append((test, reason))
1042 self.warned.append((test, reason))
1036
1043
1037 if self.showAll:
1044 if self.showAll:
1038 self.stream.writeln('warned %s' % reason)
1045 self.stream.writeln('warned %s' % reason)
1039 else:
1046 else:
1040 self.stream.write('~')
1047 self.stream.write('~')
1041 self.stream.flush()
1048 self.stream.flush()
1042
1049
1043 class TestSuite(unittest.TestSuite):
1050 class TestSuite(unittest.TestSuite):
1044 """Custom unitest TestSuite that knows how to execute concurrently."""
1051 """Custom unitest TestSuite that knows how to execute concurrently."""
1045
1052
1046 def __init__(self, runner, *args, **kwargs):
1053 def __init__(self, runner, *args, **kwargs):
1047 super(TestSuite, self).__init__(*args, **kwargs)
1054 super(TestSuite, self).__init__(*args, **kwargs)
1048
1055
1049 self._runner = runner
1056 self._runner = runner
1050
1057
1051 def run(self, result):
1058 def run(self, result):
1052 self._runner._executetests(self._tests, result=result)
1059 self._runner._executetests(self._tests, result=result)
1053
1060
1054 return result
1061 return result
1055
1062
1056 class TextTestRunner(unittest.TextTestRunner):
1063 class TextTestRunner(unittest.TextTestRunner):
1057 """Custom unittest test runner that uses appropriate settings."""
1064 """Custom unittest test runner that uses appropriate settings."""
1058
1065
1059 def _makeResult(self):
1066 def _makeResult(self):
1060 return TestResult(self.stream, self.descriptions, self.verbosity)
1067 return TestResult(self.stream, self.descriptions, self.verbosity)
1061
1068
1062 class TestRunner(object):
1069 class TestRunner(object):
1063 """Holds context for executing tests.
1070 """Holds context for executing tests.
1064
1071
1065 Tests rely on a lot of state. This object holds it for them.
1072 Tests rely on a lot of state. This object holds it for them.
1066 """
1073 """
1067
1074
1068 REQUIREDTOOLS = [
1075 REQUIREDTOOLS = [
1069 os.path.basename(sys.executable),
1076 os.path.basename(sys.executable),
1070 'diff',
1077 'diff',
1071 'grep',
1078 'grep',
1072 'unzip',
1079 'unzip',
1073 'gunzip',
1080 'gunzip',
1074 'bunzip2',
1081 'bunzip2',
1075 'sed',
1082 'sed',
1076 ]
1083 ]
1077
1084
1078 TESTTYPES = [
1085 TESTTYPES = [
1079 ('.py', PythonTest, '.out'),
1086 ('.py', PythonTest, '.out'),
1080 ('.t', TTest, ''),
1087 ('.t', TTest, ''),
1081 ]
1088 ]
1082
1089
1083 def __init__(self):
1090 def __init__(self):
1084 self.options = None
1091 self.options = None
1085 self.testdir = None
1092 self.testdir = None
1086 self.hgtmp = None
1093 self.hgtmp = None
1087 self.inst = None
1094 self.inst = None
1088 self.bindir = None
1095 self.bindir = None
1089 self.tmpbinddir = None
1096 self.tmpbinddir = None
1090 self.pythondir = None
1097 self.pythondir = None
1091 self.coveragefile = None
1098 self.coveragefile = None
1092 self.times = [] # Holds execution times of tests.
1099 self.times = [] # Holds execution times of tests.
1093 self.results = {
1100 self.results = {
1094 '.': [],
1101 '.': [],
1095 '!': [],
1102 '!': [],
1096 '~': [],
1103 '~': [],
1097 's': [],
1104 's': [],
1098 'i': [],
1105 'i': [],
1099 'u': [],
1106 'u': [],
1100 }
1107 }
1101 self.abort = [False]
1108 self.abort = [False]
1102 self._createdfiles = []
1109 self._createdfiles = []
1103 self._hgpath = None
1110 self._hgpath = None
1104
1111
1105 def run(self, args, parser=None):
1112 def run(self, args, parser=None):
1106 """Run the test suite."""
1113 """Run the test suite."""
1107 oldmask = os.umask(022)
1114 oldmask = os.umask(022)
1108 try:
1115 try:
1109 parser = parser or getparser()
1116 parser = parser or getparser()
1110 options, args = parseargs(args, parser)
1117 options, args = parseargs(args, parser)
1111 self.options = options
1118 self.options = options
1112
1119
1113 self._checktools()
1120 self._checktools()
1114 tests = self.findtests(args)
1121 tests = self.findtests(args)
1115 return self._run(tests)
1122 return self._run(tests)
1116 finally:
1123 finally:
1117 os.umask(oldmask)
1124 os.umask(oldmask)
1118
1125
1119 def _run(self, tests):
1126 def _run(self, tests):
1120 if self.options.random:
1127 if self.options.random:
1121 random.shuffle(tests)
1128 random.shuffle(tests)
1122 else:
1129 else:
1123 # keywords for slow tests
1130 # keywords for slow tests
1124 slow = 'svn gendoc check-code-hg'.split()
1131 slow = 'svn gendoc check-code-hg'.split()
1125 def sortkey(f):
1132 def sortkey(f):
1126 # run largest tests first, as they tend to take the longest
1133 # run largest tests first, as they tend to take the longest
1127 try:
1134 try:
1128 val = -os.stat(f).st_size
1135 val = -os.stat(f).st_size
1129 except OSError, e:
1136 except OSError, e:
1130 if e.errno != errno.ENOENT:
1137 if e.errno != errno.ENOENT:
1131 raise
1138 raise
1132 return -1e9 # file does not exist, tell early
1139 return -1e9 # file does not exist, tell early
1133 for kw in slow:
1140 for kw in slow:
1134 if kw in f:
1141 if kw in f:
1135 val *= 10
1142 val *= 10
1136 return val
1143 return val
1137 tests.sort(key=sortkey)
1144 tests.sort(key=sortkey)
1138
1145
1139 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1146 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1140
1147
1141 if 'PYTHONHASHSEED' not in os.environ:
1148 if 'PYTHONHASHSEED' not in os.environ:
1142 # use a random python hash seed all the time
1149 # use a random python hash seed all the time
1143 # we do the randomness ourself to know what seed is used
1150 # we do the randomness ourself to know what seed is used
1144 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1151 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1145
1152
1146 if self.options.tmpdir:
1153 if self.options.tmpdir:
1147 self.options.keep_tmpdir = True
1154 self.options.keep_tmpdir = True
1148 tmpdir = self.options.tmpdir
1155 tmpdir = self.options.tmpdir
1149 if os.path.exists(tmpdir):
1156 if os.path.exists(tmpdir):
1150 # Meaning of tmpdir has changed since 1.3: we used to create
1157 # Meaning of tmpdir has changed since 1.3: we used to create
1151 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1158 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1152 # tmpdir already exists.
1159 # tmpdir already exists.
1153 print "error: temp dir %r already exists" % tmpdir
1160 print "error: temp dir %r already exists" % tmpdir
1154 return 1
1161 return 1
1155
1162
1156 # Automatically removing tmpdir sounds convenient, but could
1163 # Automatically removing tmpdir sounds convenient, but could
1157 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1164 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1158 # or "--tmpdir=$HOME".
1165 # or "--tmpdir=$HOME".
1159 #vlog("# Removing temp dir", tmpdir)
1166 #vlog("# Removing temp dir", tmpdir)
1160 #shutil.rmtree(tmpdir)
1167 #shutil.rmtree(tmpdir)
1161 os.makedirs(tmpdir)
1168 os.makedirs(tmpdir)
1162 else:
1169 else:
1163 d = None
1170 d = None
1164 if os.name == 'nt':
1171 if os.name == 'nt':
1165 # without this, we get the default temp dir location, but
1172 # without this, we get the default temp dir location, but
1166 # in all lowercase, which causes troubles with paths (issue3490)
1173 # in all lowercase, which causes troubles with paths (issue3490)
1167 d = os.getenv('TMP')
1174 d = os.getenv('TMP')
1168 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1175 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1169 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1176 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1170
1177
1171 if self.options.with_hg:
1178 if self.options.with_hg:
1172 self.inst = None
1179 self.inst = None
1173 self.bindir = os.path.dirname(os.path.realpath(
1180 self.bindir = os.path.dirname(os.path.realpath(
1174 self.options.with_hg))
1181 self.options.with_hg))
1175 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1182 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1176 os.makedirs(self.tmpbindir)
1183 os.makedirs(self.tmpbindir)
1177
1184
1178 # This looks redundant with how Python initializes sys.path from
1185 # This looks redundant with how Python initializes sys.path from
1179 # the location of the script being executed. Needed because the
1186 # the location of the script being executed. Needed because the
1180 # "hg" specified by --with-hg is not the only Python script
1187 # "hg" specified by --with-hg is not the only Python script
1181 # executed in the test suite that needs to import 'mercurial'
1188 # executed in the test suite that needs to import 'mercurial'
1182 # ... which means it's not really redundant at all.
1189 # ... which means it's not really redundant at all.
1183 self.pythondir = self.bindir
1190 self.pythondir = self.bindir
1184 else:
1191 else:
1185 self.inst = os.path.join(self.hgtmp, "install")
1192 self.inst = os.path.join(self.hgtmp, "install")
1186 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1193 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1187 "bin")
1194 "bin")
1188 self.tmpbindir = self.bindir
1195 self.tmpbindir = self.bindir
1189 self.pythondir = os.path.join(self.inst, "lib", "python")
1196 self.pythondir = os.path.join(self.inst, "lib", "python")
1190
1197
1191 os.environ["BINDIR"] = self.bindir
1198 os.environ["BINDIR"] = self.bindir
1192 os.environ["PYTHON"] = PYTHON
1199 os.environ["PYTHON"] = PYTHON
1193
1200
1194 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1201 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1195 if self.tmpbindir != self.bindir:
1202 if self.tmpbindir != self.bindir:
1196 path = [self.tmpbindir] + path
1203 path = [self.tmpbindir] + path
1197 os.environ["PATH"] = os.pathsep.join(path)
1204 os.environ["PATH"] = os.pathsep.join(path)
1198
1205
1199 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1206 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1200 # can run .../tests/run-tests.py test-foo where test-foo
1207 # can run .../tests/run-tests.py test-foo where test-foo
1201 # adds an extension to HGRC. Also include run-test.py directory to
1208 # adds an extension to HGRC. Also include run-test.py directory to
1202 # import modules like heredoctest.
1209 # import modules like heredoctest.
1203 pypath = [self.pythondir, self.testdir,
1210 pypath = [self.pythondir, self.testdir,
1204 os.path.abspath(os.path.dirname(__file__))]
1211 os.path.abspath(os.path.dirname(__file__))]
1205 # We have to augment PYTHONPATH, rather than simply replacing
1212 # We have to augment PYTHONPATH, rather than simply replacing
1206 # it, in case external libraries are only available via current
1213 # it, in case external libraries are only available via current
1207 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1214 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1208 # are in /opt/subversion.)
1215 # are in /opt/subversion.)
1209 oldpypath = os.environ.get(IMPL_PATH)
1216 oldpypath = os.environ.get(IMPL_PATH)
1210 if oldpypath:
1217 if oldpypath:
1211 pypath.append(oldpypath)
1218 pypath.append(oldpypath)
1212 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1219 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1213
1220
1214 self.coveragefile = os.path.join(self.testdir, '.coverage')
1221 self.coveragefile = os.path.join(self.testdir, '.coverage')
1215
1222
1216 vlog("# Using TESTDIR", self.testdir)
1223 vlog("# Using TESTDIR", self.testdir)
1217 vlog("# Using HGTMP", self.hgtmp)
1224 vlog("# Using HGTMP", self.hgtmp)
1218 vlog("# Using PATH", os.environ["PATH"])
1225 vlog("# Using PATH", os.environ["PATH"])
1219 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1226 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1220
1227
1221 try:
1228 try:
1222 return self._runtests(tests) or 0
1229 return self._runtests(tests) or 0
1223 finally:
1230 finally:
1224 time.sleep(.1)
1231 time.sleep(.1)
1225 self._cleanup()
1232 self._cleanup()
1226
1233
1227 def findtests(self, args):
1234 def findtests(self, args):
1228 """Finds possible test files from arguments.
1235 """Finds possible test files from arguments.
1229
1236
1230 If you wish to inject custom tests into the test harness, this would
1237 If you wish to inject custom tests into the test harness, this would
1231 be a good function to monkeypatch or override in a derived class.
1238 be a good function to monkeypatch or override in a derived class.
1232 """
1239 """
1233 if not args:
1240 if not args:
1234 if self.options.changed:
1241 if self.options.changed:
1235 proc = Popen4('hg st --rev "%s" -man0 .' %
1242 proc = Popen4('hg st --rev "%s" -man0 .' %
1236 self.options.changed, None, 0)
1243 self.options.changed, None, 0)
1237 stdout, stderr = proc.communicate()
1244 stdout, stderr = proc.communicate()
1238 args = stdout.strip('\0').split('\0')
1245 args = stdout.strip('\0').split('\0')
1239 else:
1246 else:
1240 args = os.listdir('.')
1247 args = os.listdir('.')
1241
1248
1242 return [t for t in args
1249 return [t for t in args
1243 if os.path.basename(t).startswith('test-')
1250 if os.path.basename(t).startswith('test-')
1244 and (t.endswith('.py') or t.endswith('.t'))]
1251 and (t.endswith('.py') or t.endswith('.t'))]
1245
1252
1246 def _runtests(self, tests):
1253 def _runtests(self, tests):
1247 try:
1254 try:
1248 if self.inst:
1255 if self.inst:
1249 self._installhg()
1256 self._installhg()
1250 self._checkhglib("Testing")
1257 self._checkhglib("Testing")
1251 else:
1258 else:
1252 self._usecorrectpython()
1259 self._usecorrectpython()
1253
1260
1254 if self.options.restart:
1261 if self.options.restart:
1255 orig = list(tests)
1262 orig = list(tests)
1256 while tests:
1263 while tests:
1257 if os.path.exists(tests[0] + ".err"):
1264 if os.path.exists(tests[0] + ".err"):
1258 break
1265 break
1259 tests.pop(0)
1266 tests.pop(0)
1260 if not tests:
1267 if not tests:
1261 print "running all tests"
1268 print "running all tests"
1262 tests = orig
1269 tests = orig
1263
1270
1264 tests = [self._gettest(t, i, asunit=self.options.unittest)
1271 tests = [self._gettest(t, i, asunit=self.options.unittest)
1265 for i, t in enumerate(tests)]
1272 for i, t in enumerate(tests)]
1266
1273
1267 if self.options.unittest:
1274 if self.options.unittest:
1268 suite = TestSuite(self, tests=tests)
1275 suite = TestSuite(self, tests=tests)
1269 verbosity = 1
1276 verbosity = 1
1270 if self.options.verbose:
1277 if self.options.verbose:
1271 verbosity = 2
1278 verbosity = 2
1272 runner = TextTestRunner(verbosity=verbosity)
1279 runner = TextTestRunner(verbosity=verbosity)
1273 runner.run(suite)
1280 runner.run(suite)
1274 else:
1281 else:
1275 self._executetests(tests)
1282 self._executetests(tests)
1276
1283
1277 failed = len(self.results['!'])
1284 failed = len(self.results['!'])
1278 warned = len(self.results['~'])
1285 warned = len(self.results['~'])
1279 tested = len(self.results['.']) + failed + warned
1286 tested = len(self.results['.']) + failed + warned
1280 skipped = len(self.results['s'])
1287 skipped = len(self.results['s'])
1281 ignored = len(self.results['i'])
1288 ignored = len(self.results['i'])
1282
1289
1283 print
1290 print
1284 if not self.options.noskips:
1291 if not self.options.noskips:
1285 for s in self.results['s']:
1292 for s in self.results['s']:
1286 print "Skipped %s: %s" % s
1293 print "Skipped %s: %s" % s
1287 for s in self.results['~']:
1294 for s in self.results['~']:
1288 print "Warned %s: %s" % s
1295 print "Warned %s: %s" % s
1289 for s in self.results['!']:
1296 for s in self.results['!']:
1290 print "Failed %s: %s" % s
1297 print "Failed %s: %s" % s
1291 self._checkhglib("Tested")
1298 self._checkhglib("Tested")
1292 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1299 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1293 tested, skipped + ignored, warned, failed)
1300 tested, skipped + ignored, warned, failed)
1294 if self.results['!']:
1301 if self.results['!']:
1295 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1302 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1296 if self.options.time:
1303 if self.options.time:
1297 self._outputtimes()
1304 self._outputtimes()
1298
1305
1299 if self.options.anycoverage:
1306 if self.options.anycoverage:
1300 self._outputcoverage()
1307 self._outputcoverage()
1301 except KeyboardInterrupt:
1308 except KeyboardInterrupt:
1302 failed = True
1309 failed = True
1303 print "\ninterrupted!"
1310 print "\ninterrupted!"
1304
1311
1305 if failed:
1312 if failed:
1306 return 1
1313 return 1
1307 if warned:
1314 if warned:
1308 return 80
1315 return 80
1309
1316
1310 def _gettest(self, test, count, asunit=False):
1317 def _gettest(self, test, count, asunit=False):
1311 """Obtain a Test by looking at its filename.
1318 """Obtain a Test by looking at its filename.
1312
1319
1313 Returns a Test instance. The Test may not be runnable if it doesn't
1320 Returns a Test instance. The Test may not be runnable if it doesn't
1314 map to a known type.
1321 map to a known type.
1315 """
1322 """
1316 lctest = test.lower()
1323 lctest = test.lower()
1317 refpath = os.path.join(self.testdir, test)
1324 refpath = os.path.join(self.testdir, test)
1318
1325
1319 testcls = Test
1326 testcls = Test
1320
1327
1321 for ext, cls, out in self.TESTTYPES:
1328 for ext, cls, out in self.TESTTYPES:
1322 if lctest.endswith(ext):
1329 if lctest.endswith(ext):
1323 testcls = cls
1330 testcls = cls
1324 refpath = os.path.join(self.testdir, test + out)
1331 refpath = os.path.join(self.testdir, test + out)
1325 break
1332 break
1326
1333
1327 t = testcls(self, test, count, refpath, unittest=asunit)
1334 t = testcls(self, test, count, refpath, unittest=asunit)
1328
1335
1329 if not asunit:
1336 if not asunit:
1330 return t
1337 return t
1331
1338
1332 class MercurialTest(unittest.TestCase):
1339 class MercurialTest(unittest.TestCase):
1333 def __init__(self, name, *args, **kwargs):
1340 def __init__(self, name, *args, **kwargs):
1334 super(MercurialTest, self).__init__(*args, **kwargs)
1341 super(MercurialTest, self).__init__(*args, **kwargs)
1335 self.name = name
1342 self.name = name
1336
1343
1337 def shortDescription(self):
1344 def shortDescription(self):
1338 return self.name
1345 return self.name
1339
1346
1340 # Need to stash away the TestResult since we do custom things
1347 # Need to stash away the TestResult since we do custom things
1341 # with it.
1348 # with it.
1342 def run(self, result):
1349 def run(self, result):
1343 self._result = result
1350 self._result = result
1344
1351
1345 try:
1352 try:
1346 self.runTest()
1353 self.runTest()
1347 except KeyboardInterrupt:
1354 except KeyboardInterrupt:
1348 raise
1355 raise
1349 except SkipTest, e:
1356 except SkipTest, e:
1350 result.addSkip(self, str(e))
1357 result.addSkip(self, str(e))
1351 except IgnoreTest, e:
1358 except IgnoreTest, e:
1352 result.addIgnore(self, str(e))
1359 result.addIgnore(self, str(e))
1360 except WarnTest, e:
1361 result.addWarn(self, str(e))
1353 except self.failureException:
1362 except self.failureException:
1354 result.addFailure(self, sys.exc_info())
1363 result.addFailure(self, sys.exc_info())
1355 except Exception:
1364 except Exception:
1356 result.addError(self, sys.exc_info())
1365 result.addError(self, sys.exc_info())
1357 else:
1366 else:
1358 result.addSuccess(self)
1367 result.addSuccess(self)
1359
1368
1360 def runTest(self):
1369 def runTest(self):
1361 code, tname, msg = t.run()
1370 code, tname, msg = t.run()
1362
1371
1363 if code == '!':
1372 if code == '!':
1364 self._result.failures.append((self, msg))
1373 self._result.failures.append((self, msg))
1365 elif code == '~':
1366 self._result.addWarn(self, msg)
1367 # Codes handled in run().
1374 # Codes handled in run().
1368 elif code in ('.', 's', 'i'):
1375 elif code in ('.', 's', 'i', '~'):
1369 pass
1376 pass
1370 else:
1377 else:
1371 self.fail('Unknown test result code: %s' % code)
1378 self.fail('Unknown test result code: %s' % code)
1372
1379
1373 # We need this proxy until tearDown() is implemented.
1380 # We need this proxy until tearDown() is implemented.
1374 def cleanup(self):
1381 def cleanup(self):
1375 return t.cleanup()
1382 return t.cleanup()
1376
1383
1377 return MercurialTest(test)
1384 return MercurialTest(test)
1378
1385
1379 def _cleanup(self):
1386 def _cleanup(self):
1380 """Clean up state from this test invocation."""
1387 """Clean up state from this test invocation."""
1381
1388
1382 if self.options.keep_tmpdir:
1389 if self.options.keep_tmpdir:
1383 return
1390 return
1384
1391
1385 vlog("# Cleaning up HGTMP", self.hgtmp)
1392 vlog("# Cleaning up HGTMP", self.hgtmp)
1386 shutil.rmtree(self.hgtmp, True)
1393 shutil.rmtree(self.hgtmp, True)
1387 for f in self._createdfiles:
1394 for f in self._createdfiles:
1388 try:
1395 try:
1389 os.remove(f)
1396 os.remove(f)
1390 except OSError:
1397 except OSError:
1391 pass
1398 pass
1392
1399
1393 def _usecorrectpython(self):
1400 def _usecorrectpython(self):
1394 # Some tests run the Python interpreter. They must use the
1401 # Some tests run the Python interpreter. They must use the
1395 # same interpreter or bad things will happen.
1402 # same interpreter or bad things will happen.
1396 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1403 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1397 if getattr(os, 'symlink', None):
1404 if getattr(os, 'symlink', None):
1398 vlog("# Making python executable in test path a symlink to '%s'" %
1405 vlog("# Making python executable in test path a symlink to '%s'" %
1399 sys.executable)
1406 sys.executable)
1400 mypython = os.path.join(self.tmpbindir, pyexename)
1407 mypython = os.path.join(self.tmpbindir, pyexename)
1401 try:
1408 try:
1402 if os.readlink(mypython) == sys.executable:
1409 if os.readlink(mypython) == sys.executable:
1403 return
1410 return
1404 os.unlink(mypython)
1411 os.unlink(mypython)
1405 except OSError, err:
1412 except OSError, err:
1406 if err.errno != errno.ENOENT:
1413 if err.errno != errno.ENOENT:
1407 raise
1414 raise
1408 if self._findprogram(pyexename) != sys.executable:
1415 if self._findprogram(pyexename) != sys.executable:
1409 try:
1416 try:
1410 os.symlink(sys.executable, mypython)
1417 os.symlink(sys.executable, mypython)
1411 self._createdfiles.append(mypython)
1418 self._createdfiles.append(mypython)
1412 except OSError, err:
1419 except OSError, err:
1413 # child processes may race, which is harmless
1420 # child processes may race, which is harmless
1414 if err.errno != errno.EEXIST:
1421 if err.errno != errno.EEXIST:
1415 raise
1422 raise
1416 else:
1423 else:
1417 exedir, exename = os.path.split(sys.executable)
1424 exedir, exename = os.path.split(sys.executable)
1418 vlog("# Modifying search path to find %s as %s in '%s'" %
1425 vlog("# Modifying search path to find %s as %s in '%s'" %
1419 (exename, pyexename, exedir))
1426 (exename, pyexename, exedir))
1420 path = os.environ['PATH'].split(os.pathsep)
1427 path = os.environ['PATH'].split(os.pathsep)
1421 while exedir in path:
1428 while exedir in path:
1422 path.remove(exedir)
1429 path.remove(exedir)
1423 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1430 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1424 if not self._findprogram(pyexename):
1431 if not self._findprogram(pyexename):
1425 print "WARNING: Cannot find %s in search path" % pyexename
1432 print "WARNING: Cannot find %s in search path" % pyexename
1426
1433
1427 def _installhg(self):
1434 def _installhg(self):
1428 vlog("# Performing temporary installation of HG")
1435 vlog("# Performing temporary installation of HG")
1429 installerrs = os.path.join("tests", "install.err")
1436 installerrs = os.path.join("tests", "install.err")
1430 compiler = ''
1437 compiler = ''
1431 if self.options.compiler:
1438 if self.options.compiler:
1432 compiler = '--compiler ' + self.options.compiler
1439 compiler = '--compiler ' + self.options.compiler
1433 pure = self.options.pure and "--pure" or ""
1440 pure = self.options.pure and "--pure" or ""
1434 py3 = ''
1441 py3 = ''
1435 if sys.version_info[0] == 3:
1442 if sys.version_info[0] == 3:
1436 py3 = '--c2to3'
1443 py3 = '--c2to3'
1437
1444
1438 # Run installer in hg root
1445 # Run installer in hg root
1439 script = os.path.realpath(sys.argv[0])
1446 script = os.path.realpath(sys.argv[0])
1440 hgroot = os.path.dirname(os.path.dirname(script))
1447 hgroot = os.path.dirname(os.path.dirname(script))
1441 os.chdir(hgroot)
1448 os.chdir(hgroot)
1442 nohome = '--home=""'
1449 nohome = '--home=""'
1443 if os.name == 'nt':
1450 if os.name == 'nt':
1444 # The --home="" trick works only on OS where os.sep == '/'
1451 # The --home="" trick works only on OS where os.sep == '/'
1445 # because of a distutils convert_path() fast-path. Avoid it at
1452 # because of a distutils convert_path() fast-path. Avoid it at
1446 # least on Windows for now, deal with .pydistutils.cfg bugs
1453 # least on Windows for now, deal with .pydistutils.cfg bugs
1447 # when they happen.
1454 # when they happen.
1448 nohome = ''
1455 nohome = ''
1449 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1456 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1450 ' build %(compiler)s --build-base="%(base)s"'
1457 ' build %(compiler)s --build-base="%(base)s"'
1451 ' install --force --prefix="%(prefix)s"'
1458 ' install --force --prefix="%(prefix)s"'
1452 ' --install-lib="%(libdir)s"'
1459 ' --install-lib="%(libdir)s"'
1453 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1460 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1454 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1461 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1455 'compiler': compiler,
1462 'compiler': compiler,
1456 'base': os.path.join(self.hgtmp, "build"),
1463 'base': os.path.join(self.hgtmp, "build"),
1457 'prefix': self.inst, 'libdir': self.pythondir,
1464 'prefix': self.inst, 'libdir': self.pythondir,
1458 'bindir': self.bindir,
1465 'bindir': self.bindir,
1459 'nohome': nohome, 'logfile': installerrs})
1466 'nohome': nohome, 'logfile': installerrs})
1460 vlog("# Running", cmd)
1467 vlog("# Running", cmd)
1461 if os.system(cmd) == 0:
1468 if os.system(cmd) == 0:
1462 if not self.options.verbose:
1469 if not self.options.verbose:
1463 os.remove(installerrs)
1470 os.remove(installerrs)
1464 else:
1471 else:
1465 f = open(installerrs)
1472 f = open(installerrs)
1466 for line in f:
1473 for line in f:
1467 print line,
1474 print line,
1468 f.close()
1475 f.close()
1469 sys.exit(1)
1476 sys.exit(1)
1470 os.chdir(self.testdir)
1477 os.chdir(self.testdir)
1471
1478
1472 self._usecorrectpython()
1479 self._usecorrectpython()
1473
1480
1474 if self.options.py3k_warnings and not self.options.anycoverage:
1481 if self.options.py3k_warnings and not self.options.anycoverage:
1475 vlog("# Updating hg command to enable Py3k Warnings switch")
1482 vlog("# Updating hg command to enable Py3k Warnings switch")
1476 f = open(os.path.join(self.bindir, 'hg'), 'r')
1483 f = open(os.path.join(self.bindir, 'hg'), 'r')
1477 lines = [line.rstrip() for line in f]
1484 lines = [line.rstrip() for line in f]
1478 lines[0] += ' -3'
1485 lines[0] += ' -3'
1479 f.close()
1486 f.close()
1480 f = open(os.path.join(self.bindir, 'hg'), 'w')
1487 f = open(os.path.join(self.bindir, 'hg'), 'w')
1481 for line in lines:
1488 for line in lines:
1482 f.write(line + '\n')
1489 f.write(line + '\n')
1483 f.close()
1490 f.close()
1484
1491
1485 hgbat = os.path.join(self.bindir, 'hg.bat')
1492 hgbat = os.path.join(self.bindir, 'hg.bat')
1486 if os.path.isfile(hgbat):
1493 if os.path.isfile(hgbat):
1487 # hg.bat expects to be put in bin/scripts while run-tests.py
1494 # hg.bat expects to be put in bin/scripts while run-tests.py
1488 # installation layout put it in bin/ directly. Fix it
1495 # installation layout put it in bin/ directly. Fix it
1489 f = open(hgbat, 'rb')
1496 f = open(hgbat, 'rb')
1490 data = f.read()
1497 data = f.read()
1491 f.close()
1498 f.close()
1492 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1499 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1493 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1500 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1494 '"%~dp0python" "%~dp0hg" %*')
1501 '"%~dp0python" "%~dp0hg" %*')
1495 f = open(hgbat, 'wb')
1502 f = open(hgbat, 'wb')
1496 f.write(data)
1503 f.write(data)
1497 f.close()
1504 f.close()
1498 else:
1505 else:
1499 print 'WARNING: cannot fix hg.bat reference to python.exe'
1506 print 'WARNING: cannot fix hg.bat reference to python.exe'
1500
1507
1501 if self.options.anycoverage:
1508 if self.options.anycoverage:
1502 custom = os.path.join(self.testdir, 'sitecustomize.py')
1509 custom = os.path.join(self.testdir, 'sitecustomize.py')
1503 target = os.path.join(self.pythondir, 'sitecustomize.py')
1510 target = os.path.join(self.pythondir, 'sitecustomize.py')
1504 vlog('# Installing coverage trigger to %s' % target)
1511 vlog('# Installing coverage trigger to %s' % target)
1505 shutil.copyfile(custom, target)
1512 shutil.copyfile(custom, target)
1506 rc = os.path.join(self.testdir, '.coveragerc')
1513 rc = os.path.join(self.testdir, '.coveragerc')
1507 vlog('# Installing coverage rc to %s' % rc)
1514 vlog('# Installing coverage rc to %s' % rc)
1508 os.environ['COVERAGE_PROCESS_START'] = rc
1515 os.environ['COVERAGE_PROCESS_START'] = rc
1509 fn = os.path.join(self.inst, '..', '.coverage')
1516 fn = os.path.join(self.inst, '..', '.coverage')
1510 os.environ['COVERAGE_FILE'] = fn
1517 os.environ['COVERAGE_FILE'] = fn
1511
1518
1512 def _checkhglib(self, verb):
1519 def _checkhglib(self, verb):
1513 """Ensure that the 'mercurial' package imported by python is
1520 """Ensure that the 'mercurial' package imported by python is
1514 the one we expect it to be. If not, print a warning to stderr."""
1521 the one we expect it to be. If not, print a warning to stderr."""
1515 expecthg = os.path.join(self.pythondir, 'mercurial')
1522 expecthg = os.path.join(self.pythondir, 'mercurial')
1516 actualhg = self._gethgpath()
1523 actualhg = self._gethgpath()
1517 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1524 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1518 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1525 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1519 ' (expected %s)\n'
1526 ' (expected %s)\n'
1520 % (verb, actualhg, expecthg))
1527 % (verb, actualhg, expecthg))
1521 def _gethgpath(self):
1528 def _gethgpath(self):
1522 """Return the path to the mercurial package that is actually found by
1529 """Return the path to the mercurial package that is actually found by
1523 the current Python interpreter."""
1530 the current Python interpreter."""
1524 if self._hgpath is not None:
1531 if self._hgpath is not None:
1525 return self._hgpath
1532 return self._hgpath
1526
1533
1527 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1534 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1528 pipe = os.popen(cmd % PYTHON)
1535 pipe = os.popen(cmd % PYTHON)
1529 try:
1536 try:
1530 self._hgpath = pipe.read().strip()
1537 self._hgpath = pipe.read().strip()
1531 finally:
1538 finally:
1532 pipe.close()
1539 pipe.close()
1533
1540
1534 return self._hgpath
1541 return self._hgpath
1535
1542
1536 def _outputtimes(self):
1543 def _outputtimes(self):
1537 vlog('# Producing time report')
1544 vlog('# Producing time report')
1538 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1545 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1539 cols = '%7.3f %s'
1546 cols = '%7.3f %s'
1540 print '\n%-7s %s' % ('Time', 'Test')
1547 print '\n%-7s %s' % ('Time', 'Test')
1541 for test, timetaken in self.times:
1548 for test, timetaken in self.times:
1542 print cols % (timetaken, test)
1549 print cols % (timetaken, test)
1543
1550
1544 def _outputcoverage(self):
1551 def _outputcoverage(self):
1545 vlog('# Producing coverage report')
1552 vlog('# Producing coverage report')
1546 os.chdir(self.pythondir)
1553 os.chdir(self.pythondir)
1547
1554
1548 def covrun(*args):
1555 def covrun(*args):
1549 cmd = 'coverage %s' % ' '.join(args)
1556 cmd = 'coverage %s' % ' '.join(args)
1550 vlog('# Running: %s' % cmd)
1557 vlog('# Running: %s' % cmd)
1551 os.system(cmd)
1558 os.system(cmd)
1552
1559
1553 covrun('-c')
1560 covrun('-c')
1554 omit = ','.join(os.path.join(x, '*') for x in
1561 omit = ','.join(os.path.join(x, '*') for x in
1555 [self.bindir, self.testdir])
1562 [self.bindir, self.testdir])
1556 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1563 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1557 if self.options.htmlcov:
1564 if self.options.htmlcov:
1558 htmldir = os.path.join(self.testdir, 'htmlcov')
1565 htmldir = os.path.join(self.testdir, 'htmlcov')
1559 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1566 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1560 '"--omit=%s"' % omit)
1567 '"--omit=%s"' % omit)
1561 if self.options.annotate:
1568 if self.options.annotate:
1562 adir = os.path.join(self.testdir, 'annotated')
1569 adir = os.path.join(self.testdir, 'annotated')
1563 if not os.path.isdir(adir):
1570 if not os.path.isdir(adir):
1564 os.mkdir(adir)
1571 os.mkdir(adir)
1565 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1572 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1566
1573
1567 def _executetests(self, tests, result=None):
1574 def _executetests(self, tests, result=None):
1568 # We copy because we modify the list.
1575 # We copy because we modify the list.
1569 tests = list(tests)
1576 tests = list(tests)
1570
1577
1571 jobs = self.options.jobs
1578 jobs = self.options.jobs
1572 done = queue.Queue()
1579 done = queue.Queue()
1573 running = 0
1580 running = 0
1574
1581
1575 def job(test, result):
1582 def job(test, result):
1576 try:
1583 try:
1577 # If in unittest mode.
1584 # If in unittest mode.
1578 if result:
1585 if result:
1579 test(result)
1586 test(result)
1580 # We need to put something here to make the logic happy.
1587 # We need to put something here to make the logic happy.
1581 # This will get cleaned up later.
1588 # This will get cleaned up later.
1582 done.put(('u', None, None))
1589 done.put(('u', None, None))
1583 else:
1590 else:
1584 done.put(test.run())
1591 done.put(test.run())
1585 test.cleanup()
1592 test.cleanup()
1586 except KeyboardInterrupt:
1593 except KeyboardInterrupt:
1587 pass
1594 pass
1588 except: # re-raises
1595 except: # re-raises
1589 done.put(('!', test, 'run-test raised an error, see traceback'))
1596 done.put(('!', test, 'run-test raised an error, see traceback'))
1590 raise
1597 raise
1591
1598
1592 try:
1599 try:
1593 while tests or running:
1600 while tests or running:
1594 if not done.empty() or running == jobs or not tests:
1601 if not done.empty() or running == jobs or not tests:
1595 try:
1602 try:
1596 code, test, msg = done.get(True, 1)
1603 code, test, msg = done.get(True, 1)
1597 self.results[code].append((test, msg))
1604 self.results[code].append((test, msg))
1598 if self.options.first and code not in '.si':
1605 if self.options.first and code not in '.si':
1599 break
1606 break
1600 except queue.Empty:
1607 except queue.Empty:
1601 continue
1608 continue
1602 running -= 1
1609 running -= 1
1603 if tests and not running == jobs:
1610 if tests and not running == jobs:
1604 test = tests.pop(0)
1611 test = tests.pop(0)
1605 if self.options.loop:
1612 if self.options.loop:
1606 tests.append(test)
1613 tests.append(test)
1607 t = threading.Thread(target=job, name=test.name,
1614 t = threading.Thread(target=job, name=test.name,
1608 args=(test, result))
1615 args=(test, result))
1609 t.start()
1616 t.start()
1610 running += 1
1617 running += 1
1611 except KeyboardInterrupt:
1618 except KeyboardInterrupt:
1612 self.abort[0] = True
1619 self.abort[0] = True
1613
1620
1614 def _findprogram(self, program):
1621 def _findprogram(self, program):
1615 """Search PATH for a executable program"""
1622 """Search PATH for a executable program"""
1616 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1623 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1617 name = os.path.join(p, program)
1624 name = os.path.join(p, program)
1618 if os.name == 'nt' or os.access(name, os.X_OK):
1625 if os.name == 'nt' or os.access(name, os.X_OK):
1619 return name
1626 return name
1620 return None
1627 return None
1621
1628
1622 def _checktools(self):
1629 def _checktools(self):
1623 # Before we go any further, check for pre-requisite tools
1630 # Before we go any further, check for pre-requisite tools
1624 # stuff from coreutils (cat, rm, etc) are not tested
1631 # stuff from coreutils (cat, rm, etc) are not tested
1625 for p in self.REQUIREDTOOLS:
1632 for p in self.REQUIREDTOOLS:
1626 if os.name == 'nt' and not p.endswith('.exe'):
1633 if os.name == 'nt' and not p.endswith('.exe'):
1627 p += '.exe'
1634 p += '.exe'
1628 found = self._findprogram(p)
1635 found = self._findprogram(p)
1629 if found:
1636 if found:
1630 vlog("# Found prerequisite", p, "at", found)
1637 vlog("# Found prerequisite", p, "at", found)
1631 else:
1638 else:
1632 print "WARNING: Did not find prerequisite tool: %s " % p
1639 print "WARNING: Did not find prerequisite tool: %s " % p
1633
1640
1634 if __name__ == '__main__':
1641 if __name__ == '__main__':
1635 runner = TestRunner()
1642 runner = TestRunner()
1636 sys.exit(runner.run(sys.argv[1:]))
1643 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now