##// END OF EJS Templates
run-tests: ignore failures from rmtree...
Gregory Szorc -
r21461:a46a9198 default
parent child Browse files
Show More
@@ -1,1765 +1,1767 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 self._finished = None
359 self._finished = None
360 self._ret = None
360 self._ret = None
361 self._out = None
361 self._out = None
362 self._duration = None
362 self._duration = None
363 self._result = None
363 self._result = None
364 self._skipped = None
364 self._skipped = None
365 self._testtmp = None
365 self._testtmp = None
366
366
367 # If we're not in --debug mode and reference output file exists,
367 # If we're not in --debug mode and reference output file exists,
368 # check test output against it.
368 # check test output against it.
369 if runner.options.debug:
369 if runner.options.debug:
370 self._refout = None # to match "out is None"
370 self._refout = None # to match "out is None"
371 elif os.path.exists(refpath):
371 elif os.path.exists(refpath):
372 f = open(refpath, 'r')
372 f = open(refpath, 'r')
373 self._refout = f.read().splitlines(True)
373 self._refout = f.read().splitlines(True)
374 f.close()
374 f.close()
375 else:
375 else:
376 self._refout = []
376 self._refout = []
377
377
378 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
378 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
379 os.mkdir(self._threadtmp)
379 os.mkdir(self._threadtmp)
380
380
381 def cleanup(self):
381 def cleanup(self):
382 for entry in self._daemonpids:
382 for entry in self._daemonpids:
383 killdaemons(entry)
383 killdaemons(entry)
384
384
385 if self._threadtmp and not self._options.keep_tmpdir:
385 if self._threadtmp and not self._options.keep_tmpdir:
386 # Ignore failures here. The rmtree() in the higher level runner
387 # will try again.
386 shutil.rmtree(self._threadtmp, True)
388 shutil.rmtree(self._threadtmp, True)
387
389
388 def setUp(self):
390 def setUp(self):
389 """Tasks to perform before run()."""
391 """Tasks to perform before run()."""
390 self._finished = False
392 self._finished = False
391 self._ret = None
393 self._ret = None
392 self._out = None
394 self._out = None
393 self._duration = None
395 self._duration = None
394 self._result = None
396 self._result = None
395 self._skipped = None
397 self._skipped = None
396
398
397 self._testtmp = os.path.join(self._threadtmp,
399 self._testtmp = os.path.join(self._threadtmp,
398 os.path.basename(self._path))
400 os.path.basename(self._path))
399 os.mkdir(self._testtmp)
401 os.mkdir(self._testtmp)
400
402
401 # Remove any previous output files.
403 # Remove any previous output files.
402 if os.path.exists(self._errpath):
404 if os.path.exists(self._errpath):
403 os.remove(self._errpath)
405 os.remove(self._errpath)
404
406
405 def run(self):
407 def run(self):
406 """Run this test instance.
408 """Run this test instance.
407
409
408 This will return a tuple describing the result of the test.
410 This will return a tuple describing the result of the test.
409 """
411 """
410 if not self._unittest:
412 if not self._unittest:
411 self.setUp()
413 self.setUp()
412
414
413 if not os.path.exists(self._path):
415 if not os.path.exists(self._path):
414 return self.skip("Doesn't exist")
416 return self.skip("Doesn't exist")
415
417
416 options = self._options
418 options = self._options
417 if not (options.whitelisted and self.name in options.whitelisted):
419 if not (options.whitelisted and self.name in options.whitelisted):
418 if options.blacklist and self.name in options.blacklist:
420 if options.blacklist and self.name in options.blacklist:
419 return self.skip('blacklisted')
421 return self.skip('blacklisted')
420
422
421 if options.retest and not os.path.exists('%s.err' % self.name):
423 if options.retest and not os.path.exists('%s.err' % self.name):
422 return self.ignore('not retesting')
424 return self.ignore('not retesting')
423
425
424 if options.keywords:
426 if options.keywords:
425 f = open(self.name)
427 f = open(self.name)
426 t = f.read().lower() + self.name.lower()
428 t = f.read().lower() + self.name.lower()
427 f.close()
429 f.close()
428 for k in options.keywords.lower().split():
430 for k in options.keywords.lower().split():
429 if k in t:
431 if k in t:
430 break
432 break
431 else:
433 else:
432 return self.ignore("doesn't match keyword")
434 return self.ignore("doesn't match keyword")
433
435
434 if not os.path.basename(self.name.lower()).startswith('test-'):
436 if not os.path.basename(self.name.lower()).startswith('test-'):
435 return self.skip('not a test file')
437 return self.skip('not a test file')
436
438
437 replacements, port = self._getreplacements()
439 replacements, port = self._getreplacements()
438 env = self._getenv(port)
440 env = self._getenv(port)
439 self._daemonpids.append(env['DAEMON_PIDS'])
441 self._daemonpids.append(env['DAEMON_PIDS'])
440 self._createhgrc(env['HGRCPATH'])
442 self._createhgrc(env['HGRCPATH'])
441
443
442 vlog('# Test', self.name)
444 vlog('# Test', self.name)
443
445
444 starttime = time.time()
446 starttime = time.time()
445 try:
447 try:
446 ret, out = self._run(replacements, env)
448 ret, out = self._run(replacements, env)
447 self._duration = time.time() - starttime
449 self._duration = time.time() - starttime
448 self._finished = True
450 self._finished = True
449 self._ret = ret
451 self._ret = ret
450 self._out = out
452 self._out = out
451 except KeyboardInterrupt:
453 except KeyboardInterrupt:
452 self._duration = time.time() - starttime
454 self._duration = time.time() - starttime
453 log('INTERRUPTED: %s (after %d seconds)' % (self.name,
455 log('INTERRUPTED: %s (after %d seconds)' % (self.name,
454 self._duration))
456 self._duration))
455 raise
457 raise
456 except Exception, e:
458 except Exception, e:
457 return self.fail('Exception during execution: %s' % e, 255)
459 return self.fail('Exception during execution: %s' % e, 255)
458
460
459 def describe(ret):
461 def describe(ret):
460 if ret < 0:
462 if ret < 0:
461 return 'killed by signal: %d' % -ret
463 return 'killed by signal: %d' % -ret
462 return 'returned error code %d' % ret
464 return 'returned error code %d' % ret
463
465
464 self._skipped = False
466 self._skipped = False
465
467
466 if ret == self.SKIPPED_STATUS:
468 if ret == self.SKIPPED_STATUS:
467 if out is None: # Debug mode, nothing to parse.
469 if out is None: # Debug mode, nothing to parse.
468 missing = ['unknown']
470 missing = ['unknown']
469 failed = None
471 failed = None
470 else:
472 else:
471 missing, failed = TTest.parsehghaveoutput(out)
473 missing, failed = TTest.parsehghaveoutput(out)
472
474
473 if not missing:
475 if not missing:
474 missing = ['irrelevant']
476 missing = ['irrelevant']
475
477
476 if failed:
478 if failed:
477 self._result = self.fail('hg have failed checking for %s' %
479 self._result = self.fail('hg have failed checking for %s' %
478 failed[-1], ret)
480 failed[-1], ret)
479 else:
481 else:
480 self._skipped = True
482 self._skipped = True
481 self._result = self.skip(missing[-1])
483 self._result = self.skip(missing[-1])
482 elif ret == 'timeout':
484 elif ret == 'timeout':
483 self._result = self.fail('timed out', ret)
485 self._result = self.fail('timed out', ret)
484 elif out != self._refout:
486 elif out != self._refout:
485 info = {}
487 info = {}
486 if not options.nodiff:
488 if not options.nodiff:
487 iolock.acquire()
489 iolock.acquire()
488 if options.view:
490 if options.view:
489 os.system("%s %s %s" % (options.view, self._refpath,
491 os.system("%s %s %s" % (options.view, self._refpath,
490 self._errpath))
492 self._errpath))
491 else:
493 else:
492 info = showdiff(self._refout, out, self._refpath,
494 info = showdiff(self._refout, out, self._refpath,
493 self._errpath)
495 self._errpath)
494 iolock.release()
496 iolock.release()
495 msg = ''
497 msg = ''
496 if info.get('servefail'):
498 if info.get('servefail'):
497 msg += 'serve failed and '
499 msg += 'serve failed and '
498 if ret:
500 if ret:
499 msg += 'output changed and ' + describe(ret)
501 msg += 'output changed and ' + describe(ret)
500 else:
502 else:
501 msg += 'output changed'
503 msg += 'output changed'
502
504
503 if (ret != 0 or out != self._refout) and not self._skipped \
505 if (ret != 0 or out != self._refout) and not self._skipped \
504 and not options.debug:
506 and not options.debug:
505 f = open(self._errpath, 'wb')
507 f = open(self._errpath, 'wb')
506 for line in out:
508 for line in out:
507 f.write(line)
509 f.write(line)
508 f.close()
510 f.close()
509
511
510 self._result = self.fail(msg, ret)
512 self._result = self.fail(msg, ret)
511 elif ret:
513 elif ret:
512 self._result = self.fail(describe(ret), ret)
514 self._result = self.fail(describe(ret), ret)
513 else:
515 else:
514 self._result = self.success()
516 self._result = self.success()
515
517
516 if not self._unittest:
518 if not self._unittest:
517 self.tearDown()
519 self.tearDown()
518
520
519 return self._result
521 return self._result
520
522
521 def tearDown(self):
523 def tearDown(self):
522 """Tasks to perform after run()."""
524 """Tasks to perform after run()."""
523 for entry in self._daemonpids:
525 for entry in self._daemonpids:
524 killdaemons(entry)
526 killdaemons(entry)
525 self._daemonpids = []
527 self._daemonpids = []
526
528
527 if not self._options.keep_tmpdir:
529 if not self._options.keep_tmpdir:
528 shutil.rmtree(self._testtmp)
530 shutil.rmtree(self._testtmp, True)
529
531
530 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
532 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
531 and not self._options.debug and self._out:
533 and not self._options.debug and self._out:
532 f = open(self._errpath, 'wb')
534 f = open(self._errpath, 'wb')
533 for line in self._out:
535 for line in self._out:
534 f.write(line)
536 f.write(line)
535 f.close()
537 f.close()
536
538
537 vlog("# Ret was:", self._ret)
539 vlog("# Ret was:", self._ret)
538
540
539 # Don't print progress in unittest mode because that is handled
541 # Don't print progress in unittest mode because that is handled
540 # by TestResult.
542 # by TestResult.
541 if not self._options.verbose and not self._unittest:
543 if not self._options.verbose and not self._unittest:
542 iolock.acquire()
544 iolock.acquire()
543 sys.stdout.write(self._result[0])
545 sys.stdout.write(self._result[0])
544 sys.stdout.flush()
546 sys.stdout.flush()
545 iolock.release()
547 iolock.release()
546
548
547 self._runner.times.append((self.name, self._duration))
549 self._runner.times.append((self.name, self._duration))
548
550
549 def _run(self, replacements, env):
551 def _run(self, replacements, env):
550 # This should be implemented in child classes to run tests.
552 # This should be implemented in child classes to run tests.
551 return self._skip('unknown test type')
553 return self._skip('unknown test type')
552
554
553 def _getreplacements(self):
555 def _getreplacements(self):
554 port = self._options.port + self._count * 3
556 port = self._options.port + self._count * 3
555 r = [
557 r = [
556 (r':%s\b' % port, ':$HGPORT'),
558 (r':%s\b' % port, ':$HGPORT'),
557 (r':%s\b' % (port + 1), ':$HGPORT1'),
559 (r':%s\b' % (port + 1), ':$HGPORT1'),
558 (r':%s\b' % (port + 2), ':$HGPORT2'),
560 (r':%s\b' % (port + 2), ':$HGPORT2'),
559 ]
561 ]
560
562
561 if os.name == 'nt':
563 if os.name == 'nt':
562 r.append(
564 r.append(
563 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
565 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
564 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
566 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
565 for c in self._testtmp), '$TESTTMP'))
567 for c in self._testtmp), '$TESTTMP'))
566 else:
568 else:
567 r.append((re.escape(self._testtmp), '$TESTTMP'))
569 r.append((re.escape(self._testtmp), '$TESTTMP'))
568
570
569 return r, port
571 return r, port
570
572
571 def _getenv(self, port):
573 def _getenv(self, port):
572 env = os.environ.copy()
574 env = os.environ.copy()
573 env['TESTTMP'] = self._testtmp
575 env['TESTTMP'] = self._testtmp
574 env['HOME'] = self._testtmp
576 env['HOME'] = self._testtmp
575 env["HGPORT"] = str(port)
577 env["HGPORT"] = str(port)
576 env["HGPORT1"] = str(port + 1)
578 env["HGPORT1"] = str(port + 1)
577 env["HGPORT2"] = str(port + 2)
579 env["HGPORT2"] = str(port + 2)
578 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
580 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
579 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
581 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
580 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
582 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
581 env["HGMERGE"] = "internal:merge"
583 env["HGMERGE"] = "internal:merge"
582 env["HGUSER"] = "test"
584 env["HGUSER"] = "test"
583 env["HGENCODING"] = "ascii"
585 env["HGENCODING"] = "ascii"
584 env["HGENCODINGMODE"] = "strict"
586 env["HGENCODINGMODE"] = "strict"
585
587
586 # Reset some environment variables to well-known values so that
588 # Reset some environment variables to well-known values so that
587 # the tests produce repeatable output.
589 # the tests produce repeatable output.
588 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
590 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
589 env['TZ'] = 'GMT'
591 env['TZ'] = 'GMT'
590 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
592 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
591 env['COLUMNS'] = '80'
593 env['COLUMNS'] = '80'
592 env['TERM'] = 'xterm'
594 env['TERM'] = 'xterm'
593
595
594 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
596 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
595 'NO_PROXY').split():
597 'NO_PROXY').split():
596 if k in env:
598 if k in env:
597 del env[k]
599 del env[k]
598
600
599 # unset env related to hooks
601 # unset env related to hooks
600 for k in env.keys():
602 for k in env.keys():
601 if k.startswith('HG_'):
603 if k.startswith('HG_'):
602 del env[k]
604 del env[k]
603
605
604 return env
606 return env
605
607
606 def _createhgrc(self, path):
608 def _createhgrc(self, path):
607 # create a fresh hgrc
609 # create a fresh hgrc
608 hgrc = open(path, 'w')
610 hgrc = open(path, 'w')
609 hgrc.write('[ui]\n')
611 hgrc.write('[ui]\n')
610 hgrc.write('slash = True\n')
612 hgrc.write('slash = True\n')
611 hgrc.write('interactive = False\n')
613 hgrc.write('interactive = False\n')
612 hgrc.write('[defaults]\n')
614 hgrc.write('[defaults]\n')
613 hgrc.write('backout = -d "0 0"\n')
615 hgrc.write('backout = -d "0 0"\n')
614 hgrc.write('commit = -d "0 0"\n')
616 hgrc.write('commit = -d "0 0"\n')
615 hgrc.write('shelve = --date "0 0"\n')
617 hgrc.write('shelve = --date "0 0"\n')
616 hgrc.write('tag = -d "0 0"\n')
618 hgrc.write('tag = -d "0 0"\n')
617 if self._options.extra_config_opt:
619 if self._options.extra_config_opt:
618 for opt in self._options.extra_config_opt:
620 for opt in self._options.extra_config_opt:
619 section, key = opt.split('.', 1)
621 section, key = opt.split('.', 1)
620 assert '=' in key, ('extra config opt %s must '
622 assert '=' in key, ('extra config opt %s must '
621 'have an = for assignment' % opt)
623 'have an = for assignment' % opt)
622 hgrc.write('[%s]\n%s\n' % (section, key))
624 hgrc.write('[%s]\n%s\n' % (section, key))
623 hgrc.close()
625 hgrc.close()
624
626
625 def success(self):
627 def success(self):
626 return '.', self.name, ''
628 return '.', self.name, ''
627
629
628 def fail(self, msg, ret):
630 def fail(self, msg, ret):
629 warned = ret is False
631 warned = ret is False
630 if not self._options.nodiff:
632 if not self._options.nodiff:
631 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
633 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
632 msg))
634 msg))
633 if (not ret and self._options.interactive and
635 if (not ret and self._options.interactive and
634 os.path.exists(self._errpath)):
636 os.path.exists(self._errpath)):
635 iolock.acquire()
637 iolock.acquire()
636 print 'Accept this change? [n] ',
638 print 'Accept this change? [n] ',
637 answer = sys.stdin.readline().strip()
639 answer = sys.stdin.readline().strip()
638 iolock.release()
640 iolock.release()
639 if answer.lower() in ('y', 'yes'):
641 if answer.lower() in ('y', 'yes'):
640 if self.name.endswith('.t'):
642 if self.name.endswith('.t'):
641 rename(self._errpath, self._path)
643 rename(self._errpath, self._path)
642 else:
644 else:
643 rename(self._errpath, '%s.out' % self._path)
645 rename(self._errpath, '%s.out' % self._path)
644
646
645 return '.', self.name, ''
647 return '.', self.name, ''
646
648
647 if self._unittest:
649 if self._unittest:
648 if warned:
650 if warned:
649 raise WarnTest(msg)
651 raise WarnTest(msg)
650 else:
652 else:
651 # unittest differentiates between errored and failed.
653 # unittest differentiates between errored and failed.
652 # Failed is denoted by AssertionError (by default at least).
654 # Failed is denoted by AssertionError (by default at least).
653 raise AssertionError(msg)
655 raise AssertionError(msg)
654
656
655 return warned and '~' or '!', self.name, msg
657 return warned and '~' or '!', self.name, msg
656
658
657 def skip(self, msg):
659 def skip(self, msg):
658 if self._unittest:
660 if self._unittest:
659 raise SkipTest(msg)
661 raise SkipTest(msg)
660
662
661 if self._options.verbose:
663 if self._options.verbose:
662 log("\nSkipping %s: %s" % (self._path, msg))
664 log("\nSkipping %s: %s" % (self._path, msg))
663
665
664 return 's', self.name, msg
666 return 's', self.name, msg
665
667
666 def ignore(self, msg):
668 def ignore(self, msg):
667 if self._unittest:
669 if self._unittest:
668 raise IgnoreTest(msg)
670 raise IgnoreTest(msg)
669
671
670 return 'i', self.name, msg
672 return 'i', self.name, msg
671
673
672 class PythonTest(Test):
674 class PythonTest(Test):
673 """A Python-based test."""
675 """A Python-based test."""
674 def _run(self, replacements, env):
676 def _run(self, replacements, env):
675 py3kswitch = self._options.py3k_warnings and ' -3' or ''
677 py3kswitch = self._options.py3k_warnings and ' -3' or ''
676 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
678 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
677 vlog("# Running", cmd)
679 vlog("# Running", cmd)
678 if os.name == 'nt':
680 if os.name == 'nt':
679 replacements.append((r'\r\n', '\n'))
681 replacements.append((r'\r\n', '\n'))
680 return run(cmd, self._testtmp, self._options, replacements, env,
682 return run(cmd, self._testtmp, self._options, replacements, env,
681 self._runner.abort)
683 self._runner.abort)
682
684
683 class TTest(Test):
685 class TTest(Test):
684 """A "t test" is a test backed by a .t file."""
686 """A "t test" is a test backed by a .t file."""
685
687
686 SKIPPED_PREFIX = 'skipped: '
688 SKIPPED_PREFIX = 'skipped: '
687 FAILED_PREFIX = 'hghave check failed: '
689 FAILED_PREFIX = 'hghave check failed: '
688 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
690 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
689
691
690 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
692 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
691 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
693 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
692 {'\\': '\\\\', '\r': r'\r'})
694 {'\\': '\\\\', '\r': r'\r'})
693
695
694 def _run(self, replacements, env):
696 def _run(self, replacements, env):
695 f = open(self._path)
697 f = open(self._path)
696 lines = f.readlines()
698 lines = f.readlines()
697 f.close()
699 f.close()
698
700
699 salt, script, after, expected = self._parsetest(lines)
701 salt, script, after, expected = self._parsetest(lines)
700
702
701 # Write out the generated script.
703 # Write out the generated script.
702 fname = '%s.sh' % self._testtmp
704 fname = '%s.sh' % self._testtmp
703 f = open(fname, 'w')
705 f = open(fname, 'w')
704 for l in script:
706 for l in script:
705 f.write(l)
707 f.write(l)
706 f.close()
708 f.close()
707
709
708 cmd = '%s "%s"' % (self._options.shell, fname)
710 cmd = '%s "%s"' % (self._options.shell, fname)
709 vlog("# Running", cmd)
711 vlog("# Running", cmd)
710
712
711 exitcode, output = run(cmd, self._testtmp, self._options, replacements,
713 exitcode, output = run(cmd, self._testtmp, self._options, replacements,
712 env, self._runner.abort)
714 env, self._runner.abort)
713 # Do not merge output if skipped. Return hghave message instead.
715 # Do not merge output if skipped. Return hghave message instead.
714 # Similarly, with --debug, output is None.
716 # Similarly, with --debug, output is None.
715 if exitcode == self.SKIPPED_STATUS or output is None:
717 if exitcode == self.SKIPPED_STATUS or output is None:
716 return exitcode, output
718 return exitcode, output
717
719
718 return self._processoutput(exitcode, output, salt, after, expected)
720 return self._processoutput(exitcode, output, salt, after, expected)
719
721
720 def _hghave(self, reqs):
722 def _hghave(self, reqs):
721 # TODO do something smarter when all other uses of hghave are gone.
723 # TODO do something smarter when all other uses of hghave are gone.
722 tdir = self._testdir.replace('\\', '/')
724 tdir = self._testdir.replace('\\', '/')
723 proc = Popen4('%s -c "%s/hghave %s"' %
725 proc = Popen4('%s -c "%s/hghave %s"' %
724 (self._options.shell, tdir, ' '.join(reqs)),
726 (self._options.shell, tdir, ' '.join(reqs)),
725 self._testtmp, 0)
727 self._testtmp, 0)
726 stdout, stderr = proc.communicate()
728 stdout, stderr = proc.communicate()
727 ret = proc.wait()
729 ret = proc.wait()
728 if wifexited(ret):
730 if wifexited(ret):
729 ret = os.WEXITSTATUS(ret)
731 ret = os.WEXITSTATUS(ret)
730 if ret == 2:
732 if ret == 2:
731 print stdout
733 print stdout
732 sys.exit(1)
734 sys.exit(1)
733
735
734 return ret == 0
736 return ret == 0
735
737
736 def _parsetest(self, lines):
738 def _parsetest(self, lines):
737 # We generate a shell script which outputs unique markers to line
739 # We generate a shell script which outputs unique markers to line
738 # up script results with our source. These markers include input
740 # up script results with our source. These markers include input
739 # line number and the last return code.
741 # line number and the last return code.
740 salt = "SALT" + str(time.time())
742 salt = "SALT" + str(time.time())
741 def addsalt(line, inpython):
743 def addsalt(line, inpython):
742 if inpython:
744 if inpython:
743 script.append('%s %d 0\n' % (salt, line))
745 script.append('%s %d 0\n' % (salt, line))
744 else:
746 else:
745 script.append('echo %s %s $?\n' % (salt, line))
747 script.append('echo %s %s $?\n' % (salt, line))
746
748
747 script = []
749 script = []
748
750
749 # After we run the shell script, we re-unify the script output
751 # After we run the shell script, we re-unify the script output
750 # with non-active parts of the source, with synchronization by our
752 # with non-active parts of the source, with synchronization by our
751 # SALT line number markers. The after table contains the non-active
753 # SALT line number markers. The after table contains the non-active
752 # components, ordered by line number.
754 # components, ordered by line number.
753 after = {}
755 after = {}
754
756
755 # Expected shell script output.
757 # Expected shell script output.
756 expected = {}
758 expected = {}
757
759
758 pos = prepos = -1
760 pos = prepos = -1
759
761
760 # True or False when in a true or false conditional section
762 # True or False when in a true or false conditional section
761 skipping = None
763 skipping = None
762
764
763 # We keep track of whether or not we're in a Python block so we
765 # We keep track of whether or not we're in a Python block so we
764 # can generate the surrounding doctest magic.
766 # can generate the surrounding doctest magic.
765 inpython = False
767 inpython = False
766
768
767 if self._options.debug:
769 if self._options.debug:
768 script.append('set -x\n')
770 script.append('set -x\n')
769 if os.getenv('MSYSTEM'):
771 if os.getenv('MSYSTEM'):
770 script.append('alias pwd="pwd -W"\n')
772 script.append('alias pwd="pwd -W"\n')
771
773
772 for n, l in enumerate(lines):
774 for n, l in enumerate(lines):
773 if not l.endswith('\n'):
775 if not l.endswith('\n'):
774 l += '\n'
776 l += '\n'
775 if l.startswith('#if'):
777 if l.startswith('#if'):
776 lsplit = l.split()
778 lsplit = l.split()
777 if len(lsplit) < 2 or lsplit[0] != '#if':
779 if len(lsplit) < 2 or lsplit[0] != '#if':
778 after.setdefault(pos, []).append(' !!! invalid #if\n')
780 after.setdefault(pos, []).append(' !!! invalid #if\n')
779 if skipping is not None:
781 if skipping is not None:
780 after.setdefault(pos, []).append(' !!! nested #if\n')
782 after.setdefault(pos, []).append(' !!! nested #if\n')
781 skipping = not self._hghave(lsplit[1:])
783 skipping = not self._hghave(lsplit[1:])
782 after.setdefault(pos, []).append(l)
784 after.setdefault(pos, []).append(l)
783 elif l.startswith('#else'):
785 elif l.startswith('#else'):
784 if skipping is None:
786 if skipping is None:
785 after.setdefault(pos, []).append(' !!! missing #if\n')
787 after.setdefault(pos, []).append(' !!! missing #if\n')
786 skipping = not skipping
788 skipping = not skipping
787 after.setdefault(pos, []).append(l)
789 after.setdefault(pos, []).append(l)
788 elif l.startswith('#endif'):
790 elif l.startswith('#endif'):
789 if skipping is None:
791 if skipping is None:
790 after.setdefault(pos, []).append(' !!! missing #if\n')
792 after.setdefault(pos, []).append(' !!! missing #if\n')
791 skipping = None
793 skipping = None
792 after.setdefault(pos, []).append(l)
794 after.setdefault(pos, []).append(l)
793 elif skipping:
795 elif skipping:
794 after.setdefault(pos, []).append(l)
796 after.setdefault(pos, []).append(l)
795 elif l.startswith(' >>> '): # python inlines
797 elif l.startswith(' >>> '): # python inlines
796 after.setdefault(pos, []).append(l)
798 after.setdefault(pos, []).append(l)
797 prepos = pos
799 prepos = pos
798 pos = n
800 pos = n
799 if not inpython:
801 if not inpython:
800 # We've just entered a Python block. Add the header.
802 # We've just entered a Python block. Add the header.
801 inpython = True
803 inpython = True
802 addsalt(prepos, False) # Make sure we report the exit code.
804 addsalt(prepos, False) # Make sure we report the exit code.
803 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
805 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
804 addsalt(n, True)
806 addsalt(n, True)
805 script.append(l[2:])
807 script.append(l[2:])
806 elif l.startswith(' ... '): # python inlines
808 elif l.startswith(' ... '): # python inlines
807 after.setdefault(prepos, []).append(l)
809 after.setdefault(prepos, []).append(l)
808 script.append(l[2:])
810 script.append(l[2:])
809 elif l.startswith(' $ '): # commands
811 elif l.startswith(' $ '): # commands
810 if inpython:
812 if inpython:
811 script.append('EOF\n')
813 script.append('EOF\n')
812 inpython = False
814 inpython = False
813 after.setdefault(pos, []).append(l)
815 after.setdefault(pos, []).append(l)
814 prepos = pos
816 prepos = pos
815 pos = n
817 pos = n
816 addsalt(n, False)
818 addsalt(n, False)
817 cmd = l[4:].split()
819 cmd = l[4:].split()
818 if len(cmd) == 2 and cmd[0] == 'cd':
820 if len(cmd) == 2 and cmd[0] == 'cd':
819 l = ' $ cd %s || exit 1\n' % cmd[1]
821 l = ' $ cd %s || exit 1\n' % cmd[1]
820 script.append(l[4:])
822 script.append(l[4:])
821 elif l.startswith(' > '): # continuations
823 elif l.startswith(' > '): # continuations
822 after.setdefault(prepos, []).append(l)
824 after.setdefault(prepos, []).append(l)
823 script.append(l[4:])
825 script.append(l[4:])
824 elif l.startswith(' '): # results
826 elif l.startswith(' '): # results
825 # Queue up a list of expected results.
827 # Queue up a list of expected results.
826 expected.setdefault(pos, []).append(l[2:])
828 expected.setdefault(pos, []).append(l[2:])
827 else:
829 else:
828 if inpython:
830 if inpython:
829 script.append('EOF\n')
831 script.append('EOF\n')
830 inpython = False
832 inpython = False
831 # Non-command/result. Queue up for merged output.
833 # Non-command/result. Queue up for merged output.
832 after.setdefault(pos, []).append(l)
834 after.setdefault(pos, []).append(l)
833
835
834 if inpython:
836 if inpython:
835 script.append('EOF\n')
837 script.append('EOF\n')
836 if skipping is not None:
838 if skipping is not None:
837 after.setdefault(pos, []).append(' !!! missing #endif\n')
839 after.setdefault(pos, []).append(' !!! missing #endif\n')
838 addsalt(n + 1, False)
840 addsalt(n + 1, False)
839
841
840 return salt, script, after, expected
842 return salt, script, after, expected
841
843
842 def _processoutput(self, exitcode, output, salt, after, expected):
844 def _processoutput(self, exitcode, output, salt, after, expected):
843 # Merge the script output back into a unified test.
845 # Merge the script output back into a unified test.
844 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
846 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
845 if exitcode != 0:
847 if exitcode != 0:
846 warnonly = 3
848 warnonly = 3
847
849
848 pos = -1
850 pos = -1
849 postout = []
851 postout = []
850 for l in output:
852 for l in output:
851 lout, lcmd = l, None
853 lout, lcmd = l, None
852 if salt in l:
854 if salt in l:
853 lout, lcmd = l.split(salt, 1)
855 lout, lcmd = l.split(salt, 1)
854
856
855 if lout:
857 if lout:
856 if not lout.endswith('\n'):
858 if not lout.endswith('\n'):
857 lout += ' (no-eol)\n'
859 lout += ' (no-eol)\n'
858
860
859 # Find the expected output at the current position.
861 # Find the expected output at the current position.
860 el = None
862 el = None
861 if expected.get(pos, None):
863 if expected.get(pos, None):
862 el = expected[pos].pop(0)
864 el = expected[pos].pop(0)
863
865
864 r = TTest.linematch(el, lout)
866 r = TTest.linematch(el, lout)
865 if isinstance(r, str):
867 if isinstance(r, str):
866 if r == '+glob':
868 if r == '+glob':
867 lout = el[:-1] + ' (glob)\n'
869 lout = el[:-1] + ' (glob)\n'
868 r = '' # Warn only this line.
870 r = '' # Warn only this line.
869 elif r == '-glob':
871 elif r == '-glob':
870 lout = ''.join(el.rsplit(' (glob)', 1))
872 lout = ''.join(el.rsplit(' (glob)', 1))
871 r = '' # Warn only this line.
873 r = '' # Warn only this line.
872 else:
874 else:
873 log('\ninfo, unknown linematch result: %r\n' % r)
875 log('\ninfo, unknown linematch result: %r\n' % r)
874 r = False
876 r = False
875 if r:
877 if r:
876 postout.append(' ' + el)
878 postout.append(' ' + el)
877 else:
879 else:
878 if self.NEEDESCAPE(lout):
880 if self.NEEDESCAPE(lout):
879 lout = TTest.stringescape('%s (esc)\n' %
881 lout = TTest.stringescape('%s (esc)\n' %
880 lout.rstrip('\n'))
882 lout.rstrip('\n'))
881 postout.append(' ' + lout) # Let diff deal with it.
883 postout.append(' ' + lout) # Let diff deal with it.
882 if r != '': # If line failed.
884 if r != '': # If line failed.
883 warnonly = 3 # for sure not
885 warnonly = 3 # for sure not
884 elif warnonly == 1: # Is "not yet" and line is warn only.
886 elif warnonly == 1: # Is "not yet" and line is warn only.
885 warnonly = 2 # Yes do warn.
887 warnonly = 2 # Yes do warn.
886
888
887 if lcmd:
889 if lcmd:
888 # Add on last return code.
890 # Add on last return code.
889 ret = int(lcmd.split()[1])
891 ret = int(lcmd.split()[1])
890 if ret != 0:
892 if ret != 0:
891 postout.append(' [%s]\n' % ret)
893 postout.append(' [%s]\n' % ret)
892 if pos in after:
894 if pos in after:
893 # Merge in non-active test bits.
895 # Merge in non-active test bits.
894 postout += after.pop(pos)
896 postout += after.pop(pos)
895 pos = int(lcmd.split()[0])
897 pos = int(lcmd.split()[0])
896
898
897 if pos in after:
899 if pos in after:
898 postout += after.pop(pos)
900 postout += after.pop(pos)
899
901
900 if warnonly == 2:
902 if warnonly == 2:
901 exitcode = False # Set exitcode to warned.
903 exitcode = False # Set exitcode to warned.
902
904
903 return exitcode, postout
905 return exitcode, postout
904
906
905 @staticmethod
907 @staticmethod
906 def rematch(el, l):
908 def rematch(el, l):
907 try:
909 try:
908 # use \Z to ensure that the regex matches to the end of the string
910 # use \Z to ensure that the regex matches to the end of the string
909 if os.name == 'nt':
911 if os.name == 'nt':
910 return re.match(el + r'\r?\n\Z', l)
912 return re.match(el + r'\r?\n\Z', l)
911 return re.match(el + r'\n\Z', l)
913 return re.match(el + r'\n\Z', l)
912 except re.error:
914 except re.error:
913 # el is an invalid regex
915 # el is an invalid regex
914 return False
916 return False
915
917
916 @staticmethod
918 @staticmethod
917 def globmatch(el, l):
919 def globmatch(el, l):
918 # The only supported special characters are * and ? plus / which also
920 # The only supported special characters are * and ? plus / which also
919 # matches \ on windows. Escaping of these characters is supported.
921 # matches \ on windows. Escaping of these characters is supported.
920 if el + '\n' == l:
922 if el + '\n' == l:
921 if os.altsep:
923 if os.altsep:
922 # matching on "/" is not needed for this line
924 # matching on "/" is not needed for this line
923 return '-glob'
925 return '-glob'
924 return True
926 return True
925 i, n = 0, len(el)
927 i, n = 0, len(el)
926 res = ''
928 res = ''
927 while i < n:
929 while i < n:
928 c = el[i]
930 c = el[i]
929 i += 1
931 i += 1
930 if c == '\\' and el[i] in '*?\\/':
932 if c == '\\' and el[i] in '*?\\/':
931 res += el[i - 1:i + 1]
933 res += el[i - 1:i + 1]
932 i += 1
934 i += 1
933 elif c == '*':
935 elif c == '*':
934 res += '.*'
936 res += '.*'
935 elif c == '?':
937 elif c == '?':
936 res += '.'
938 res += '.'
937 elif c == '/' and os.altsep:
939 elif c == '/' and os.altsep:
938 res += '[/\\\\]'
940 res += '[/\\\\]'
939 else:
941 else:
940 res += re.escape(c)
942 res += re.escape(c)
941 return TTest.rematch(res, l)
943 return TTest.rematch(res, l)
942
944
943 @staticmethod
945 @staticmethod
944 def linematch(el, l):
946 def linematch(el, l):
945 if el == l: # perfect match (fast)
947 if el == l: # perfect match (fast)
946 return True
948 return True
947 if el:
949 if el:
948 if el.endswith(" (esc)\n"):
950 if el.endswith(" (esc)\n"):
949 el = el[:-7].decode('string-escape') + '\n'
951 el = el[:-7].decode('string-escape') + '\n'
950 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
952 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
951 return True
953 return True
952 if el.endswith(" (re)\n"):
954 if el.endswith(" (re)\n"):
953 return TTest.rematch(el[:-6], l)
955 return TTest.rematch(el[:-6], l)
954 if el.endswith(" (glob)\n"):
956 if el.endswith(" (glob)\n"):
955 return TTest.globmatch(el[:-8], l)
957 return TTest.globmatch(el[:-8], l)
956 if os.altsep and l.replace('\\', '/') == el:
958 if os.altsep and l.replace('\\', '/') == el:
957 return '+glob'
959 return '+glob'
958 return False
960 return False
959
961
960 @staticmethod
962 @staticmethod
961 def parsehghaveoutput(lines):
963 def parsehghaveoutput(lines):
962 '''Parse hghave log lines.
964 '''Parse hghave log lines.
963
965
964 Return tuple of lists (missing, failed):
966 Return tuple of lists (missing, failed):
965 * the missing/unknown features
967 * the missing/unknown features
966 * the features for which existence check failed'''
968 * the features for which existence check failed'''
967 missing = []
969 missing = []
968 failed = []
970 failed = []
969 for line in lines:
971 for line in lines:
970 if line.startswith(TTest.SKIPPED_PREFIX):
972 if line.startswith(TTest.SKIPPED_PREFIX):
971 line = line.splitlines()[0]
973 line = line.splitlines()[0]
972 missing.append(line[len(TTest.SKIPPED_PREFIX):])
974 missing.append(line[len(TTest.SKIPPED_PREFIX):])
973 elif line.startswith(TTest.FAILED_PREFIX):
975 elif line.startswith(TTest.FAILED_PREFIX):
974 line = line.splitlines()[0]
976 line = line.splitlines()[0]
975 failed.append(line[len(TTest.FAILED_PREFIX):])
977 failed.append(line[len(TTest.FAILED_PREFIX):])
976
978
977 return missing, failed
979 return missing, failed
978
980
979 @staticmethod
981 @staticmethod
980 def _escapef(m):
982 def _escapef(m):
981 return TTest.ESCAPEMAP[m.group(0)]
983 return TTest.ESCAPEMAP[m.group(0)]
982
984
983 @staticmethod
985 @staticmethod
984 def _stringescape(s):
986 def _stringescape(s):
985 return TTest.ESCAPESUB(TTest._escapef, s)
987 return TTest.ESCAPESUB(TTest._escapef, s)
986
988
987
989
988 wifexited = getattr(os, "WIFEXITED", lambda x: False)
990 wifexited = getattr(os, "WIFEXITED", lambda x: False)
989 def run(cmd, wd, options, replacements, env, abort):
991 def run(cmd, wd, options, replacements, env, abort):
990 """Run command in a sub-process, capturing the output (stdout and stderr).
992 """Run command in a sub-process, capturing the output (stdout and stderr).
991 Return a tuple (exitcode, output). output is None in debug mode."""
993 Return a tuple (exitcode, output). output is None in debug mode."""
992 # TODO: Use subprocess.Popen if we're running on Python 2.4
994 # TODO: Use subprocess.Popen if we're running on Python 2.4
993 if options.debug:
995 if options.debug:
994 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
996 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
995 ret = proc.wait()
997 ret = proc.wait()
996 return (ret, None)
998 return (ret, None)
997
999
998 proc = Popen4(cmd, wd, options.timeout, env)
1000 proc = Popen4(cmd, wd, options.timeout, env)
999 def cleanup():
1001 def cleanup():
1000 terminate(proc)
1002 terminate(proc)
1001 ret = proc.wait()
1003 ret = proc.wait()
1002 if ret == 0:
1004 if ret == 0:
1003 ret = signal.SIGTERM << 8
1005 ret = signal.SIGTERM << 8
1004 killdaemons(env['DAEMON_PIDS'])
1006 killdaemons(env['DAEMON_PIDS'])
1005 return ret
1007 return ret
1006
1008
1007 output = ''
1009 output = ''
1008 proc.tochild.close()
1010 proc.tochild.close()
1009
1011
1010 try:
1012 try:
1011 output = proc.fromchild.read()
1013 output = proc.fromchild.read()
1012 except KeyboardInterrupt:
1014 except KeyboardInterrupt:
1013 vlog('# Handling keyboard interrupt')
1015 vlog('# Handling keyboard interrupt')
1014 cleanup()
1016 cleanup()
1015 raise
1017 raise
1016
1018
1017 ret = proc.wait()
1019 ret = proc.wait()
1018 if wifexited(ret):
1020 if wifexited(ret):
1019 ret = os.WEXITSTATUS(ret)
1021 ret = os.WEXITSTATUS(ret)
1020
1022
1021 if proc.timeout:
1023 if proc.timeout:
1022 ret = 'timeout'
1024 ret = 'timeout'
1023
1025
1024 if ret:
1026 if ret:
1025 killdaemons(env['DAEMON_PIDS'])
1027 killdaemons(env['DAEMON_PIDS'])
1026
1028
1027 if abort[0]:
1029 if abort[0]:
1028 raise KeyboardInterrupt()
1030 raise KeyboardInterrupt()
1029
1031
1030 for s, r in replacements:
1032 for s, r in replacements:
1031 output = re.sub(s, r, output)
1033 output = re.sub(s, r, output)
1032 return ret, output.splitlines(True)
1034 return ret, output.splitlines(True)
1033
1035
1034 iolock = threading.Lock()
1036 iolock = threading.Lock()
1035
1037
1036 class SkipTest(Exception):
1038 class SkipTest(Exception):
1037 """Raised to indicate that a test is to be skipped."""
1039 """Raised to indicate that a test is to be skipped."""
1038
1040
1039 class IgnoreTest(Exception):
1041 class IgnoreTest(Exception):
1040 """Raised to indicate that a test is to be ignored."""
1042 """Raised to indicate that a test is to be ignored."""
1041
1043
1042 class WarnTest(Exception):
1044 class WarnTest(Exception):
1043 """Raised to indicate that a test warned."""
1045 """Raised to indicate that a test warned."""
1044
1046
1045 class TestResult(unittest._TextTestResult):
1047 class TestResult(unittest._TextTestResult):
1046 """Holds results when executing via unittest."""
1048 """Holds results when executing via unittest."""
1047 # Don't worry too much about accessing the non-public _TextTestResult.
1049 # Don't worry too much about accessing the non-public _TextTestResult.
1048 # It is relatively common in Python testing tools.
1050 # It is relatively common in Python testing tools.
1049 def __init__(self, options, *args, **kwargs):
1051 def __init__(self, options, *args, **kwargs):
1050 super(TestResult, self).__init__(*args, **kwargs)
1052 super(TestResult, self).__init__(*args, **kwargs)
1051
1053
1052 self._options = options
1054 self._options = options
1053
1055
1054 # unittest.TestResult didn't have skipped until 2.7. We need to
1056 # unittest.TestResult didn't have skipped until 2.7. We need to
1055 # polyfill it.
1057 # polyfill it.
1056 self.skipped = []
1058 self.skipped = []
1057
1059
1058 # We have a custom "ignored" result that isn't present in any Python
1060 # We have a custom "ignored" result that isn't present in any Python
1059 # unittest implementation. It is very similar to skipped. It may make
1061 # unittest implementation. It is very similar to skipped. It may make
1060 # sense to map it into skip some day.
1062 # sense to map it into skip some day.
1061 self.ignored = []
1063 self.ignored = []
1062
1064
1063 # We have a custom "warned" result that isn't present in any Python
1065 # We have a custom "warned" result that isn't present in any Python
1064 # unittest implementation. It is very similar to failed. It may make
1066 # unittest implementation. It is very similar to failed. It may make
1065 # sense to map it into fail some day.
1067 # sense to map it into fail some day.
1066 self.warned = []
1068 self.warned = []
1067
1069
1068 def addFailure(self, *args, **kwargs):
1070 def addFailure(self, *args, **kwargs):
1069 super(TestResult, self).addFailure(*args, **kwargs)
1071 super(TestResult, self).addFailure(*args, **kwargs)
1070
1072
1071 if self._options.first:
1073 if self._options.first:
1072 self.stop()
1074 self.stop()
1073
1075
1074 def addError(self, *args, **kwargs):
1076 def addError(self, *args, **kwargs):
1075 super(TestResult, self).addError(*args, **kwargs)
1077 super(TestResult, self).addError(*args, **kwargs)
1076
1078
1077 if self._options.first:
1079 if self._options.first:
1078 self.stop()
1080 self.stop()
1079
1081
1080 # Polyfill.
1082 # Polyfill.
1081 def addSkip(self, test, reason):
1083 def addSkip(self, test, reason):
1082 self.skipped.append((test, reason))
1084 self.skipped.append((test, reason))
1083
1085
1084 if self.showAll:
1086 if self.showAll:
1085 self.stream.writeln('skipped %s' % reason)
1087 self.stream.writeln('skipped %s' % reason)
1086 else:
1088 else:
1087 self.stream.write('s')
1089 self.stream.write('s')
1088 self.stream.flush()
1090 self.stream.flush()
1089
1091
1090 def addIgnore(self, test, reason):
1092 def addIgnore(self, test, reason):
1091 self.ignored.append((test, reason))
1093 self.ignored.append((test, reason))
1092
1094
1093 if self.showAll:
1095 if self.showAll:
1094 self.stream.writeln('ignored %s' % reason)
1096 self.stream.writeln('ignored %s' % reason)
1095 else:
1097 else:
1096 self.stream.write('i')
1098 self.stream.write('i')
1097 self.stream.flush()
1099 self.stream.flush()
1098
1100
1099 def addWarn(self, test, reason):
1101 def addWarn(self, test, reason):
1100 self.warned.append((test, reason))
1102 self.warned.append((test, reason))
1101
1103
1102 if self._options.first:
1104 if self._options.first:
1103 self.stop()
1105 self.stop()
1104
1106
1105 if self.showAll:
1107 if self.showAll:
1106 self.stream.writeln('warned %s' % reason)
1108 self.stream.writeln('warned %s' % reason)
1107 else:
1109 else:
1108 self.stream.write('~')
1110 self.stream.write('~')
1109 self.stream.flush()
1111 self.stream.flush()
1110
1112
1111 class TestSuite(unittest.TestSuite):
1113 class TestSuite(unittest.TestSuite):
1112 """Custom unitest TestSuite that knows how to execute concurrently."""
1114 """Custom unitest TestSuite that knows how to execute concurrently."""
1113
1115
1114 def __init__(self, runner, *args, **kwargs):
1116 def __init__(self, runner, *args, **kwargs):
1115 super(TestSuite, self).__init__(*args, **kwargs)
1117 super(TestSuite, self).__init__(*args, **kwargs)
1116
1118
1117 self._runner = runner
1119 self._runner = runner
1118
1120
1119 def run(self, result):
1121 def run(self, result):
1120 self._runner._executetests(self._tests, result=result)
1122 self._runner._executetests(self._tests, result=result)
1121
1123
1122 return result
1124 return result
1123
1125
1124 class TextTestRunner(unittest.TextTestRunner):
1126 class TextTestRunner(unittest.TextTestRunner):
1125 """Custom unittest test runner that uses appropriate settings."""
1127 """Custom unittest test runner that uses appropriate settings."""
1126
1128
1127 def __init__(self, runner, *args, **kwargs):
1129 def __init__(self, runner, *args, **kwargs):
1128 super(TextTestRunner, self).__init__(*args, **kwargs)
1130 super(TextTestRunner, self).__init__(*args, **kwargs)
1129
1131
1130 self._runner = runner
1132 self._runner = runner
1131
1133
1132 def run(self, test):
1134 def run(self, test):
1133 result = TestResult(self._runner.options, self.stream,
1135 result = TestResult(self._runner.options, self.stream,
1134 self.descriptions, self.verbosity)
1136 self.descriptions, self.verbosity)
1135
1137
1136 test(result)
1138 test(result)
1137
1139
1138 failed = len(result.failures)
1140 failed = len(result.failures)
1139 warned = len(result.warned)
1141 warned = len(result.warned)
1140 skipped = len(result.skipped)
1142 skipped = len(result.skipped)
1141 ignored = len(result.ignored)
1143 ignored = len(result.ignored)
1142
1144
1143 self.stream.writeln('')
1145 self.stream.writeln('')
1144
1146
1145 if not self._runner.options.noskips:
1147 if not self._runner.options.noskips:
1146 for test, msg in result.skipped:
1148 for test, msg in result.skipped:
1147 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1149 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1148 for test, msg in result.warned:
1150 for test, msg in result.warned:
1149 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1151 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1150 for test, msg in result.failures:
1152 for test, msg in result.failures:
1151 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1153 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1152 for test, msg in result.errors:
1154 for test, msg in result.errors:
1153 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1155 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1154
1156
1155 self._runner._checkhglib('Tested')
1157 self._runner._checkhglib('Tested')
1156
1158
1157 # This differs from unittest's default output in that we don't count
1159 # This differs from unittest's default output in that we don't count
1158 # skipped and ignored tests as part of the total test count.
1160 # skipped and ignored tests as part of the total test count.
1159 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1161 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1160 % (result.testsRun - skipped - ignored,
1162 % (result.testsRun - skipped - ignored,
1161 skipped + ignored, warned, failed))
1163 skipped + ignored, warned, failed))
1162 if failed:
1164 if failed:
1163 self.stream.writeln('python hash seed: %s' %
1165 self.stream.writeln('python hash seed: %s' %
1164 os.environ['PYTHONHASHSEED'])
1166 os.environ['PYTHONHASHSEED'])
1165 if self._runner.options.time:
1167 if self._runner.options.time:
1166 self._runner._outputtimes()
1168 self._runner._outputtimes()
1167
1169
1168 class TestRunner(object):
1170 class TestRunner(object):
1169 """Holds context for executing tests.
1171 """Holds context for executing tests.
1170
1172
1171 Tests rely on a lot of state. This object holds it for them.
1173 Tests rely on a lot of state. This object holds it for them.
1172 """
1174 """
1173
1175
1174 REQUIREDTOOLS = [
1176 REQUIREDTOOLS = [
1175 os.path.basename(sys.executable),
1177 os.path.basename(sys.executable),
1176 'diff',
1178 'diff',
1177 'grep',
1179 'grep',
1178 'unzip',
1180 'unzip',
1179 'gunzip',
1181 'gunzip',
1180 'bunzip2',
1182 'bunzip2',
1181 'sed',
1183 'sed',
1182 ]
1184 ]
1183
1185
1184 TESTTYPES = [
1186 TESTTYPES = [
1185 ('.py', PythonTest, '.out'),
1187 ('.py', PythonTest, '.out'),
1186 ('.t', TTest, ''),
1188 ('.t', TTest, ''),
1187 ]
1189 ]
1188
1190
1189 def __init__(self):
1191 def __init__(self):
1190 self.options = None
1192 self.options = None
1191 self.testdir = None
1193 self.testdir = None
1192 self.hgtmp = None
1194 self.hgtmp = None
1193 self.inst = None
1195 self.inst = None
1194 self.bindir = None
1196 self.bindir = None
1195 self.tmpbinddir = None
1197 self.tmpbinddir = None
1196 self.pythondir = None
1198 self.pythondir = None
1197 self.coveragefile = None
1199 self.coveragefile = None
1198 self.times = [] # Holds execution times of tests.
1200 self.times = [] # Holds execution times of tests.
1199 self.results = {
1201 self.results = {
1200 '.': [],
1202 '.': [],
1201 '!': [],
1203 '!': [],
1202 '~': [],
1204 '~': [],
1203 's': [],
1205 's': [],
1204 'i': [],
1206 'i': [],
1205 'u': [],
1207 'u': [],
1206 }
1208 }
1207 self.abort = [False]
1209 self.abort = [False]
1208 self._createdfiles = []
1210 self._createdfiles = []
1209 self._hgpath = None
1211 self._hgpath = None
1210
1212
1211 def run(self, args, parser=None):
1213 def run(self, args, parser=None):
1212 """Run the test suite."""
1214 """Run the test suite."""
1213 oldmask = os.umask(022)
1215 oldmask = os.umask(022)
1214 try:
1216 try:
1215 parser = parser or getparser()
1217 parser = parser or getparser()
1216 options, args = parseargs(args, parser)
1218 options, args = parseargs(args, parser)
1217 self.options = options
1219 self.options = options
1218
1220
1219 self._checktools()
1221 self._checktools()
1220 tests = self.findtests(args)
1222 tests = self.findtests(args)
1221 return self._run(tests)
1223 return self._run(tests)
1222 finally:
1224 finally:
1223 os.umask(oldmask)
1225 os.umask(oldmask)
1224
1226
1225 def _run(self, tests):
1227 def _run(self, tests):
1226 if self.options.random:
1228 if self.options.random:
1227 random.shuffle(tests)
1229 random.shuffle(tests)
1228 else:
1230 else:
1229 # keywords for slow tests
1231 # keywords for slow tests
1230 slow = 'svn gendoc check-code-hg'.split()
1232 slow = 'svn gendoc check-code-hg'.split()
1231 def sortkey(f):
1233 def sortkey(f):
1232 # run largest tests first, as they tend to take the longest
1234 # run largest tests first, as they tend to take the longest
1233 try:
1235 try:
1234 val = -os.stat(f).st_size
1236 val = -os.stat(f).st_size
1235 except OSError, e:
1237 except OSError, e:
1236 if e.errno != errno.ENOENT:
1238 if e.errno != errno.ENOENT:
1237 raise
1239 raise
1238 return -1e9 # file does not exist, tell early
1240 return -1e9 # file does not exist, tell early
1239 for kw in slow:
1241 for kw in slow:
1240 if kw in f:
1242 if kw in f:
1241 val *= 10
1243 val *= 10
1242 return val
1244 return val
1243 tests.sort(key=sortkey)
1245 tests.sort(key=sortkey)
1244
1246
1245 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1247 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1246
1248
1247 if 'PYTHONHASHSEED' not in os.environ:
1249 if 'PYTHONHASHSEED' not in os.environ:
1248 # use a random python hash seed all the time
1250 # use a random python hash seed all the time
1249 # we do the randomness ourself to know what seed is used
1251 # we do the randomness ourself to know what seed is used
1250 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1252 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1251
1253
1252 if self.options.tmpdir:
1254 if self.options.tmpdir:
1253 self.options.keep_tmpdir = True
1255 self.options.keep_tmpdir = True
1254 tmpdir = self.options.tmpdir
1256 tmpdir = self.options.tmpdir
1255 if os.path.exists(tmpdir):
1257 if os.path.exists(tmpdir):
1256 # Meaning of tmpdir has changed since 1.3: we used to create
1258 # Meaning of tmpdir has changed since 1.3: we used to create
1257 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1259 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1258 # tmpdir already exists.
1260 # tmpdir already exists.
1259 print "error: temp dir %r already exists" % tmpdir
1261 print "error: temp dir %r already exists" % tmpdir
1260 return 1
1262 return 1
1261
1263
1262 # Automatically removing tmpdir sounds convenient, but could
1264 # Automatically removing tmpdir sounds convenient, but could
1263 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1265 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1264 # or "--tmpdir=$HOME".
1266 # or "--tmpdir=$HOME".
1265 #vlog("# Removing temp dir", tmpdir)
1267 #vlog("# Removing temp dir", tmpdir)
1266 #shutil.rmtree(tmpdir)
1268 #shutil.rmtree(tmpdir)
1267 os.makedirs(tmpdir)
1269 os.makedirs(tmpdir)
1268 else:
1270 else:
1269 d = None
1271 d = None
1270 if os.name == 'nt':
1272 if os.name == 'nt':
1271 # without this, we get the default temp dir location, but
1273 # without this, we get the default temp dir location, but
1272 # in all lowercase, which causes troubles with paths (issue3490)
1274 # in all lowercase, which causes troubles with paths (issue3490)
1273 d = os.getenv('TMP')
1275 d = os.getenv('TMP')
1274 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1276 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1275 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1277 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1276
1278
1277 if self.options.with_hg:
1279 if self.options.with_hg:
1278 self.inst = None
1280 self.inst = None
1279 self.bindir = os.path.dirname(os.path.realpath(
1281 self.bindir = os.path.dirname(os.path.realpath(
1280 self.options.with_hg))
1282 self.options.with_hg))
1281 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1283 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1282 os.makedirs(self.tmpbindir)
1284 os.makedirs(self.tmpbindir)
1283
1285
1284 # This looks redundant with how Python initializes sys.path from
1286 # This looks redundant with how Python initializes sys.path from
1285 # the location of the script being executed. Needed because the
1287 # the location of the script being executed. Needed because the
1286 # "hg" specified by --with-hg is not the only Python script
1288 # "hg" specified by --with-hg is not the only Python script
1287 # executed in the test suite that needs to import 'mercurial'
1289 # executed in the test suite that needs to import 'mercurial'
1288 # ... which means it's not really redundant at all.
1290 # ... which means it's not really redundant at all.
1289 self.pythondir = self.bindir
1291 self.pythondir = self.bindir
1290 else:
1292 else:
1291 self.inst = os.path.join(self.hgtmp, "install")
1293 self.inst = os.path.join(self.hgtmp, "install")
1292 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1294 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1293 "bin")
1295 "bin")
1294 self.tmpbindir = self.bindir
1296 self.tmpbindir = self.bindir
1295 self.pythondir = os.path.join(self.inst, "lib", "python")
1297 self.pythondir = os.path.join(self.inst, "lib", "python")
1296
1298
1297 os.environ["BINDIR"] = self.bindir
1299 os.environ["BINDIR"] = self.bindir
1298 os.environ["PYTHON"] = PYTHON
1300 os.environ["PYTHON"] = PYTHON
1299
1301
1300 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1302 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1301 if self.tmpbindir != self.bindir:
1303 if self.tmpbindir != self.bindir:
1302 path = [self.tmpbindir] + path
1304 path = [self.tmpbindir] + path
1303 os.environ["PATH"] = os.pathsep.join(path)
1305 os.environ["PATH"] = os.pathsep.join(path)
1304
1306
1305 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1307 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1306 # can run .../tests/run-tests.py test-foo where test-foo
1308 # can run .../tests/run-tests.py test-foo where test-foo
1307 # adds an extension to HGRC. Also include run-test.py directory to
1309 # adds an extension to HGRC. Also include run-test.py directory to
1308 # import modules like heredoctest.
1310 # import modules like heredoctest.
1309 pypath = [self.pythondir, self.testdir,
1311 pypath = [self.pythondir, self.testdir,
1310 os.path.abspath(os.path.dirname(__file__))]
1312 os.path.abspath(os.path.dirname(__file__))]
1311 # We have to augment PYTHONPATH, rather than simply replacing
1313 # We have to augment PYTHONPATH, rather than simply replacing
1312 # it, in case external libraries are only available via current
1314 # it, in case external libraries are only available via current
1313 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1315 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1314 # are in /opt/subversion.)
1316 # are in /opt/subversion.)
1315 oldpypath = os.environ.get(IMPL_PATH)
1317 oldpypath = os.environ.get(IMPL_PATH)
1316 if oldpypath:
1318 if oldpypath:
1317 pypath.append(oldpypath)
1319 pypath.append(oldpypath)
1318 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1320 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1319
1321
1320 self.coveragefile = os.path.join(self.testdir, '.coverage')
1322 self.coveragefile = os.path.join(self.testdir, '.coverage')
1321
1323
1322 vlog("# Using TESTDIR", self.testdir)
1324 vlog("# Using TESTDIR", self.testdir)
1323 vlog("# Using HGTMP", self.hgtmp)
1325 vlog("# Using HGTMP", self.hgtmp)
1324 vlog("# Using PATH", os.environ["PATH"])
1326 vlog("# Using PATH", os.environ["PATH"])
1325 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1327 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1326
1328
1327 try:
1329 try:
1328 return self._runtests(tests) or 0
1330 return self._runtests(tests) or 0
1329 finally:
1331 finally:
1330 time.sleep(.1)
1332 time.sleep(.1)
1331 self._cleanup()
1333 self._cleanup()
1332
1334
1333 def findtests(self, args):
1335 def findtests(self, args):
1334 """Finds possible test files from arguments.
1336 """Finds possible test files from arguments.
1335
1337
1336 If you wish to inject custom tests into the test harness, this would
1338 If you wish to inject custom tests into the test harness, this would
1337 be a good function to monkeypatch or override in a derived class.
1339 be a good function to monkeypatch or override in a derived class.
1338 """
1340 """
1339 if not args:
1341 if not args:
1340 if self.options.changed:
1342 if self.options.changed:
1341 proc = Popen4('hg st --rev "%s" -man0 .' %
1343 proc = Popen4('hg st --rev "%s" -man0 .' %
1342 self.options.changed, None, 0)
1344 self.options.changed, None, 0)
1343 stdout, stderr = proc.communicate()
1345 stdout, stderr = proc.communicate()
1344 args = stdout.strip('\0').split('\0')
1346 args = stdout.strip('\0').split('\0')
1345 else:
1347 else:
1346 args = os.listdir('.')
1348 args = os.listdir('.')
1347
1349
1348 return [t for t in args
1350 return [t for t in args
1349 if os.path.basename(t).startswith('test-')
1351 if os.path.basename(t).startswith('test-')
1350 and (t.endswith('.py') or t.endswith('.t'))]
1352 and (t.endswith('.py') or t.endswith('.t'))]
1351
1353
1352 def _runtests(self, tests):
1354 def _runtests(self, tests):
1353 try:
1355 try:
1354 if self.inst:
1356 if self.inst:
1355 self._installhg()
1357 self._installhg()
1356 self._checkhglib("Testing")
1358 self._checkhglib("Testing")
1357 else:
1359 else:
1358 self._usecorrectpython()
1360 self._usecorrectpython()
1359
1361
1360 if self.options.restart:
1362 if self.options.restart:
1361 orig = list(tests)
1363 orig = list(tests)
1362 while tests:
1364 while tests:
1363 if os.path.exists(tests[0] + ".err"):
1365 if os.path.exists(tests[0] + ".err"):
1364 break
1366 break
1365 tests.pop(0)
1367 tests.pop(0)
1366 if not tests:
1368 if not tests:
1367 print "running all tests"
1369 print "running all tests"
1368 tests = orig
1370 tests = orig
1369
1371
1370 tests = [self._gettest(t, i, asunit=self.options.unittest)
1372 tests = [self._gettest(t, i, asunit=self.options.unittest)
1371 for i, t in enumerate(tests)]
1373 for i, t in enumerate(tests)]
1372
1374
1373 failed = False
1375 failed = False
1374 warned = False
1376 warned = False
1375
1377
1376 if self.options.unittest:
1378 if self.options.unittest:
1377 suite = TestSuite(self, tests=tests)
1379 suite = TestSuite(self, tests=tests)
1378 verbosity = 1
1380 verbosity = 1
1379 if self.options.verbose:
1381 if self.options.verbose:
1380 verbosity = 2
1382 verbosity = 2
1381 runner = TextTestRunner(self, verbosity=verbosity)
1383 runner = TextTestRunner(self, verbosity=verbosity)
1382 runner.run(suite)
1384 runner.run(suite)
1383 else:
1385 else:
1384 self._executetests(tests)
1386 self._executetests(tests)
1385
1387
1386 failed = len(self.results['!'])
1388 failed = len(self.results['!'])
1387 warned = len(self.results['~'])
1389 warned = len(self.results['~'])
1388 tested = len(self.results['.']) + failed + warned
1390 tested = len(self.results['.']) + failed + warned
1389 skipped = len(self.results['s'])
1391 skipped = len(self.results['s'])
1390 ignored = len(self.results['i'])
1392 ignored = len(self.results['i'])
1391
1393
1392 print
1394 print
1393 if not self.options.noskips:
1395 if not self.options.noskips:
1394 for s in self.results['s']:
1396 for s in self.results['s']:
1395 print "Skipped %s: %s" % s
1397 print "Skipped %s: %s" % s
1396 for s in self.results['~']:
1398 for s in self.results['~']:
1397 print "Warned %s: %s" % s
1399 print "Warned %s: %s" % s
1398 for s in self.results['!']:
1400 for s in self.results['!']:
1399 print "Failed %s: %s" % s
1401 print "Failed %s: %s" % s
1400 self._checkhglib("Tested")
1402 self._checkhglib("Tested")
1401 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1403 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1402 tested, skipped + ignored, warned, failed)
1404 tested, skipped + ignored, warned, failed)
1403 if self.results['!']:
1405 if self.results['!']:
1404 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1406 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1405 if self.options.time:
1407 if self.options.time:
1406 self._outputtimes()
1408 self._outputtimes()
1407
1409
1408 if self.options.anycoverage:
1410 if self.options.anycoverage:
1409 self._outputcoverage()
1411 self._outputcoverage()
1410 except KeyboardInterrupt:
1412 except KeyboardInterrupt:
1411 failed = True
1413 failed = True
1412 print "\ninterrupted!"
1414 print "\ninterrupted!"
1413
1415
1414 if failed:
1416 if failed:
1415 return 1
1417 return 1
1416 if warned:
1418 if warned:
1417 return 80
1419 return 80
1418
1420
1419 def _gettest(self, test, count, asunit=False):
1421 def _gettest(self, test, count, asunit=False):
1420 """Obtain a Test by looking at its filename.
1422 """Obtain a Test by looking at its filename.
1421
1423
1422 Returns a Test instance. The Test may not be runnable if it doesn't
1424 Returns a Test instance. The Test may not be runnable if it doesn't
1423 map to a known type.
1425 map to a known type.
1424 """
1426 """
1425 lctest = test.lower()
1427 lctest = test.lower()
1426 refpath = os.path.join(self.testdir, test)
1428 refpath = os.path.join(self.testdir, test)
1427
1429
1428 testcls = Test
1430 testcls = Test
1429
1431
1430 for ext, cls, out in self.TESTTYPES:
1432 for ext, cls, out in self.TESTTYPES:
1431 if lctest.endswith(ext):
1433 if lctest.endswith(ext):
1432 testcls = cls
1434 testcls = cls
1433 refpath = os.path.join(self.testdir, test + out)
1435 refpath = os.path.join(self.testdir, test + out)
1434 break
1436 break
1435
1437
1436 t = testcls(self, test, count, refpath, unittest=asunit)
1438 t = testcls(self, test, count, refpath, unittest=asunit)
1437
1439
1438 if not asunit:
1440 if not asunit:
1439 return t
1441 return t
1440
1442
1441 class MercurialTest(unittest.TestCase):
1443 class MercurialTest(unittest.TestCase):
1442 def __init__(self, name, *args, **kwargs):
1444 def __init__(self, name, *args, **kwargs):
1443 super(MercurialTest, self).__init__(*args, **kwargs)
1445 super(MercurialTest, self).__init__(*args, **kwargs)
1444 self.name = name
1446 self.name = name
1445
1447
1446 def shortDescription(self):
1448 def shortDescription(self):
1447 return self.name
1449 return self.name
1448
1450
1449 # Need to stash away the TestResult since we do custom things
1451 # Need to stash away the TestResult since we do custom things
1450 # with it.
1452 # with it.
1451 def run(self, result):
1453 def run(self, result):
1452 result.startTest(self)
1454 result.startTest(self)
1453 try:
1455 try:
1454 try:
1456 try:
1455 t.setUp()
1457 t.setUp()
1456 except (KeyboardInterrupt, SystemExit):
1458 except (KeyboardInterrupt, SystemExit):
1457 raise
1459 raise
1458 except Exception:
1460 except Exception:
1459 result.addError(self, sys.exc_info())
1461 result.addError(self, sys.exc_info())
1460 return
1462 return
1461
1463
1462 success = False
1464 success = False
1463 try:
1465 try:
1464 self.runTest()
1466 self.runTest()
1465 except KeyboardInterrupt:
1467 except KeyboardInterrupt:
1466 raise
1468 raise
1467 except SkipTest, e:
1469 except SkipTest, e:
1468 result.addSkip(self, str(e))
1470 result.addSkip(self, str(e))
1469 except IgnoreTest, e:
1471 except IgnoreTest, e:
1470 result.addIgnore(self, str(e))
1472 result.addIgnore(self, str(e))
1471 except WarnTest, e:
1473 except WarnTest, e:
1472 result.addWarn(self, str(e))
1474 result.addWarn(self, str(e))
1473 except self.failureException:
1475 except self.failureException:
1474 result.addFailure(self, sys.exc_info())
1476 result.addFailure(self, sys.exc_info())
1475 except Exception:
1477 except Exception:
1476 result.addError(self, sys.exc_info())
1478 result.addError(self, sys.exc_info())
1477 else:
1479 else:
1478 success = True
1480 success = True
1479
1481
1480 try:
1482 try:
1481 t.tearDown()
1483 t.tearDown()
1482 except (KeyboardInterrupt, SystemExit):
1484 except (KeyboardInterrupt, SystemExit):
1483 raise
1485 raise
1484 except Exception:
1486 except Exception:
1485 result.addError(self, sys.exc_info())
1487 result.addError(self, sys.exc_info())
1486 success = False
1488 success = False
1487
1489
1488 if success:
1490 if success:
1489 result.addSuccess(self)
1491 result.addSuccess(self)
1490 finally:
1492 finally:
1491 result.stopTest(self)
1493 result.stopTest(self)
1492
1494
1493 def runTest(self):
1495 def runTest(self):
1494 code, tname, msg = t.run()
1496 code, tname, msg = t.run()
1495
1497
1496 # All non-success conditions should be exceptions and should
1498 # All non-success conditions should be exceptions and should
1497 # be caught in run().
1499 # be caught in run().
1498 assert code == '.'
1500 assert code == '.'
1499
1501
1500 # We need this proxy until tearDown() is implemented.
1502 # We need this proxy until tearDown() is implemented.
1501 def cleanup(self):
1503 def cleanup(self):
1502 return t.cleanup()
1504 return t.cleanup()
1503
1505
1504 return MercurialTest(test)
1506 return MercurialTest(test)
1505
1507
1506 def _cleanup(self):
1508 def _cleanup(self):
1507 """Clean up state from this test invocation."""
1509 """Clean up state from this test invocation."""
1508
1510
1509 if self.options.keep_tmpdir:
1511 if self.options.keep_tmpdir:
1510 return
1512 return
1511
1513
1512 vlog("# Cleaning up HGTMP", self.hgtmp)
1514 vlog("# Cleaning up HGTMP", self.hgtmp)
1513 shutil.rmtree(self.hgtmp, True)
1515 shutil.rmtree(self.hgtmp, True)
1514 for f in self._createdfiles:
1516 for f in self._createdfiles:
1515 try:
1517 try:
1516 os.remove(f)
1518 os.remove(f)
1517 except OSError:
1519 except OSError:
1518 pass
1520 pass
1519
1521
1520 def _usecorrectpython(self):
1522 def _usecorrectpython(self):
1521 # Some tests run the Python interpreter. They must use the
1523 # Some tests run the Python interpreter. They must use the
1522 # same interpreter or bad things will happen.
1524 # same interpreter or bad things will happen.
1523 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1525 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1524 if getattr(os, 'symlink', None):
1526 if getattr(os, 'symlink', None):
1525 vlog("# Making python executable in test path a symlink to '%s'" %
1527 vlog("# Making python executable in test path a symlink to '%s'" %
1526 sys.executable)
1528 sys.executable)
1527 mypython = os.path.join(self.tmpbindir, pyexename)
1529 mypython = os.path.join(self.tmpbindir, pyexename)
1528 try:
1530 try:
1529 if os.readlink(mypython) == sys.executable:
1531 if os.readlink(mypython) == sys.executable:
1530 return
1532 return
1531 os.unlink(mypython)
1533 os.unlink(mypython)
1532 except OSError, err:
1534 except OSError, err:
1533 if err.errno != errno.ENOENT:
1535 if err.errno != errno.ENOENT:
1534 raise
1536 raise
1535 if self._findprogram(pyexename) != sys.executable:
1537 if self._findprogram(pyexename) != sys.executable:
1536 try:
1538 try:
1537 os.symlink(sys.executable, mypython)
1539 os.symlink(sys.executable, mypython)
1538 self._createdfiles.append(mypython)
1540 self._createdfiles.append(mypython)
1539 except OSError, err:
1541 except OSError, err:
1540 # child processes may race, which is harmless
1542 # child processes may race, which is harmless
1541 if err.errno != errno.EEXIST:
1543 if err.errno != errno.EEXIST:
1542 raise
1544 raise
1543 else:
1545 else:
1544 exedir, exename = os.path.split(sys.executable)
1546 exedir, exename = os.path.split(sys.executable)
1545 vlog("# Modifying search path to find %s as %s in '%s'" %
1547 vlog("# Modifying search path to find %s as %s in '%s'" %
1546 (exename, pyexename, exedir))
1548 (exename, pyexename, exedir))
1547 path = os.environ['PATH'].split(os.pathsep)
1549 path = os.environ['PATH'].split(os.pathsep)
1548 while exedir in path:
1550 while exedir in path:
1549 path.remove(exedir)
1551 path.remove(exedir)
1550 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1552 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1551 if not self._findprogram(pyexename):
1553 if not self._findprogram(pyexename):
1552 print "WARNING: Cannot find %s in search path" % pyexename
1554 print "WARNING: Cannot find %s in search path" % pyexename
1553
1555
1554 def _installhg(self):
1556 def _installhg(self):
1555 vlog("# Performing temporary installation of HG")
1557 vlog("# Performing temporary installation of HG")
1556 installerrs = os.path.join("tests", "install.err")
1558 installerrs = os.path.join("tests", "install.err")
1557 compiler = ''
1559 compiler = ''
1558 if self.options.compiler:
1560 if self.options.compiler:
1559 compiler = '--compiler ' + self.options.compiler
1561 compiler = '--compiler ' + self.options.compiler
1560 pure = self.options.pure and "--pure" or ""
1562 pure = self.options.pure and "--pure" or ""
1561 py3 = ''
1563 py3 = ''
1562 if sys.version_info[0] == 3:
1564 if sys.version_info[0] == 3:
1563 py3 = '--c2to3'
1565 py3 = '--c2to3'
1564
1566
1565 # Run installer in hg root
1567 # Run installer in hg root
1566 script = os.path.realpath(sys.argv[0])
1568 script = os.path.realpath(sys.argv[0])
1567 hgroot = os.path.dirname(os.path.dirname(script))
1569 hgroot = os.path.dirname(os.path.dirname(script))
1568 os.chdir(hgroot)
1570 os.chdir(hgroot)
1569 nohome = '--home=""'
1571 nohome = '--home=""'
1570 if os.name == 'nt':
1572 if os.name == 'nt':
1571 # The --home="" trick works only on OS where os.sep == '/'
1573 # The --home="" trick works only on OS where os.sep == '/'
1572 # because of a distutils convert_path() fast-path. Avoid it at
1574 # because of a distutils convert_path() fast-path. Avoid it at
1573 # least on Windows for now, deal with .pydistutils.cfg bugs
1575 # least on Windows for now, deal with .pydistutils.cfg bugs
1574 # when they happen.
1576 # when they happen.
1575 nohome = ''
1577 nohome = ''
1576 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1578 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1577 ' build %(compiler)s --build-base="%(base)s"'
1579 ' build %(compiler)s --build-base="%(base)s"'
1578 ' install --force --prefix="%(prefix)s"'
1580 ' install --force --prefix="%(prefix)s"'
1579 ' --install-lib="%(libdir)s"'
1581 ' --install-lib="%(libdir)s"'
1580 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1582 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1581 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1583 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1582 'compiler': compiler,
1584 'compiler': compiler,
1583 'base': os.path.join(self.hgtmp, "build"),
1585 'base': os.path.join(self.hgtmp, "build"),
1584 'prefix': self.inst, 'libdir': self.pythondir,
1586 'prefix': self.inst, 'libdir': self.pythondir,
1585 'bindir': self.bindir,
1587 'bindir': self.bindir,
1586 'nohome': nohome, 'logfile': installerrs})
1588 'nohome': nohome, 'logfile': installerrs})
1587 vlog("# Running", cmd)
1589 vlog("# Running", cmd)
1588 if os.system(cmd) == 0:
1590 if os.system(cmd) == 0:
1589 if not self.options.verbose:
1591 if not self.options.verbose:
1590 os.remove(installerrs)
1592 os.remove(installerrs)
1591 else:
1593 else:
1592 f = open(installerrs)
1594 f = open(installerrs)
1593 for line in f:
1595 for line in f:
1594 print line,
1596 print line,
1595 f.close()
1597 f.close()
1596 sys.exit(1)
1598 sys.exit(1)
1597 os.chdir(self.testdir)
1599 os.chdir(self.testdir)
1598
1600
1599 self._usecorrectpython()
1601 self._usecorrectpython()
1600
1602
1601 if self.options.py3k_warnings and not self.options.anycoverage:
1603 if self.options.py3k_warnings and not self.options.anycoverage:
1602 vlog("# Updating hg command to enable Py3k Warnings switch")
1604 vlog("# Updating hg command to enable Py3k Warnings switch")
1603 f = open(os.path.join(self.bindir, 'hg'), 'r')
1605 f = open(os.path.join(self.bindir, 'hg'), 'r')
1604 lines = [line.rstrip() for line in f]
1606 lines = [line.rstrip() for line in f]
1605 lines[0] += ' -3'
1607 lines[0] += ' -3'
1606 f.close()
1608 f.close()
1607 f = open(os.path.join(self.bindir, 'hg'), 'w')
1609 f = open(os.path.join(self.bindir, 'hg'), 'w')
1608 for line in lines:
1610 for line in lines:
1609 f.write(line + '\n')
1611 f.write(line + '\n')
1610 f.close()
1612 f.close()
1611
1613
1612 hgbat = os.path.join(self.bindir, 'hg.bat')
1614 hgbat = os.path.join(self.bindir, 'hg.bat')
1613 if os.path.isfile(hgbat):
1615 if os.path.isfile(hgbat):
1614 # hg.bat expects to be put in bin/scripts while run-tests.py
1616 # hg.bat expects to be put in bin/scripts while run-tests.py
1615 # installation layout put it in bin/ directly. Fix it
1617 # installation layout put it in bin/ directly. Fix it
1616 f = open(hgbat, 'rb')
1618 f = open(hgbat, 'rb')
1617 data = f.read()
1619 data = f.read()
1618 f.close()
1620 f.close()
1619 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1621 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1620 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1622 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1621 '"%~dp0python" "%~dp0hg" %*')
1623 '"%~dp0python" "%~dp0hg" %*')
1622 f = open(hgbat, 'wb')
1624 f = open(hgbat, 'wb')
1623 f.write(data)
1625 f.write(data)
1624 f.close()
1626 f.close()
1625 else:
1627 else:
1626 print 'WARNING: cannot fix hg.bat reference to python.exe'
1628 print 'WARNING: cannot fix hg.bat reference to python.exe'
1627
1629
1628 if self.options.anycoverage:
1630 if self.options.anycoverage:
1629 custom = os.path.join(self.testdir, 'sitecustomize.py')
1631 custom = os.path.join(self.testdir, 'sitecustomize.py')
1630 target = os.path.join(self.pythondir, 'sitecustomize.py')
1632 target = os.path.join(self.pythondir, 'sitecustomize.py')
1631 vlog('# Installing coverage trigger to %s' % target)
1633 vlog('# Installing coverage trigger to %s' % target)
1632 shutil.copyfile(custom, target)
1634 shutil.copyfile(custom, target)
1633 rc = os.path.join(self.testdir, '.coveragerc')
1635 rc = os.path.join(self.testdir, '.coveragerc')
1634 vlog('# Installing coverage rc to %s' % rc)
1636 vlog('# Installing coverage rc to %s' % rc)
1635 os.environ['COVERAGE_PROCESS_START'] = rc
1637 os.environ['COVERAGE_PROCESS_START'] = rc
1636 fn = os.path.join(self.inst, '..', '.coverage')
1638 fn = os.path.join(self.inst, '..', '.coverage')
1637 os.environ['COVERAGE_FILE'] = fn
1639 os.environ['COVERAGE_FILE'] = fn
1638
1640
1639 def _checkhglib(self, verb):
1641 def _checkhglib(self, verb):
1640 """Ensure that the 'mercurial' package imported by python is
1642 """Ensure that the 'mercurial' package imported by python is
1641 the one we expect it to be. If not, print a warning to stderr."""
1643 the one we expect it to be. If not, print a warning to stderr."""
1642 expecthg = os.path.join(self.pythondir, 'mercurial')
1644 expecthg = os.path.join(self.pythondir, 'mercurial')
1643 actualhg = self._gethgpath()
1645 actualhg = self._gethgpath()
1644 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1646 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1645 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1647 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1646 ' (expected %s)\n'
1648 ' (expected %s)\n'
1647 % (verb, actualhg, expecthg))
1649 % (verb, actualhg, expecthg))
1648 def _gethgpath(self):
1650 def _gethgpath(self):
1649 """Return the path to the mercurial package that is actually found by
1651 """Return the path to the mercurial package that is actually found by
1650 the current Python interpreter."""
1652 the current Python interpreter."""
1651 if self._hgpath is not None:
1653 if self._hgpath is not None:
1652 return self._hgpath
1654 return self._hgpath
1653
1655
1654 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1656 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1655 pipe = os.popen(cmd % PYTHON)
1657 pipe = os.popen(cmd % PYTHON)
1656 try:
1658 try:
1657 self._hgpath = pipe.read().strip()
1659 self._hgpath = pipe.read().strip()
1658 finally:
1660 finally:
1659 pipe.close()
1661 pipe.close()
1660
1662
1661 return self._hgpath
1663 return self._hgpath
1662
1664
1663 def _outputtimes(self):
1665 def _outputtimes(self):
1664 vlog('# Producing time report')
1666 vlog('# Producing time report')
1665 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1667 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1666 cols = '%7.3f %s'
1668 cols = '%7.3f %s'
1667 print '\n%-7s %s' % ('Time', 'Test')
1669 print '\n%-7s %s' % ('Time', 'Test')
1668 for test, timetaken in self.times:
1670 for test, timetaken in self.times:
1669 print cols % (timetaken, test)
1671 print cols % (timetaken, test)
1670
1672
1671 def _outputcoverage(self):
1673 def _outputcoverage(self):
1672 vlog('# Producing coverage report')
1674 vlog('# Producing coverage report')
1673 os.chdir(self.pythondir)
1675 os.chdir(self.pythondir)
1674
1676
1675 def covrun(*args):
1677 def covrun(*args):
1676 cmd = 'coverage %s' % ' '.join(args)
1678 cmd = 'coverage %s' % ' '.join(args)
1677 vlog('# Running: %s' % cmd)
1679 vlog('# Running: %s' % cmd)
1678 os.system(cmd)
1680 os.system(cmd)
1679
1681
1680 covrun('-c')
1682 covrun('-c')
1681 omit = ','.join(os.path.join(x, '*') for x in
1683 omit = ','.join(os.path.join(x, '*') for x in
1682 [self.bindir, self.testdir])
1684 [self.bindir, self.testdir])
1683 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1685 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1684 if self.options.htmlcov:
1686 if self.options.htmlcov:
1685 htmldir = os.path.join(self.testdir, 'htmlcov')
1687 htmldir = os.path.join(self.testdir, 'htmlcov')
1686 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1688 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1687 '"--omit=%s"' % omit)
1689 '"--omit=%s"' % omit)
1688 if self.options.annotate:
1690 if self.options.annotate:
1689 adir = os.path.join(self.testdir, 'annotated')
1691 adir = os.path.join(self.testdir, 'annotated')
1690 if not os.path.isdir(adir):
1692 if not os.path.isdir(adir):
1691 os.mkdir(adir)
1693 os.mkdir(adir)
1692 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1694 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1693
1695
1694 def _executetests(self, tests, result=None):
1696 def _executetests(self, tests, result=None):
1695 # We copy because we modify the list.
1697 # We copy because we modify the list.
1696 tests = list(tests)
1698 tests = list(tests)
1697
1699
1698 jobs = self.options.jobs
1700 jobs = self.options.jobs
1699 done = queue.Queue()
1701 done = queue.Queue()
1700 running = 0
1702 running = 0
1701
1703
1702 def job(test, result):
1704 def job(test, result):
1703 try:
1705 try:
1704 # If in unittest mode.
1706 # If in unittest mode.
1705 if result:
1707 if result:
1706 test(result)
1708 test(result)
1707 # We need to put something here to make the logic happy.
1709 # We need to put something here to make the logic happy.
1708 # This will get cleaned up later.
1710 # This will get cleaned up later.
1709 done.put(('u', None, None))
1711 done.put(('u', None, None))
1710 else:
1712 else:
1711 done.put(test.run())
1713 done.put(test.run())
1712 test.cleanup()
1714 test.cleanup()
1713 except KeyboardInterrupt:
1715 except KeyboardInterrupt:
1714 pass
1716 pass
1715 except: # re-raises
1717 except: # re-raises
1716 done.put(('!', test, 'run-test raised an error, see traceback'))
1718 done.put(('!', test, 'run-test raised an error, see traceback'))
1717 raise
1719 raise
1718
1720
1719 try:
1721 try:
1720 while tests or running:
1722 while tests or running:
1721 if not done.empty() or running == jobs or not tests:
1723 if not done.empty() or running == jobs or not tests:
1722 try:
1724 try:
1723 code, test, msg = done.get(True, 1)
1725 code, test, msg = done.get(True, 1)
1724 self.results[code].append((test, msg))
1726 self.results[code].append((test, msg))
1725 if self.options.first and code not in '.si':
1727 if self.options.first and code not in '.si':
1726 break
1728 break
1727 if result and result.shouldStop:
1729 if result and result.shouldStop:
1728 break
1730 break
1729 except queue.Empty:
1731 except queue.Empty:
1730 continue
1732 continue
1731 running -= 1
1733 running -= 1
1732 if tests and not running == jobs:
1734 if tests and not running == jobs:
1733 test = tests.pop(0)
1735 test = tests.pop(0)
1734 if self.options.loop:
1736 if self.options.loop:
1735 tests.append(test)
1737 tests.append(test)
1736 t = threading.Thread(target=job, name=test.name,
1738 t = threading.Thread(target=job, name=test.name,
1737 args=(test, result))
1739 args=(test, result))
1738 t.start()
1740 t.start()
1739 running += 1
1741 running += 1
1740 except KeyboardInterrupt:
1742 except KeyboardInterrupt:
1741 self.abort[0] = True
1743 self.abort[0] = True
1742
1744
1743 def _findprogram(self, program):
1745 def _findprogram(self, program):
1744 """Search PATH for a executable program"""
1746 """Search PATH for a executable program"""
1745 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1747 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1746 name = os.path.join(p, program)
1748 name = os.path.join(p, program)
1747 if os.name == 'nt' or os.access(name, os.X_OK):
1749 if os.name == 'nt' or os.access(name, os.X_OK):
1748 return name
1750 return name
1749 return None
1751 return None
1750
1752
1751 def _checktools(self):
1753 def _checktools(self):
1752 # Before we go any further, check for pre-requisite tools
1754 # Before we go any further, check for pre-requisite tools
1753 # stuff from coreutils (cat, rm, etc) are not tested
1755 # stuff from coreutils (cat, rm, etc) are not tested
1754 for p in self.REQUIREDTOOLS:
1756 for p in self.REQUIREDTOOLS:
1755 if os.name == 'nt' and not p.endswith('.exe'):
1757 if os.name == 'nt' and not p.endswith('.exe'):
1756 p += '.exe'
1758 p += '.exe'
1757 found = self._findprogram(p)
1759 found = self._findprogram(p)
1758 if found:
1760 if found:
1759 vlog("# Found prerequisite", p, "at", found)
1761 vlog("# Found prerequisite", p, "at", found)
1760 else:
1762 else:
1761 print "WARNING: Did not find prerequisite tool: %s " % p
1763 print "WARNING: Did not find prerequisite tool: %s " % p
1762
1764
1763 if __name__ == '__main__':
1765 if __name__ == '__main__':
1764 runner = TestRunner()
1766 runner = TestRunner()
1765 sys.exit(runner.run(sys.argv[1:]))
1767 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now