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