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