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