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