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