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