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